[PATCH v2 10/14] iommu/riscv: Pre-map IMSIC MSI targets
Andrew Jones
andrew.jones at oss.qualcomm.com
Fri Jul 24 08:12:14 PDT 2026
Prepare for irq_compose_msi_msg() to redirect IMSIC-targeted MSIs
through the IOMMU with an O(1) lookup. Unlike fixed-target MSI
controllers, IMSIC target PAs change on irq_set_affinity(), which
may run in atomic context, so compose cannot map the selected
target on demand.
Pre-map each populated IMSIC MSI target page into a domain-local
IOVA table when the device's paging domain first allocates
IMSIC-remapped IRQs, so every possible target IOVA is available at
lookup time. The table is indexed by a transformed IMSIC physical
address using the same transformation defined in the IOMMU
specification for the MSI table (and the function will be used for
the MSI table when that support is added later).
The table is sized by riscv_iommu_ir_msi_iova_count(), i.e.
BIT(group_index_bits + hart_index_bits), rather than
num_possible_cpus(). This is because riscv_iommu_ir_msi_iova_idx()
derives its index from hart_index_bits and group_index_bits, which
are sized for the group with the most harts, i.e. there may be a
sparse mapping.
Select IRQ_MSI_IOMMU so iommu_dma_map_msi() and iommufd_sw_map_msi()
are available to the driver.
Signed-off-by: Andrew Jones <andrew.jones at oss.qualcomm.com>
---
drivers/iommu/riscv/Kconfig | 1 +
drivers/iommu/riscv/iommu-ir.c | 108 +++++++++++++++++++++++++++++++++
drivers/iommu/riscv/iommu.c | 12 +++-
drivers/iommu/riscv/iommu.h | 6 ++
4 files changed, 124 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/riscv/Kconfig b/drivers/iommu/riscv/Kconfig
index b86e5ab94183..f557b55c2c14 100644
--- a/drivers/iommu/riscv/Kconfig
+++ b/drivers/iommu/riscv/Kconfig
@@ -10,6 +10,7 @@ config RISCV_IOMMU
select GENERIC_PT
select IOMMU_PT
select IOMMU_PT_RISCV64
+ select IRQ_MSI_IOMMU
help
Support for implementations of the RISC-V IOMMU architecture that
complements the RISC-V MMU capabilities, providing similar address
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
index 5873addf2a1b..39ca4c89a2a3 100644
--- a/drivers/iommu/riscv/iommu-ir.c
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -5,11 +5,100 @@
* Copyright (c) 2026 Qualcomm Technologies, Inc.
*/
#include <linux/cleanup.h>
+#include <linux/irqchip/riscv-imsic.h>
#include <linux/msi.h>
#include <linux/slab.h>
#include "iommu.h"
+/*
+ * Compute the MSI index for an MSI physical address using the
+ * IOMMU "extract" function (RISC-V IOMMU spec section 2.3.3).
+ */
+static size_t riscv_iommu_ir_extract_msi_idx(phys_addr_t pa)
+{
+ const struct imsic_global_config *global = imsic_get_global_config();
+ phys_addr_t mask, addr = pa >> 12;
+ size_t idx;
+
+ mask = BIT(global->hart_index_bits + global->guest_index_bits) - 1;
+ idx = addr & mask;
+
+ if (global->group_index_bits) {
+ phys_addr_t group_mask = BIT(global->group_index_bits) - 1;
+ phys_addr_t group_shift = global->group_index_shift - 12;
+ phys_addr_t group = (addr >> group_shift) & group_mask;
+
+ idx |= group << fls64(mask);
+ }
+
+ return idx;
+}
+
+static size_t riscv_iommu_ir_msi_iova_idx(phys_addr_t pa)
+{
+ const struct imsic_global_config *global = imsic_get_global_config();
+
+ /* msi_iova[] is only used for the host imsics */
+ return riscv_iommu_ir_extract_msi_idx(pa) >> global->guest_index_bits;
+}
+
+static size_t riscv_iommu_ir_msi_iova_count(void)
+{
+ const struct imsic_global_config *global = imsic_get_global_config();
+
+ return BIT(global->group_index_bits + global->hart_index_bits);
+}
+
+static int riscv_iommu_ir_build_msi_iova(struct riscv_iommu_domain *domain, struct device *dev)
+{
+ const struct imsic_global_config *global = imsic_get_global_config();
+ struct iommu_domain *d = &domain->domain;
+ dma_addr_t *msi_iova;
+ unsigned int cpu;
+ size_t idx;
+ int ret;
+
+ guard(mutex)(&domain->mutex);
+
+ if (domain->msi_iova)
+ return 0;
+
+ switch (d->cookie_type) {
+ case IOMMU_COOKIE_DMA_IOVA:
+ case IOMMU_COOKIE_DMA_MSI:
+ case IOMMU_COOKIE_IOMMUFD:
+ break;
+ default:
+ return 0;
+ }
+
+ msi_iova = kcalloc(riscv_iommu_ir_msi_iova_count(), sizeof(*msi_iova), GFP_KERNEL);
+ if (!msi_iova)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ const struct imsic_local_config *local = per_cpu_ptr(global->local, cpu);
+ phys_addr_t pa = local->msi_pa;
+ unsigned int shift;
+
+ if (!pa)
+ continue;
+
+ idx = riscv_iommu_ir_msi_iova_idx(pa);
+ ret = iommu_dma_map_msi(d, dev, pa, IMSIC_MMIO_PAGE_SZ, &msi_iova[idx], &shift);
+ if (ret)
+ goto err_free;
+ }
+
+ domain->msi_iova = msi_iova;
+ return 0;
+
+err_free:
+ kfree(msi_iova);
+ return ret;
+}
+
static struct irq_chip riscv_iommu_ir_irq_chip = {
.name = "IOMMU-IR",
.irq_ack = irq_chip_ack_parent,
@@ -22,9 +111,25 @@ static int riscv_iommu_ir_irq_domain_alloc_irqs(struct irq_domain *irqdomain,
unsigned int irq_base, unsigned int nr_irqs,
void *arg)
{
+ struct riscv_iommu_info *info = irqdomain->host_data;
+ struct riscv_iommu_domain *domain;
struct irq_data *data;
int i, ret;
+ /*
+ * MSI IOVAs are domain-local, just like DMA IOVAs. The device must be
+ * quiesced, including MSI teardown, before switching away from or freeing
+ * the domain. iommu_dma_map_msi() requires the group mutex to be held;
+ * take it around the domain lookup too so info->domain can't change
+ * out from under the build.
+ */
+ iommu_group_mutex_lock(info->dev);
+ domain = rcu_access_pointer(info->domain);
+ ret = domain ? riscv_iommu_ir_build_msi_iova(domain, info->dev) : 0;
+ iommu_group_mutex_unlock(info->dev);
+ if (ret)
+ return ret;
+
ret = irq_domain_alloc_irqs_parent(irqdomain, irq_base, nr_irqs, arg);
if (ret)
return ret;
@@ -116,4 +221,7 @@ int riscv_iommu_ir_attach_paging_domain(struct iommu_domain *iommu_domain, struc
void riscv_iommu_ir_free_paging_domain(struct iommu_domain *iommu_domain)
{
+ struct riscv_iommu_domain *domain = iommu_domain_to_riscv(iommu_domain);
+
+ kfree(domain->msi_iova);
}
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 98ce9ec50e65..5631af2c9ab1 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -810,9 +810,6 @@ static int riscv_iommu_iodir_set_mode(struct riscv_iommu_device *iommu,
return 0;
}
-#define iommu_domain_to_riscv(iommu_domain) \
- container_of(iommu_domain, struct riscv_iommu_domain, domain)
-
/*
* Linkage between an iommu_domain and attached devices.
*
@@ -1336,6 +1333,8 @@ static struct iommu_domain *riscv_iommu_alloc_paging_domain(struct device *dev)
if (!domain)
return ERR_PTR(-ENOMEM);
+ mutex_init(&domain->mutex);
+
INIT_LIST_HEAD_RCU(&domain->bonds);
spin_lock_init(&domain->lock);
/*
@@ -1449,6 +1448,13 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev)
if (!info)
return ERR_PTR(-ENOMEM);
+ /*
+ * Set info->dev before creating the irqdomain: riscv_iommu_ir_irq_domain_create()
+ * publishes the irqdomain via dev_set_msi_domain(), making the .alloc callback
+ * (which dereferences info->dev) reachable.
+ */
+ info->dev = dev;
+
if (imsic_enabled()) {
irqdomain = riscv_iommu_ir_irq_domain_create(dev, info);
if (IS_ERR(irqdomain)) {
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 9a8fd5300675..5f79e4cba375 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -31,14 +31,20 @@ struct riscv_iommu_domain {
};
struct list_head bonds;
spinlock_t lock; /* protect bonds list updates. */
+ struct mutex mutex; /* serializes sleepable, domain-wide setups */
int pscid;
+ dma_addr_t *msi_iova;
};
PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain);
+#define iommu_domain_to_riscv(iommu_domain) \
+ container_of(iommu_domain, struct riscv_iommu_domain, domain)
+
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
struct riscv_iommu_info {
struct riscv_iommu_domain __rcu *domain;
struct irq_domain *irqdomain;
+ struct device *dev;
};
struct riscv_iommu_device;
--
2.43.0
More information about the linux-riscv
mailing list