[RFC PATCH v3 14/14] iommu/riscv: Implement IRQ forwarding
Andrew Jones
andrew.jones at oss.qualcomm.com
Mon Sep 28 07:31:13 PDT 2026
Like DMA translation tables, MSI remapping tables are owned by the IOMMU
domain. A single table therefore governs every device attached to the
guest domain, which also requires those devices to share the same guest
interrupt-file address layout. The table must become visible before the
first IRQ is forwarded and remain active until the last forwarded IRQ
returns to host delivery.
Populate and publish the table on that first transition, and remove it
on the last. Keep domain-wide and per-device forwarding counts so domain
attachment can preserve the table lifetime without unnecessarily
blocking unrelated devices in the same IOMMU group.
A vCPU migration may also change a live target without changing the
forwarding state. Update MSI PTEs with the ordering required when moving
between basic and MRIF modes, including break-before-make when both live
MRIF words must change.
Signed-off-by: Andrew Jones <andrew.jones at oss.qualcomm.com>
---
drivers/iommu/riscv/iommu-bits.h | 27 ++++++++
drivers/iommu/riscv/iommu-ir.c | 114 ++++++++++++++++++++++++++++---
2 files changed, 131 insertions(+), 10 deletions(-)
diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index 65d1f2250011..51a2e660b06a 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -702,6 +702,8 @@ struct riscv_iommu_msipte {
/* Fields on pte */
#define RISCV_IOMMU_MSIPTE_V BIT_ULL(0)
#define RISCV_IOMMU_MSIPTE_M GENMASK_ULL(2, 1)
+#define RISCV_IOMMU_MSIPTE_M_MRIF 1
+#define RISCV_IOMMU_MSIPTE_M_BASIC 3
#define RISCV_IOMMU_MSIPTE_MRIF_ADDR GENMASK_ULL(53, 7) /* When M == 1 (MRIF mode) */
#define RISCV_IOMMU_MSIPTE_PPN RISCV_IOMMU_PPN_FIELD /* When M == 3 (basic mode) */
#define RISCV_IOMMU_MSIPTE_C BIT_ULL(63)
@@ -711,6 +713,31 @@ struct riscv_iommu_msipte {
#define RISCV_IOMMU_MSIPTE_MRIF_NPPN RISCV_IOMMU_PPN_FIELD
#define RISCV_IOMMU_MSIPTE_MRIF_NID_MSB BIT_ULL(60)
+static inline void riscv_iommu_msipte_set_basic(struct riscv_iommu_msipte *msipte, u64 addr)
+{
+ msipte->pte = FIELD_PREP(RISCV_IOMMU_MSIPTE_M, RISCV_IOMMU_MSIPTE_M_BASIC) |
+ FIELD_PREP(RISCV_IOMMU_MSIPTE_PPN, addr >> 12) |
+ RISCV_IOMMU_MSIPTE_V;
+ msipte->mrif_info = 0;
+}
+
+static inline void riscv_iommu_msipte_set_mrif(struct riscv_iommu_msipte *msipte,
+ u64 mrif_addr, u64 notice_addr, u32 notice_id)
+{
+ msipte->pte = FIELD_PREP(RISCV_IOMMU_MSIPTE_M, RISCV_IOMMU_MSIPTE_M_MRIF) |
+ FIELD_PREP(RISCV_IOMMU_MSIPTE_MRIF_ADDR, mrif_addr >> 9) |
+ RISCV_IOMMU_MSIPTE_V;
+ msipte->mrif_info = FIELD_PREP(RISCV_IOMMU_MSIPTE_MRIF_NPPN, notice_addr >> 12) |
+ FIELD_PREP(RISCV_IOMMU_MSIPTE_MRIF_NID, notice_id) |
+ FIELD_PREP(RISCV_IOMMU_MSIPTE_MRIF_NID_MSB, notice_id >> 10);
+}
+
+static inline void riscv_iommu_msipte_clear(struct riscv_iommu_msipte *msipte)
+{
+ msipte->pte = 0;
+ msipte->mrif_info = 0;
+}
+
/* Helper functions: command structure builders. */
static inline void riscv_iommu_cmd_inval_vma(struct riscv_iommu_command *cmd)
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
index 12a5d0bc77f2..b3f0a56475ed 100644
--- a/drivers/iommu/riscv/iommu-ir.c
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -93,6 +93,17 @@ static int riscv_iommu_ir_validate_targets(const struct riscv_iommu_ir_vcpu_info
return 0;
}
+static void riscv_iommu_ir_set_target(struct riscv_iommu_msipte *msipte,
+ const struct riscv_iommu_ir_target *target)
+{
+ if (target->type == RISCV_IOMMU_IR_TARGET_IMSIC) {
+ riscv_iommu_msipte_set_basic(msipte, target->hpa);
+ } else {
+ riscv_iommu_msipte_set_mrif(msipte, target->mrif_hpa,
+ target->notice_hpa, target->notice_id);
+ }
+}
+
static int riscv_iommu_ir_activate(struct riscv_iommu_msi_table *msi_table,
struct riscv_iommu_device *iommu,
struct riscv_iommu_ir_vcpu_info *vcpu_info)
@@ -134,19 +145,101 @@ static int riscv_iommu_ir_activate(struct riscv_iommu_msi_table *msi_table,
return -EOPNOTSUPP;
}
- return -EOPNOTSUPP;
+ for (unsigned int i = 0; i < vcpu_info->nr_targets; i++) {
+ const struct riscv_iommu_ir_target *target = &vcpu_info->targets[i];
+ size_t idx = riscv_iommu_ir_extract(target->gpa >> IMSIC_MMIO_PAGE_SHIFT,
+ vcpu_info->msi_addr_mask);
+ struct riscv_iommu_msipte *msipte = &msi_table->root[idx];
+
+ /* A populated entry in the initially clear table indicates duplicate targets. */
+ if (msipte->pte || msipte->mrif_info) {
+ memset(msi_table->root, 0, array_size(nr_ptes, sizeof(*msi_table->root)));
+ return -EINVAL;
+ }
+
+ riscv_iommu_ir_set_target(msipte, target);
+ }
+
+ msi_table->required_caps = required_caps;
+ msi_table->owner = vcpu_info->owner;
+ msi_table->msi_addr_mask = vcpu_info->msi_addr_mask;
+ msi_table->msi_addr_pattern = vcpu_info->msi_addr_pattern;
+
+ riscv_iommu_msi_table_inval_all(msi_table);
+ riscv_iommu_msi_table_update(msi_table, true);
+
+ return 0;
}
static int riscv_iommu_ir_deactivate(struct riscv_iommu_msi_table *msi_table)
{
- return -EOPNOTSUPP;
+ riscv_iommu_msi_table_update(msi_table, false);
+
+ memset(msi_table->root, 0, array_size(msi_table->nr_ptes, sizeof(*msi_table->root)));
+ msi_table->required_caps = 0;
+ msi_table->owner = NULL;
+ msi_table->msi_addr_mask = 0;
+ msi_table->msi_addr_pattern = 0;
+
+ return 0;
+}
+
+static void riscv_iommu_ir_update_msipte(struct riscv_iommu_msi_table *msi_table,
+ struct riscv_iommu_msipte *msipte,
+ const struct riscv_iommu_ir_target *target)
+{
+ u64 pte = READ_ONCE(msipte->pte);
+ u64 mrif_info = READ_ONCE(msipte->mrif_info);
+ bool old_mrif = FIELD_GET(RISCV_IOMMU_MSIPTE_M, pte) == RISCV_IOMMU_MSIPTE_M_MRIF;
+ bool new_mrif = target->type == RISCV_IOMMU_IR_TARGET_MRIF;
+ struct riscv_iommu_msipte new = { 0 };
+
+ riscv_iommu_ir_set_target(&new, target);
+
+ if (pte == new.pte && mrif_info == new.mrif_info)
+ return;
+
+ if (!old_mrif && new_mrif) {
+ /* Basic mode ignores mrif_info, so prepare it before switching modes. */
+ WRITE_ONCE(msipte->mrif_info, new.mrif_info);
+ dma_wmb();
+ WRITE_ONCE(msipte->pte, new.pte);
+ } else if (old_mrif && !new_mrif) {
+ /* Basic mode ignores mrif_info, so switch modes before clearing it. */
+ WRITE_ONCE(msipte->pte, new.pte);
+ dma_wmb();
+ riscv_iommu_msi_table_inval(msi_table, target->gpa);
+ WRITE_ONCE(msipte->mrif_info, 0);
+ return;
+ } else if (pte == new.pte) {
+ WRITE_ONCE(msipte->mrif_info, new.mrif_info);
+ } else if (mrif_info == new.mrif_info) {
+ WRITE_ONCE(msipte->pte, new.pte);
+ } else {
+ /*
+ * Both words are active in MRIF mode, so use break-before-make.
+ * The UPDATE_TARGET contract requires producers to be quiesced.
+ */
+ WRITE_ONCE(msipte->pte, 0);
+ dma_wmb();
+ riscv_iommu_msi_table_inval(msi_table, target->gpa);
+ WRITE_ONCE(msipte->mrif_info, new.mrif_info);
+ dma_wmb();
+ WRITE_ONCE(msipte->pte, new.pte);
+ return;
+ }
+
+ dma_wmb();
+ riscv_iommu_msi_table_inval(msi_table, target->gpa);
}
static int riscv_iommu_ir_update_target(struct riscv_iommu_msi_table *msi_table,
struct riscv_iommu_ir_vcpu_info *vcpu_info)
{
const struct riscv_iommu_ir_target *target = &vcpu_info->target;
+ struct riscv_iommu_msipte *msipte;
u64 required_caps = 0;
+ size_t idx;
int ret;
ret = riscv_iommu_ir_validate_target(vcpu_info, target, &required_caps);
@@ -157,7 +250,15 @@ static int riscv_iommu_ir_update_target(struct riscv_iommu_msi_table *msi_table,
if (!riscv_iommu_msi_table_check_caps(msi_table, required_caps))
return -EOPNOTSUPP;
- return -EOPNOTSUPP;
+ idx = riscv_iommu_ir_extract(target->gpa >> IMSIC_MMIO_PAGE_SHIFT,
+ msi_table->msi_addr_mask);
+ msipte = &msi_table->root[idx];
+ if (!(READ_ONCE(msipte->pte) & RISCV_IOMMU_MSIPTE_V))
+ return -EINVAL;
+
+ riscv_iommu_ir_update_msipte(msi_table, msipte, target);
+ msi_table->required_caps = required_caps;
+ return 0;
}
static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
@@ -182,13 +283,6 @@ static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
info->nr_forwarded_irqs--;
irqd_clr_forwarded_to_vcpu(data);
- if (!msi_table->nr_forwarded_irqs) {
- msi_table->required_caps = 0;
- msi_table->owner = NULL;
- msi_table->msi_addr_mask = 0;
- msi_table->msi_addr_pattern = 0;
- }
-
return 0;
}
--
2.43.0
More information about the linux-riscv
mailing list