[PATCH v6 09/16] iommu/riscv: Reserve an MSI IOVA window for iommufd

From: Andrew Jones

Date: Fri Sep 25 2026 - 11:29:12 EST


iommufd requires an IOMMU_RESV_SW_MSI region to allocate stable IOVAs
for MSI targets. Advertise such a region when the device uses an IMSIC
MSI hierarchy so interrupt remapping can map IMSIC pages instead of
falling back to physical addresses.

Reserve one page per possible CPU, sufficient for each supervisor IMSIC
page. Use a 16 MiB base instead of the 128 MiB convention used by ARM
SMMU. kvmtool places its guest IMSIC at 128 MiB, so with IRQ bypass the
domain-wide guest MSI address match can also capture host-delivered
MSI writes to the SW MSI window and misdeliver them to the guest.

16 MiB falls in a hole in the current kvmtool and QEMU virt memory maps,
away from their guest IMSIC windows and RAM. Keep the window below
4 GiB so devices with 32-bit MSI address registers can use it.

This only avoids known conflicts. A VMM can place a guest's IMSIC at
any valid address, including the chosen SW MSI range. As long as a
fixed SW MSI window is required, changing its base cannot solve this
in general or guarantee disjoint host and guest MSI address spaces.

Signed-off-by: Andrew Jones <andrew.jones@xxxxxxxxxxxxxxxx>
Tested-by: Fangyu Yu <fangyu.yu@xxxxxxxxxxxxxxxxx>
---
drivers/iommu/riscv/iommu.c | 20 ++++++++++++++++++++
drivers/iommu/riscv/iommu.h | 4 ++++
drivers/irqchip/irq-riscv-imsic-state.c | 22 ++++++++++++++++++++++
include/linux/irqchip/riscv-imsic.h | 6 ++++++
4 files changed, 52 insertions(+)

diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index fbdca69c436e..dcad99131670 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -20,10 +20,12 @@
#include <linux/init.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
+#include <linux/irqchip/riscv-imsic.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/generic_pt/iommu.h>

+#include "../dma-iommu.h"
#include "../iommu-pages.h"
#include "iommu-bits.h"
#include "iommu.h"
@@ -1514,6 +1516,23 @@ static void riscv_iommu_release_device(struct device *dev)
kfree_rcu_mightsleep(info);
}

+static void riscv_iommu_get_resv_regions(struct device *dev, struct list_head *head)
+{
+ struct iommu_resv_region *region;
+
+ if (imsic_dev_has_imsic_msi_parent(dev)) {
+ /* Each hart has one S-mode IMSIC page, a.k.a MSI target page */
+ region = iommu_alloc_resv_region(RISCV_IOMMU_MSI_IOVA_BASE,
+ (size_t)num_possible_cpus() * PAGE_SIZE,
+ IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO,
+ IOMMU_RESV_SW_MSI, GFP_KERNEL);
+ if (region)
+ list_add_tail(&region->list, head);
+ }
+
+ iommu_dma_get_resv_regions(dev, head);
+}
+
static const struct iommu_ops riscv_iommu_ops = {
.of_xlate = riscv_iommu_of_xlate,
.capable = riscv_iommu_capable,
@@ -1524,6 +1543,7 @@ static const struct iommu_ops riscv_iommu_ops = {
.device_group = riscv_iommu_device_group,
.probe_device = riscv_iommu_probe_device,
.release_device = riscv_iommu_release_device,
+ .get_resv_regions = riscv_iommu_get_resv_regions,
};

static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 5676001548cc..6d5c70e9ac6d 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -15,9 +15,13 @@
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/iopoll.h>
+#include <linux/sizes.h>

#include "iommu-bits.h"

+/* IOVA base for the SW MSI reservation */
+#define RISCV_IOMMU_MSI_IOVA_BASE SZ_16M
+
struct riscv_iommu_device;

struct riscv_iommu_queue {
diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c
index b8d1bbbf42f7..df38a7670a89 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.c
+++ b/drivers/irqchip/irq-riscv-imsic-state.c
@@ -64,6 +64,28 @@ const struct imsic_global_config *imsic_get_global_config(void)
}
EXPORT_SYMBOL_GPL(imsic_get_global_config);

+/**
+ * imsic_dev_has_imsic_msi_parent - Check for an IMSIC MSI parent
+ * @dev: Device to check
+ *
+ * Return: true if @dev's MSI domain or any parent domain is the IMSIC base
+ * domain.
+ */
+bool imsic_dev_has_imsic_msi_parent(struct device *dev)
+{
+ struct irq_domain *domain;
+
+ if (!imsic || !imsic->base_domain)
+ return false;
+
+ for (domain = dev_get_msi_domain(dev); domain; domain = domain->parent)
+ if (domain == imsic->base_domain)
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(imsic_dev_has_imsic_msi_parent);
+
static bool __imsic_eix_read_clear(unsigned long id, bool pend)
{
unsigned long isel, imask;
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index 61af3a5bea09..662cb0442424 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -81,6 +81,7 @@ struct imsic_global_config {
#ifdef CONFIG_RISCV_IMSIC

const struct imsic_global_config *imsic_get_global_config(void);
+bool imsic_dev_has_imsic_msi_parent(struct device *dev);

#else

@@ -89,6 +90,11 @@ static inline const struct imsic_global_config *imsic_get_global_config(void)
return NULL;
}

+static inline bool imsic_dev_has_imsic_msi_parent(struct device *dev)
+{
+ return false;
+}
+
#endif

#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_RISCV_IMSIC)
--
2.43.0