On 5/19/26 12:36 PM, Shameer Kolothum wrote:
> From: Nicolin Chen <[email protected]>
>
> Tegra241 CMDQV exposes control and status registers in the CMDQ-V
> Config page (offset [0x0, 0x10000)) used to configure virtual command
> queue allocation and interrupt behavior.
>
> Add read/write emulation for the CMDQ-V Config region
> ([CMDQV_BASE, CMDQV_CMDQ_BASE]), backed by a simple register cache.
> This includes CONFIG, PARAM, STATUS, VI error and interrupt maps, CMDQ
> allocation map and the VINTF0 related registers defined in the CMDQ-V
> Config space. Only VINTF0 is supported; VINTF1-63 are not.
>
> Dispatch writes on access size: Introduced writel_mmio for 4-byte and
> writell_mmio for 8-byte. Reads need no split as the MMIO framework masks
> the returned value to the access size.
>
> Signed-off-by: Nicolin Chen <[email protected]>
> Co-developed-by: Shameer Kolothum <[email protected]>
> Signed-off-by: Shameer Kolothum <[email protected]>
> ---
> hw/arm/tegra241-cmdqv.h | 110 +++++++++++++++++++++++
> hw/arm/tegra241-cmdqv.c | 188 +++++++++++++++++++++++++++++++++++++++-
> hw/arm/trace-events | 4 +
> 3 files changed, 298 insertions(+), 4 deletions(-)
>
> diff --git a/hw/arm/tegra241-cmdqv.h b/hw/arm/tegra241-cmdqv.h
> index 54854421f2..ace327443a 100644
> --- a/hw/arm/tegra241-cmdqv.h
> +++ b/hw/arm/tegra241-cmdqv.h
> @@ -10,10 +10,15 @@
> #ifndef HW_ARM_TEGRA241_CMDQV_H
> #define HW_ARM_TEGRA241_CMDQV_H
>
> +#include "hw/core/registerfields.h"
> +
> #define CMDQV_VER 1
> #define CMDQV_NUM_CMDQ_LOG2 1
> #define CMDQV_NUM_SID_PER_VI_LOG2 4
>
> +#define TEGRA241_CMDQV_MAX_CMDQ (1U << CMDQV_NUM_CMDQ_LOG2)
> +#define TEGRA241_CMDQV_MAX_NUM_SID (1U << CMDQV_NUM_SID_PER_VI_LOG2)
> +
> /*
> * Tegra241 CMDQV MMIO layout (64KB pages)
> *
> @@ -31,8 +36,113 @@ typedef struct Tegra241CMDQV {
> MemoryRegion mmio_cmdqv;
> qemu_irq irq;
> IOMMUFDVeventq *veventq;
> +
> + /* CMDQ-V Config page register cache */
> + uint32_t config;
> + uint32_t param;
> + uint32_t status;
> + uint32_t vi_err_map[2];
> + uint32_t vi_int_mask[2];
> + uint32_t cmdq_err_map[4];
> + uint32_t cmdq_alloc_map[TEGRA241_CMDQV_MAX_CMDQ];
> +
> + /* VINTF0 register cache (within CMDQ-V Config page) */
> + uint32_t vintf_config;
> + uint32_t vintf_status;
> + uint32_t vintf_sid_match[TEGRA241_CMDQV_MAX_NUM_SID];
> + uint32_t vintf_sid_replace[TEGRA241_CMDQV_MAX_NUM_SID];
> + uint32_t vintf_cmdq_err_map[4];
> } Tegra241CMDQV;
>
> +/* CMDQ-V Config page registers (offset 0x00000) */
> +REG32(CONFIG, 0x0)
> +FIELD(CONFIG, CMDQV_EN, 0, 1)
> +FIELD(CONFIG, CMDQV_PER_CMD_OFFSET, 1, 3)
> +FIELD(CONFIG, CMDQ_MAX_CLK_BATCH, 4, 8)
> +FIELD(CONFIG, CMDQ_MAX_CMD_BATCH, 12, 8)
> +FIELD(CONFIG, CONS_DRAM_EN, 20, 1)
> +
> +REG32(PARAM, 0x4)
> +FIELD(PARAM, CMDQV_VER, 0, 4)
> +FIELD(PARAM, CMDQV_NUM_CMDQ_LOG2, 4, 4)
> +FIELD(PARAM, CMDQV_NUM_VI_LOG2, 8, 4)
> +FIELD(PARAM, CMDQV_NUM_SID_PER_VI_LOG2, 12, 4)
> +
> +REG32(STATUS, 0x8)
> +FIELD(STATUS, CMDQV_ENABLED, 0, 1)
> +
> +/* SMMU_CMDQV_VI_ERR_MAP_0/1 definitions */
> +#define A_VI_ERR_MAP_0 0x14
> +#define A_VI_ERR_MAP_1 0x18
> +#define V_VI_ERR_MAP_NO_ERROR (0)
> +#define V_VI_ERR_MAP_ERROR (1)
> +
> +/* SMMU_CMDQV_VI_INT_MASK_0/1 definitions */
> +#define A_VI_INT_MASK_0 0x1c
> +#define A_VI_INT_MASK_1 0x20
> +#define V_VI_INT_MASK_NOT_MASKED (0)
> +#define V_VI_INT_MASK_MASKED (1)
> +
> +/* SMMU_CMDQV_CMDQ_ERR_MAP_0-3 definitions */
> +#define A_CMDQ_ERR_MAP_0 0x24
> +#define A_CMDQ_ERR_MAP_1 0x28
> +#define A_CMDQ_ERR_MAP_2 0x2c
> +#define A_CMDQ_ERR_MAP_3 0x30
> +
> +/*
> + * CMDQ_ALLOC_MAP: one entry per physical VCMDQ. Hardware supports up to 128
> + * entries (CMDQV_NUM_CMDQ_LOG2=7), but QEMU only exposes
> + * TEGRA241_CMDQV_MAX_CMDQ (=2) VCMDQs per VM so only entries 0 and 1 are
> + * defined here.
> + */
> +/* 2 identical register entries */
> +#define SMMU_CMDQV_CMDQ_ALLOC_MAP_(i) \
> + REG32(CMDQ_ALLOC_MAP_##i, 0x200 + i * 4) \
> + FIELD(CMDQ_ALLOC_MAP_##i, ALLOC, 0, 1) \
> + FIELD(CMDQ_ALLOC_MAP_##i, LVCMDQ, 1, 7) \
> + FIELD(CMDQ_ALLOC_MAP_##i, VIRT_INTF_INDX, 15, 6)
> +
> +SMMU_CMDQV_CMDQ_ALLOC_MAP_(0)
> +SMMU_CMDQV_CMDQ_ALLOC_MAP_(1)
> +
> +/* SMMU_CMDQV_VINTF0 registers (only VINTF0 is exposed to the guest) */
> +REG32(VINTF0_CONFIG, 0x1000)
> +FIELD(VINTF0_CONFIG, ENABLE, 0, 1)
> +FIELD(VINTF0_CONFIG, VMID, 1, 16)
> +FIELD(VINTF0_CONFIG, HYP_OWN, 17, 1)
> +
> +REG32(VINTF0_STATUS, 0x1004)
> +FIELD(VINTF0_STATUS, ENABLE_OK, 0, 1)
> +FIELD(VINTF0_STATUS, STATUS, 1, 3)
> +FIELD(VINTF0_STATUS, VI_NUM_LVCMDQ, 16, 8)
> +
> +#define V_VINTF_STATUS_NO_ERROR (0 << 1)
> +#define V_VINTF_STATUS_VCMDQ_ERROR (1 << 1)
> +
> +/*
> + * SMMU_CMDQV_VINTF0_SID_MATCH/_REPLACE: 16 entries per VINTF
> + * (CMDQV_NUM_SID_PER_VI_LOG2=4). Only _0 and _15 are defined,
> + * used as switch case range bounds.
> + */
> +REG32(VINTF0_SID_MATCH_0, 0x1040)
> +FIELD(VINTF0_SID_MATCH_0, ENABLE, 0, 1)
> +FIELD(VINTF0_SID_MATCH_0, VIRT_SID, 1, 20)
> +#define A_VINTF0_SID_MATCH_15 (A_VINTF0_SID_MATCH_0 + 15 * 4)
> +
> +REG32(VINTF0_SID_REPLACE_0, 0x1080)
> +FIELD(VINTF0_SID_REPLACE_0, PHYS_SID, 0, 20)
> +#define A_VINTF0_SID_REPLACE_15 (A_VINTF0_SID_REPLACE_0 + 15 * 4)
> +
> +/*
> + * SMMU_CMDQV_VINTF0_LVCMDQ_ERR_MAP: 4 registers per VINTF covering 32
> logical
> + * VCMDQs each. With TEGRA241_CMDQV_MAX_CMDQ=2, only MAP_0 bits [1:0] carry
> + * error state. MAP_1..MAP_3 always read as 0. Only _0 and _3 are defined,
> + * used as switch case range bounds.
> + */
> +REG32(VINTF0_LVCMDQ_ERR_MAP_0, 0x10c0)
> +FIELD(VINTF0_LVCMDQ_ERR_MAP_0, LVCMDQ_ERR_MAP, 0, 32)
> +#define A_VINTF0_LVCMDQ_ERR_MAP_3 (A_VINTF0_LVCMDQ_ERR_MAP_0 + 3 * 4)
> +
> const SMMUv3AccelCmdqvOps *tegra241_cmdqv_get_ops(void);
>
> #endif /* HW_ARM_TEGRA241_CMDQV_H */
> diff --git a/hw/arm/tegra241-cmdqv.c b/hw/arm/tegra241-cmdqv.c
> index 6a6cfc2f71..813b9a61ce 100644
> --- a/hw/arm/tegra241-cmdqv.c
> +++ b/hw/arm/tegra241-cmdqv.c
> @@ -8,20 +8,200 @@
> */
>
> #include "qemu/osdep.h"
> +#include "qemu/log.h"
>
> #include "hw/arm/smmuv3.h"
> #include "hw/arm/smmuv3-common.h"
> #include "smmuv3-accel.h"
> #include "tegra241-cmdqv.h"
> +#include "trace.h"
>
> -static uint64_t tegra241_cmdqv_read_mmio(void *opaque, hwaddr offset,
> unsigned size)
> +static uint64_t tegra241_cmdqv_config_vintf_read(Tegra241CMDQV *cmdqv,
> + hwaddr offset)
> {
> - return 0;
> + int i;
> +
> + switch (offset) {
> + case A_VINTF0_CONFIG:
> + return cmdqv->vintf_config;
> + case A_VINTF0_STATUS:
> + return cmdqv->vintf_status;
> + case A_VINTF0_SID_MATCH_0 ... A_VINTF0_SID_MATCH_15:
> + i = (offset - A_VINTF0_SID_MATCH_0) / 4;
> + return cmdqv->vintf_sid_match[i];
> + case A_VINTF0_SID_REPLACE_0 ... A_VINTF0_SID_REPLACE_15:
> + i = (offset - A_VINTF0_SID_REPLACE_0) / 4;
> + return cmdqv->vintf_sid_replace[i];
> + case A_VINTF0_LVCMDQ_ERR_MAP_0 ... A_VINTF0_LVCMDQ_ERR_MAP_3:
> + i = (offset - A_VINTF0_LVCMDQ_ERR_MAP_0) / 4;
> + return cmdqv->vintf_cmdq_err_map[i];
> + default:
> + /*
> + * GLB_FILT_CFG_0 (offset 0xC) and GLB_FILT_DATA_0 (offset 0x10) are
> + * filter config and filter data registers. They are not required for
> + * normal VINTF operation and are not emulated.
> + */
> + qemu_log_mask(LOG_UNIMP, "%s unhandled read access at 0x%" PRIx64
> "\n",
> + __func__, offset);
> + return 0;
> + }
> +}
> +
> +static void tegra241_cmdqv_config_vintf_write(Tegra241CMDQV *cmdqv,
> + hwaddr offset, uint64_t value)
> +{
> + int i;
> +
> + switch (offset) {
> + case A_VINTF0_CONFIG:
> + /*
> + * Mask out HYP_OWN on guest writes. This bit selects Hypervisor (1)
> vs
> + * Guest (0) ownership of the CMDQ. Force it to 0 so the VINTF always
> + * remains guest-owned.
> + */
> + value &= ~R_VINTF0_CONFIG_HYP_OWN_MASK;
> +
> + cmdqv->vintf_config = value;
> + if (value & R_VINTF0_CONFIG_ENABLE_MASK) {
> + cmdqv->vintf_status |= R_VINTF0_STATUS_ENABLE_OK_MASK;
> + } else {
> + cmdqv->vintf_status &= ~R_VINTF0_STATUS_ENABLE_OK_MASK;
> + }
> + break;
> + case A_VINTF0_SID_MATCH_0 ... A_VINTF0_SID_MATCH_15:
> + i = (offset - A_VINTF0_SID_MATCH_0) / 4;
> + cmdqv->vintf_sid_match[i] = value;
> + break;
> + case A_VINTF0_SID_REPLACE_0 ... A_VINTF0_SID_REPLACE_15:
> + i = (offset - A_VINTF0_SID_REPLACE_0) / 4;
> + cmdqv->vintf_sid_replace[i] = value;
> + break;
> + default:
> + /*
> + * GLB_FILT_CFG_0 (offset 0xC) and GLB_FILT_DATA_0 (offset 0x10) are
> + * filter config and filter data registers. They are not required for
> + * normal VINTF operation and are not emulated.
> + */
> + qemu_log_mask(LOG_UNIMP, "%s unhandled write access at 0x%" PRIx64
> "\n",
> + __func__, offset);
> + return;
> + }
> }
>
> -static void tegra241_cmdqv_write_mmio(void *opaque, hwaddr offset, uint64_t
> value,
> - unsigned size)
> +static uint64_t tegra241_cmdqv_read_mmio(void *opaque, hwaddr offset,
> + unsigned size)
> {
> + Tegra241CMDQV *cmdqv = (Tegra241CMDQV *)opaque;
> + uint64_t val = 0;
> +
> + if (offset >= TEGRA241_CMDQV_IO_LEN) {
> + qemu_log_mask(LOG_UNIMP,
> + "%s offset 0x%" PRIx64 " off limit (0x%x)\n", __func__,
> + offset, TEGRA241_CMDQV_IO_LEN);
> + goto out;
> + }
> +
> + switch (offset) {
> + case A_CONFIG:
> + val = cmdqv->config;
> + break;
> + case A_PARAM:
> + val = cmdqv->param;
> + break;
> + case A_STATUS:
> + val = cmdqv->status;
> + break;
> + case A_VI_ERR_MAP_0 ... A_VI_ERR_MAP_1:
> + val = cmdqv->vi_err_map[(offset - A_VI_ERR_MAP_0) / 4];
> + break;
> + case A_VI_INT_MASK_0 ... A_VI_INT_MASK_1:
> + val = cmdqv->vi_int_mask[(offset - A_VI_INT_MASK_0) / 4];
> + break;
> + case A_CMDQ_ERR_MAP_0 ... A_CMDQ_ERR_MAP_3:
> + val = cmdqv->cmdq_err_map[(offset - A_CMDQ_ERR_MAP_0) / 4];
> + break;
> + case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1:
> + val = cmdqv->cmdq_alloc_map[(offset - A_CMDQ_ALLOC_MAP_0) / 4];
> + break;
> + case A_VINTF0_CONFIG ... A_VINTF0_LVCMDQ_ERR_MAP_3:
> + val = tegra241_cmdqv_config_vintf_read(cmdqv, offset);
> + break;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s unhandled read access at 0x%" PRIx64
> "\n",
> + __func__, offset);
> + }
> +
> +out:
> + trace_tegra241_cmdqv_read_mmio(offset, val, size);
> + return val;
> +}
> +
> +/* 4-byte MMIO write handler. */
> +static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
> + uint32_t value)
> +{
> + switch (offset) {
> + case A_CONFIG:
> + cmdqv->config = value;
> + if (value & R_CONFIG_CMDQV_EN_MASK) {
> + cmdqv->status |= R_STATUS_CMDQV_ENABLED_MASK;
> + } else {
> + cmdqv->status &= ~R_STATUS_CMDQV_ENABLED_MASK;
> + }
> + break;
> + case A_VI_INT_MASK_0 ... A_VI_INT_MASK_1:
> + cmdqv->vi_int_mask[(offset - A_VI_INT_MASK_0) / 4] = value;
> + break;
> + case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1:
> + cmdqv->cmdq_alloc_map[(offset - A_CMDQ_ALLOC_MAP_0) / 4] = value;
> + break;
> + case A_VINTF0_CONFIG ... A_VINTF0_LVCMDQ_ERR_MAP_3:
> + tegra241_cmdqv_config_vintf_write(cmdqv, offset, value);
> + break;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s unhandled write access at 0x%" PRIx64
> "\n",
> + __func__, offset);
> + }
> +}
> +
> +/*
> + * 8-byte MMIO write handler.
> + */
> +static void tegra241_cmdqv_writell_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
> + uint64_t value)
> +{
> + qemu_log_mask(LOG_UNIMP,
> + "%s unhandled 64-bit write at 0x%" PRIx64 " (WI)\n",
> + __func__, offset);
> +}
> +
> +static void tegra241_cmdqv_write_mmio(void *opaque, hwaddr offset,
> + uint64_t value, unsigned size)
> +{
> + Tegra241CMDQV *cmdqv = (Tegra241CMDQV *)opaque;
> +
> + if (offset >= TEGRA241_CMDQV_IO_LEN) {
> + qemu_log_mask(LOG_UNIMP,
> + "%s offset 0x%" PRIx64 " off limit (0x%x)\n", __func__,
> + offset, TEGRA241_CMDQV_IO_LEN);
> + goto out;
> + }
> +
> + switch (size) {
> + case 4:
> + tegra241_cmdqv_writel_mmio(cmdqv, offset, value);
> + break;
> + case 8:
> + tegra241_cmdqv_writell_mmio(cmdqv, offset, value);
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s bad write size %u at 0x%" PRIx64 "\n",
> + __func__, size, offset);
> + }
> +
> +out:
> + trace_tegra241_cmdqv_write_mmio(offset, value, size);
> }
>
> static void tegra241_cmdqv_free_viommu(SMMUv3State *s)
> diff --git a/hw/arm/trace-events b/hw/arm/trace-events
> index 3457536fb0..8c61d66a26 100644
> --- a/hw/arm/trace-events
> +++ b/hw/arm/trace-events
> @@ -72,6 +72,10 @@ smmuv3_accel_unset_iommu_device(int devfn, uint32_t devid)
> "devfn=0x%x (idev dev
> smmuv3_accel_translate_ste(uint32_t vsid, uint32_t hwpt_id, uint64_t ste_1,
> uint64_t ste_0) "vSID=0x%x hwpt_id=0x%x ste=%"PRIx64":%"PRIx64
> smmuv3_accel_install_ste(uint32_t vsid, const char * type, uint32_t hwpt_id)
> "vSID=0x%x ste type=%s hwpt_id=0x%x"
>
> +# tegra241-cmdqv
> +tegra241_cmdqv_read_mmio(uint64_t offset, uint64_t val, unsigned size)
> "offset: 0x%"PRIx64" val: 0x%"PRIx64" size: 0x%x"
> +tegra241_cmdqv_write_mmio(uint64_t offset, uint64_t val, unsigned size)
> "offset: 0x%"PRIx64" val: 0x%"PRIx64" size: 0x%x"
> +
> # strongarm.c
> strongarm_uart_update_parameters(const char *label, int speed, char parity,
> int data_bits, int stop_bits) "%s speed=%d parity=%c data=%d stop=%d"
> strongarm_ssp_read_underrun(void) "SSP rx underrun"
Reviewed-by: Eric Auger <[email protected]>
Eric