* [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support
@ 2026-09-28 14:30 Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains Andrew Jones
` (13 more replies)
0 siblings, 14 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:30 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
This series adds the RISC-V IOMMU side of IRQ bypass, allowing MSIs from
assigned devices to be delivered directly to guest IMSIC interrupt files.
It uses the IOMMU's flat MSI page table to translate guest IMSIC addresses
to their host backing, with support for both hardware guest interrupt
files (VS-files) and memory-resident interrupt files (MRIFs).
It has been a year since v2[1], and much of the design and implementation
has changed. Host MSI remapping, IOMMU_DMA, and VFIO enablement have been
split into a prerequisite series[3]. This posting contains only the
IOMMU/IRQ-side support. The KVM patches will be posted separately: they
are currently a minimal client for testing this interface, with further
work needed for general device assignment.
The design evolved while considering two-stage guests in the discussions
with Jason on v4 of the host MSI-remapping series[2]. With a guest vIOMMU,
the address in the device's MSI message may be any guest IOVA which the
guest's first-stage page tables map to a guest IMSIC GPA. The hypervisor
cannot determine the target vCPU simply by decoding that IOVA. Instead,
the intended hardware path is:
Guest MSI IOVA -> S1 -> guest IMSIC GPA -> MSI table -> VS-file or MRIF
An MSI-table match completes the translation; addresses outside the MSI
pattern use the ordinary second-stage page tables. With S1 Bare, the
device uses the guest IMSIC GPA directly. Populating the MSI table with
all guest IMSIC targets allows guest changes to S1 mappings or interrupt
affinity without corresponding MSI-table updates. When a vCPU's host
backing changes, the hypervisor updates the entry for that guest IMSIC
GPA, leaving the device message and guest S1 mapping unchanged.
The MSI table belongs to the second-stage IOMMU domain and is shared by
all devices attached to it. RISC-V requires second-stage translation to
be enabled when using the MSI table. This is separate from host MSI
remapping, which uses ordinary page-table mappings prepared by the common
DMA-IOMMU/iommufd code and composed by the IMSIC driver.
A per-IOMMU interrupt-remapping irqdomain, installed as the MSI parent of
eligible devices at probe time, provides the irq_set_vcpu_affinity()
entry point. The IRQ hierarchy remains stable across IOMMU domain changes.
The callback obtains the device from the MSI descriptor and operates on
its currently attached second-stage domain. The irqdomain has no private
per-IRQ mapping state or MSI table of its own.
The affinity protocol supplies an owner, the guest IMSIC address pattern
and mask, and the complete set of guest targets. The first forwarded IRQ
populates the table and installs its configuration in every attached
device context. Further IRQs share that table after checking the owner
and address layout. A separate target-update command changes a guest
IMSIC's backing without changing per-IRQ forwarding state. The last IRQ
to stop forwarding removes the table configuration from the devices.
The main changes since v2 are therefore:
- Separate host MSI remapping from guest IRQ bypass, and use second-stage
domains for the guest MSI tables
- Replace per-IRQ MSI-entry mapping and reference counting with complete
guest-topology setup, domain-wide table lifetime, and individual target
updates. This is intended to accommodate guest-controlled S1 mappings.
- Add IOMMU-side MRIF support alongside hardware guest interrupt files.
- Move the KVM client out of this series
This remains an RFC, particularly for the relationship between the IRQ
hierarchy and the domain-owned MSI table. Jason raised the alternative of
a per-VM irqdomain owning the table[2]; the arrangement proposed here
keeps the IRQ hierarchy stable and ties the mappings to the S2 domain
shared by the devices.
The dependency stack is based on linux-iommu/next at 634706fe6e36, with:
- "iommu/riscv: Enable MSI remapping, IOMMU_DMA and VFIO" v6[3].
- Fangyu's "iommu/riscv: Add hardware dirty tracking for second-stage
domains" RFC v4[4], which also supplies second-stage domain allocation
and page-table support.
The branch below contains all of the above, this series, and the minimal
KVM IRQ-bypass client:
https://github.com/jones-drew/linux/commits/riscv/iommu-irqbypass-rfc-v3-kvm/
Testing requires userspace to select a second-stage domain, for example
by allocating an iommufd HWPT with IOMMU_HWPT_ALLOC_NEST_PARENT. The
corresponding kvmtool branch is available here:
https://github.com/jones-drew/kvmtool/commits/iommufd-nested-rfc-v1/
LLM-based coding assistants were used during development for code
exploration, patch review, test execution, and drafting and editing
commit messages and this cover letter. I reviewed and finalized all
resulting code and text. Per-patch Assisted-by tags are omitted in
light of the ongoing discussion about simplifying coding-assistant
attribution.
Thanks,
drew
[1] https://lore.kernel.org/all/20250920203851.2205115-20-ajones@ventanamicro.com/
[2] https://lore.kernel.org/all/20260820214150.545737-3-andrew.jones@oss.qualcomm.com/
[3] https://lore.kernel.org/all/20260925151659.419512-1-andrew.jones@oss.qualcomm.com/
[4] https://lore.kernel.org/all/20260915032828.11250-1-fangyu.yu@linux.alibaba.com/
Andrew Jones (14):
iommu/riscv: Allocate MSI tables for second-stage domains
iommu/riscv: Prepare domain bonds for outer locking
iommu/riscv: Serialize MSI table publication with domain attachment
iommu/riscv: Reject live S2 replacement with forwarded IRQs
iommu/riscv: Derive the IOMMU from the device in IODIR updates
iommu/riscv: Cache the programmed device context
iommu/riscv: Prepare MSI table updates for interrupt remapping
irqchip/riscv-imsic: Define IOMMU IRQ bypass protocol
genirq/msi: Provide DOMAIN_BUS_MSI_REMAP
iommu/riscv: Add IRQ domain for interrupt remapping
iommu/riscv: Prepare info->domain for concurrent RCU access
iommu/riscv: Prepare interrupt remapping for IRQ bypass
iommu/riscv: Validate IRQ forwarding requests
iommu/riscv: Implement IRQ forwarding
drivers/iommu/riscv/Makefile | 1 +
drivers/iommu/riscv/iommu-bits.h | 27 ++
drivers/iommu/riscv/iommu-ir.c | 505 ++++++++++++++++++++++++
drivers/iommu/riscv/iommu.c | 352 +++++++++++++++--
drivers/iommu/riscv/iommu.h | 56 +++
drivers/irqchip/irq-msi-lib.c | 8 +-
drivers/irqchip/irq-riscv-imsic-state.c | 6 +
include/linux/irqchip/riscv-imsic.h | 60 +++
include/linux/irqdomain_defs.h | 1 +
9 files changed, 974 insertions(+), 42 deletions(-)
create mode 100644 drivers/iommu/riscv/iommu-ir.c
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-10-05 13:25 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 02/14] iommu/riscv: Prepare domain bonds for outer locking Andrew Jones
` (12 subsequent siblings)
13 siblings, 1 reply; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
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@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
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 02/14] iommu/riscv: Prepare domain bonds for outer locking
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 03/14] iommu/riscv: Serialize MSI table publication with domain attachment Andrew Jones
` (11 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
IRQ bypass will serialize attachment under a raw spinlock. Bond updates
retain their own lock, so make it raw for safe PREEMPT_RT nesting.
MSI forwarding will acquire the MSI table lock under the hardirq-safe
IRQ descriptor lock. Nesting the bond lock below the MSI table lock
therefore requires IRQ protection even for domains without an MSI
table, where attachment takes only the bond lock. Use irqsave/irqrestore
in both bond helpers to avoid a HARDIRQ-safe to HARDIRQ-unsafe lock
dependency.
Also split allocation from list insertion so a future outer lock can be
taken after the sleeping allocation.
No functional change intended.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 34 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 3bbb4d0d0a85..09ec8c3e4a72 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -858,7 +858,7 @@ struct riscv_iommu_domain {
struct pt_iommu_riscv_64 riscvpt;
};
struct list_head bonds;
- spinlock_t lock; /* protect bonds list updates. */
+ raw_spinlock_t lock; /* protect bonds list updates. */
struct mutex mutex; /* serialize domain state updates */
int pscid;
int gscid;
@@ -896,34 +896,27 @@ struct riscv_iommu_bond {
struct device *dev;
};
-static int riscv_iommu_bond_link(struct riscv_iommu_domain *domain,
- struct device *dev)
+static void riscv_iommu_bond_link(struct riscv_iommu_domain *domain,
+ struct riscv_iommu_bond *bond)
{
- struct riscv_iommu_device *iommu = dev_to_iommu(dev);
- struct riscv_iommu_bond *bond;
+ struct riscv_iommu_device *iommu = dev_to_iommu(bond->dev);
struct list_head *bonds;
-
- bond = kzalloc_obj(*bond);
- if (!bond)
- return -ENOMEM;
- bond->dev = dev;
+ unsigned long flags;
/*
* List of devices attached to the domain is arranged based on
* managed IOMMU device.
*/
- spin_lock(&domain->lock);
+ raw_spin_lock_irqsave(&domain->lock, flags);
list_for_each(bonds, &domain->bonds)
if (dev_to_iommu(list_entry(bonds, struct riscv_iommu_bond, list)->dev) == iommu)
break;
list_add_rcu(&bond->list, bonds);
- spin_unlock(&domain->lock);
+ raw_spin_unlock_irqrestore(&domain->lock, flags);
/* Synchronize with riscv_iommu_iotlb_inval() sequence. See comment below. */
smp_mb();
-
- return 0;
}
static void riscv_iommu_bond_unlink(struct riscv_iommu_domain *domain,
@@ -932,12 +925,13 @@ static void riscv_iommu_bond_unlink(struct riscv_iommu_domain *domain,
struct riscv_iommu_device *iommu = dev_to_iommu(dev);
struct riscv_iommu_bond *bond, *found = NULL;
struct riscv_iommu_command cmd;
+ unsigned long flags;
int count = 0;
if (!domain)
return;
- spin_lock(&domain->lock);
+ raw_spin_lock_irqsave(&domain->lock, flags);
list_for_each_entry(bond, &domain->bonds, list) {
if (found && count)
break;
@@ -948,7 +942,7 @@ static void riscv_iommu_bond_unlink(struct riscv_iommu_domain *domain,
}
if (found)
list_del_rcu(&found->list);
- spin_unlock(&domain->lock);
+ raw_spin_unlock_irqrestore(&domain->lock, flags);
kfree_rcu(found, rcu);
/*
@@ -1391,6 +1385,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_domain *domain = iommu_domain_to_riscv(iommu_domain);
struct riscv_iommu_device *iommu = dev_to_iommu(dev);
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ struct riscv_iommu_bond *bond;
struct pt_iommu_riscv_64_hw_info pt_info;
struct riscv_iommu_dc dc = {0};
int ret;
@@ -1421,9 +1416,12 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
dc.ta = FIELD_PREP(RISCV_IOMMU_PC_TA_PSCID, domain->pscid) |
RISCV_IOMMU_PC_TA_V;
- if (riscv_iommu_bond_link(domain, dev))
+ bond = kzalloc_obj(*bond);
+ if (!bond)
return -ENOMEM;
+ bond->dev = dev;
+ riscv_iommu_bond_link(domain, bond);
riscv_iommu_iodir_update(iommu, dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = domain;
@@ -1475,7 +1473,7 @@ riscv_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD_RCU(&domain->bonds);
- spin_lock_init(&domain->lock);
+ raw_spin_lock_init(&domain->lock);
mutex_init(&domain->mutex);
iommu = dev_to_iommu(dev);
cfg.common.hw_max_oasz_lg2 = 56;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 03/14] iommu/riscv: Serialize MSI table publication with domain attachment
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 02/14] iommu/riscv: Prepare domain bonds for outer locking Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 04/14] iommu/riscv: Reject live S2 replacement with forwarded IRQs Andrew Jones
` (10 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
DMA mappings update entries behind the page-table root installed in each
attached device context. The existing bond and barrier protocol ensures
that attachment either observes a completed page-table update or is
included in its IOTLB invalidation.
MSI forwarding also changes whether the MSI page-table configuration is
installed in each device context. The first forwarded interrupt
publishes the configuration to all devices bonded to the domain, while
the last removes it. These domain-wide updates can run concurrently with
attachment because devices in different IOMMU groups have different
group mutexes.
Serialize attachment to domains with MSI tables against forwarding state
and device-context updates. During domain replacement, lock both old and
new MSI tables because either domain may process forwarding changes for
other attached devices while this device moves. This also prevents an
old-domain RCU walk from overwriting the newly installed device context.
Order the locks by address to prevent opposite-direction replacements
from deadlocking. Domains without MSI tables continue to use only the
bond lock for list updates.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 64 +++++++++++++++++++++++++++++++++++++
drivers/iommu/riscv/iommu.h | 2 ++
2 files changed, 66 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 09ec8c3e4a72..57f2884dec42 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -874,6 +874,60 @@ struct riscv_iommu_info {
struct riscv_iommu_domain *domain;
};
+static struct riscv_iommu_msi_table *riscv_iommu_domain_msi_table(struct iommu_domain *iommu_domain)
+{
+ struct riscv_iommu_domain *domain;
+
+ if (!iommu_domain || !(iommu_domain->type & __IOMMU_DOMAIN_PAGING))
+ return NULL;
+
+ domain = iommu_domain_to_riscv(iommu_domain);
+ if (!domain->msi_table.nr_ptes)
+ return NULL;
+
+ return &domain->msi_table;
+}
+
+static unsigned long riscv_iommu_msi_tables_lock(struct iommu_domain *domain1,
+ struct iommu_domain *domain2)
+{
+ struct riscv_iommu_msi_table *first = riscv_iommu_domain_msi_table(domain1);
+ struct riscv_iommu_msi_table *second = riscv_iommu_domain_msi_table(domain2);
+ unsigned long flags = 0;
+
+ /* Address order is stable when the domains reverse roles. */
+ if (!first || (second && first > second))
+ swap(first, second);
+
+ if (!first)
+ return flags;
+
+ raw_spin_lock_irqsave(&first->lock, flags);
+ if (second && second != first)
+ raw_spin_lock_nested(&second->lock, SINGLE_DEPTH_NESTING);
+
+ return flags;
+}
+
+static void riscv_iommu_msi_tables_unlock(struct iommu_domain *domain1,
+ struct iommu_domain *domain2,
+ unsigned long flags)
+{
+ struct riscv_iommu_msi_table *first = riscv_iommu_domain_msi_table(domain1);
+ struct riscv_iommu_msi_table *second = riscv_iommu_domain_msi_table(domain2);
+
+ /* Recreate the lock order and release the pair in reverse. */
+ if (!first || (second && first > second))
+ swap(first, second);
+
+ if (!first)
+ return;
+
+ if (second && second != first)
+ raw_spin_unlock(&second->lock);
+ raw_spin_unlock_irqrestore(&first->lock, flags);
+}
+
/*
* Linkage between an iommu_domain and attached devices.
*
@@ -1388,6 +1442,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_bond *bond;
struct pt_iommu_riscv_64_hw_info pt_info;
struct riscv_iommu_dc dc = {0};
+ unsigned long flags;
int ret;
pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info);
@@ -1421,10 +1476,12 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
return -ENOMEM;
bond->dev = dev;
+ flags = riscv_iommu_msi_tables_lock(old, iommu_domain);
riscv_iommu_bond_link(domain, bond);
riscv_iommu_iodir_update(iommu, dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = domain;
+ riscv_iommu_msi_tables_unlock(old, iommu_domain, flags);
return 0;
}
@@ -1474,6 +1531,7 @@ riscv_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
INIT_LIST_HEAD_RCU(&domain->bonds);
raw_spin_lock_init(&domain->lock);
+ raw_spin_lock_init(&domain->msi_table.lock);
mutex_init(&domain->mutex);
iommu = dev_to_iommu(dev);
cfg.common.hw_max_oasz_lg2 = 56;
@@ -1569,13 +1627,16 @@ static int riscv_iommu_attach_blocking_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);
struct riscv_iommu_dc dc = {0};
+ unsigned long flags;
dc.fsc = RISCV_IOMMU_FSC_BARE;
/* Make device context invalid, translation requests will fault w/ #258 */
+ flags = riscv_iommu_msi_tables_lock(old, NULL);
riscv_iommu_iodir_update(iommu, dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
+ riscv_iommu_msi_tables_unlock(old, NULL, flags);
return 0;
}
@@ -1594,13 +1655,16 @@ 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);
struct riscv_iommu_dc dc = {0};
+ unsigned long flags;
dc.fsc = RISCV_IOMMU_FSC_BARE;
dc.ta = RISCV_IOMMU_PC_TA_V;
+ flags = riscv_iommu_msi_tables_lock(old, NULL);
riscv_iommu_iodir_update(iommu, dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
+ riscv_iommu_msi_tables_unlock(old, NULL, flags);
return 0;
}
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 6bea9da71ff3..2876703a6698 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -69,6 +69,8 @@ struct riscv_iommu_device {
};
struct riscv_iommu_msi_table {
+ /* Protects attachment, interrupt forwarding state, and MSI PTE updates. */
+ raw_spinlock_t lock;
unsigned int nr_ptes;
struct riscv_iommu_msipte *root;
u64 msi_addr_mask;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 04/14] iommu/riscv: Reject live S2 replacement with forwarded IRQs
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (2 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 03/14] iommu/riscv: Serialize MSI table publication with domain attachment Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 05/14] iommu/riscv: Derive the IOMMU from the device in IODIR updates Andrew Jones
` (9 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
MSI forwarding state is tied to the S2 domain MSI table. Replacing a
device's S2 domain while one of its IRQs is forwarded would leave the
IRQ state referring to the detached table.
Reject direct S2-to-S2 replacement when the moving device has forwarded
IRQs. Introduce a per-device nr_forwarded_irqs counter, rather than
domain-wide accounting, so other devices in the same IOMMU group can
still move and can be rolled back if a later device fails.
Later patches which introduce interrupt remapping support will manage
the newly introduced nr_forwarded_irqs counter.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 57f2884dec42..d48667112cd9 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -872,6 +872,7 @@ PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain);
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
struct riscv_iommu_info {
struct riscv_iommu_domain *domain;
+ unsigned int nr_forwarded_irqs;
};
static struct riscv_iommu_msi_table *riscv_iommu_domain_msi_table(struct iommu_domain *iommu_domain)
@@ -1432,6 +1433,36 @@ static int riscv_iommu_msi_table_alloc(struct riscv_iommu_domain *domain,
return 0;
}
+static bool riscv_iommu_can_attach_paging_domain(struct iommu_domain *iommu_domain,
+ struct device *dev,
+ struct iommu_domain *old)
+{
+ struct riscv_iommu_domain *domain = iommu_domain_to_riscv(iommu_domain);
+ struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ bool new_is_s2 = domain->gscid;
+ struct riscv_iommu_msi_table *new_msi_table, *old_msi_table;
+
+ if (iommu_domain == old)
+ return true;
+
+ new_msi_table = riscv_iommu_domain_msi_table(iommu_domain);
+ old_msi_table = riscv_iommu_domain_msi_table(old);
+
+ if (new_msi_table)
+ lockdep_assert_held(&new_msi_table->lock);
+ if (old_msi_table)
+ lockdep_assert_held(&old_msi_table->lock);
+
+ /*
+ * Per-device accounting allows other devices in the same IOMMU group
+ * to move or roll back while this device has forwarded interrupts.
+ */
+ if (new_is_s2 && old_msi_table && info->nr_forwarded_irqs)
+ return false;
+
+ return true;
+}
+
static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
struct device *dev,
struct iommu_domain *old)
@@ -1477,6 +1508,11 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
bond->dev = dev;
flags = riscv_iommu_msi_tables_lock(old, iommu_domain);
+ if (!riscv_iommu_can_attach_paging_domain(iommu_domain, dev, old)) {
+ riscv_iommu_msi_tables_unlock(old, iommu_domain, flags);
+ kfree(bond);
+ return -EBUSY;
+ }
riscv_iommu_bond_link(domain, bond);
riscv_iommu_iodir_update(iommu, dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 05/14] iommu/riscv: Derive the IOMMU from the device in IODIR updates
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (3 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 04/14] iommu/riscv: Reject live S2 replacement with forwarded IRQs Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 06/14] iommu/riscv: Cache the programmed device context Andrew Jones
` (8 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
All callers of riscv_iommu_iodir_update() already pass the managed
device. Derive the IOMMU instance from that device instead of requiring
callers to pass both objects.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index d48667112cd9..2cee87ffc122 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1265,10 +1265,10 @@ static void riscv_iommu_iodir_iotinval(struct riscv_iommu_device *iommu,
* device is not quiesced might be disruptive, potentially causing
* interim translation faults.
*/
-static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
- struct device *dev, struct riscv_iommu_dc *new_dc)
+static void riscv_iommu_iodir_update(struct device *dev, struct riscv_iommu_dc *new_dc)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
struct riscv_iommu_dc *dc;
struct riscv_iommu_command cmd;
bool sync_required = false;
@@ -1514,7 +1514,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
return -EBUSY;
}
riscv_iommu_bond_link(domain, bond);
- riscv_iommu_iodir_update(iommu, dev, &dc);
+ riscv_iommu_iodir_update(dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = domain;
riscv_iommu_msi_tables_unlock(old, iommu_domain, flags);
@@ -1660,7 +1660,6 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
struct device *dev,
struct iommu_domain *old)
{
- struct riscv_iommu_device *iommu = dev_to_iommu(dev);
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
struct riscv_iommu_dc dc = {0};
unsigned long flags;
@@ -1669,7 +1668,7 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
/* Make device context invalid, translation requests will fault w/ #258 */
flags = riscv_iommu_msi_tables_lock(old, NULL);
- riscv_iommu_iodir_update(iommu, dev, &dc);
+ riscv_iommu_iodir_update(dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
riscv_iommu_msi_tables_unlock(old, NULL, flags);
@@ -1688,7 +1687,6 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
struct device *dev,
struct iommu_domain *old)
{
- struct riscv_iommu_device *iommu = dev_to_iommu(dev);
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
struct riscv_iommu_dc dc = {0};
unsigned long flags;
@@ -1697,7 +1695,7 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
dc.ta = RISCV_IOMMU_PC_TA_V;
flags = riscv_iommu_msi_tables_lock(old, NULL);
- riscv_iommu_iodir_update(iommu, dev, &dc);
+ riscv_iommu_iodir_update(dev, &dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
riscv_iommu_msi_tables_unlock(old, NULL, flags);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 06/14] iommu/riscv: Cache the programmed device context
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (4 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 05/14] iommu/riscv: Derive the IOMMU from the device in IODIR updates Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 07/14] iommu/riscv: Prepare MSI table updates for interrupt remapping Andrew Jones
` (7 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
Interrupt-remapping state needs to update the MSI fields of an existing
device context without rebuilding its translation fields. Keep the
software copy in the per-device information.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 2cee87ffc122..86d5795a12dd 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -872,6 +872,7 @@ PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain);
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
struct riscv_iommu_info {
struct riscv_iommu_domain *domain;
+ struct riscv_iommu_dc dc;
unsigned int nr_forwarded_irqs;
};
@@ -1513,8 +1514,9 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
kfree(bond);
return -EBUSY;
}
+ info->dc = dc;
riscv_iommu_bond_link(domain, bond);
- riscv_iommu_iodir_update(dev, &dc);
+ riscv_iommu_iodir_update(dev, &info->dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = domain;
riscv_iommu_msi_tables_unlock(old, iommu_domain, flags);
@@ -1668,7 +1670,8 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
/* Make device context invalid, translation requests will fault w/ #258 */
flags = riscv_iommu_msi_tables_lock(old, NULL);
- riscv_iommu_iodir_update(dev, &dc);
+ info->dc = dc;
+ riscv_iommu_iodir_update(dev, &info->dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
riscv_iommu_msi_tables_unlock(old, NULL, flags);
@@ -1695,7 +1698,8 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
dc.ta = RISCV_IOMMU_PC_TA_V;
flags = riscv_iommu_msi_tables_lock(old, NULL);
- riscv_iommu_iodir_update(dev, &dc);
+ info->dc = dc;
+ riscv_iommu_iodir_update(dev, &info->dc);
riscv_iommu_bond_unlink(info->domain, dev);
info->domain = NULL;
riscv_iommu_msi_tables_unlock(old, NULL, flags);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 07/14] iommu/riscv: Prepare MSI table updates for interrupt remapping
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (5 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 06/14] iommu/riscv: Cache the programmed device context Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 08/14] irqchip/riscv-imsic: Define IOMMU IRQ bypass protocol Andrew Jones
` (6 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
Guest interrupt-file mappings require interrupt-remapping code to
install or remove the MSI page-table pointer and address extraction
fields in every device context attached to a domain. Devices joining an
active MSI domain also need their device contexts initialized, so check
for an active MSI domain at attach time.
Add helpers to publish those fields to bonded devices and to invalidate
one or all MSI PTE translations. These helpers will be used by the IRQ
bypass implementation.
The per-device nr_forwarded_irqs counter is insufficient to detect
active MSI tables for the domain, so we introduce another counter which
is domain-wide. Like info->nr_forwarded_irqs, later patches which
introduce interrupt remapping support will manage the newly introduced
nr_forwarded_irqs counter.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 65 +++++++++++++++++++++++++++++++++++++
drivers/iommu/riscv/iommu.h | 5 +++
2 files changed, 70 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 86d5795a12dd..db4539fbcb95 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1189,6 +1189,28 @@ static void riscv_iommu_iotlb_inval(struct riscv_iommu_domain *domain,
rcu_read_unlock();
}
+void riscv_iommu_msi_table_inval(struct riscv_iommu_msi_table *msi_table, unsigned long addr)
+{
+ struct iommu_iotlb_gather gather = {
+ .start = addr,
+ .end = addr + SZ_4K - 1,
+ .pt.leaf_levels_bitmap = 1,
+ };
+ struct riscv_iommu_domain *domain;
+
+ domain = container_of(msi_table, struct riscv_iommu_domain, msi_table);
+ riscv_iommu_iotlb_inval(domain, &gather);
+}
+
+void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *msi_table)
+{
+ struct iommu_iotlb_gather gather = { 0 };
+ struct riscv_iommu_domain *domain;
+
+ domain = container_of(msi_table, struct riscv_iommu_domain, msi_table);
+ riscv_iommu_iotlb_inval(domain, &gather);
+}
+
#define RISCV_IOMMU_FSC_BARE 0
/*
* This function sends IOTINVAL commands as required by the RISC-V
@@ -1311,6 +1333,11 @@ static void riscv_iommu_iodir_update(struct device *dev, struct riscv_iommu_dc *
WRITE_ONCE(dc->fsc, new_dc->fsc);
WRITE_ONCE(dc->ta, new_dc->ta & RISCV_IOMMU_PC_TA_PSCID);
WRITE_ONCE(dc->iohgatp, new_dc->iohgatp);
+ if (iommu->caps & RISCV_IOMMU_CAPABILITIES_MSI_FLAT) {
+ WRITE_ONCE(dc->msiptp, new_dc->msiptp);
+ WRITE_ONCE(dc->msi_addr_mask, new_dc->msi_addr_mask);
+ WRITE_ONCE(dc->msi_addr_pattern, new_dc->msi_addr_pattern);
+ }
/* Update device context, write TC.V as the last step. */
dma_wmb();
WRITE_ONCE(dc->tc, tc);
@@ -1329,6 +1356,40 @@ static void riscv_iommu_iodir_update(struct device *dev, struct riscv_iommu_dc *
riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
}
+static void riscv_iommu_msi_table_set_dc(struct riscv_iommu_msi_table *msi_table,
+ struct riscv_iommu_dc *dc, bool activate)
+{
+ if (activate) {
+ dc->msiptp = virt_to_pfn(msi_table->root) |
+ FIELD_PREP(RISCV_IOMMU_DC_MSIPTP_MODE,
+ RISCV_IOMMU_DC_MSIPTP_MODE_FLAT);
+ dc->msi_addr_mask = msi_table->msi_addr_mask;
+ dc->msi_addr_pattern = msi_table->msi_addr_pattern;
+ } else {
+ dc->msiptp = 0;
+ dc->msi_addr_mask = 0;
+ dc->msi_addr_pattern = 0;
+ }
+}
+
+void riscv_iommu_msi_table_update(struct riscv_iommu_msi_table *msi_table, bool activate)
+{
+ struct riscv_iommu_domain *domain;
+ struct riscv_iommu_bond *bond;
+
+ /* The MSI table lock excludes bond updates for this domain. */
+ lockdep_assert_held(&msi_table->lock);
+
+ domain = container_of(msi_table, struct riscv_iommu_domain, msi_table);
+
+ list_for_each_entry(bond, &domain->bonds, list) {
+ struct riscv_iommu_info *info = dev_iommu_priv_get(bond->dev);
+
+ riscv_iommu_msi_table_set_dc(msi_table, &info->dc, activate);
+ riscv_iommu_iodir_update(bond->dev, &info->dc);
+ }
+}
+
/*
* IOVA page translation tree management.
*/
@@ -1514,6 +1575,10 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
kfree(bond);
return -EBUSY;
}
+
+ if (domain->msi_table.nr_forwarded_irqs)
+ riscv_iommu_msi_table_set_dc(&domain->msi_table, &dc, true);
+
info->dc = dc;
riscv_iommu_bond_link(domain, bond);
riscv_iommu_iodir_update(dev, &info->dc);
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 2876703a6698..deb57b6a6c4b 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -71,6 +71,7 @@ struct riscv_iommu_device {
struct riscv_iommu_msi_table {
/* Protects attachment, interrupt forwarding state, and MSI PTE updates. */
raw_spinlock_t lock;
+ unsigned int nr_forwarded_irqs;
unsigned int nr_ptes;
struct riscv_iommu_msipte *root;
u64 msi_addr_mask;
@@ -81,6 +82,10 @@ 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);
+void riscv_iommu_msi_table_inval(struct riscv_iommu_msi_table *msi_table, unsigned long addr);
+void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *msi_table);
+void riscv_iommu_msi_table_update(struct riscv_iommu_msi_table *msi_table, bool activate);
+
#define riscv_iommu_readl(iommu, addr) \
readl_relaxed((iommu)->reg + (addr))
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 08/14] irqchip/riscv-imsic: Define IOMMU IRQ bypass protocol
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (6 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 07/14] iommu/riscv: Prepare MSI table updates for interrupt remapping Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 09/14] genirq/msi: Provide DOMAIN_BUS_MSI_REMAP Andrew Jones
` (5 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
Define the RISC-V IOMMU payload for irq_set_vcpu_affinity(), including
domain-wide target setup and individual target updates for IMSIC and
MRIF targets.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
include/linux/irqchip/riscv-imsic.h | 52 +++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index 662cb0442424..d024a6524baa 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -104,4 +104,56 @@ struct fwnode_handle *imsic_acpi_get_fwnode(struct device *dev);
static inline struct fwnode_handle *imsic_acpi_get_fwnode(struct device *dev) { return NULL; }
#endif
+enum riscv_iommu_ir_cmd {
+ RISCV_IOMMU_IR_INVALID_CMD,
+ RISCV_IOMMU_IR_FORWARD,
+ RISCV_IOMMU_IR_UPDATE_TARGET,
+};
+
+enum riscv_iommu_ir_target_type {
+ RISCV_IOMMU_IR_TARGET_INVALID,
+ RISCV_IOMMU_IR_TARGET_IMSIC,
+ RISCV_IOMMU_IR_TARGET_MRIF,
+};
+
+struct riscv_iommu_ir_target {
+ u64 gpa;
+ enum riscv_iommu_ir_target_type type;
+ union {
+ u64 hpa;
+ struct {
+ u64 mrif_hpa;
+ u64 notice_hpa;
+ u32 notice_id;
+ };
+ };
+};
+
+/*
+ * FORWARD enables forwarding for one IRQ and provides the complete target array
+ * so the first forwarded IRQ can initialize the domain MSI table. UPDATE_TARGET
+ * replaces one target without changing per-IRQ forwarding state. The caller
+ * must quiesce all MSI producers using the table when changing both words of a
+ * live MRIF PTE because the update temporarily invalidates the entry. A NULL
+ * vcpu_info disables forwarding for the IRQ.
+ *
+ * A well-formed request which is incompatible with the producer or current
+ * domain configuration returns -EOPNOTSUPP, allowing the caller to fall back
+ * to host delivery. Invalid payloads return -EINVAL and operational errors are
+ * propagated.
+ */
+struct riscv_iommu_ir_vcpu_info {
+ enum riscv_iommu_ir_cmd cmd;
+ const void *owner;
+ u64 msi_addr_mask;
+ u64 msi_addr_pattern;
+ union {
+ struct {
+ const struct riscv_iommu_ir_target *targets;
+ unsigned int nr_targets;
+ };
+ struct riscv_iommu_ir_target target;
+ };
+};
+
#endif
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 09/14] genirq/msi: Provide DOMAIN_BUS_MSI_REMAP
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (7 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 08/14] irqchip/riscv-imsic: Define IOMMU IRQ bypass protocol Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 10/14] iommu/riscv: Add IRQ domain for interrupt remapping Andrew Jones
` (4 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li, Nutty Liu
Add a bus token for MSI domains that remap interrupts, needed by the
upcoming RISC-V IOMMU interrupt-remapping domain to distinguish itself
from NEXUS domains. The token is generic because remapping itself is
the only property that needs to be conveyed -- there is nothing
RISC-V-specific about it.
Such a domain implements init_dev_msi_info() via
msi_parent_init_dev_msi_info(), which leaves 'domain' pointing at the
NEXUS domain while 'real_parent' points at the remap domain itself.
Accept that combination in msi_lib_init_dev_msi_info(); no other
msi-lib changes are needed.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
---
drivers/irqchip/irq-msi-lib.c | 8 ++++----
include/linux/irqdomain_defs.h | 1 +
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c
index 45e0ed3134ce..9027dfbab15e 100644
--- a/drivers/irqchip/irq-msi-lib.c
+++ b/drivers/irqchip/irq-msi-lib.c
@@ -36,14 +36,14 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
return false;
/*
- * MSI parent domain specific settings. For now there is only the
- * root parent domain, e.g. NEXUS, acting as a MSI parent, but it is
- * possible to stack MSI parents. See x86 vector -> irq remapping
+ * MSI parent domain specific settings. There may be only the root
+ * parent domain, e.g. NEXUS, acting as a MSI parent, or there may
+ * be stacked MSI parents, typically used for remapping.
*/
if (domain->bus_token == pops->bus_select_token) {
if (WARN_ON_ONCE(domain != real_parent))
return false;
- } else {
+ } else if (real_parent->bus_token != DOMAIN_BUS_MSI_REMAP) {
WARN_ON_ONCE(1);
return false;
}
diff --git a/include/linux/irqdomain_defs.h b/include/linux/irqdomain_defs.h
index 3a03bdfeeee9..954cf585b3c4 100644
--- a/include/linux/irqdomain_defs.h
+++ b/include/linux/irqdomain_defs.h
@@ -26,6 +26,7 @@ enum irq_domain_bus_token {
DOMAIN_BUS_AMDVI,
DOMAIN_BUS_DEVICE_MSI,
DOMAIN_BUS_WIRED_TO_MSI,
+ DOMAIN_BUS_MSI_REMAP,
};
#endif /* _LINUX_IRQDOMAIN_DEFS_H */
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 10/14] iommu/riscv: Add IRQ domain for interrupt remapping
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (8 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 09/14] genirq/msi: Provide DOMAIN_BUS_MSI_REMAP Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 11/14] iommu/riscv: Prepare info->domain for concurrent RCU access Andrew Jones
` (3 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
Create a per-IOMMU MSI parent irqdomain as the hierarchy hook for
future interrupt remapping. The remapping tables will be owned by the
attached paging domain since the MSI mappings live in its MSI table.
The IRQ domain is created lazily from probe_device(), installed on
eligible devices for their managed lifetime, and removed with the
physical IOMMU. This is only the initial skeleton: it does not remap
interrupts yet, and non-paging IOMMU domains will fall back to the raw
IMSIC physical address when remapping is added.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/Makefile | 1 +
drivers/iommu/riscv/iommu-ir.c | 148 ++++++++++++++++++++++++
drivers/iommu/riscv/iommu.c | 25 ++--
drivers/iommu/riscv/iommu.h | 36 ++++++
drivers/irqchip/irq-riscv-imsic-state.c | 6 +
include/linux/irqchip/riscv-imsic.h | 8 ++
6 files changed, 213 insertions(+), 11 deletions(-)
create mode 100644 drivers/iommu/riscv/iommu-ir.c
diff --git a/drivers/iommu/riscv/Makefile b/drivers/iommu/riscv/Makefile
index b5929f9f23e6..891810146fce 100644
--- a/drivers/iommu/riscv/Makefile
+++ b/drivers/iommu/riscv/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += iommu.o iommu-platform.o
obj-$(CONFIG_RISCV_IOMMU_PCI) += iommu-pci.o
+obj-$(CONFIG_RISCV_IMSIC) += iommu-ir.o
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
new file mode 100644
index 000000000000..c5603cb9f258
--- /dev/null
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU Interrupt Remapping
+ *
+ * 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"
+
+static struct irq_chip riscv_iommu_ir_irq_chip = {
+ .name = "IOMMU-IR",
+ .irq_ack = irq_chip_ack_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+};
+
+static int riscv_iommu_ir_irq_domain_alloc_irqs(struct irq_domain *irqdomain,
+ unsigned int irq_base, unsigned int nr_irqs,
+ void *arg)
+{
+ int ret;
+
+ ret = irq_domain_alloc_irqs_parent(irqdomain, irq_base, nr_irqs, arg);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < nr_irqs; i++) {
+ ret = irq_domain_set_hwirq_and_chip(irqdomain, irq_base + i,
+ irq_base + i,
+ &riscv_iommu_ir_irq_chip, NULL);
+ if (ret) {
+ irq_domain_free_irqs_parent(irqdomain, irq_base, nr_irqs);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops riscv_iommu_ir_irq_domain_ops = {
+ .alloc = riscv_iommu_ir_irq_domain_alloc_irqs,
+ .free = irq_domain_free_irqs_parent,
+};
+
+static const struct msi_parent_ops riscv_iommu_ir_msi_parent_ops = {
+ .prefix = "IR-",
+ .supported_flags = MSI_GENERIC_FLAGS_MASK |
+ MSI_FLAG_PCI_MSIX,
+ .required_flags = MSI_FLAG_USE_DEF_DOM_OPS |
+ MSI_FLAG_USE_DEF_CHIP_OPS |
+ MSI_FLAG_PCI_MSI_MASK_PARENT,
+ .chip_flags = MSI_CHIP_FLAG_SET_ACK,
+ .init_dev_msi_info = msi_parent_init_dev_msi_info,
+};
+
+static struct irq_domain *riscv_iommu_ir_irq_domain_create(struct riscv_iommu_device *iommu,
+ struct irq_domain *irqparent)
+{
+ char *fwname __free(kfree) = NULL;
+ struct irq_domain *irqdomain;
+ struct fwnode_handle *fn;
+
+ fwname = kasprintf(GFP_KERNEL, "IOMMU-IR-%s", dev_name(iommu->dev));
+ if (!fwname)
+ return ERR_PTR(-ENOMEM);
+
+ fn = irq_domain_alloc_named_fwnode(fwname);
+ if (!fn)
+ return ERR_PTR(-ENOMEM);
+
+ irqdomain = irq_domain_create_hierarchy(irqparent, 0, 0, fn,
+ &riscv_iommu_ir_irq_domain_ops, iommu);
+ if (!irqdomain) {
+ irq_domain_free_fwnode(fn);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /*
+ * The RISC-V IOMMU doesn't validate MSI data, so we can't set
+ * IRQ_DOMAIN_FLAG_ISOLATED_MSI. The means VFIO requires its
+ * allow_unsafe_interrupts module parameter.
+ */
+ irqdomain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
+ irqdomain->msi_parent_ops = &riscv_iommu_ir_msi_parent_ops;
+ irq_domain_update_bus_token(irqdomain, DOMAIN_BUS_MSI_REMAP);
+
+ return irqdomain;
+}
+
+void riscv_iommu_ir_irq_domain_remove(struct riscv_iommu_device *iommu)
+{
+ struct irq_domain *irqdomain = iommu->irqdomain;
+ struct fwnode_handle *fn;
+
+ if (!irqdomain)
+ return;
+
+ fn = irqdomain->fwnode;
+ irq_domain_remove(irqdomain);
+ iommu->irqdomain = NULL;
+ irq_domain_free_fwnode(fn);
+}
+
+int riscv_iommu_ir_probe_device(struct riscv_iommu_device *iommu, struct device *dev,
+ struct riscv_iommu_info *info)
+{
+ struct irq_domain *msi_parent = dev_get_msi_domain(iommu->dev);
+ struct irq_domain *irqdomain;
+
+ info->old_msi_parent = NULL;
+
+ if (iommu->fctl & RISCV_IOMMU_FCTL_WSI)
+ msi_parent = imsic_get_base_domain();
+ else if (!imsic_dev_has_imsic_msi_parent(iommu->dev))
+ return 0;
+
+ if (!msi_parent || dev_get_msi_domain(dev) != msi_parent)
+ return 0;
+
+ irqdomain = iommu->irqdomain;
+ if (!irqdomain) {
+ irqdomain = riscv_iommu_ir_irq_domain_create(iommu, msi_parent);
+ if (IS_ERR(irqdomain))
+ return PTR_ERR(irqdomain);
+ iommu->irqdomain = irqdomain;
+ } else if (irqdomain->parent != msi_parent) {
+ return 0;
+ }
+
+ if (!device_link_add(dev, iommu->dev, DL_FLAG_AUTOREMOVE_SUPPLIER))
+ return -ENODEV;
+
+ info->old_msi_parent = msi_parent;
+ dev_set_msi_domain(dev, irqdomain);
+
+ return 0;
+}
+
+void riscv_iommu_ir_release_device(struct device *dev, struct riscv_iommu_info *info)
+{
+ if (info->old_msi_parent)
+ dev_set_msi_domain(dev, info->old_msi_parent);
+}
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index db4539fbcb95..7a8b40d311ea 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -869,13 +869,6 @@ 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 *domain;
- struct riscv_iommu_dc dc;
- unsigned int nr_forwarded_irqs;
-};
-
static struct riscv_iommu_msi_table *riscv_iommu_domain_msi_table(struct iommu_domain *iommu_domain)
{
struct riscv_iommu_domain *domain;
@@ -1465,13 +1458,15 @@ static bool riscv_iommu_iohgatp_supported(struct riscv_iommu_device *iommu,
}
static int riscv_iommu_msi_table_alloc(struct riscv_iommu_domain *domain,
- struct riscv_iommu_device *iommu)
+ struct riscv_iommu_device *iommu,
+ struct riscv_iommu_info *info)
{
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))
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_MSI_FLAT) ||
+ !riscv_iommu_ir_device_enabled(info))
return 0;
guard(mutex)(&domain->mutex);
@@ -1544,7 +1539,7 @@ 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);
+ ret = riscv_iommu_msi_table_alloc(domain, iommu, info);
if (ret)
return ret;
@@ -1811,8 +1806,8 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev)
struct riscv_iommu_device *iommu;
struct riscv_iommu_info *info;
struct riscv_iommu_dc *dc;
+ int i, ret;
u64 tc;
- int i;
if (!fwspec || !fwspec->iommu_fwnode->dev || !fwspec->num_ids)
return ERR_PTR(-ENODEV);
@@ -1847,6 +1842,12 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev)
WRITE_ONCE(dc->tc, tc);
}
+ ret = riscv_iommu_ir_probe_device(iommu, dev, info);
+ if (ret) {
+ kfree(info);
+ return ERR_PTR(ret);
+ }
+
dev_iommu_priv_set(dev, info);
return &iommu->iommu;
@@ -1856,6 +1857,7 @@ static void riscv_iommu_release_device(struct device *dev)
{
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ riscv_iommu_ir_release_device(dev, info);
kfree_rcu_mightsleep(info);
}
@@ -1950,6 +1952,7 @@ void riscv_iommu_remove(struct riscv_iommu_device *iommu)
riscv_iommu_iodir_set_mode(iommu, RISCV_IOMMU_DDTP_IOMMU_MODE_OFF);
riscv_iommu_queue_disable(&iommu->cmdq);
riscv_iommu_queue_disable(&iommu->fltq);
+ riscv_iommu_ir_irq_domain_remove(iommu);
}
int riscv_iommu_init(struct riscv_iommu_device *iommu)
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index deb57b6a6c4b..4c1c681ba2a2 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -15,6 +15,7 @@
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/iopoll.h>
+#include <linux/irqdomain.h>
#include <linux/sizes.h>
#include "iommu-bits.h"
@@ -23,6 +24,7 @@
#define RISCV_IOMMU_MSI_IOVA_BASE SZ_16M
struct riscv_iommu_device;
+struct riscv_iommu_domain;
struct riscv_iommu_queue {
atomic_t prod; /* unbounded producer allocation index */
@@ -66,6 +68,9 @@ struct riscv_iommu_device {
unsigned int ddt_mode;
dma_addr_t ddt_phys;
u64 *ddt_root;
+
+ /* MSI remapping */
+ struct irq_domain *irqdomain;
};
struct riscv_iommu_msi_table {
@@ -78,6 +83,19 @@ struct riscv_iommu_msi_table {
u64 msi_addr_pattern;
};
+/* Private IOMMU data for managed devices, dev_iommu_priv_* */
+struct riscv_iommu_info {
+ struct riscv_iommu_domain *domain;
+ struct riscv_iommu_dc dc;
+ struct irq_domain *old_msi_parent;
+ unsigned int nr_forwarded_irqs;
+};
+
+static inline bool riscv_iommu_ir_device_enabled(const struct riscv_iommu_info *info)
+{
+ return info->old_msi_parent != NULL;
+}
+
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);
@@ -86,6 +104,24 @@ void riscv_iommu_msi_table_inval(struct riscv_iommu_msi_table *msi_table, unsign
void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *msi_table);
void riscv_iommu_msi_table_update(struct riscv_iommu_msi_table *msi_table, bool activate);
+#ifdef CONFIG_RISCV_IMSIC
+void riscv_iommu_ir_irq_domain_remove(struct riscv_iommu_device *iommu);
+int riscv_iommu_ir_probe_device(struct riscv_iommu_device *iommu, struct device *dev,
+ struct riscv_iommu_info *info);
+void riscv_iommu_ir_release_device(struct device *dev, struct riscv_iommu_info *info);
+#else
+static inline void riscv_iommu_ir_irq_domain_remove(struct riscv_iommu_device *iommu) { }
+static inline int riscv_iommu_ir_probe_device(struct riscv_iommu_device *iommu,
+ struct device *dev,
+ struct riscv_iommu_info *info)
+{
+ info->old_msi_parent = NULL;
+ return 0;
+}
+static inline void riscv_iommu_ir_release_device(struct device *dev,
+ struct riscv_iommu_info *info) { }
+#endif
+
#define riscv_iommu_readl(iommu, addr) \
readl_relaxed((iommu)->reg + (addr))
diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c
index abd1cdb640ea..0e6c86840242 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.c
+++ b/drivers/irqchip/irq-riscv-imsic-state.c
@@ -64,6 +64,12 @@ const struct imsic_global_config *imsic_get_global_config(void)
}
EXPORT_SYMBOL_GPL(imsic_get_global_config);
+struct irq_domain *imsic_get_base_domain(void)
+{
+ return imsic ? imsic->base_domain : NULL;
+}
+EXPORT_SYMBOL_GPL(imsic_get_base_domain);
+
/**
* imsic_dev_has_imsic_msi_parent - Check for an IMSIC MSI parent
* @dev: Device to check
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index d024a6524baa..02a836ce03b4 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -11,6 +11,8 @@
#include <linux/device.h>
#include <linux/fwnode.h>
+struct irq_domain;
+
#define IMSIC_MMIO_PAGE_SHIFT 12
#define IMSIC_MMIO_PAGE_SZ BIT(IMSIC_MMIO_PAGE_SHIFT)
#define IMSIC_MMIO_PAGE_LE 0x00
@@ -81,6 +83,7 @@ struct imsic_global_config {
#ifdef CONFIG_RISCV_IMSIC
const struct imsic_global_config *imsic_get_global_config(void);
+struct irq_domain *imsic_get_base_domain(void);
bool imsic_dev_has_imsic_msi_parent(struct device *dev);
#else
@@ -90,6 +93,11 @@ static inline const struct imsic_global_config *imsic_get_global_config(void)
return NULL;
}
+static inline struct irq_domain *imsic_get_base_domain(void)
+{
+ return NULL;
+}
+
static inline bool imsic_dev_has_imsic_msi_parent(struct device *dev)
{
return false;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 11/14] iommu/riscv: Prepare info->domain for concurrent RCU access
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (9 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 10/14] iommu/riscv: Add IRQ domain for interrupt remapping Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 12/14] iommu/riscv: Prepare interrupt remapping for IRQ bypass Andrew Jones
` (2 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
Upcoming interrupt-remapping callbacks need to resolve a device's
currently attached domain from IRQ context, concurrent with domain
switches.
Protect info->domain with RCU and wait for readers before freeing paging
domains. Provide an RCU accessor for the domain-owned MSI table so
interrupt-remapping code does not need the private domain definition.
Use the stable old domain supplied by the attachment callback when
unlinking its bond instead of dereferencing the now RCU-protected
info->domain.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu.c | 34 +++++++++++++++++++++++++---------
drivers/iommu/riscv/iommu.h | 5 ++++-
2 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 7a8b40d311ea..514470291fd9 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -869,6 +869,16 @@ 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)
+struct riscv_iommu_msi_table *riscv_iommu_msi_table_rcu(struct riscv_iommu_info *info)
+{
+ struct riscv_iommu_domain *domain;
+
+ lockdep_assert_in_rcu_read_lock();
+ domain = rcu_dereference(info->domain);
+
+ return domain ? &domain->msi_table : NULL;
+}
+
static struct riscv_iommu_msi_table *riscv_iommu_domain_msi_table(struct iommu_domain *iommu_domain)
{
struct riscv_iommu_domain *domain;
@@ -968,18 +978,22 @@ static void riscv_iommu_bond_link(struct riscv_iommu_domain *domain,
smp_mb();
}
-static void riscv_iommu_bond_unlink(struct riscv_iommu_domain *domain,
+static void riscv_iommu_bond_unlink(struct iommu_domain *iommu_domain,
struct device *dev)
{
- struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+ struct riscv_iommu_domain *domain;
+ struct riscv_iommu_device *iommu;
struct riscv_iommu_bond *bond, *found = NULL;
struct riscv_iommu_command cmd;
unsigned long flags;
int count = 0;
- if (!domain)
+ if (!iommu_domain || !(iommu_domain->type & __IOMMU_DOMAIN_PAGING))
return;
+ domain = iommu_domain_to_riscv(iommu_domain);
+ iommu = dev_to_iommu(dev);
+
raw_spin_lock_irqsave(&domain->lock, flags);
list_for_each_entry(bond, &domain->bonds, list) {
if (found && count)
@@ -1415,6 +1429,8 @@ static void riscv_iommu_free_paging_domain(struct iommu_domain *iommu_domain)
WARN_ON(!list_empty(&domain->bonds));
+ synchronize_rcu();
+
if (domain->pscid > 0)
ida_free(&riscv_iommu_pscids, domain->pscid);
if (domain->gscid > 0)
@@ -1577,8 +1593,8 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
info->dc = dc;
riscv_iommu_bond_link(domain, bond);
riscv_iommu_iodir_update(dev, &info->dc);
- riscv_iommu_bond_unlink(info->domain, dev);
- info->domain = domain;
+ riscv_iommu_bond_unlink(old, dev);
+ rcu_assign_pointer(info->domain, domain);
riscv_iommu_msi_tables_unlock(old, iommu_domain, flags);
return 0;
@@ -1732,8 +1748,8 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
flags = riscv_iommu_msi_tables_lock(old, NULL);
info->dc = dc;
riscv_iommu_iodir_update(dev, &info->dc);
- riscv_iommu_bond_unlink(info->domain, dev);
- info->domain = NULL;
+ riscv_iommu_bond_unlink(old, dev);
+ rcu_assign_pointer(info->domain, NULL);
riscv_iommu_msi_tables_unlock(old, NULL, flags);
return 0;
@@ -1760,8 +1776,8 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
flags = riscv_iommu_msi_tables_lock(old, NULL);
info->dc = dc;
riscv_iommu_iodir_update(dev, &info->dc);
- riscv_iommu_bond_unlink(info->domain, dev);
- info->domain = NULL;
+ riscv_iommu_bond_unlink(old, dev);
+ rcu_assign_pointer(info->domain, NULL);
riscv_iommu_msi_tables_unlock(old, NULL, flags);
return 0;
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 4c1c681ba2a2..ed973979d795 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -16,6 +16,7 @@
#include <linux/types.h>
#include <linux/iopoll.h>
#include <linux/irqdomain.h>
+#include <linux/rcupdate.h>
#include <linux/sizes.h>
#include "iommu-bits.h"
@@ -85,7 +86,7 @@ struct riscv_iommu_msi_table {
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
struct riscv_iommu_info {
- struct riscv_iommu_domain *domain;
+ struct riscv_iommu_domain __rcu *domain;
struct riscv_iommu_dc dc;
struct irq_domain *old_msi_parent;
unsigned int nr_forwarded_irqs;
@@ -100,6 +101,8 @@ 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);
+/* Caller must hold rcu_read_lock() while using the returned pointer. */
+struct riscv_iommu_msi_table *riscv_iommu_msi_table_rcu(struct riscv_iommu_info *info);
void riscv_iommu_msi_table_inval(struct riscv_iommu_msi_table *msi_table, unsigned long addr);
void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *msi_table);
void riscv_iommu_msi_table_update(struct riscv_iommu_msi_table *msi_table, bool activate);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 12/14] iommu/riscv: Prepare interrupt remapping for IRQ bypass
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (10 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 11/14] iommu/riscv: Prepare info->domain for concurrent RCU access Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 13/14] iommu/riscv: Validate IRQ forwarding requests Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 14/14] iommu/riscv: Implement IRQ forwarding Andrew Jones
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
IRQ bypass changes the meaning of an interrupt from host delivery to
delivery through the guest's MSI table. The transition therefore has to
follow the device's current IOMMU domain and must not race with a domain
replacement.
Wire the interrupt-remapping irqdomain into irq_set_vcpu_affinity() and
serialize the callback with MSI-table publication and attachment. Since
one table is shared by every device in the domain, also require all
forwarded interrupts to agree on its owner and address layout.
Leave the hardware operations as stubs so the locking and state-machine
requirements are established before MSI PTE programming is added.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu-ir.c | 140 ++++++++++++++++++++++++++++++++-
drivers/iommu/riscv/iommu.h | 1 +
2 files changed, 140 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
index c5603cb9f258..22da76987c7c 100644
--- a/drivers/iommu/riscv/iommu-ir.c
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -11,12 +11,150 @@
#include "iommu.h"
+static int riscv_iommu_ir_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *mask, bool force)
+{
+ if (irqd_is_forwarded_to_vcpu(data))
+ return -EINVAL;
+
+ return irq_chip_set_affinity_parent(data, mask, force);
+}
+
+static int riscv_iommu_ir_activate(struct riscv_iommu_msi_table *msi_table,
+ struct riscv_iommu_ir_vcpu_info *vcpu_info)
+{
+ return -EOPNOTSUPP;
+}
+
+static int riscv_iommu_ir_deactivate(struct riscv_iommu_msi_table *msi_table)
+{
+ return -EOPNOTSUPP;
+}
+
+static int riscv_iommu_ir_update_target(struct riscv_iommu_msi_table *msi_table,
+ struct riscv_iommu_ir_vcpu_info *vcpu_info)
+{
+ return -EOPNOTSUPP;
+}
+
+static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
+ struct riscv_iommu_info *info,
+ struct riscv_iommu_ir_vcpu_info *vcpu_info,
+ struct riscv_iommu_msi_table *msi_table)
+{
+ int ret;
+
+ if (!vcpu_info) {
+ if (WARN_ON_ONCE(!msi_table->nr_forwarded_irqs || !info->nr_forwarded_irqs))
+ return -EINVAL;
+
+ if (msi_table->nr_forwarded_irqs == 1) {
+ ret = riscv_iommu_ir_deactivate(msi_table);
+ if (ret)
+ return ret;
+ }
+
+ msi_table->nr_forwarded_irqs--;
+ info->nr_forwarded_irqs--;
+ irqd_clr_forwarded_to_vcpu(data);
+
+ if (!msi_table->nr_forwarded_irqs) {
+ msi_table->owner = NULL;
+ msi_table->msi_addr_mask = 0;
+ msi_table->msi_addr_pattern = 0;
+ }
+
+ return 0;
+ }
+
+ if (!msi_table->nr_forwarded_irqs) {
+ if (vcpu_info->cmd == RISCV_IOMMU_IR_UPDATE_TARGET ||
+ irqd_is_forwarded_to_vcpu(data))
+ return -EINVAL;
+
+ ret = riscv_iommu_ir_activate(msi_table, vcpu_info);
+ if (ret)
+ return ret;
+
+ msi_table->nr_forwarded_irqs++;
+ info->nr_forwarded_irqs++;
+ irqd_set_forwarded_to_vcpu(data);
+
+ return 0;
+ }
+
+ if (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)
+ return -EOPNOTSUPP;
+
+ if (vcpu_info->cmd == RISCV_IOMMU_IR_FORWARD) {
+ if (!irqd_is_forwarded_to_vcpu(data)) {
+ msi_table->nr_forwarded_irqs++;
+ info->nr_forwarded_irqs++;
+ irqd_set_forwarded_to_vcpu(data);
+ }
+ return 0;
+ }
+
+ return riscv_iommu_ir_update_target(msi_table, vcpu_info);
+}
+
+static int riscv_iommu_ir_irq_set_vcpu_affinity(struct irq_data *data, void *arg)
+{
+ struct riscv_iommu_ir_vcpu_info *vcpu_info = arg;
+ struct riscv_iommu_msi_table *msi_table;
+ struct riscv_iommu_info *info;
+ struct msi_desc *desc;
+ struct device *dev;
+ int ret;
+
+ if (!vcpu_info && !irqd_is_forwarded_to_vcpu(data))
+ return 0;
+
+ if (vcpu_info && vcpu_info->cmd != RISCV_IOMMU_IR_FORWARD &&
+ (vcpu_info->cmd != RISCV_IOMMU_IR_UPDATE_TARGET || !irqd_is_forwarded_to_vcpu(data)))
+ return -EINVAL;
+
+ desc = irq_data_get_msi_desc(data);
+ if (WARN_ON_ONCE(!desc))
+ return -EINVAL;
+
+ dev = msi_desc_to_dev(desc);
+ info = dev_iommu_priv_get(dev);
+ if (WARN_ON_ONCE(!info))
+ return -EINVAL;
+
+ scoped_guard(rcu) {
+ /*
+ * RCU keeps the table alive, but the device may switch domains before
+ * the table is locked. Recheck the association under the lock.
+ */
+ for (;;) {
+ msi_table = riscv_iommu_msi_table_rcu(info);
+ if (!msi_table || !msi_table->root)
+ return -EOPNOTSUPP;
+
+ raw_spin_lock(&msi_table->lock);
+ if (msi_table == riscv_iommu_msi_table_rcu(info))
+ break;
+ raw_spin_unlock(&msi_table->lock);
+ }
+ }
+
+ ret = riscv_iommu_ir_irq_set_vcpu_affinity_locked(data, info, vcpu_info, msi_table);
+ raw_spin_unlock(&msi_table->lock);
+
+ return ret;
+}
+
static struct irq_chip riscv_iommu_ir_irq_chip = {
.name = "IOMMU-IR",
.irq_ack = irq_chip_ack_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
- .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_affinity = riscv_iommu_ir_irq_set_affinity,
+ .irq_set_vcpu_affinity = riscv_iommu_ir_irq_set_vcpu_affinity,
};
static int riscv_iommu_ir_irq_domain_alloc_irqs(struct irq_domain *irqdomain,
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index ed973979d795..a42f0b6a88d4 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -82,6 +82,7 @@ struct riscv_iommu_msi_table {
struct riscv_iommu_msipte *root;
u64 msi_addr_mask;
u64 msi_addr_pattern;
+ const void *owner;
};
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 13/14] iommu/riscv: Validate IRQ forwarding requests
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (11 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 12/14] iommu/riscv: Prepare interrupt remapping for IRQ bypass Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 14/14] iommu/riscv: Implement IRQ forwarding Andrew Jones
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
irq_set_vcpu_affinity() receives an architecture-specific description
of a guest MSI topology. Invalid address layouts or targets could select
the wrong MSI PTE, while accepting a mode unsupported by one of the
IOMMUs serving a shared domain could make interrupt delivery fail only
for some devices.
Validate the request before the table can be activated or updated and
distinguish malformed input from a valid configuration that hardware
cannot support. Check capabilities across all IOMMUs bonded to the
domain, and retain the requirements of an active table so later target
updates and device attachments cannot weaken them.
Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
---
drivers/iommu/riscv/iommu-ir.c | 127 ++++++++++++++++++++++++++++++++-
drivers/iommu/riscv/iommu.c | 31 ++++++++
drivers/iommu/riscv/iommu.h | 2 +
3 files changed, 159 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
index 22da76987c7c..12a5d0bc77f2 100644
--- a/drivers/iommu/riscv/iommu-ir.c
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -20,9 +20,120 @@ static int riscv_iommu_ir_irq_set_affinity(struct irq_data *data,
return irq_chip_set_affinity_parent(data, mask, force);
}
+/* IOMMU extract (RISC-V IOMMU spec section 2.3.3) applied to the two-run IMSIC topology */
+static size_t riscv_iommu_ir_extract(u64 value, u64 mask)
+{
+ /* Separate the optional group index from the low guest and hart indices. */
+ u64 upper_mask = mask & (mask + 1);
+ u64 lower_mask = mask ^ upper_mask;
+ unsigned int shift;
+
+ if (!upper_mask)
+ return value & lower_mask;
+
+ shift = __ffs64(upper_mask) - fls64(lower_mask);
+ return (value & lower_mask) | ((value & upper_mask) >> shift);
+}
+
+static size_t riscv_iommu_ir_nr_ptes(const struct riscv_iommu_ir_vcpu_info *vcpu_info)
+{
+ return riscv_iommu_ir_extract(vcpu_info->msi_addr_mask, vcpu_info->msi_addr_mask) + 1;
+}
+
+static int riscv_iommu_ir_validate_target(const struct riscv_iommu_ir_vcpu_info *vcpu_info,
+ const struct riscv_iommu_ir_target *target,
+ u64 *required_caps)
+{
+ u64 addr = target->gpa >> IMSIC_MMIO_PAGE_SHIFT;
+
+ if (!IS_ALIGNED(target->gpa, IMSIC_MMIO_PAGE_SZ) ||
+ (addr & ~vcpu_info->msi_addr_mask) != vcpu_info->msi_addr_pattern)
+ return -EINVAL;
+
+ switch (target->type) {
+ case RISCV_IOMMU_IR_TARGET_IMSIC:
+ if (!IS_ALIGNED(target->hpa, IMSIC_MMIO_PAGE_SZ) ||
+ (target->hpa >> IMSIC_MMIO_PAGE_SHIFT) > FIELD_MAX(RISCV_IOMMU_MSIPTE_PPN))
+ return -EINVAL;
+ *required_caps |= RISCV_IOMMU_CAPABILITIES_MSI_FLAT;
+ break;
+ case RISCV_IOMMU_IR_TARGET_MRIF:
+ if (!IS_ALIGNED(target->mrif_hpa, SZ_512) ||
+ (target->mrif_hpa >> 9) > FIELD_MAX(RISCV_IOMMU_MSIPTE_MRIF_ADDR) ||
+ !IS_ALIGNED(target->notice_hpa, IMSIC_MMIO_PAGE_SZ) ||
+ (target->notice_hpa >> IMSIC_MMIO_PAGE_SHIFT) >
+ FIELD_MAX(RISCV_IOMMU_MSIPTE_MRIF_NPPN) ||
+ target->notice_id >= IMSIC_MAX_ID)
+ return -EINVAL;
+ *required_caps |= RISCV_IOMMU_CAPABILITIES_MSI_FLAT |
+ RISCV_IOMMU_CAPABILITIES_MSI_MRIF;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int riscv_iommu_ir_validate_targets(const struct riscv_iommu_ir_vcpu_info *vcpu_info,
+ size_t nr_ptes, u64 *required_caps)
+{
+ int ret;
+
+ if (!vcpu_info->targets || !vcpu_info->nr_targets || vcpu_info->nr_targets > nr_ptes)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < vcpu_info->nr_targets; i++) {
+ ret = riscv_iommu_ir_validate_target(vcpu_info, &vcpu_info->targets[i],
+ required_caps);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
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)
{
+ u64 pattern = vcpu_info->msi_addr_pattern;
+ u64 mask = vcpu_info->msi_addr_mask;
+ u64 required_caps = 0;
+ size_t nr_ptes;
+ int ret;
+
+ if (!vcpu_info->owner || (pattern & mask) ||
+ ((pattern | mask) & ~RISCV_IOMMU_DC_MSI_ADDR_MASK))
+ return -EINVAL;
+
+ /*
+ * riscv_iommu_ir_extract() requires the mask to contain the low
+ * guest/hart index run and at most one additional group index run.
+ */
+ mask &= mask + 1;
+ if (mask) {
+ mask >>= __ffs64(mask);
+ if (mask & (mask + 1))
+ return -EINVAL;
+ }
+
+ nr_ptes = riscv_iommu_ir_nr_ptes(vcpu_info);
+
+ ret = riscv_iommu_ir_validate_targets(vcpu_info, nr_ptes, &required_caps);
+ if (ret)
+ return ret;
+
+ if (!riscv_iommu_msi_table_check_caps(msi_table, required_caps))
+ return -EOPNOTSUPP;
+
+ if (nr_ptes > msi_table->nr_ptes) {
+ dev_warn_once(iommu->dev,
+ "guest MSI topology requires %zu PTEs, but the IOMMU domain only supports %u; using host IRQ delivery\n",
+ nr_ptes, msi_table->nr_ptes);
+ return -EOPNOTSUPP;
+ }
+
return -EOPNOTSUPP;
}
@@ -34,6 +145,18 @@ static int riscv_iommu_ir_deactivate(struct riscv_iommu_msi_table *msi_table)
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;
+ u64 required_caps = 0;
+ int ret;
+
+ ret = riscv_iommu_ir_validate_target(vcpu_info, target, &required_caps);
+ if (ret)
+ return ret;
+
+ required_caps |= msi_table->required_caps;
+ if (!riscv_iommu_msi_table_check_caps(msi_table, required_caps))
+ return -EOPNOTSUPP;
+
return -EOPNOTSUPP;
}
@@ -42,6 +165,7 @@ static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
struct riscv_iommu_ir_vcpu_info *vcpu_info,
struct riscv_iommu_msi_table *msi_table)
{
+ struct riscv_iommu_device *iommu = data->domain->host_data;
int ret;
if (!vcpu_info) {
@@ -59,6 +183,7 @@ static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
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;
@@ -72,7 +197,7 @@ static int riscv_iommu_ir_irq_set_vcpu_affinity_locked(struct irq_data *data,
irqd_is_forwarded_to_vcpu(data))
return -EINVAL;
- ret = riscv_iommu_ir_activate(msi_table, vcpu_info);
+ ret = riscv_iommu_ir_activate(msi_table, iommu, vcpu_info);
if (ret)
return ret;
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 514470291fd9..55c167df2c2e 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1441,6 +1441,33 @@ static void riscv_iommu_free_paging_domain(struct iommu_domain *iommu_domain)
kfree(domain);
}
+bool riscv_iommu_msi_table_check_caps(struct riscv_iommu_msi_table *msi_table, u64 iommu_caps)
+{
+ struct riscv_iommu_domain *domain;
+ struct riscv_iommu_device *iommu, *prev = NULL;
+ struct riscv_iommu_bond *bond;
+ bool supported = true;
+
+ /* The MSI table lock excludes bond updates for this domain. */
+ lockdep_assert_held(&msi_table->lock);
+
+ domain = container_of(msi_table, struct riscv_iommu_domain, msi_table);
+
+ /* Bonds are grouped by IOMMU, so validate each IOMMU once. */
+ list_for_each_entry(bond, &domain->bonds, list) {
+ iommu = dev_to_iommu(bond->dev);
+ if (iommu == prev)
+ continue;
+ if ((iommu->caps & iommu_caps) != iommu_caps) {
+ supported = false;
+ break;
+ }
+ prev = iommu;
+ }
+
+ return supported;
+}
+
static bool riscv_iommu_fsc_supported(struct riscv_iommu_device *iommu,
int mode)
{
@@ -1512,6 +1539,8 @@ static bool riscv_iommu_can_attach_paging_domain(struct iommu_domain *iommu_doma
{
struct riscv_iommu_domain *domain = iommu_domain_to_riscv(iommu_domain);
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+ u64 required_caps = domain->msi_table.required_caps;
bool new_is_s2 = domain->gscid;
struct riscv_iommu_msi_table *new_msi_table, *old_msi_table;
@@ -1532,6 +1561,8 @@ static bool riscv_iommu_can_attach_paging_domain(struct iommu_domain *iommu_doma
*/
if (new_is_s2 && old_msi_table && info->nr_forwarded_irqs)
return false;
+ if (new_is_s2 && required_caps && (iommu->caps & required_caps) != required_caps)
+ return false;
return true;
}
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index a42f0b6a88d4..9852962e245b 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -83,6 +83,7 @@ struct riscv_iommu_msi_table {
u64 msi_addr_mask;
u64 msi_addr_pattern;
const void *owner;
+ u64 required_caps; /* RISCV_IOMMU_CAPABILITIES_* required by active MSI PTEs */
};
/* Private IOMMU data for managed devices, dev_iommu_priv_* */
@@ -104,6 +105,7 @@ void riscv_iommu_disable(struct riscv_iommu_device *iommu);
/* Caller must hold rcu_read_lock() while using the returned pointer. */
struct riscv_iommu_msi_table *riscv_iommu_msi_table_rcu(struct riscv_iommu_info *info);
+bool riscv_iommu_msi_table_check_caps(struct riscv_iommu_msi_table *msi_table, u64 iommu_caps);
void riscv_iommu_msi_table_inval(struct riscv_iommu_msi_table *msi_table, unsigned long addr);
void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *msi_table);
void riscv_iommu_msi_table_update(struct riscv_iommu_msi_table *msi_table, bool activate);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH v3 14/14] iommu/riscv: Implement IRQ forwarding
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
` (12 preceding siblings ...)
2026-09-28 14:31 ` [RFC PATCH v3 13/14] iommu/riscv: Validate IRQ forwarding requests Andrew Jones
@ 2026-09-28 14:31 ` Andrew Jones
13 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-09-28 14:31 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
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@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
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains
2026-09-28 14:31 ` [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains Andrew Jones
@ 2026-10-05 13:25 ` Andrew Jones
0 siblings, 0 replies; 16+ messages in thread
From: Andrew Jones @ 2026-10-05 13:25 UTC (permalink / raw)
To: iommu, kvm-riscv, kvm, linux-riscv, linux-kernel
Cc: tomasz.jeznach, jgg, jgg, joro, will, robin.murphy, pjw, palmer,
tglx, anup, atish.patra, fangyu.yu, zhangzhanpeng.jasper,
zong.li
On Mon, Sep 28, 2026 at 04:31:00PM +0200, Andrew Jones wrote:
> 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@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)");
I never liked the need for this parameter. I think I have a way to remove
it, ensure table size supports (and no more than supports) the guest's
IMSIC topology, and moves ownership of the MSI table to the hypervisor
(which, since the MSI table is only needed for VM's, is likely the better
owner). I'll experiment with that idea for the next version.
Thanks,
drew
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-10-05 13:25 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 14:30 [RFC PATCH v3 00/14] iommu/riscv: Add irqbypass support Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 01/14] iommu/riscv: Allocate MSI tables for second-stage domains Andrew Jones
2026-10-05 13:25 ` Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 02/14] iommu/riscv: Prepare domain bonds for outer locking Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 03/14] iommu/riscv: Serialize MSI table publication with domain attachment Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 04/14] iommu/riscv: Reject live S2 replacement with forwarded IRQs Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 05/14] iommu/riscv: Derive the IOMMU from the device in IODIR updates Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 06/14] iommu/riscv: Cache the programmed device context Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 07/14] iommu/riscv: Prepare MSI table updates for interrupt remapping Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 08/14] irqchip/riscv-imsic: Define IOMMU IRQ bypass protocol Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 09/14] genirq/msi: Provide DOMAIN_BUS_MSI_REMAP Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 10/14] iommu/riscv: Add IRQ domain for interrupt remapping Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 11/14] iommu/riscv: Prepare info->domain for concurrent RCU access Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 12/14] iommu/riscv: Prepare interrupt remapping for IRQ bypass Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 13/14] iommu/riscv: Validate IRQ forwarding requests Andrew Jones
2026-09-28 14:31 ` [RFC PATCH v3 14/14] iommu/riscv: Implement IRQ forwarding Andrew Jones
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®