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 Signed-off-by: Wu Fei Signed-off-by: Gong Shuai --- 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