[PATCH v8 16/25] iommu/arm-smmu-v3-kvm: Emulate CMDQ for host
Mostafa Saleh
smostafa at google.com
Tue Sep 22 06:12:49 PDT 2026
Don't allow access to the command queue from the host:
- ARM_SMMU_CMDQ_BASE: Only allowed to be written when CMDQ is disabled, we
use it to keep track of the host command queue base.
Reads return the saved value.
- ARM_SMMU_CMDQ_PROD: Writes trigger command queue emulation which sanitise
and filters the whole range. Reads returns the host copy.
- ARM_SMMU_CMDQ_CONS: Writes move the sw copy of the cons, but the host
can't skip commands once submitted. Reads return the emulated value and
the error bits in the actual cons.
Also add emulation for IDR1.CMDQS to return the hypervisor command queue
size which can be equal to or smaller to the HW size.
Signed-off-by: Mostafa Saleh <smostafa at google.com>
---
.../iommu/arm/arm-smmu-v3/pkvm/arm-smmu-v3.c | 151 +++++++++++++++++-
1 file changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/pkvm/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/pkvm/arm-smmu-v3.c
index 38b8ecc5cc10..8c67e348f4a3 100644
--- a/drivers/iommu/arm/arm-smmu-v3/pkvm/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/pkvm/arm-smmu-v3.c
@@ -111,7 +111,6 @@ static int smmu_unshare_pages(phys_addr_t addr, size_t size)
return 0;
}
-__maybe_unused
static bool smmu_cmdq_has_space(struct arm_smmu_queue *cmdq, u32 n)
{
struct arm_smmu_ll_queue *llq = &cmdq->llq;
@@ -340,6 +339,105 @@ static int smmu_init(void)
return ret;
}
+static bool smmu_filter_command(struct hyp_arm_smmu_v3_device *smmu, u64 *command)
+{
+ u64 type = FIELD_GET(CMDQ_0_OP, command[0]);
+
+ switch (type) {
+ case CMDQ_OP_CFGI_STE:
+ /* TBD: SHADOW_STE*/
+ break;
+ case CMDQ_OP_CFGI_ALL:
+ {
+ /*
+ * Linux doesn't use range STE invalidation, and only use this
+ * for CFGI_ALL, which is done on reset and not on an new STE
+ * being used.
+ * Although, this is not architectural we rely on the current Linux
+ * implementation.
+ */
+ if ((FIELD_GET(CMDQ_CFGI_1_RANGE, command[1]) != 31))
+ return true;
+ break;
+ }
+ case CMDQ_OP_TLBI_NH_ASID:
+ case CMDQ_OP_TLBI_NH_VA:
+ case CMDQ_OP_TLBI_NH_ALL:
+ case 0x13: /* CMD_TLBI_NH_VAA: Not used by Linux */
+ {
+ /* Only allow VMID = 0 */
+ if (FIELD_GET(CMDQ_TLBI_0_VMID, command[0]) != 0)
+ return true;
+ break;
+ }
+ case CMDQ_OP_PREFETCH_CFG:
+ case CMDQ_OP_CFGI_CD:
+ case CMDQ_OP_CFGI_CD_ALL:
+ case CMDQ_OP_TLBI_NSNH_ALL:
+ case CMDQ_OP_PRI_RESP:
+ case CMDQ_OP_RESUME:
+ break;
+ case CMDQ_OP_CMD_SYNC:
+ if (FIELD_GET(CMDQ_SYNC_0_CS, command[0]) == CMDQ_SYNC_0_CS_IRQ) {
+ /* Do not allow MSI */
+ command[0] &= ~CMDQ_SYNC_0_CS;
+ command[0] |= FIELD_PREP(CMDQ_SYNC_0_CS, CMDQ_SYNC_0_CS_SEV);
+ command[1] &= ~CMDQ_SYNC_1_MSIADDR_MASK;
+ }
+ break;
+ default:
+ /* Deny unknown commands */
+ return true;
+ }
+
+ return false;
+}
+
+static int smmu_emulate_cmdq_insert(struct hyp_arm_smmu_v3_device *smmu)
+{
+ u64 *host_cmdq = hyp_phys_to_virt(smmu->cmdq_host.base_dma);
+ bool use_wfe = smmu->features & ARM_SMMU_FEAT_SEV;
+ u64 cmd[CMDQ_ENT_DWORDS];
+ int idx, ret;
+ u32 pending;
+ bool skip;
+
+ if (!is_cmdq_enabled(smmu))
+ return 0;
+
+ pending = (1 << (smmu->cmdq_host.llq.max_n_shift)) - queue_space(&smmu->cmdq_host.llq);
+
+ hyp_spin_lock(&smmu->hw_lock);
+ /* Wait for the command queue to have some space. */
+ ret = smmu_wait(use_wfe, smmu_cmdq_has_space(&smmu->cmdq, pending));
+ if (ret) {
+ hyp_spin_unlock(&smmu->hw_lock);
+ return ret;
+ }
+
+ while (pending--) {
+ int i;
+
+ idx = Q_IDX(&smmu->cmdq_host.llq, smmu->cmdq_host.llq.cons);
+ queue_inc_cons(&smmu->cmdq_host.llq);
+
+ /* Copy the command to local buffer avoiding TOCTOU */
+ for (i = 0; i < CMDQ_ENT_DWORDS; ++i)
+ cmd[i] = le64_to_cpu(READ_ONCE(host_cmdq[idx * CMDQ_ENT_DWORDS + i]));
+
+ skip = smmu_filter_command(smmu, cmd);
+ if (WARN_ON(skip))
+ continue;
+ smmu_add_cmd_raw(smmu, cmd);
+ }
+
+ writel(smmu->cmdq.llq.prod, smmu->cmdq.prod_reg);
+
+ ret = smmu_wait(use_wfe, smmu_cmdq_empty(&smmu->cmdq));
+ hyp_spin_unlock(&smmu->hw_lock);
+ return ret;
+}
+
static void smmu_emulate_cmdq_enable(struct hyp_arm_smmu_v3_device *smmu)
{
u32 shift = smmu->cmdq_host.q_base & Q_BASE_LOG2SIZE;
@@ -381,18 +479,51 @@ static bool smmu_dabt_device(struct hyp_arm_smmu_v3_device *smmu,
*/
mask = read_only & ~(IDR0_S2P | IDR0_VMID16 | IDR0_MSI | IDR0_HYP | IDR0_ATS);
break;
- /* Passthrough the register access for bisectability, handled later */
case ARM_SMMU_CMDQ_BASE:
+ /*
+ * Although allowed to use smaller size, we rely on the SMMUv3 driver
+ * using 64-bit store instruction for simplicity.
+ */
+ if (len != sizeof(u64))
+ break;
if (is_write) {
/* Not allowed by the architecture */
if (is_cmdq_enabled(smmu))
break;
smmu->cmdq_host.q_base = val;
+ goto out_ret;
+ } else {
+ val = smmu->cmdq_host.q_base;
+ goto out_update_regs;
}
- mask = read_write;
- break;
case ARM_SMMU_CMDQ_PROD:
+ if (len != sizeof(u32))
+ break;
+ if (is_write) {
+ smmu->cmdq_host.llq.prod = val;
+ WARN_ON(smmu_emulate_cmdq_insert(smmu));
+ goto out_ret;
+ } else {
+ val = smmu->cmdq_host.llq.prod;
+ goto out_update_regs;
+ }
case ARM_SMMU_CMDQ_CONS:
+ if (len != sizeof(u32))
+ break;
+ if (is_write) {
+ if (WARN_ON(is_cmdq_enabled(smmu)))
+ break;
+
+ smmu->cmdq_host.llq.cons = val;
+ goto out_ret;
+ } else {
+ /* Propagate errors back to the host.*/
+ u32 cons = readl_relaxed(smmu->base + ARM_SMMU_CMDQ_CONS);
+
+ val = smmu->cmdq_host.llq.cons | (CMDQ_CONS_ERR & cons);
+ goto out_update_regs;
+ }
+ /* Passthrough the register access for bisectability, handled later */
case ARM_SMMU_STRTAB_BASE:
case ARM_SMMU_STRTAB_BASE_CFG:
case ARM_SMMU_GBPA:
@@ -468,10 +599,20 @@ static bool smmu_dabt_device(struct hyp_arm_smmu_v3_device *smmu,
mask = read_write;
break;
/* Allowed RO 32 bit registers. */
+ case ARM_SMMU_IDR1:
+ if (len != sizeof(u32))
+ break;
+ /* Cap CMDQS to the shadow queue size. */
+ if (!is_write) {
+ val = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
+ val &= ~IDR1_CMDQS;
+ val |= FIELD_PREP(IDR1_CMDQS, smmu->cmdq.llq.max_n_shift);
+ goto out_update_regs;
+ }
+ fallthrough;
case ARM_SMMU_IIDR:
case ARM_SMMU_IDR5:
case ARM_SMMU_IDR3:
- case ARM_SMMU_IDR1:
case ARM_SMMU_GERROR:
if (len != sizeof(u32))
break;
--
2.55.0.1082.g2b9226bbc0-goog
More information about the linux-arm-kernel
mailing list