[RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains
Andrew Jones
andrew.jones at oss.qualcomm.com
Mon Sep 28 07:31:00 PDT 2026
IRQ bypass maps guest IMSIC addresses through a flat-mode MSI page
table owned by the second-stage domain. Allocate the table lazily
during device attachment when the attaching IOMMU supports MSI_FLAT,
and free it when the domain is destroyed.
The required capacity depends on hypervisor support and VMM policy.
Expose the runtime-writable riscv_iommu.nr_msi_ptes parameter to set the
MSI table capacity of new second-stage domains. It defaults to 512
entries (8 KiB), and zero disables MSI table allocation for new domains.
Existing domains retain the capacity selected when they were created.
Signed-off-by: Andrew Jones <andrew.jones at oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 61 +++++++++++++++++++++++++++++++++++++
drivers/iommu/riscv/iommu.h | 7 +++++
2 files changed, 68 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 0953aaf594cb..3bbb4d0d0a85 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -22,6 +22,8 @@
#include <linux/iopoll.h>
#include <linux/irqchip/riscv-imsic.h>
#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/generic_pt/iommu.h>
@@ -30,6 +32,16 @@
#include "iommu-bits.h"
#include "iommu.h"
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "riscv_iommu."
+
+#define RISCV_IOMMU_DEFAULT_NR_MSI_PTES 512
+
+/* A zero value disables guest MSI table allocation. */
+static unsigned int riscv_iommu_nr_msi_ptes = RISCV_IOMMU_DEFAULT_NR_MSI_PTES;
+module_param_named(nr_msi_ptes, riscv_iommu_nr_msi_ptes, uint, 0644);
+MODULE_PARM_DESC(nr_msi_ptes, "Number of PTEs for new second-stage domains (default: 512)");
+
/* Timeouts in [us] */
#define RISCV_IOMMU_QCSR_TIMEOUT 150000
#define RISCV_IOMMU_QUEUE_TIMEOUT 150000
@@ -847,8 +859,10 @@ struct riscv_iommu_domain {
};
struct list_head bonds;
spinlock_t lock; /* protect bonds list updates. */
+ struct mutex mutex; /* serialize domain state updates */
int pscid;
int gscid;
+ struct riscv_iommu_msi_table msi_table;
};
PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain);
@@ -1303,6 +1317,7 @@ static void riscv_iommu_free_paging_domain(struct iommu_domain *iommu_domain)
ida_free(&riscv_iommu_gscids, domain->gscid);
pt_iommu_deinit(&domain->riscvpt.iommu);
+ iommu_free_pages(domain->msi_table.root);
kfree(domain);
}
@@ -1338,6 +1353,37 @@ static bool riscv_iommu_iohgatp_supported(struct riscv_iommu_device *iommu,
return false;
}
+static int riscv_iommu_msi_table_alloc(struct riscv_iommu_domain *domain,
+ struct riscv_iommu_device *iommu)
+{
+ struct riscv_iommu_msi_table *msi_table = &domain->msi_table;
+ struct riscv_iommu_msipte *root;
+ size_t size;
+
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_MSI_FLAT))
+ return 0;
+
+ guard(mutex)(&domain->mutex);
+
+ if (msi_table->root)
+ return 0;
+
+ if (!msi_table->nr_ptes)
+ return 0;
+
+ size = array_size(msi_table->nr_ptes, sizeof(*msi_table->root));
+ if (size == SIZE_MAX)
+ return -EOVERFLOW;
+
+ root = iommu_alloc_pages_node_sz(NUMA_NO_NODE, GFP_KERNEL_ACCOUNT, size);
+ if (!root)
+ return -ENOMEM;
+
+ msi_table->root = root;
+
+ return 0;
+}
+
static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
struct device *dev,
struct iommu_domain *old)
@@ -1347,6 +1393,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
struct pt_iommu_riscv_64_hw_info pt_info;
struct riscv_iommu_dc dc = {0};
+ int ret;
pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info);
@@ -1354,6 +1401,10 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
if (!riscv_iommu_iohgatp_supported(iommu, pt_info.iohgatp_mode))
return -ENODEV;
+ ret = riscv_iommu_msi_table_alloc(domain, iommu);
+ if (ret)
+ return ret;
+
dc.iohgatp =
FIELD_PREP(RISCV_IOMMU_DC_IOHGATP_MODE, pt_info.iohgatp_mode) |
FIELD_PREP(RISCV_IOMMU_DC_IOHGATP_GSCID, domain->gscid) |
@@ -1425,6 +1476,7 @@ riscv_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
INIT_LIST_HEAD_RCU(&domain->bonds);
spin_lock_init(&domain->lock);
+ mutex_init(&domain->mutex);
iommu = dev_to_iommu(dev);
cfg.common.hw_max_oasz_lg2 = 56;
/*
@@ -1484,6 +1536,15 @@ riscv_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
ret = -ENOMEM;
goto err_free;
}
+
+ /*
+ * Capture the current number of MSI PTEs now since it must remain
+ * stable for the lifetime of the domain.
+ */
+ kernel_param_lock(THIS_MODULE);
+ domain->msi_table.nr_ptes = riscv_iommu_nr_msi_ptes;
+ kernel_param_unlock(THIS_MODULE);
+
cfg.common.features |= BIT(PT_FEAT_RISCV_S2);
if (iommu->caps & RISCV_IOMMU_CAPABILITIES_AMO_HWAD)
domain->domain.dirty_ops = &riscv_iommu_dirty_ops;
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 6d5c70e9ac6d..6bea9da71ff3 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -68,6 +68,13 @@ struct riscv_iommu_device {
u64 *ddt_root;
};
+struct riscv_iommu_msi_table {
+ unsigned int nr_ptes;
+ struct riscv_iommu_msipte *root;
+ u64 msi_addr_mask;
+ u64 msi_addr_pattern;
+};
+
int riscv_iommu_init(struct riscv_iommu_device *iommu);
void riscv_iommu_remove(struct riscv_iommu_device *iommu);
void riscv_iommu_disable(struct riscv_iommu_device *iommu);
--
2.43.0
More information about the linux-riscv
mailing list