[RFC PATCH 06/12] iommu/riscv: Add vIOMMU cache invalidation
Gong Shuai
gong.shuai at sanechips.com.cn
Wed Oct 14 23:52:26 PDT 2026
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 at sanechips.com.cn>
Signed-off-by: Wu Fei <wu.fei9 at sanechips.com.cn>
Signed-off-by: Gong Shuai <gong.shuai at 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
More information about the kvm-riscv
mailing list