[PATCH v4 14/21] iommu/riscv: Gate direct identity boundary switches with live MSIs
Andrew Jones <[email protected]>
| Newsgroups | dev.linux.lists.iommu,org.infradead.lists.linux-riscv,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
MSI composition may run concurrently with a domain switch and derives the target address from the attached domain. A stale domain read across a direct paging-to-identity or identity-to-paging transition can select the wrong addressing mode, so reject these transitions while IRQs remain allocated. Keep the blocking domain available as an immediate fail-stop mode. Since it does not retain the previous translation mode, transitions through it cannot be checked reliably and still require callers to quiesce MSI state. Paging-to-paging replacement remains supported because the new domain's MSI IOVA table is prepared before attachment. Signed-off-by: Andrew Jones <[email protected]> --- drivers/iommu/riscv/iommu-ir.c | 38 ++++++++++++++++++++++++++++++++++ drivers/iommu/riscv/iommu.c | 9 ++++++++ drivers/iommu/riscv/iommu.h | 6 ++++++ 3 files changed, 53 insertions(+) diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c index 5718f90affd6..f61b65f29888 100644 --- a/drivers/iommu/riscv/iommu-ir.c +++ b/drivers/iommu/riscv/iommu-ir.c @@ -278,6 +278,44 @@ void riscv_iommu_ir_irq_domain_remove(struct device *dev, struct riscv_iommu_inf irq_domain_free_fwnode(fn); } +int riscv_iommu_ir_check_attach_paging_domain(struct iommu_domain *iommu_domain, + struct device *dev, + struct iommu_domain *old) +{ + struct riscv_iommu_info *info = dev_iommu_priv_get(dev); + + /* + * IOMMU_DOMAIN_BLOCKED is intentionally not checked. The immediate old + * domain does not reveal whether BLOCKED was entered from identity or + * paging, so rejecting it would also reject valid same-mode restores. + * IDENTITY -> BLOCKED -> PAGING can therefore bypass this check; callers + * must quiesce and tear down MSIs before making such a change. + */ + if (old && old->type == IOMMU_DOMAIN_IDENTITY && info->nr_irqs) + return -EBUSY; + + return 0; +} + +int riscv_iommu_ir_check_attach_identity_domain(struct iommu_domain *iommu_domain, + struct device *dev, + struct iommu_domain *old) +{ + struct riscv_iommu_info *info = dev_iommu_priv_get(dev); + + /* + * IOMMU_DOMAIN_BLOCKED is intentionally not checked. The immediate old + * domain does not reveal whether BLOCKED was entered from identity or + * paging, so rejecting it would also reject valid same-mode restores. + * PAGING -> BLOCKED -> IDENTITY can therefore bypass this check; callers + * must quiesce and tear down MSIs before making such a change. + */ + if (old && (old->type & __IOMMU_DOMAIN_PAGING) && info->nr_irqs) + return -EBUSY; + + return 0; +} + int riscv_iommu_ir_attach_paging_domain(struct iommu_domain *iommu_domain, struct device *dev, struct iommu_domain *old) { diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 1a30bac88101..22cccc69bd2c 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -1280,6 +1280,10 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain, u64 fsc, ta; int ret; + ret = riscv_iommu_ir_check_attach_paging_domain(iommu_domain, dev, old); + if (ret) + return ret; + pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info); if (!riscv_iommu_pt_supported(iommu, pt_info.fsc_iosatp_mode)) @@ -1398,6 +1402,11 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain, { struct riscv_iommu_device *iommu = dev_to_iommu(dev); struct riscv_iommu_info *info = dev_iommu_priv_get(dev); + int ret; + + ret = riscv_iommu_ir_check_attach_identity_domain(iommu_domain, dev, old); + if (ret) + return ret; riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, RISCV_IOMMU_PC_TA_V); riscv_iommu_bond_unlink(old, dev); diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index 24006c1c1722..19e2c0f29301 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -103,6 +103,12 @@ struct irq_domain *riscv_iommu_ir_irq_domain_create(struct device *dev, struct riscv_iommu_info *info); void riscv_iommu_ir_irq_domain_publish(struct device *dev); void riscv_iommu_ir_irq_domain_remove(struct device *dev, struct riscv_iommu_info *info); +int riscv_iommu_ir_check_attach_paging_domain(struct iommu_domain *iommu_domain, + struct device *dev, + struct iommu_domain *old); +int riscv_iommu_ir_check_attach_identity_domain(struct iommu_domain *iommu_domain, + struct device *dev, + struct iommu_domain *old); int riscv_iommu_ir_attach_paging_domain(struct iommu_domain *iommu_domain, struct device *dev, struct iommu_domain *old); void riscv_iommu_ir_free_paging_domain(struct iommu_domain *iommu_domain); -- 2.43.0