[RFC PATCH v2 5/9] iommu/riscv: Add nested domain allocation
Gong Shuai
gong.shuai at sanechips.com.cn
Wed Oct 28 23:11:27 PDT 2026
From: Wu Fei <wu.fei9 at sanechips.com.cn>
A nested domain describes two-stage translation through guest and
host page tables. Iommufd exposes this domain as a nested hwpt.
User space needs to provide the guest stage 1 configuration when
allocating a nested hwpt. Validate the configuration before storing
it for use in a physical device context.
Signed-off-by: Wu Fei <wu.fei9 at sanechips.com.cn>
Co-developed-by: Gong Shuai <gong.shuai at sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai at sanechips.com.cn>
---
drivers/iommu/riscv/iommu.c | 151 ++++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index a8c19c9e45d4..371d60902eaa 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1662,7 +1662,158 @@ 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 tc = arg->dc[0];
+ u64 fsc = arg->dc[3];
+ u64 mode;
+
+ if (!(tc & RISCV_IOMMU_DC_TC_V)) {
+ arg->dc[0] = 0;
+ arg->dc[3] = 0;
+ return 0;
+ }
+
+ /*
+ * Reject reserved/custom bits, the physical IOMMU may treat the
+ * DC as misconfigured.
+ */
+ if (tc & ~RISCV_IOMMU_DC_TC_DEFINED)
+ return -EINVAL;
+ if (fsc & ~(RISCV_IOMMU_DC_FSC_PPN | RISCV_IOMMU_DC_FSC_MODE))
+ return -EINVAL;
+
+ /* Process directory tables are not supported now. */
+ if (tc & RISCV_IOMMU_DC_TC_PDTV)
+ return -EOPNOTSUPP;
+
+ /* With PDTV=0 the DPE bit is reserved for future use. */
+ if (tc & RISCV_IOMMU_DC_TC_DPE)
+ return -EINVAL;
+
+ /* Only 64-bit (SXL=0) stage-1 is supported. */
+ if (tc & RISCV_IOMMU_DC_TC_SXL)
+ return -EOPNOTSUPP;
+
+ /* ATS/PRI/T2GPA/PRPR consistency. */
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_ATS) &&
+ (tc & (RISCV_IOMMU_DC_TC_EN_ATS | RISCV_IOMMU_DC_TC_EN_PRI |
+ RISCV_IOMMU_DC_TC_PRPR)))
+ return -EINVAL;
+ if ((tc & RISCV_IOMMU_DC_TC_EN_PRI) &&
+ !(tc & RISCV_IOMMU_DC_TC_EN_ATS))
+ return -EINVAL;
+ if ((tc & RISCV_IOMMU_DC_TC_PRPR) &&
+ !(tc & RISCV_IOMMU_DC_TC_EN_PRI))
+ return -EINVAL;
+ if ((tc & RISCV_IOMMU_DC_TC_T2GPA) &&
+ (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_T2GPA) ||
+ !(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.
+ */
+ tc &= ~RISCV_IOMMU_DC_TC_GADE;
+ if (iommu->caps & RISCV_IOMMU_CAPABILITIES_AMO_HWAD)
+ 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 ((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) &&
+ (!!(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, fsc);
+ if (mode == RISCV_IOMMU_DC_FSC_MODE_BARE) {
+ /* No stage-1 translation, root pointer is unused. */
+ fsc = 0;
+ } else {
+ 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;
+ }
+
+ arg->dc[0] = tc;
+ arg->dc[3] = fsc;
+
+ 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, dc);
+ 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.dc[0];
+ nested_domain->iosatp = arg.dc[3];
+
+ 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
More information about the kvm-riscv
mailing list