* [RFC PATCH 01/12] iommu/riscv: Use DC.tc.V as the device context valid bit
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types Gong Shuai
` (11 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
riscv_iommu_iodir_update() used bit 0 of the translation attributes
argument as the "device context valid" flag. Now that it takes a full
device context, take the validity bit from DC.tc.V.
The defined bits of the caller's DC.tc are no longer dropped. This is
not a functional change for the existing callers: the paging and identity
domains set V, and the blocking domain leaves the context invalid. Nested
translation needs those bits to reach the hardware.
Use the DC prefixed field macros for device context fields while here,
instead of the PC (process context) ones.
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu-bits.h | 6 ++++++
drivers/iommu/riscv/iommu.c | 26 ++++++++++++--------------
2 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index 51a2e660b06a..2a8230a0c1bb 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -348,6 +348,12 @@ struct riscv_iommu_dc {
#define RISCV_IOMMU_DC_TC_SBE BIT_ULL(10)
#define RISCV_IOMMU_DC_TC_SXL BIT_ULL(11)
+/*
+ * All bits of dc.tc as defined by the spec. Bits 12-23 and 32-63
+ * are reserved for future standard use, bits 24-31 are for custom use.
+ */
+#define RISCV_IOMMU_DC_TC_DEFINED GENMASK_ULL(11, 0)
+
/* Second-stage (aka G-stage) context fields */
#define RISCV_IOMMU_DC_IOHGATP_PPN RISCV_IOMMU_ATP_PPN_FIELD
#define RISCV_IOMMU_DC_IOHGATP_GSCID GENMASK_ULL(59, 44)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 55c167df2c2e..dd97c2731d95 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1218,7 +1218,6 @@ void riscv_iommu_msi_table_inval_all(struct riscv_iommu_msi_table *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
* IOMMU specification (Section 6.3.1 and 6.3.2 in 1.0 spec version)
@@ -1329,16 +1328,14 @@ static void riscv_iommu_iodir_update(struct device *dev, struct riscv_iommu_dc *
riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
/*
- * For device context with DC_TC_PDTV = 0, translation attributes valid bit
- * is stored as DC_TC_V bit (both sharing the same location at BIT(0)).
+ * Only defined bits are writable by the driver.
*/
for (i = 0; i < fwspec->num_ids; i++) {
dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
- tc = READ_ONCE(dc->tc);
- tc |= new_dc->ta & RISCV_IOMMU_DC_TC_V;
+ tc = new_dc->tc & RISCV_IOMMU_DC_TC_DEFINED;
WRITE_ONCE(dc->fsc, new_dc->fsc);
- WRITE_ONCE(dc->ta, new_dc->ta & RISCV_IOMMU_PC_TA_PSCID);
+ WRITE_ONCE(dc->ta, new_dc->ta & RISCV_IOMMU_DC_TA_PSCID);
WRITE_ONCE(dc->iohgatp, new_dc->iohgatp);
if (iommu->caps & RISCV_IOMMU_CAPABILITIES_MSI_FLAT) {
WRITE_ONCE(dc->msiptp, new_dc->msiptp);
@@ -1600,11 +1597,11 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
if (!riscv_iommu_fsc_supported(iommu, pt_info.fsc_iosatp_mode))
return -ENODEV;
- dc.fsc = FIELD_PREP(RISCV_IOMMU_PC_FSC_MODE, pt_info.fsc_iosatp_mode) |
- FIELD_PREP(RISCV_IOMMU_PC_FSC_PPN, pt_info.ppn);
+ dc.fsc = FIELD_PREP(RISCV_IOMMU_DC_FSC_MODE, pt_info.fsc_iosatp_mode) |
+ FIELD_PREP(RISCV_IOMMU_DC_FSC_PPN, pt_info.ppn);
}
- dc.ta = FIELD_PREP(RISCV_IOMMU_PC_TA_PSCID, domain->pscid) |
- RISCV_IOMMU_PC_TA_V;
+ dc.ta = FIELD_PREP(RISCV_IOMMU_DC_TA_PSCID, domain->pscid);
+ dc.tc |= RISCV_IOMMU_DC_TC_V;
bond = kzalloc_obj(*bond);
if (!bond)
@@ -1773,9 +1770,10 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_dc dc = {0};
unsigned long flags;
- dc.fsc = RISCV_IOMMU_FSC_BARE;
+ dc.fsc = RISCV_IOMMU_DC_FSC_MODE_BARE;
+ dc.tc = 0;
- /* Make device context invalid, translation requests will fault w/ #258 */
+ /* Make device context invalid (DC.tc.V = 0), translation requests will fault w/ #258 */
flags = riscv_iommu_msi_tables_lock(old, NULL);
info->dc = dc;
riscv_iommu_iodir_update(dev, &info->dc);
@@ -1801,8 +1799,8 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_dc dc = {0};
unsigned long flags;
- dc.fsc = RISCV_IOMMU_FSC_BARE;
- dc.ta = RISCV_IOMMU_PC_TA_V;
+ dc.fsc = RISCV_IOMMU_DC_FSC_MODE_BARE;
+ dc.tc = RISCV_IOMMU_DC_TC_V;
flags = riscv_iommu_msi_tables_lock(old, NULL);
info->dc = dc;
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 01/12] iommu/riscv: Use DC.tc.V as the device context valid bit Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 23:54 ` Jason Gunthorpe
2026-10-15 6:52 ` [RFC PATCH 03/12] iommufd: Add RISC-V IOMMU cache invalidation data type Gong Shuai
` (10 subsequent siblings)
12 siblings, 1 reply; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Introduce the user-space API data for RISC-V IOMMU nested domains:
- IOMMU_HWPT_DATA_RISCV_S1 together with struct iommu_hwpt_riscv, which
carries the first-stage(guest) device-context fields (tc and iosatp)
for allocating a nested hwpt;
- IOMMU_VIOMMU_TYPE_RISCV so user space can request a RISC-V IOMMU
vIOMMU object.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Co-developed-by: Gong Shuai <gong.shuai@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
include/uapi/linux/iommufd.h | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..7458878386c5 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -506,18 +506,31 @@ struct iommu_hwpt_amd_guest {
__aligned_u64 dte[4];
};
+/**
+ * struct iommu_hwpt_riscv - RISC-V IOMMU stage-1 page table data
+ * (IOMMU_HWPT_DATA_RISCV_S1)
+ * @tc: Guest device context translation control
+ * @iosatp: Guest device context stage-1 root page table pointer
+ */
+struct iommu_hwpt_riscv {
+ __aligned_u64 tc;
+ __aligned_u64 iosatp;
+};
+
/**
* enum iommu_hwpt_data_type - IOMMU HWPT Data Type
* @IOMMU_HWPT_DATA_NONE: no data
* @IOMMU_HWPT_DATA_VTD_S1: Intel VT-d stage-1 page table
* @IOMMU_HWPT_DATA_ARM_SMMUV3: ARM SMMUv3 Context Descriptor Table
* @IOMMU_HWPT_DATA_AMD_GUEST: AMD IOMMU guest page table
+ * @IOMMU_HWPT_DATA_RISCV_S1: RISC-V IOMMU stage-1 page table
*/
enum iommu_hwpt_data_type {
IOMMU_HWPT_DATA_NONE = 0,
IOMMU_HWPT_DATA_VTD_S1 = 1,
IOMMU_HWPT_DATA_ARM_SMMUV3 = 2,
IOMMU_HWPT_DATA_AMD_GUEST = 3,
+ IOMMU_HWPT_DATA_RISCV_S1 = 4,
};
/**
@@ -1095,6 +1108,7 @@ struct iommu_fault_alloc {
* @IOMMU_VIOMMU_TYPE_ARM_SMMUV3: ARM SMMUv3 driver specific type
* @IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM
* SMMUv3) enabled ARM SMMUv3 type
+ * @IOMMU_VIOMMU_TYPE_RISCV: RISC-V IOMMU driver specific type
*/
enum iommu_viommu_type {
IOMMU_VIOMMU_TYPE_DEFAULT = 0,
@@ -1105,6 +1119,7 @@ enum iommu_viommu_type {
* VMM must wire the HYP_OWN bit to 0 in guest VINTF_CONFIG register
*/
IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV = 2,
+ IOMMU_VIOMMU_TYPE_RISCV = 3,
};
/**
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types
2026-10-15 6:52 ` [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types Gong Shuai
@ 2026-10-15 23:54 ` Jason Gunthorpe
2026-10-16 7:30 ` Gong Shuai
0 siblings, 1 reply; 21+ messages in thread
From: Jason Gunthorpe @ 2026-10-15 23:54 UTC (permalink / raw)
To: Gong Shuai
Cc: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu, Anup Patel,
Atish Patra, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei, Zhao Qingye,
Gong Shuai
On Thu, Oct 15, 2026 at 02:52:22PM +0800, Gong Shuai wrote:
> +/**
> + * struct iommu_hwpt_riscv - RISC-V IOMMU stage-1 page table data
> + * (IOMMU_HWPT_DATA_RISCV_S1)
> + * @tc: Guest device context translation control
> + * @iosatp: Guest device context stage-1 root page table pointer
> + */
> +struct iommu_hwpt_riscv {
> + __aligned_u64 tc;
> + __aligned_u64 iosatp;
> +};
Like the others you need a spec reference explaining exactly what
these fields are.
Usually it should be the entire guest structure, is it only 16 bytes?
But this looks broadly right otherwise
Jason
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types
2026-10-15 23:54 ` Jason Gunthorpe
@ 2026-10-16 7:30 ` Gong Shuai
2026-10-16 15:00 ` Jason Gunthorpe
0 siblings, 1 reply; 21+ messages in thread
From: Gong Shuai @ 2026-10-16 7:30 UTC (permalink / raw)
To: jgg
Cc: alex, andrew.jones, anup, aou, atish.patra, fangyu.yu,
gong.shuai, gsh517025, iommu, joro, kevin.tian, kvm-riscv, kvm,
linux-kernel, linux-riscv, palmer, pbonzini, pjw, robin.murphy,
seanjc, tomasz.jeznach, will, wu.fei9, zhao.qingye
Hi Jason,
Thank you for your review.
> > +/**
> > + * struct iommu_hwpt_riscv - RISC-V IOMMU stage-1 page table data
> > + * (IOMMU_HWPT_DATA_RISCV_S1)
> > + * @tc: Guest device context translation control
> > + * @iosatp: Guest device context stage-1 root page table pointer
> > + */
> > +struct iommu_hwpt_riscv {
> > + __aligned_u64 tc;
> > + __aligned_u64 iosatp;
> > +};
>
> Like the others you need a spec reference explaining exactly what
> these fields are.
I'll add references to the device-context sections of the RISC-V IOMMU
specification ("Leaf DDT entry", "Device-context fields", "Translation
control (tc)", and "First-Stage context (fsc)"), and I'll also include
detailed field information, as ARM does.
Also, I'll rename the field from iosatp to fsc. It holds iosatp when
PDTV=0 and process-directory-table pointer (pdtp) when PDTV=1, and the
two share the same layout, so the name should cover both interpretations
and the uAPI won't need to change when Process-Directory-Table (PDT)
support is added.
>
> Usually it should be the entire guest structure, is it only 16 bytes?
The RISC-V device-context is 32 bytes in base format and 64 bytes in
extended format, but it is not entirely guest-owned in a nested setup:
iohgatp, ta and the extended-format MSI fields are configured by the host.
The guest-owned part is therefore exactly tc and fsc fields, so 16 bytes
is all we take.
Thanks,
Shuai
>
> But this looks broadly right otherwise
>
> Jason
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types
2026-10-16 7:30 ` Gong Shuai
@ 2026-10-16 15:00 ` Jason Gunthorpe
0 siblings, 0 replies; 21+ messages in thread
From: Jason Gunthorpe @ 2026-10-16 15:00 UTC (permalink / raw)
To: Gong Shuai
Cc: alex, andrew.jones, anup, aou, atish.patra, fangyu.yu, gsh517025,
iommu, joro, kevin.tian, kvm-riscv, kvm, linux-kernel,
linux-riscv, palmer, pbonzini, pjw, robin.murphy, seanjc,
tomasz.jeznach, will, wu.fei9, zhao.qingye
On Fri, Oct 16, 2026 at 03:30:20PM +0800, Gong Shuai wrote:
> Hi Jason,
>
> Thank you for your review.
>
> > > +/**
> > > + * struct iommu_hwpt_riscv - RISC-V IOMMU stage-1 page table data
> > > + * (IOMMU_HWPT_DATA_RISCV_S1)
> > > + * @tc: Guest device context translation control
> > > + * @iosatp: Guest device context stage-1 root page table pointer
> > > + */
> > > +struct iommu_hwpt_riscv {
> > > + __aligned_u64 tc;
> > > + __aligned_u64 iosatp;
> > > +};
> >
> > Like the others you need a spec reference explaining exactly what
> > these fields are.
>
> I'll add references to the device-context sections of the RISC-V IOMMU
> specification ("Leaf DDT entry", "Device-context fields", "Translation
> control (tc)", and "First-Stage context (fsc)"), and I'll also include
> detailed field information, as ARM does.
>
> Also, I'll rename the field from iosatp to fsc. It holds iosatp when
> PDTV=0 and process-directory-table pointer (pdtp) when PDTV=1, and the
> two share the same layout, so the name should cover both interpretations
> and the uAPI won't need to change when Process-Directory-Table (PDT)
> support is added.
You might be better off just to have a data[] array of the device
context. I prefer the fields are not parsed out in userspace..
Future specs may add more guest controlled bits and in other places?
Jason
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC PATCH 03/12] iommufd: Add RISC-V IOMMU cache invalidation data type
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 01/12] iommu/riscv: Use DC.tc.V as the device context valid bit Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 02/12] iommufd: Add RISC-V IOMMU hwpt and vIOMMU types Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 04/12] iommu/riscv: Add vIOMMU object support Gong Shuai
` (9 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Add IOMMU_VIOMMU_INVALIDATE_DATA_RISCV and the matching
struct iommu_viommu_riscv_invalidate, so user space can pass RISC-V
IOMMU IOTINVAL commands to the vIOMMU cache invalidation path using a
payload layout that is shared with the kernel, instead of relying on the
driver internal struct riscv_iommu_command.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Co-developed-by: Gong Shuai <gong.shuai@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
include/uapi/linux/iommufd.h | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 7458878386c5..36e6187bb34b 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -903,10 +903,12 @@ struct iommu_hwpt_get_dirty_bitmap {
* Data Type
* @IOMMU_HWPT_INVALIDATE_DATA_VTD_S1: Invalidation data for VTD_S1
* @IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3: Invalidation data for ARM SMMUv3
+ * @IOMMU_VIOMMU_INVALIDATE_DATA_RISCV: Invalidation data for RISC-V IOMMU
*/
enum iommu_hwpt_invalidate_data_type {
IOMMU_HWPT_INVALIDATE_DATA_VTD_S1 = 0,
IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3 = 1,
+ IOMMU_VIOMMU_INVALIDATE_DATA_RISCV = 2,
};
/**
@@ -967,6 +969,31 @@ struct iommu_viommu_arm_smmuv3_invalidate {
__aligned_le64 cmd[2];
};
+/**
+ * struct iommu_viommu_riscv_invalidate - RISC-V IOMMU cache invalidation
+ * (IOMMU_VIOMMU_INVALIDATE_DATA_RISCV)
+ * @cmd: 128-bit cache invalidation command that runs in the IOMMU command
+ * queue, in the format defined by the "IOMMU Page-table cache
+ * invalidation" section of the RISC-V IOMMU specification. Must be
+ * little-endian.
+ *
+ * Supported command list only when passing in a vIOMMU via @hwpt_id:
+ * IOTINVAL.VMA
+ *
+ * The isolation operands are owned by the kernel, which overwrites them
+ * before the command is issued:
+ * GV = 1 and GSCID = the GSCID assigned to the vIOMMU
+ * PSCV = 1 and PSCID = 0
+ *
+ * All other reserved bits must be 0.
+ *
+ * -EIO will be returned if the command is not supported, -EINVAL if it is
+ * malformed.
+ */
+struct iommu_viommu_riscv_invalidate {
+ __aligned_le64 cmd[2];
+};
+
/**
* struct iommu_hwpt_invalidate - ioctl(IOMMU_HWPT_INVALIDATE)
* @size: sizeof(struct iommu_hwpt_invalidate)
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 04/12] iommu/riscv: Add vIOMMU object support
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (2 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 03/12] iommufd: Add RISC-V IOMMU cache invalidation data type Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 05/12] iommu/riscv: Add nested domain allocation Gong Shuai
` (8 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Implement the vIOMMU ops so user space can allocate a RISC-V IOMMU
vIOMMU on a stage-2 parent domain and query the size to allocate for
it. The object keeps the parent domain and the GSCID the parent was
allocated, which the nested domain and cache invalidation support added
on top of it will need.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu.c | 60 +++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index dd97c2731d95..bf02cd984d86 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -19,6 +19,7 @@
#include <linux/dma-map-ops.h>
#include <linux/init.h>
#include <linux/iommu.h>
+#include <linux/iommufd.h>
#include <linux/iopoll.h>
#include <linux/irqchip/riscv-imsic.h>
#include <linux/kernel.h>
@@ -862,6 +863,7 @@ struct riscv_iommu_domain {
struct mutex mutex; /* serialize domain state updates */
int pscid;
int gscid;
+ bool nest_parent;
struct riscv_iommu_msi_table msi_table;
};
PT_IOMMU_CHECK_DOMAIN(struct riscv_iommu_domain, riscvpt.iommu, domain);
@@ -1497,6 +1499,20 @@ static bool riscv_iommu_iohgatp_supported(struct riscv_iommu_device *iommu,
return false;
}
+static bool riscv_iommu_supports_s1(struct riscv_iommu_device *iommu)
+{
+ return (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV57) ||
+ (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV48) ||
+ (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV39);
+}
+
+static bool riscv_iommu_supports_s2(struct riscv_iommu_device *iommu)
+{
+ return (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV57X4) ||
+ (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV48X4) ||
+ (iommu->caps & RISCV_IOMMU_CAPABILITIES_SV39X4);
+}
+
static int riscv_iommu_msi_table_alloc(struct riscv_iommu_domain *domain,
struct riscv_iommu_device *iommu,
struct riscv_iommu_info *info)
@@ -1734,6 +1750,7 @@ riscv_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
ret = -ENOMEM;
goto err_free;
}
+ domain->nest_parent = !!(flags & IOMMU_HWPT_ALLOC_NEST_PARENT);
/*
* Capture the current number of MSI PTEs now since it must remain
@@ -1906,6 +1923,47 @@ static void riscv_iommu_release_device(struct device *dev)
kfree_rcu_mightsleep(info);
}
+struct riscv_viommu {
+ struct iommufd_viommu core;
+ struct riscv_iommu_device *iommu;
+ struct riscv_iommu_domain *s2_parent;
+ int gscid;
+};
+
+static const struct iommufd_viommu_ops riscv_viommu_ops = {
+};
+
+static size_t riscv_iommu_get_viommu_size(struct device *dev,
+ enum iommu_viommu_type viommu_type)
+{
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+
+ if (viommu_type != IOMMU_VIOMMU_TYPE_RISCV)
+ return 0;
+
+ if (!riscv_iommu_supports_s1(iommu) || !riscv_iommu_supports_s2(iommu))
+ return 0;
+
+ return VIOMMU_STRUCT_SIZE(struct riscv_viommu, core);
+}
+
+static int riscv_viommu_init(struct iommufd_viommu *viommu,
+ struct iommu_domain *parent_domain,
+ const struct iommu_user_data *user_data)
+{
+ struct riscv_viommu *rv_viommu = container_of(viommu, struct riscv_viommu, core);
+ struct riscv_iommu_device *iommu =
+ container_of(viommu->iommu_dev, struct riscv_iommu_device, iommu);
+ struct riscv_iommu_domain *s2_parent = iommu_domain_to_riscv(parent_domain);
+
+ rv_viommu->iommu = iommu;
+ rv_viommu->s2_parent = s2_parent;
+ rv_viommu->gscid = s2_parent->gscid;
+ viommu->ops = &riscv_viommu_ops;
+
+ return 0;
+}
+
static void riscv_iommu_get_resv_regions(struct device *dev, struct list_head *head)
{
struct iommu_resv_region *region;
@@ -1930,6 +1988,8 @@ static const struct iommu_ops riscv_iommu_ops = {
.blocked_domain = &riscv_iommu_blocking_domain,
.release_domain = &riscv_iommu_blocking_domain,
.domain_alloc_paging_flags = riscv_iommu_domain_alloc_paging_flags,
+ .get_viommu_size = riscv_iommu_get_viommu_size,
+ .viommu_init = riscv_viommu_init,
.device_group = riscv_iommu_device_group,
.probe_device = riscv_iommu_probe_device,
.release_device = riscv_iommu_release_device,
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 05/12] iommu/riscv: Add nested domain allocation
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (3 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 04/12] iommu/riscv: Add vIOMMU object support Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 06/12] iommu/riscv: Add vIOMMU cache invalidation Gong Shuai
` (7 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Implement alloc_domain_nested for the vIOMMU, which builds a nested
domain out of the stage-1 device context that the guest provides. The
context is sanitized before it is stored, since it is entirely user
controlled and bits this IOMMU or this host does not support would
otherwise be programmed into the hardware unchanged.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Co-developed-by: Gong Shuai <gong.shuai@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu.c | 146 ++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index bf02cd984d86..8046cadefbd8 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1930,7 +1930,153 @@ struct riscv_viommu {
int gscid;
};
+struct riscv_iommu_nested_domain {
+ struct iommu_domain domain;
+ struct riscv_viommu *viommu;
+
+ u64 tc;
+ u64 iosatp;
+};
+
+#define to_riscv_iommu_nested_domain(domain) \
+ container_of(domain, struct riscv_iommu_nested_domain, domain)
+
+static void riscv_iommu_domain_nested_free(struct iommu_domain *domain)
+{
+ kfree(to_riscv_iommu_nested_domain(domain));
+}
+
+static const struct iommu_domain_ops riscv_iommu_nested_ops = {
+ .free = riscv_iommu_domain_nested_free,
+};
+
+static int riscv_iommu_validate_vdc(struct riscv_iommu_device *iommu,
+ struct iommu_hwpt_riscv *arg)
+{
+ u64 mode;
+
+ if (!(arg->tc & RISCV_IOMMU_DC_TC_V)) {
+ arg->tc = 0;
+ arg->iosatp = 0;
+ return 0;
+ }
+
+ /*
+ * Reject reserved/custom bits, the physical IOMMU may treat the
+ * DC as misconfigured.
+ */
+ if (arg->tc & ~RISCV_IOMMU_DC_TC_DEFINED)
+ return -EINVAL;
+ if (arg->iosatp & ~(RISCV_IOMMU_DC_FSC_PPN | RISCV_IOMMU_DC_FSC_MODE))
+ return -EINVAL;
+
+ /* Process directory tables are not supported now. */
+ if (arg->tc & RISCV_IOMMU_DC_TC_PDTV)
+ return -EOPNOTSUPP;
+
+ /* With PDTV=0 the DPE bit is reserved for future use. */
+ if (arg->tc & RISCV_IOMMU_DC_TC_DPE)
+ return -EINVAL;
+
+ /* Only 64-bit (SXL=0) stage-1 is supported. */
+ if (arg->tc & RISCV_IOMMU_DC_TC_SXL)
+ return -EOPNOTSUPP;
+
+ /* ATS/PRI/T2GPA/PRPR consistency. */
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_ATS) &&
+ (arg->tc & (RISCV_IOMMU_DC_TC_EN_ATS | RISCV_IOMMU_DC_TC_EN_PRI |
+ RISCV_IOMMU_DC_TC_PRPR)))
+ return -EINVAL;
+ if ((arg->tc & RISCV_IOMMU_DC_TC_EN_PRI) &&
+ !(arg->tc & RISCV_IOMMU_DC_TC_EN_ATS))
+ return -EINVAL;
+ if ((arg->tc & RISCV_IOMMU_DC_TC_PRPR) &&
+ !(arg->tc & RISCV_IOMMU_DC_TC_EN_PRI))
+ return -EINVAL;
+ if ((arg->tc & RISCV_IOMMU_DC_TC_T2GPA) &&
+ (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_T2GPA) ||
+ !(arg->tc & RISCV_IOMMU_DC_TC_EN_ATS)))
+ return -EINVAL;
+
+ /*
+ * A/D update behavior on stage-2 PTEs is owned by the host,
+ * decide it's value from the AMO_HWAD capability.
+ */
+ arg->tc &= ~RISCV_IOMMU_DC_TC_GADE;
+ if (iommu->caps & RISCV_IOMMU_CAPABILITIES_AMO_HWAD)
+ arg->tc |= RISCV_IOMMU_DC_TC_GADE;
+
+ /*
+ * A/D update behavior on stage-1 PTEs is owned by the guest but
+ * needs AMO_HWAD support.
+ */
+ if ((arg->tc & RISCV_IOMMU_DC_TC_SADE) &&
+ !(iommu->caps & RISCV_IOMMU_CAPABILITIES_AMO_HWAD))
+ return -EOPNOTSUPP;
+
+ /* SBE must match the IOMMU byte order unless END is supported. */
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_END) &&
+ (!!(arg->tc & RISCV_IOMMU_DC_TC_SBE) !=
+ !!(iommu->fctl & RISCV_IOMMU_FCTL_BE)))
+ return -EINVAL;
+
+ /* Validate the stage-1 mode. */
+ mode = FIELD_GET(RISCV_IOMMU_DC_FSC_MODE, arg->iosatp);
+ if (mode == RISCV_IOMMU_DC_FSC_MODE_BARE) {
+ /* No stage-1 translation, root pointer is unused. */
+ arg->iosatp = 0;
+ return 0;
+ }
+ switch (mode) {
+ case RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV39:
+ case RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV48:
+ case RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV57:
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!riscv_iommu_fsc_supported(iommu, mode))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static struct iommu_domain *
+riscv_viommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
+ const struct iommu_user_data *user_data)
+{
+ struct riscv_viommu *rv_viommu = container_of(viommu, struct riscv_viommu, core);
+ struct riscv_iommu_nested_domain *nested_domain;
+ struct iommu_hwpt_riscv arg;
+ int ret;
+
+ if (flags)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ ret = iommu_copy_struct_from_user(&arg, user_data,
+ IOMMU_HWPT_DATA_RISCV_S1, iosatp);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = riscv_iommu_validate_vdc(rv_viommu->iommu, &arg);
+ if (ret)
+ return ERR_PTR(ret);
+
+ nested_domain = kzalloc_obj(*nested_domain, GFP_KERNEL_ACCOUNT);
+ if (!nested_domain)
+ return ERR_PTR(-ENOMEM);
+
+ nested_domain->domain.type = IOMMU_DOMAIN_NESTED;
+ nested_domain->domain.ops = &riscv_iommu_nested_ops;
+ nested_domain->viommu = rv_viommu;
+ nested_domain->tc = arg.tc;
+ nested_domain->iosatp = arg.iosatp;
+
+ return &nested_domain->domain;
+}
+
static const struct iommufd_viommu_ops riscv_viommu_ops = {
+ .alloc_domain_nested = riscv_viommu_alloc_domain_nested,
};
static size_t riscv_iommu_get_viommu_size(struct device *dev,
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 06/12] iommu/riscv: Add vIOMMU cache invalidation
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (4 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 05/12] iommu/riscv: Add nested domain allocation Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type Gong Shuai
` (6 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
Implement cache_invalidate for the vIOMMU, which takes IOTINVAL commands
from user space in the struct iommu_viommu_riscv_invalidate format and
forwards them to the physical IOMMU command queue.
The uAPI payload is little-endian, so convert it to the native command
words the hardware queue expects. Only IOTINVAL.VMA is accepted;
reserved bits are rejected. The guest-owned GV, GSCID and PSCV/PSCID
operands are replaced with the ones the host actually programs, so a
guest cannot invalidate caches that belong to another guest.
Co-developed-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu-bits.h | 13 +++++
drivers/iommu/riscv/iommu.c | 93 ++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+)
diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index 2a8230a0c1bb..68190d6cc060 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -486,6 +486,19 @@ struct riscv_iommu_command {
/* dword1[61:10] is the 4K-aligned page address */
#define RISCV_IOMMU_CMD1_IOTINVAL_ADDR GENMASK_ULL(61, 10)
+/*
+ * All standard bits of the IOTINVAL command. The remaining bits are
+ * reserved for future standard use, forwarding them to the physical IOMMU
+ * may make it treat the command as illegal.
+ */
+#define RISCV_IOMMU_CMD0_IOTINVAL_STANDARD \
+ (RISCV_IOMMU_CMD0_OPCODE | RISCV_IOMMU_CMD0_FUNC | \
+ RISCV_IOMMU_CMD0_IOTINVAL_AV | RISCV_IOMMU_CMD0_IOTINVAL_PSCID | \
+ RISCV_IOMMU_CMD0_IOTINVAL_PSCV | RISCV_IOMMU_CMD0_IOTINVAL_GV | \
+ RISCV_IOMMU_CMD0_IOTINVAL_NL | RISCV_IOMMU_CMD0_IOTINVAL_GSCID)
+#define RISCV_IOMMU_CMD1_IOTINVAL_STANDARD \
+ (RISCV_IOMMU_CMD1_IOTINVAL_S | RISCV_IOMMU_CMD1_IOTINVAL_ADDR)
+
/* 3.1.2 IOMMU Command Queue Fences */
/* Fields on dword0 */
#define RISCV_IOMMU_CMD_IOFENCE_OPCODE 2
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 8046cadefbd8..c34331bcd661 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -2075,8 +2075,101 @@ riscv_viommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
return &nested_domain->domain;
}
+/*
+ * One invalidation command, viewed as either the little-endian uAPI
+ * payload or the native command words.
+ */
+struct riscv_viommu_invalidation_cmd {
+ union {
+ struct riscv_iommu_command cmd;
+ struct iommu_viommu_riscv_invalidate ucmd;
+ };
+};
+
+static int riscv_viommu_cache_invalidate(struct iommufd_viommu *viommu,
+ struct iommu_user_data_array *array)
+{
+ struct riscv_viommu *rv_viommu = container_of(viommu, struct riscv_viommu, core);
+ struct riscv_iommu_device *iommu = rv_viommu->iommu;
+ struct riscv_viommu_invalidation_cmd *cmds;
+ unsigned int i = 0;
+ int ret;
+
+ if (array->type != IOMMU_VIOMMU_INVALIDATE_DATA_RISCV)
+ return -EINVAL;
+
+ /* entry_num==0 is the data_type support probe */
+ if (!array->entry_num)
+ return 0;
+
+ static_assert(sizeof(*cmds) == 2 * sizeof(u64));
+
+ cmds = kcalloc(array->entry_num, sizeof(*cmds), GFP_KERNEL);
+ if (!cmds)
+ return -ENOMEM;
+
+ ret = iommu_copy_struct_from_full_user_array(cmds, sizeof(*cmds), array,
+ IOMMU_VIOMMU_INVALIDATE_DATA_RISCV);
+ if (ret)
+ goto out_free;
+
+ for (i = 0; i < array->entry_num; i++) {
+ struct riscv_iommu_command *cmd = &cmds[i].cmd;
+ u64 opcode, func;
+
+ /* The uAPI payload is little-endian */
+ cmd->dword0 = le64_to_cpu(cmds[i].ucmd.cmd[0]);
+ cmd->dword1 = le64_to_cpu(cmds[i].ucmd.cmd[1]);
+
+ opcode = FIELD_GET(RISCV_IOMMU_CMD0_OPCODE, cmd->dword0);
+ if (opcode != RISCV_IOMMU_CMD_IOTINVAL_OPCODE) {
+ /* Only page-table cache invalidation is forwarded */
+ ret = -EIO;
+ goto out_free;
+ }
+
+ /* Reject reserved bits instead of forwarding them to the physical IOMMU */
+ if (cmd->dword0 & ~RISCV_IOMMU_CMD0_IOTINVAL_STANDARD ||
+ cmd->dword1 & ~RISCV_IOMMU_CMD1_IOTINVAL_STANDARD) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ func = FIELD_GET(RISCV_IOMMU_CMD0_FUNC, cmd->dword0);
+ if (func != RISCV_IOMMU_CMD_IOTINVAL_FUNC_VMA) {
+ ret = -EIO;
+ goto out_free;
+ }
+
+ /*
+ * The guest operates in its own GSCID/PSCID namespace.
+ * The physical IOMMU uses the host-assigned GSCID and
+ * PSCID=0. Enforce both for correct cache invalidation.
+ */
+ cmd->dword0 &= ~(RISCV_IOMMU_CMD0_IOTINVAL_GV |
+ RISCV_IOMMU_CMD0_IOTINVAL_GSCID |
+ RISCV_IOMMU_CMD0_IOTINVAL_PSCV |
+ RISCV_IOMMU_CMD0_IOTINVAL_PSCID);
+ cmd->dword0 |= FIELD_PREP(RISCV_IOMMU_CMD0_IOTINVAL_GSCID,
+ rv_viommu->gscid) |
+ RISCV_IOMMU_CMD0_IOTINVAL_GV |
+ FIELD_PREP(RISCV_IOMMU_CMD0_IOTINVAL_PSCID, 0) |
+ RISCV_IOMMU_CMD0_IOTINVAL_PSCV;
+
+ riscv_iommu_cmd_send(iommu, cmd);
+ }
+
+ riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
+
+out_free:
+ array->entry_num = i;
+ kfree(cmds);
+ return ret;
+}
+
static const struct iommufd_viommu_ops riscv_viommu_ops = {
.alloc_domain_nested = riscv_viommu_alloc_domain_nested,
+ .cache_invalidate = riscv_viommu_cache_invalidate,
};
static size_t riscv_iommu_get_viommu_size(struct device *dev,
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (5 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 06/12] iommu/riscv: Add vIOMMU cache invalidation Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 23:55 ` Jason Gunthorpe
2026-10-15 6:52 ` [RFC PATCH 08/12] iommu/riscv: Add riscv_iommu_hw_info Gong Shuai
` (5 subsequent siblings)
12 siblings, 1 reply; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Add IOMMU_HW_INFO_TYPE_RISCV and struct iommu_hw_info_riscv, which
reports the RISC-V IOMMU Capabilities register through
IOMMU_GET_HW_INFO.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
include/uapi/linux/iommufd.h | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 36e6187bb34b..acfef0338cd1 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -716,6 +716,14 @@ struct iommu_hw_info_amd {
__aligned_u64 efr2;
};
+/**
+ * struct iommu_hw_info_riscv - RISC-V IOMMU device info
+ * @capabilities: Value of RISC-V IOMMU Capabilities register
+ */
+struct iommu_hw_info_riscv {
+ __u64 capabilities;
+};
+
/**
* enum iommu_hw_info_type - IOMMU Hardware Info Types
* @IOMMU_HW_INFO_TYPE_NONE: Output by the drivers that do not report hardware
@@ -726,6 +734,7 @@ struct iommu_hw_info_amd {
* @IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM
* SMMUv3) info type
* @IOMMU_HW_INFO_TYPE_AMD: AMD IOMMU info type
+ * @IOMMU_HW_INFO_TYPE_RISCV: RISC-V IOMMU info type
*/
enum iommu_hw_info_type {
IOMMU_HW_INFO_TYPE_NONE = 0,
@@ -734,6 +743,7 @@ enum iommu_hw_info_type {
IOMMU_HW_INFO_TYPE_ARM_SMMUV3 = 2,
IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV = 3,
IOMMU_HW_INFO_TYPE_AMD = 4,
+ IOMMU_HW_INFO_TYPE_RISCV = 5,
};
/**
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type
2026-10-15 6:52 ` [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type Gong Shuai
@ 2026-10-15 23:55 ` Jason Gunthorpe
2026-10-16 7:43 ` Gong Shuai
0 siblings, 1 reply; 21+ messages in thread
From: Jason Gunthorpe @ 2026-10-15 23:55 UTC (permalink / raw)
To: Gong Shuai
Cc: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu, Anup Patel,
Atish Patra, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei, Zhao Qingye,
Gong Shuai
On Thu, Oct 15, 2026 at 02:52:27PM +0800, Gong Shuai wrote:
> From: Wu Fei <wu.fei9@sanechips.com.cn>
>
> Add IOMMU_HW_INFO_TYPE_RISCV and struct iommu_hw_info_riscv, which
> reports the RISC-V IOMMU Capabilities register through
> IOMMU_GET_HW_INFO.
>
> Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
> Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
> ---
> include/uapi/linux/iommufd.h | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
> index 36e6187bb34b..acfef0338cd1 100644
> --- a/include/uapi/linux/iommufd.h
> +++ b/include/uapi/linux/iommufd.h
> @@ -716,6 +716,14 @@ struct iommu_hw_info_amd {
> __aligned_u64 efr2;
> };
>
> +/**
> + * struct iommu_hw_info_riscv - RISC-V IOMMU device info
> + * @capabilities: Value of RISC-V IOMMU Capabilities register
> + */
> +struct iommu_hw_info_riscv {
> + __u64 capabilities;
> +};
> +
Same here, spec reference.
Also be careful to always use __aligned_u64, check all the patches.
JAson
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type
2026-10-15 23:55 ` Jason Gunthorpe
@ 2026-10-16 7:43 ` Gong Shuai
0 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-16 7:43 UTC (permalink / raw)
To: jgg
Cc: alex, andrew.jones, anup, aou, atish.patra, fangyu.yu,
gong.shuai, gsh517025, iommu, joro, kevin.tian, kvm-riscv, kvm,
linux-kernel, linux-riscv, palmer, pbonzini, pjw, robin.murphy,
seanjc, tomasz.jeznach, will, wu.fei9, zhao.qingye
Hi Jason,
> > +/**
> > + * struct iommu_hw_info_riscv - RISC-V IOMMU device info
> > + * @capabilities: Value of RISC-V IOMMU Capabilities register
> > + */
> > +struct iommu_hw_info_riscv {
> > + __u64 capabilities;
> > +};
> > +
>
> Same here, spec reference.
>
> Also be careful to always use __aligned_u64, check all the patches.
Will add the spec reference ("IOMMU capabilities (capabilities)") and
switch to __aligned_u64. I checked the uAPI structures touched by this
series, and this is the only place that used __u64.
Thanks,
Shuai
>
> JAson
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC PATCH 08/12] iommu/riscv: Add riscv_iommu_hw_info
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (6 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 07/12] iommufd: Add RISC-V IOMMU hardware info type Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 09/12] iommu/riscv: Add riscv_iommu_attach_dev_nested Gong Shuai
` (4 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Implement the hw_info op, report the IOMMU Capabilities register value
to user space as IOMMU_HW_INFO_TYPE_RISCV so VMMs can discover the
hardware capabilities of the physical IOMMU.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index c34331bcd661..e59a6a97589e 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -2220,9 +2220,27 @@ static void riscv_iommu_get_resv_regions(struct device *dev, struct list_head *h
iommu_dma_get_resv_regions(dev, head);
}
+static void *riscv_iommu_hw_info(struct device *dev, u32 *length,
+ enum iommu_hw_info_type *type)
+{
+ struct iommu_hw_info_riscv *info;
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+
+ info = kzalloc_obj(*info);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ info->capabilities = iommu->caps;
+ *length = sizeof(*info);
+ *type = IOMMU_HW_INFO_TYPE_RISCV;
+
+ return info;
+}
+
static const struct iommu_ops riscv_iommu_ops = {
.of_xlate = riscv_iommu_of_xlate,
.capable = riscv_iommu_capable,
+ .hw_info = riscv_iommu_hw_info,
.identity_domain = &riscv_iommu_identity_domain,
.blocked_domain = &riscv_iommu_blocking_domain,
.release_domain = &riscv_iommu_blocking_domain,
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 09/12] iommu/riscv: Add riscv_iommu_attach_dev_nested
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (7 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 08/12] iommu/riscv: Add riscv_iommu_hw_info Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 10/12] RISC-V: KVM: Accept auto-mode interrupt files for IMSIC IRQ bypass Gong Shuai
` (3 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
From: Wu Fei <wu.fei9@sanechips.com.cn>
Attach a device to a nested domain by combining the guest-provided
stage-1 context (tc, iosatp) with the host-owned second-stage context
in the device directory entry.
The device is bonded to the stage-2 parent domain so that it shares the
parent's invalidation and interrupt-remapping state: the guest MSI page
table is allocated when missing, and an active table's MSI configuration
is preserved across the device context update. Since the iommu core
passes the nested domain object as the old domain while the bond lives
on the parent, resolve the bond-holding domain before unlinking.
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Co-developed-by: Gong Shuai <gong.shuai@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu.c | 89 ++++++++++++++++++++++++++++++++++++-
1 file changed, 87 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index e59a6a97589e..a484bf97c2f5 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -871,6 +871,13 @@ 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)
+/*
+ * Resolve the domain holding the device bond: when the previously attached
+ * domain is a nested domain, the device is bonded to its stage-2 parent,
+ * which also owns the MSI table.
+ */
+static struct iommu_domain *riscv_iommu_old_bond_domain(struct iommu_domain *old);
+
struct riscv_iommu_msi_table *riscv_iommu_msi_table_rcu(struct riscv_iommu_info *info)
{
struct riscv_iommu_domain *domain;
@@ -1593,6 +1600,8 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
unsigned long flags;
int ret;
+ old = riscv_iommu_old_bond_domain(old);
+
pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info);
if (domain->gscid) {
@@ -1787,6 +1796,8 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_dc dc = {0};
unsigned long flags;
+ old = riscv_iommu_old_bond_domain(old);
+
dc.fsc = RISCV_IOMMU_DC_FSC_MODE_BARE;
dc.tc = 0;
@@ -1816,6 +1827,8 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
struct riscv_iommu_dc dc = {0};
unsigned long flags;
+ old = riscv_iommu_old_bond_domain(old);
+
dc.fsc = RISCV_IOMMU_DC_FSC_MODE_BARE;
dc.tc = RISCV_IOMMU_DC_TC_V;
@@ -1938,8 +1951,79 @@ struct riscv_iommu_nested_domain {
u64 iosatp;
};
-#define to_riscv_iommu_nested_domain(domain) \
- container_of(domain, struct riscv_iommu_nested_domain, domain)
+#define to_riscv_iommu_nested_domain(domain_ptr) \
+ container_of(domain_ptr, struct riscv_iommu_nested_domain, domain)
+
+static struct iommu_domain *riscv_iommu_old_bond_domain(struct iommu_domain *old)
+{
+ if (old && old->type == IOMMU_DOMAIN_NESTED)
+ return &to_riscv_iommu_nested_domain(old)->viommu->s2_parent->domain;
+ return old;
+}
+
+static int riscv_iommu_attach_dev_nested(struct iommu_domain *domain,
+ struct device *dev,
+ struct iommu_domain *old_domain)
+{
+ struct riscv_iommu_nested_domain *nested_domain =
+ to_riscv_iommu_nested_domain(domain);
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+ struct riscv_iommu_domain *s2_parent = nested_domain->viommu->s2_parent;
+ struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ struct pt_iommu_riscv_64_hw_info pt_info;
+ struct iommu_domain *old = riscv_iommu_old_bond_domain(old_domain);
+ struct riscv_iommu_bond *bond;
+ struct riscv_iommu_dc dc = {0};
+ unsigned long flags;
+ int ret;
+
+ if (nested_domain->viommu->iommu != iommu)
+ return -EINVAL;
+
+ if (!s2_parent->nest_parent)
+ return -EINVAL;
+
+ pt_iommu_riscv_64_hw_info(&s2_parent->riscvpt, &pt_info);
+
+ dc.tc = nested_domain->tc;
+ dc.fsc = nested_domain->iosatp;
+
+ dc.iohgatp = FIELD_PREP(RISCV_IOMMU_DC_IOHGATP_MODE, pt_info.iohgatp_mode) |
+ FIELD_PREP(RISCV_IOMMU_DC_IOHGATP_PPN, pt_info.ppn) |
+ FIELD_PREP(RISCV_IOMMU_DC_IOHGATP_GSCID, s2_parent->gscid);
+
+ /* SVA/PASID are not supported now, keep PSCID at 0. */
+ dc.ta = FIELD_PREP(RISCV_IOMMU_DC_TA_PSCID, 0);
+
+ ret = riscv_iommu_msi_table_alloc(s2_parent, iommu, info);
+ if (ret)
+ return ret;
+
+ bond = kzalloc_obj(*bond);
+ if (!bond)
+ return -ENOMEM;
+ bond->dev = dev;
+
+ flags = riscv_iommu_msi_tables_lock(old, &s2_parent->domain);
+ if (!riscv_iommu_can_attach_paging_domain(&s2_parent->domain, dev, old)) {
+ riscv_iommu_msi_tables_unlock(old, &s2_parent->domain, flags);
+ kfree(bond);
+ return -EBUSY;
+ }
+
+ /* Keep an active guest MSI table published across the DC update. */
+ if (s2_parent->msi_table.nr_forwarded_irqs)
+ riscv_iommu_msi_table_set_dc(&s2_parent->msi_table, &dc, true);
+
+ info->dc = dc;
+ riscv_iommu_bond_link(s2_parent, bond);
+ riscv_iommu_iodir_update(dev, &info->dc);
+ riscv_iommu_bond_unlink(old, dev);
+ rcu_assign_pointer(info->domain, s2_parent);
+ riscv_iommu_msi_tables_unlock(old, &s2_parent->domain, flags);
+
+ return 0;
+}
static void riscv_iommu_domain_nested_free(struct iommu_domain *domain)
{
@@ -1947,6 +2031,7 @@ static void riscv_iommu_domain_nested_free(struct iommu_domain *domain)
}
static const struct iommu_domain_ops riscv_iommu_nested_ops = {
+ .attach_dev = riscv_iommu_attach_dev_nested,
.free = riscv_iommu_domain_nested_free,
};
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 10/12] RISC-V: KVM: Accept auto-mode interrupt files for IMSIC IRQ bypass
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (8 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 09/12] iommu/riscv: Add riscv_iommu_attach_dev_nested Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 11/12] iommu/riscv: Accept unpublished interrupt files in IRQ forwarding Gong Shuai
` (2 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
The IRQ bypass required the AIA to be in HWACCEL mode, while the KVM
default is AUTO, which publishes an IMSIC VS-file whenever one is
available and only falls back to the SW-file when the host has none
left. Rejecting AUTO leaves every assigned device on host MSI delivery,
which cannot work for a device attached to a nested domain because its
stage-1 page tables belong to the guest, so the device keeps the host
MSI address composed at vector activation and its MSIs fault in the
guest stage-1.
Co-developed-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
arch/riscv/kvm/aia_imsic.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c
index 37e27f28dc62..010f9a90f107 100644
--- a/arch/riscv/kvm/aia_imsic.c
+++ b/arch/riscv/kvm/aia_imsic.c
@@ -981,10 +981,13 @@ static int imsic_irqfd_forward(struct kvm_kernel_irqfd *irqfd,
* MSI-X because pci_write_msi_msg() masks an MSI-X vector around the
* update, which conventional MSI cannot provide.
*/
- if (kvm->arch.aia.mode != KVM_DEV_RISCV_AIA_MODE_HWACCEL ||
- !imsic_irqfd_is_msix(irqfd))
+ if (!imsic_irqfd_is_msix(irqfd))
return -EOPNOTSUPP;
+ /*
+ * The IOMMU writes MSIs straight into the VS-file, so bypass is
+ * gated by the hardware backing checks below, not the AIA mode.
+ */
if (!imsic_targets_valid(&kvm->arch.aia)) {
pr_debug("KVM: IMSIC IRQ bypass requires all VS-files\n");
return -EOPNOTSUPP;
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 11/12] iommu/riscv: Accept unpublished interrupt files in IRQ forwarding
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (9 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 10/12] RISC-V: KVM: Accept auto-mode interrupt files for IMSIC IRQ bypass Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-15 6:52 ` [RFC PATCH 12/12] RISC-V: KVM: Forward MSIs for guests with a vIOMMU Gong Shuai
2026-10-16 0:00 ` [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Jason Gunthorpe
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
A guest with a vIOMMU needs the complete IMSIC topology of the VM in the
MSI table, since the hardware resolves the target vCPU from the MSI
address on its own. That table is installed when the first device MSI is
forwarded, which can happen before every vCPU has its interrupt file
published, so a request for the whole VM cannot require all of them.
Skip the unpublished ones instead of rejecting the whole request. An MSI
that resolves to one of them faults only for that vCPU and is reported
in the fault queue, whereas a failed request would take IRQ bypass away
from the entire virtual machine.
Co-developed-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
drivers/iommu/riscv/iommu-ir.c | 25 +++++++++++++++++++++----
include/linux/irqchip/riscv-imsic.h | 14 ++++++++------
2 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/drivers/iommu/riscv/iommu-ir.c b/drivers/iommu/riscv/iommu-ir.c
index b3f0a56475ed..94971959a581 100644
--- a/drivers/iommu/riscv/iommu-ir.c
+++ b/drivers/iommu/riscv/iommu-ir.c
@@ -78,18 +78,28 @@ static int riscv_iommu_ir_validate_target(const struct riscv_iommu_ir_vcpu_info
static int riscv_iommu_ir_validate_targets(const struct riscv_iommu_ir_vcpu_info *vcpu_info,
size_t nr_ptes, u64 *required_caps)
{
+ unsigned int nr_valid = 0;
int ret;
- if (!vcpu_info->targets || !vcpu_info->nr_targets || vcpu_info->nr_targets > nr_ptes)
+ if (!vcpu_info->targets || !vcpu_info->nr_targets)
return -EINVAL;
for (unsigned int i = 0; i < vcpu_info->nr_targets; i++) {
+ /* Interrupt files the host has not published yet are skipped. */
+ if (vcpu_info->targets[i].type == RISCV_IOMMU_IR_TARGET_INVALID)
+ continue;
+
ret = riscv_iommu_ir_validate_target(vcpu_info, &vcpu_info->targets[i],
required_caps);
if (ret)
return ret;
+ nr_valid++;
}
+ /* The table only has to be able to deliver some interrupt file. */
+ if (!nr_valid || nr_valid > nr_ptes)
+ return -EINVAL;
+
return 0;
}
@@ -147,9 +157,16 @@ static int riscv_iommu_ir_activate(struct riscv_iommu_msi_table *msi_table,
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];
+ size_t idx;
+ struct riscv_iommu_msipte *msipte;
+
+ /* Interrupt files the host has not published yet are skipped. */
+ if (target->type == RISCV_IOMMU_IR_TARGET_INVALID)
+ continue;
+
+ idx = riscv_iommu_ir_extract(target->gpa >> IMSIC_MMIO_PAGE_SHIFT,
+ vcpu_info->msi_addr_mask);
+ msipte = &msi_table->root[idx];
/* A populated entry in the initially clear table indicates duplicate targets. */
if (msipte->pte || msipte->mrif_info) {
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index 02a836ce03b4..947c406d512b 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -138,12 +138,14 @@ struct riscv_iommu_ir_target {
};
/*
- * 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.
+ * FORWARD enables forwarding for one IRQ and provides the target array so the
+ * first forwarded IRQ can initialize the domain MSI table. Entries of type
+ * RISCV_IOMMU_IR_TARGET_INVALID are skipped, so a caller may forward the whole
+ * virtual machine topology while only some of its interrupt files are hardware
+ * backed. 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
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* [RFC PATCH 12/12] RISC-V: KVM: Forward MSIs for guests with a vIOMMU
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (10 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 11/12] iommu/riscv: Accept unpublished interrupt files in IRQ forwarding Gong Shuai
@ 2026-10-15 6:52 ` Gong Shuai
2026-10-16 0:00 ` [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Jason Gunthorpe
12 siblings, 0 replies; 21+ messages in thread
From: Gong Shuai @ 2026-10-15 6:52 UTC (permalink / raw)
To: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu
Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Jason Gunthorpe, Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei,
Zhao Qingye, Gong Shuai, Gong Shuai
The IRQ bypass names the target vCPU by matching the device MSI address
against the guest IMSIC addresses of the vCPUs, so it only works when the
guest programs the device with those addresses directly. A guest with a
vIOMMU programs an address from its own IOVA space instead, which the
IOMMU resolves through the guest stage-1 page tables before it looks the
address up in the MSI table, so no vCPU can be named at that point.
Falling back to host delivery is not an option either. The device would
keep the host IMSIC address composed at vector activation, and the
guest's own stage-1 page tables do not map it, so its MSIs fault instead
of reaching an interrupt file.
The MSI table already holds the VM-wide IMSIC topology, so the hardware
finds the target vCPU by itself once the address is translated. Forward
such IRQs for the whole VM and program the device with the MSI message
composed by the guest.
Such a producer can reach any vCPU of the VM, so unforward it whenever a
VS-file is released and retarget it whenever a vCPU migrates.
Co-developed-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Wu Fei <wu.fei9@sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai@sanechips.com.cn>
---
arch/riscv/kvm/aia_imsic.c | 67 +++++++++++++++++++++++++++-----------
include/linux/kvm_irqfd.h | 2 ++
2 files changed, 50 insertions(+), 19 deletions(-)
diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c
index 010f9a90f107..862db53c66a5 100644
--- a/arch/riscv/kvm/aia_imsic.c
+++ b/arch/riscv/kvm/aia_imsic.c
@@ -758,7 +758,7 @@ int kvm_riscv_irqfd_unforward(struct kvm_kernel_irqfd *irqfd)
lockdep_assert_held(&kvm->irqfds.lock);
- if (!irqfd->irq_bypass_vcpu)
+ if (!irqfd->irq_bypass_vcpu && !irqfd->irq_bypass_nested)
return 0;
irq_data = irq_get_irq_data(irqfd->producer->irq);
@@ -775,6 +775,7 @@ int kvm_riscv_irqfd_unforward(struct kvm_kernel_irqfd *irqfd)
return ret;
irqfd->irq_bypass_vcpu = NULL;
+ irqfd->irq_bypass_nested = false;
return 0;
}
@@ -788,7 +789,11 @@ static int imsic_vcpu_unforward(struct kvm_vcpu *vcpu)
guard(spinlock_irqsave)(&kvm->irqfds.lock);
list_for_each_entry(irqfd, &kvm->irqfds.items, list) {
- if (irqfd->irq_bypass_vcpu != vcpu)
+ /*
+ * Nested-forwarded producers can target any vCPU of the VM,
+ * unforward them on any VS-file release.
+ */
+ if (!irqfd->irq_bypass_nested && irqfd->irq_bypass_vcpu != vcpu)
continue;
ret = kvm_riscv_irqfd_unforward(irqfd);
@@ -902,7 +907,7 @@ static int imsic_vsfile_target_replace(struct kvm_vcpu *vcpu, phys_addr_t vsfile
vcpu_info.target.type = RISCV_IOMMU_IR_TARGET_IMSIC;
vcpu_info.target.hpa = vsfile_pa;
list_for_each_entry(irqfd, &kvm->irqfds.items, list) {
- if (!irqfd->irq_bypass_vcpu)
+ if (!irqfd->irq_bypass_vcpu && !irqfd->irq_bypass_nested)
continue;
ret = irq_set_vcpu_affinity(irqfd->producer->irq, &vcpu_info);
@@ -931,16 +936,10 @@ static bool imsic_targets_valid(struct kvm_aia *aia)
return true;
}
-static struct kvm_vcpu *imsic_irqfd_route_vcpu(struct kvm *kvm,
- struct kvm_kernel_irq_routing_entry *irq_entry)
+static struct kvm_vcpu *imsic_irqfd_route_vcpu(struct kvm *kvm, gpa_t target)
{
struct kvm_vcpu *vcpu;
unsigned long index;
- gpa_t target;
-
- target = ((gpa_t)irq_entry->msi.address_hi << 32) | irq_entry->msi.address_lo;
- if (target & (IMSIC_MMIO_PAGE_SZ - 1))
- return NULL;
kvm_for_each_vcpu(index, vcpu, kvm) {
if (target == vcpu->arch.aia_context.imsic_addr)
@@ -968,6 +967,9 @@ static int imsic_irqfd_forward(struct kvm_kernel_irqfd *irqfd,
.data = irq_entry->msi.data,
};
struct kvm_vcpu *vcpu;
+ gpa_t target, gpa;
+ unsigned int i;
+ bool nested = false;
int ret;
lockdep_assert_held(&kvm->irqfds.lock);
@@ -988,24 +990,51 @@ static int imsic_irqfd_forward(struct kvm_kernel_irqfd *irqfd,
* The IOMMU writes MSIs straight into the VS-file, so bypass is
* gated by the hardware backing checks below, not the AIA mode.
*/
- if (!imsic_targets_valid(&kvm->arch.aia)) {
- pr_debug("KVM: IMSIC IRQ bypass requires all VS-files\n");
+ target = ((gpa_t)irq_entry->msi.address_hi << 32) | irq_entry->msi.address_lo;
+ if (target & (IMSIC_MMIO_PAGE_SZ - 1))
return -EOPNOTSUPP;
- }
- /* Leave guest messages that name no vIMSIC to software injection. */
- vcpu = imsic_irqfd_route_vcpu(kvm, irq_entry);
- if (!vcpu)
- return -EOPNOTSUPP;
+ vcpu = imsic_irqfd_route_vcpu(kvm, target);
+ if (vcpu) {
+ /*
+ * The guest programmed a guest interrupt file address, so every
+ * vCPU of the VM must be hardware backed for the complete
+ * topology to be installable.
+ */
+ if (!imsic_targets_valid(aia)) {
+ pr_debug("KVM: IMSIC IRQ bypass requires all VS-files\n");
+ return -EOPNOTSUPP;
+ }
+ gpa = vcpu->arch.aia_context.imsic_addr;
+ } else {
+ /*
+ * A vIOMMU guest programs the device with an IOVA-space MSI
+ * address, which names no vCPU until the IOMMU translates it
+ * and resolves the target through its MSI table of published
+ * VS-files. Forward such IRQs for the whole VM. The address
+ * pattern comes from any published target, as only the
+ * mask-covered index bits differ between them.
+ */
+ for (i = 0; i < aia->nr_targets; i++)
+ if (aia->targets[i].type == RISCV_IOMMU_IR_TARGET_IMSIC)
+ break;
+ if (i == aia->nr_targets)
+ return -EOPNOTSUPP;
+ gpa = aia->targets[i].gpa;
+ nested = true;
+ }
- vcpu_info.msi_addr_pattern = (vcpu->arch.aia_context.imsic_addr >> IMSIC_MMIO_PAGE_SHIFT) &
+ vcpu_info.msi_addr_pattern = (gpa >> IMSIC_MMIO_PAGE_SHIFT) &
~vcpu_info.msi_addr_mask;
ret = irq_set_vcpu_affinity(irqfd->producer->irq, &vcpu_info);
if (ret)
return ret;
pci_write_msi_msg(irqfd->producer->irq, &msg);
- irqfd->irq_bypass_vcpu = vcpu;
+ if (nested)
+ irqfd->irq_bypass_nested = true;
+ else
+ irqfd->irq_bypass_vcpu = vcpu;
return 0;
}
diff --git a/include/linux/kvm_irqfd.h b/include/linux/kvm_irqfd.h
index ef8c134ded8a..39c73157e086 100644
--- a/include/linux/kvm_irqfd.h
+++ b/include/linux/kvm_irqfd.h
@@ -60,6 +60,8 @@ struct kvm_kernel_irqfd {
struct irq_bypass_producer *producer;
struct kvm_vcpu *irq_bypass_vcpu;
+ /* Forwarded to any vCPU of the VM (guest-controlled MSI target) */
+ bool irq_bypass_nested;
struct list_head vcpu_list;
void *irq_bypass_data;
};
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support
2026-10-15 6:52 [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Gong Shuai
` (11 preceding siblings ...)
2026-10-15 6:52 ` [RFC PATCH 12/12] RISC-V: KVM: Forward MSIs for guests with a vIOMMU Gong Shuai
@ 2026-10-16 0:00 ` Jason Gunthorpe
2026-10-16 9:16 ` Gong Shuai
12 siblings, 1 reply; 21+ messages in thread
From: Jason Gunthorpe @ 2026-10-16 0:00 UTC (permalink / raw)
To: Gong Shuai
Cc: kvm, kvm-riscv, linux-riscv, linux-kernel, iommu, Anup Patel,
Atish Patra, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Tomasz Jeznach, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Paolo Bonzini, Sean Christopherson,
Kevin Tian, Andrew Jones, Fangyu Yu, Wu Fei, Zhao Qingye,
Gong Shuai
On Thu, Oct 15, 2026 at 02:52:20PM +0800, Gong Shuai wrote:
> Add nested translation support to the RISC-V IOMMU driver, so a KVM guest
> with its own vIOMMU can own the first stage of a device context while the
> host keeps the second stage, and add the interrupt support. This is an RFC
> and comments on the design are welcome.
The iommufd parts looked reasonable to me
I feel some mild skepticism about all the MSI parts, it would be good
to get more of the interrupt stuff actually landed before posting this
again.
Please try harder on the commit messages, do not use AI to write them
like this. They need to explain why this patch exists and what problem
it is solving not some inscrutible description of what it contains.
You'll need other riscv people to help you with reviewing it.
Jason
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support
2026-10-16 0:00 ` [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support Jason Gunthorpe
@ 2026-10-16 9:16 ` Gong Shuai
2026-10-16 14:25 ` Jason Gunthorpe
0 siblings, 1 reply; 21+ messages in thread
From: Gong Shuai @ 2026-10-16 9:16 UTC (permalink / raw)
To: jgg
Cc: alex, andrew.jones, anup, aou, atish.patra, fangyu.yu,
gong.shuai, gsh517025, iommu, joro, kevin.tian, kvm-riscv, kvm,
linux-kernel, linux-riscv, palmer, pbonzini, pjw, robin.murphy,
seanjc, tomasz.jeznach, will, wu.fei9, zhao.qingye
Hi Jason,
Thanks for the review again.
> > Add nested translation support to the RISC-V IOMMU driver, so a KVM guest
> > with its own vIOMMU can own the first stage of a device context while the
> > host keeps the second stage, and add the interrupt support. This is an RFC
> > and comments on the design are welcome.
>
> The iommufd parts looked reasonable to me
>
> I feel some mild skepticism about all the MSI parts, it would be good
> to get more of the interrupt stuff actually landed before posting this
> again.
Agreed on the MSI parts. They are a working proof to exercise the
nested flow end to end, not a finished design. The reason they are
in this series at all is that the NVMe device we use for testing
depends on MSI interrupts. Without this support, DMA succeeds but
the MSIs fault, so the driver ends up polling for completions after
timeouts.
We will keep following the irqbypass groundwork as it evolves, and
continue this work once it has landed.
>
> Please try harder on the commit messages, do not use AI to write them
> like this. They need to explain why this patch exists and what problem
> it is solving not some inscrutible description of what it contains.
I will rewrite all of them.
Thanks,
Shuai
>
> You'll need other riscv people to help you with reviewing it.
>
> Jason
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 0/12] iommu/riscv: Add nested translation (vIOMMU) support
2026-10-16 9:16 ` Gong Shuai
@ 2026-10-16 14:25 ` Jason Gunthorpe
0 siblings, 0 replies; 21+ messages in thread
From: Jason Gunthorpe @ 2026-10-16 14:25 UTC (permalink / raw)
To: Gong Shuai
Cc: alex, andrew.jones, anup, aou, atish.patra, fangyu.yu, gsh517025,
iommu, joro, kevin.tian, kvm-riscv, kvm, linux-kernel,
linux-riscv, palmer, pbonzini, pjw, robin.murphy, seanjc,
tomasz.jeznach, will, wu.fei9, zhao.qingye
On Fri, Oct 16, 2026 at 05:16:10PM +0800, Gong Shuai wrote:
> Hi Jason,
>
> Thanks for the review again.
>
> > > Add nested translation support to the RISC-V IOMMU driver, so a KVM guest
> > > with its own vIOMMU can own the first stage of a device context while the
> > > host keeps the second stage, and add the interrupt support. This is an RFC
> > > and comments on the design are welcome.
> >
> > The iommufd parts looked reasonable to me
> >
> > I feel some mild skepticism about all the MSI parts, it would be good
> > to get more of the interrupt stuff actually landed before posting this
> > again.
>
> Agreed on the MSI parts. They are a working proof to exercise the
> nested flow end to end, not a finished design. The reason they are
> in this series at all is that the NVMe device we use for testing
> depends on MSI interrupts. Without this support, DMA succeeds but
> the MSIs fault, so the driver ends up polling for completions after
> timeouts.
>
> We will keep following the irqbypass groundwork as it evolves, and
> continue this work once it has landed.
It seems to me the riscv interrupt architecture is a terrible fit for
Linux, so I would prefer you keep that out of a viommu series and try
to address interrupt support in VFIO through the other series.
viommu shouldn't change anything about how interrupts work..
Without the msi stuff I think you could get this merged without a big
trouble.
Jason
^ permalink raw reply [flat|nested] 21+ messages in thread