[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.