@samitouri / QOSamiQemu / commits / ab9ead11f0

hw/arm/tegra241-cmdqv: Emulate CMDQ-V Config region

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 <nicolinc@nvidia.com> Reviewed-by: Eric Auger <eric.auger@redhat.com> Tested-by: Nicolin Chen <nicolinc@nvidia.com> Tested-by: Eric Auger <eric.auger@redhat.com> Signed-off-by: Shameer Kolothum <skolothumtho@nvidia.com> Message-id: 20260609112552.378999-16-skolothumtho@nvidia.com Co-developed-by: Shameer Kolothum <skolothumtho@nvidia.com> Signed-off-by: Shameer Kolothum <skolothumtho@nvidia.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Nicolin Chen committed Jun 9, 2026 at 12:25 UTC ab9ead11f03d8abc0c069309d516a8abb75eebe7
3 files changed +294 -1
hw/arm/tegra241-cmdqv.c
+180 -1
@@ -8,21 +8,200 @@
8 */
9
10 #include "qemu/osdep.h"
11 +#include "qemu/log.h"
12
13 #include "hw/arm/smmuv3.h"
14 #include "hw/arm/smmuv3-common.h"
15 #include "smmuv3-accel.h"
16 #include "tegra241-cmdqv.h"
17 +#include "trace.h"
18 +
19 +static uint64_t tegra241_cmdqv_config_vintf_read(Tegra241CMDQV *cmdqv,
20 + hwaddr offset)
21 +{
22 + int i;
23 +
24 + switch (offset) {
25 + case A_VINTF0_CONFIG:
26 + return cmdqv->vintf_config;
27 + case A_VINTF0_STATUS:
28 + return cmdqv->vintf_status;
29 + case A_VINTF0_SID_MATCH_0 ... A_VINTF0_SID_MATCH_15:
30 + i = (offset - A_VINTF0_SID_MATCH_0) / 4;
31 + return cmdqv->vintf_sid_match[i];
32 + case A_VINTF0_SID_REPLACE_0 ... A_VINTF0_SID_REPLACE_15:
33 + i = (offset - A_VINTF0_SID_REPLACE_0) / 4;
34 + return cmdqv->vintf_sid_replace[i];
35 + case A_VINTF0_LVCMDQ_ERR_MAP_0 ... A_VINTF0_LVCMDQ_ERR_MAP_3:
36 + i = (offset - A_VINTF0_LVCMDQ_ERR_MAP_0) / 4;
37 + return cmdqv->vintf_cmdq_err_map[i];
38 + default:
39 + /*
40 + * GLB_FILT_CFG_0 (offset 0xC) and GLB_FILT_DATA_0 (offset 0x10) are
41 + * filter config and filter data registers. They are not required for
42 + * normal VINTF operation and are not emulated.
43 + */
44 + qemu_log_mask(LOG_UNIMP, "%s unhandled read access at 0x%" PRIx64 "\n",
45 + __func__, offset);
46 + return 0;
47 + }
48 +}
49 +
50 +static void tegra241_cmdqv_config_vintf_write(Tegra241CMDQV *cmdqv,
51 + hwaddr offset, uint64_t value)
52 +{
53 + int i;
54 +
55 + switch (offset) {
56 + case A_VINTF0_CONFIG:
57 + /*
58 + * Mask out HYP_OWN on guest writes. This bit selects Hypervisor (1) vs
59 + * Guest (0) ownership of the CMDQ. Force it to 0 so the VINTF always
60 + * remains guest-owned.
61 + */
62 + value &= ~R_VINTF0_CONFIG_HYP_OWN_MASK;
63 +
64 + cmdqv->vintf_config = value;
65 + if (value & R_VINTF0_CONFIG_ENABLE_MASK) {
66 + cmdqv->vintf_status |= R_VINTF0_STATUS_ENABLE_OK_MASK;
67 + } else {
68 + cmdqv->vintf_status &= ~R_VINTF0_STATUS_ENABLE_OK_MASK;
69 + }
70 + break;
71 + case A_VINTF0_SID_MATCH_0 ... A_VINTF0_SID_MATCH_15:
72 + i = (offset - A_VINTF0_SID_MATCH_0) / 4;
73 + cmdqv->vintf_sid_match[i] = value;
74 + break;
75 + case A_VINTF0_SID_REPLACE_0 ... A_VINTF0_SID_REPLACE_15:
76 + i = (offset - A_VINTF0_SID_REPLACE_0) / 4;
77 + cmdqv->vintf_sid_replace[i] = value;
78 + break;
79 + default:
80 + /*
81 + * GLB_FILT_CFG_0 (offset 0xC) and GLB_FILT_DATA_0 (offset 0x10) are
82 + * filter config and filter data registers. They are not required for
83 + * normal VINTF operation and are not emulated.
84 + */
85 + qemu_log_mask(LOG_UNIMP, "%s unhandled write access at 0x%" PRIx64 "\n",
86 + __func__, offset);
87 + return;
88 + }
89 +}
90
91 static uint64_t tegra241_cmdqv_read_mmio(void *opaque, hwaddr offset,
92 unsigned size)
93 {
20 - return 0;
94 + Tegra241CMDQV *cmdqv = (Tegra241CMDQV *)opaque;
95 + uint64_t val = 0;
96 +
97 + if (offset >= TEGRA241_CMDQV_IO_LEN) {
98 + qemu_log_mask(LOG_UNIMP,
99 + "%s offset 0x%" PRIx64 " off limit (0x%x)\n", __func__,
100 + offset, TEGRA241_CMDQV_IO_LEN);
101 + goto out;
102 + }
103 +
104 + switch (offset) {
105 + case A_CONFIG:
106 + val = cmdqv->config;
107 + break;
108 + case A_PARAM:
109 + val = cmdqv->param;
110 + break;
111 + case A_STATUS:
112 + val = cmdqv->status;
113 + break;
114 + case A_VI_ERR_MAP_0 ... A_VI_ERR_MAP_1:
115 + val = cmdqv->vi_err_map[(offset - A_VI_ERR_MAP_0) / 4];
116 + break;
117 + case A_VI_INT_MASK_0 ... A_VI_INT_MASK_1:
118 + val = cmdqv->vi_int_mask[(offset - A_VI_INT_MASK_0) / 4];
119 + break;
120 + case A_CMDQ_ERR_MAP_0 ... A_CMDQ_ERR_MAP_3:
121 + val = cmdqv->cmdq_err_map[(offset - A_CMDQ_ERR_MAP_0) / 4];
122 + break;
123 + case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1:
124 + val = cmdqv->cmdq_alloc_map[(offset - A_CMDQ_ALLOC_MAP_0) / 4];
125 + break;
126 + case A_VINTF0_CONFIG ... A_VINTF0_LVCMDQ_ERR_MAP_3:
127 + val = tegra241_cmdqv_config_vintf_read(cmdqv, offset);
128 + break;
129 + default:
130 + qemu_log_mask(LOG_UNIMP, "%s unhandled read access at 0x%" PRIx64 "\n",
131 + __func__, offset);
132 + }
133 +
134 +out:
135 + trace_tegra241_cmdqv_read_mmio(offset, val, size);
136 + return val;
137 +}
138 +
139 +/* 4-byte MMIO write handler. */
140 +static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
141 + uint32_t value)
142 +{
143 + switch (offset) {
144 + case A_CONFIG:
145 + cmdqv->config = value;
146 + if (value & R_CONFIG_CMDQV_EN_MASK) {
147 + cmdqv->status |= R_STATUS_CMDQV_ENABLED_MASK;
148 + } else {
149 + cmdqv->status &= ~R_STATUS_CMDQV_ENABLED_MASK;
150 + }
151 + break;
152 + case A_VI_INT_MASK_0 ... A_VI_INT_MASK_1:
153 + cmdqv->vi_int_mask[(offset - A_VI_INT_MASK_0) / 4] = value;
154 + break;
155 + case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1:
156 + cmdqv->cmdq_alloc_map[(offset - A_CMDQ_ALLOC_MAP_0) / 4] = value;
157 + break;
158 + case A_VINTF0_CONFIG ... A_VINTF0_LVCMDQ_ERR_MAP_3:
159 + tegra241_cmdqv_config_vintf_write(cmdqv, offset, value);
160 + break;
161 + default:
162 + qemu_log_mask(LOG_UNIMP, "%s unhandled write access at 0x%" PRIx64 "\n",
163 + __func__, offset);
164 + }
165 +}
166 +
167 +/*
168 + * 8-byte MMIO write handler.
169 + */
170 +static void tegra241_cmdqv_writell_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
171 + uint64_t value)
172 +{
173 + qemu_log_mask(LOG_UNIMP,
174 + "%s unhandled 64-bit write at 0x%" PRIx64 " (WI)\n",
175 + __func__, offset);
176 }
177
178 static void tegra241_cmdqv_write_mmio(void *opaque, hwaddr offset,
179 uint64_t value, unsigned size)
180 {
181 + Tegra241CMDQV *cmdqv = (Tegra241CMDQV *)opaque;
182 +
183 + if (offset >= TEGRA241_CMDQV_IO_LEN) {
184 + qemu_log_mask(LOG_UNIMP,
185 + "%s offset 0x%" PRIx64 " off limit (0x%x)\n", __func__,
186 + offset, TEGRA241_CMDQV_IO_LEN);
187 + goto out;
188 + }
189 +
190 + switch (size) {
191 + case 4:
192 + tegra241_cmdqv_writel_mmio(cmdqv, offset, value);
193 + break;
194 + case 8:
195 + tegra241_cmdqv_writell_mmio(cmdqv, offset, value);
196 + break;
197 + default:
198 + qemu_log_mask(LOG_GUEST_ERROR,
199 + "%s bad write size %u at 0x%" PRIx64 "\n",
200 + __func__, size, offset);
201 + }
202 +
203 +out:
204 + trace_tegra241_cmdqv_write_mmio(offset, value, size);
205 }
206
207 static void tegra241_cmdqv_free_viommu(SMMUv3State *s)
hw/arm/tegra241-cmdqv.h
+110
@@ -10,10 +10,15 @@
10 #ifndef HW_ARM_TEGRA241_CMDQV_H
11 #define HW_ARM_TEGRA241_CMDQV_H
12
13 +#include "hw/core/registerfields.h"
14 +
15 #define CMDQV_VER 1
16 #define CMDQV_NUM_CMDQ_LOG2 1
17 #define CMDQV_NUM_SID_PER_VI_LOG2 4
18
19 +#define TEGRA241_CMDQV_MAX_CMDQ (1U << CMDQV_NUM_CMDQ_LOG2)
20 +#define TEGRA241_CMDQV_MAX_NUM_SID (1U << CMDQV_NUM_SID_PER_VI_LOG2)
21 +
22 /*
23 * Tegra241 CMDQV MMIO layout (64KB pages)
24 *
@@ -36,8 +41,113 @@ typedef struct Tegra241CMDQV {
41 qemu_irq irq;
42 IOMMUFDVeventq *veventq;
43 void *vintf_page0;
44 +
45 + /* CMDQ-V Config page register cache */
46 + uint32_t config;
47 + uint32_t param;
48 + uint32_t status;
49 + uint32_t vi_err_map[2];
50 + uint32_t vi_int_mask[2];
51 + uint32_t cmdq_err_map[4];
52 + uint32_t cmdq_alloc_map[TEGRA241_CMDQV_MAX_CMDQ];
53 +
54 + /* VINTF0 register cache (within CMDQ-V Config page) */
55 + uint32_t vintf_config;
56 + uint32_t vintf_status;
57 + uint32_t vintf_sid_match[TEGRA241_CMDQV_MAX_NUM_SID];
58 + uint32_t vintf_sid_replace[TEGRA241_CMDQV_MAX_NUM_SID];
59 + uint32_t vintf_cmdq_err_map[4];
60 } Tegra241CMDQV;
61
62 +/* CMDQ-V Config page registers (offset 0x00000) */
63 +REG32(CONFIG, 0x0)
64 +FIELD(CONFIG, CMDQV_EN, 0, 1)
65 +FIELD(CONFIG, CMDQV_PER_CMD_OFFSET, 1, 3)
66 +FIELD(CONFIG, CMDQ_MAX_CLK_BATCH, 4, 8)
67 +FIELD(CONFIG, CMDQ_MAX_CMD_BATCH, 12, 8)
68 +FIELD(CONFIG, CONS_DRAM_EN, 20, 1)
69 +
70 +REG32(PARAM, 0x4)
71 +FIELD(PARAM, CMDQV_VER, 0, 4)
72 +FIELD(PARAM, CMDQV_NUM_CMDQ_LOG2, 4, 4)
73 +FIELD(PARAM, CMDQV_NUM_VI_LOG2, 8, 4)
74 +FIELD(PARAM, CMDQV_NUM_SID_PER_VI_LOG2, 12, 4)
75 +
76 +REG32(STATUS, 0x8)
77 +FIELD(STATUS, CMDQV_ENABLED, 0, 1)
78 +
79 +/* SMMU_CMDQV_VI_ERR_MAP_0/1 definitions */
80 +#define A_VI_ERR_MAP_0 0x14
81 +#define A_VI_ERR_MAP_1 0x18
82 +#define V_VI_ERR_MAP_NO_ERROR (0)
83 +#define V_VI_ERR_MAP_ERROR (1)
84 +
85 +/* SMMU_CMDQV_VI_INT_MASK_0/1 definitions */
86 +#define A_VI_INT_MASK_0 0x1c
87 +#define A_VI_INT_MASK_1 0x20
88 +#define V_VI_INT_MASK_NOT_MASKED (0)
89 +#define V_VI_INT_MASK_MASKED (1)
90 +
91 +/* SMMU_CMDQV_CMDQ_ERR_MAP_0-3 definitions */
92 +#define A_CMDQ_ERR_MAP_0 0x24
93 +#define A_CMDQ_ERR_MAP_1 0x28
94 +#define A_CMDQ_ERR_MAP_2 0x2c
95 +#define A_CMDQ_ERR_MAP_3 0x30
96 +
97 +/*
98 + * CMDQ_ALLOC_MAP: one entry per physical VCMDQ. Hardware supports up to 128
99 + * entries (CMDQV_NUM_CMDQ_LOG2=7), but QEMU only exposes
100 + * TEGRA241_CMDQV_MAX_CMDQ (=2) VCMDQs per VM so only entries 0 and 1 are
101 + * defined here.
102 + */
103 +/* 2 identical register entries */
104 +#define SMMU_CMDQV_CMDQ_ALLOC_MAP_(i) \
105 + REG32(CMDQ_ALLOC_MAP_##i, 0x200 + i * 4) \
106 + FIELD(CMDQ_ALLOC_MAP_##i, ALLOC, 0, 1) \
107 + FIELD(CMDQ_ALLOC_MAP_##i, LVCMDQ, 1, 7) \
108 + FIELD(CMDQ_ALLOC_MAP_##i, VIRT_INTF_INDX, 15, 6)
109 +
110 +SMMU_CMDQV_CMDQ_ALLOC_MAP_(0)
111 +SMMU_CMDQV_CMDQ_ALLOC_MAP_(1)
112 +
113 +/* SMMU_CMDQV_VINTF0 registers (only VINTF0 is exposed to the guest) */
114 +REG32(VINTF0_CONFIG, 0x1000)
115 +FIELD(VINTF0_CONFIG, ENABLE, 0, 1)
116 +FIELD(VINTF0_CONFIG, VMID, 1, 16)
117 +FIELD(VINTF0_CONFIG, HYP_OWN, 17, 1)
118 +
119 +REG32(VINTF0_STATUS, 0x1004)
120 +FIELD(VINTF0_STATUS, ENABLE_OK, 0, 1)
121 +FIELD(VINTF0_STATUS, STATUS, 1, 3)
122 +FIELD(VINTF0_STATUS, VI_NUM_LVCMDQ, 16, 8)
123 +
124 +#define V_VINTF_STATUS_NO_ERROR (0 << 1)
125 +#define V_VINTF_STATUS_VCMDQ_ERROR (1 << 1)
126 +
127 +/*
128 + * SMMU_CMDQV_VINTF0_SID_MATCH/_REPLACE: 16 entries per VINTF
129 + * (CMDQV_NUM_SID_PER_VI_LOG2=4). Only _0 and _15 are defined,
130 + * used as switch case range bounds.
131 + */
132 +REG32(VINTF0_SID_MATCH_0, 0x1040)
133 +FIELD(VINTF0_SID_MATCH_0, ENABLE, 0, 1)
134 +FIELD(VINTF0_SID_MATCH_0, VIRT_SID, 1, 20)
135 +#define A_VINTF0_SID_MATCH_15 (A_VINTF0_SID_MATCH_0 + 15 * 4)
136 +
137 +REG32(VINTF0_SID_REPLACE_0, 0x1080)
138 +FIELD(VINTF0_SID_REPLACE_0, PHYS_SID, 0, 20)
139 +#define A_VINTF0_SID_REPLACE_15 (A_VINTF0_SID_REPLACE_0 + 15 * 4)
140 +
141 +/*
142 + * SMMU_CMDQV_VINTF0_LVCMDQ_ERR_MAP: 4 registers per VINTF covering 32 logical
143 + * VCMDQs each. With TEGRA241_CMDQV_MAX_CMDQ=2, only MAP_0 bits [1:0] carry
144 + * error state. MAP_1..MAP_3 always read as 0. Only _0 and _3 are defined,
145 + * used as switch case range bounds.
146 + */
147 +REG32(VINTF0_LVCMDQ_ERR_MAP_0, 0x10c0)
148 +FIELD(VINTF0_LVCMDQ_ERR_MAP_0, LVCMDQ_ERR_MAP, 0, 32)
149 +#define A_VINTF0_LVCMDQ_ERR_MAP_3 (A_VINTF0_LVCMDQ_ERR_MAP_0 + 3 * 4)
150 +
151 const SMMUv3AccelCmdqvOps *tegra241_cmdqv_get_ops(void);
152
153 #endif /* HW_ARM_TEGRA241_CMDQV_H */
hw/arm/trace-events
+4
@@ -72,6 +72,10 @@ smmuv3_accel_unset_iommu_device(int devfn, uint32_t devid) "devfn=0x%x (idev dev
72 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
73 smmuv3_accel_install_ste(uint32_t vsid, const char * type, uint32_t hwpt_id) "vSID=0x%x ste type=%s hwpt_id=0x%x"
74
75 +# tegra241-cmdqv
76 +tegra241_cmdqv_read_mmio(uint64_t offset, uint64_t val, unsigned size) "offset: 0x%"PRIx64" val: 0x%"PRIx64" size: 0x%x"
77 +tegra241_cmdqv_write_mmio(uint64_t offset, uint64_t val, unsigned size) "offset: 0x%"PRIx64" val: 0x%"PRIx64" size: 0x%x"
78 +
79 # strongarm.c
80 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"
81 strongarm_ssp_read_underrun(void) "SSP rx underrun"