[RFC PATCH 05/12] iommu/riscv: Add nested domain allocation
Gong Shuai
gong.shuai at sanechips.com.cn
Wed Oct 14 23:52:25 PDT 2026
From: Wu Fei <wu.fei9 at 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 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 | 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
More information about the kvm-riscv
mailing list