@samitouri / QOSamiQemu / commits / 6833652987

hw/arm/tegra241-cmdqv: Allocate HW VCMDQs once configured

Add support for allocating IOMMUFD hardware queues when the guest programs the VCMDQ BASE registers. VCMDQ_EN lives in VCMDQ_CONFIG, which is on the VINTF Page0 region that a later patch installs into guest MMIO — so QEMU won't trap its writes. Allocate the hardware queue instead once all of these are set: BASE programmed, CMDQ_ALLOC_MAP.ALLOC, and CMDQV / VINTF enabled. Each precondition write retries the allocation, so the guest may program them in any order. iommufd_backend_alloc_hw_queue() needs the guest physical address of the VCMDQ ring buffer, so allocation is deferred until the guest has populated BASE. If a hardware queue was previously allocated for the same VCMDQ, free it before reallocation. All allocated VCMDQs are freed when CMDQV or VINTF is disabled, when the ALLOC bit is cleared, or on reset. On allocation failure, set CMDQ_INIT_ERR and clear CMDQ_EN_OK in the cache so trapped guest reads see the failure rather than a queue that looks live. Clear them on a later successful allocation. A guest CMDQ_EN write then sets CMDQ_EN_OK only if CMDQ_INIT_ERR is clear. Signed-off-by: Nicolin Chen <nicolinc@nvidia.com> Tested-by: Nicolin Chen <nicolinc@nvidia.com> Tested-by: Eric Auger <eric.auger@redhat.com> Signed-off-by: Shameer Kolothum <skolothumtho@nvidia.com> Reviewed-by: Eric Auger <eric.auger@redhat.com> Message-id: 20260609112552.378999-19-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 68336529877af422c0d7c087432998d69af5384f
2 files changed +171 -11
hw/arm/tegra241-cmdqv.c
+160 -11
@@ -16,6 +16,96 @@
16 #include "tegra241-cmdqv.h"
17 #include "trace.h"
18
19 +static void tegra241_cmdqv_free_vcmdq(Tegra241CMDQV *cmdqv, int index)
20 +{
21 + IOMMUFDViommu *viommu = cmdqv->s_accel->viommu;
22 + IOMMUFDHWqueue *vcmdq = cmdqv->vcmdq[index];
23 +
24 + if (!vcmdq) {
25 + return;
26 + }
27 + iommufd_backend_free_id(viommu->iommufd, vcmdq->hw_queue_id);
28 + g_free(vcmdq);
29 + cmdqv->vcmdq[index] = NULL;
30 +}
31 +
32 +/*
33 + * A VCMDQ's HW queue can be allocated once the guest has programmed:
34 + * - VCMDQ_BASE (ring buffer GPA and size). This only checks that BASE is
35 + * non-zero, not that both the _L and _H halves have been written; a
36 + * half-written BASE may pass here, but the write of the second half
37 + * re-runs setup and reallocates with the complete address.
38 + * - the VINTF mapping (CMDQ_ALLOC_MAP.ALLOC).
39 + * - both the CMDQV global enable and the VINTF enable.
40 + */
41 +static bool tegra241_cmdqv_vcmdq_ready_to_alloc(Tegra241CMDQV *cmdqv, int index)
42 +{
43 + return cmdqv->vcmdq_base[index] &&
44 + (cmdqv->cmdq_alloc_map[index] & R_CMDQ_ALLOC_MAP_0_ALLOC_MASK) &&
45 + tegra241_cmdqv_enabled(cmdqv) && tegra241_vintf_enabled(cmdqv);
46 +}
47 +
48 +/*
49 + * Allocate a host HW VCMDQ from the current cached BASE / size for @index.
50 + * No-op (returns true) until the VCMDQ is ready to be allocated.
51 + */
52 +static bool tegra241_cmdqv_setup_vcmdq(Tegra241CMDQV *cmdqv, int index,
53 + Error **errp)
54 +{
55 + SMMUv3AccelState *accel = cmdqv->s_accel;
56 + uint64_t base_mask = (uint64_t)R_VCMDQ0_BASE_L_ADDR_MASK |
57 + (uint64_t)R_VCMDQ0_BASE_H_ADDR_MASK << 32;
58 + uint64_t addr = cmdqv->vcmdq_base[index] & base_mask;
59 + uint64_t log2 = cmdqv->vcmdq_base[index] & R_VCMDQ0_BASE_L_LOG2SIZE_MASK;
60 + uint64_t size = 1ULL << (log2 + 4);
61 + IOMMUFDViommu *viommu = accel->viommu;
62 + IOMMUFDHWqueue *hw_queue;
63 + uint32_t hw_queue_id;
64 +
65 + if (!tegra241_cmdqv_vcmdq_ready_to_alloc(cmdqv, index)) {
66 + return true;
67 + }
68 +
69 + tegra241_cmdqv_free_vcmdq(cmdqv, index);
70 +
71 + if (!iommufd_backend_alloc_hw_queue(viommu->iommufd, viommu->viommu_id,
72 + IOMMU_HW_QUEUE_TYPE_TEGRA241_CMDQV,
73 + index, addr, size, &hw_queue_id,
74 + errp)) {
75 + /* Record the failure in the cache. */
76 + cmdqv->vcmdq_gerror[index] |= R_VCMDQ0_GERROR_CMDQ_INIT_ERR_MASK;
77 + cmdqv->vcmdq_status[index] &= ~R_VCMDQ0_STATUS_CMDQ_EN_OK_MASK;
78 + return false;
79 + }
80 + hw_queue = g_new(IOMMUFDHWqueue, 1);
81 + hw_queue->hw_queue_id = hw_queue_id;
82 + hw_queue->viommu = viommu;
83 + cmdqv->vcmdq[index] = hw_queue;
84 +
85 + cmdqv->vcmdq_gerror[index] &= ~R_VCMDQ0_GERROR_CMDQ_INIT_ERR_MASK;
86 + cmdqv->vcmdq_status[index] |= R_VCMDQ0_STATUS_CMDQ_EN_OK_MASK;
87 +
88 + return true;
89 +}
90 +
91 +static void tegra241_cmdqv_free_all_vcmdq(Tegra241CMDQV *cmdqv)
92 +{
93 + /* uapi/linux/iommufd.h: hw_queue destroy must be in descending @index. */
94 + for (int i = (TEGRA241_CMDQV_MAX_CMDQ - 1); i >= 0; i--) {
95 + tegra241_cmdqv_free_vcmdq(cmdqv, i);
96 + }
97 +}
98 +
99 +static void tegra241_cmdqv_setup_all_vcmdq(Tegra241CMDQV *cmdqv,
100 + Error **errp)
101 +{
102 + for (int i = 0; i < TEGRA241_CMDQV_MAX_CMDQ; i++) {
103 + if (!tegra241_cmdqv_setup_vcmdq(cmdqv, i, errp)) {
104 + return;
105 + }
106 + }
107 +}
108 +
109 /*
110 * Read a VCMDQ Page 0 register (control/status) using VCMDQ0_* offsets.
111 *
@@ -143,7 +233,12 @@ static void tegra241_cmdqv_write_vcmdq_page0(Tegra241CMDQV *cmdqv,
233 break;
234 case A_VCMDQ0_CONFIG:
235 if (value & R_VCMDQ0_CONFIG_CMDQ_EN_MASK) {
146 - cmdqv->vcmdq_status[index] |= R_VCMDQ0_STATUS_CMDQ_EN_OK_MASK;
236 + /* Report init error if any. */
237 + if (!(cmdqv->vcmdq_gerror[index] &
238 + R_VCMDQ0_GERROR_CMDQ_INIT_ERR_MASK)) {
239 + cmdqv->vcmdq_status[index] |=
240 + R_VCMDQ0_STATUS_CMDQ_EN_OK_MASK;
241 + }
242 } else {
243 cmdqv->vcmdq_status[index] &= ~R_VCMDQ0_STATUS_CMDQ_EN_OK_MASK;
244 }
@@ -167,16 +262,19 @@ static void tegra241_cmdqv_write_vcmdq_page0(Tegra241CMDQV *cmdqv,
262 */
263 static void tegra241_cmdqv_write_vcmdq_page1(Tegra241CMDQV *cmdqv,
264 hwaddr offset0, int index,
170 - uint32_t value, bool direct)
265 + uint32_t value, bool direct,
266 + Error **errp)
267 {
268 switch (offset0) {
269 case A_VCMDQ0_BASE_L:
270 cmdqv->vcmdq_base[index] =
271 deposit64(cmdqv->vcmdq_base[index], 0, 32, value);
272 + tegra241_cmdqv_setup_vcmdq(cmdqv, index, errp);
273 break;
274 case A_VCMDQ0_BASE_H:
275 cmdqv->vcmdq_base[index] =
276 deposit64(cmdqv->vcmdq_base[index], 32, 32, value);
277 + tegra241_cmdqv_setup_vcmdq(cmdqv, index, errp);
278 break;
279 case A_VCMDQ0_CONS_INDX_BASE_DRAM_L:
280 cmdqv->vcmdq_cons_indx_base[index] =
@@ -200,11 +298,13 @@ static void tegra241_cmdqv_write_vcmdq_page1(Tegra241CMDQV *cmdqv,
298 */
299 static void tegra241_cmdqv_write_vcmdq_page1_64(Tegra241CMDQV *cmdqv,
300 hwaddr offset0, int index,
203 - uint64_t value, bool direct)
301 + uint64_t value, bool direct,
302 + Error **errp)
303 {
304 switch (offset0) {
305 case A_VCMDQ0_BASE_L:
306 cmdqv->vcmdq_base[index] = value;
307 + tegra241_cmdqv_setup_vcmdq(cmdqv, index, errp);
308 break;
309 case A_VCMDQ0_CONS_INDX_BASE_DRAM_L:
310 cmdqv->vcmdq_cons_indx_base[index] = value;
@@ -219,7 +319,8 @@ static void tegra241_cmdqv_write_vcmdq_page1_64(Tegra241CMDQV *cmdqv,
319 }
320
321 static void tegra241_cmdqv_config_vintf_write(Tegra241CMDQV *cmdqv,
222 - hwaddr offset, uint64_t value)
322 + hwaddr offset, uint64_t value,
323 + Error **errp)
324 {
325 int i;
326
@@ -235,7 +336,13 @@ static void tegra241_cmdqv_config_vintf_write(Tegra241CMDQV *cmdqv,
336 cmdqv->vintf_config = value;
337 if (value & R_VINTF0_CONFIG_ENABLE_MASK) {
338 cmdqv->vintf_status |= R_VINTF0_STATUS_ENABLE_OK_MASK;
339 + /*
340 + * VCMDQs whose BASE was programmed before VINTF was
341 + * enabled need their hw_queue allocated now.
342 + */
343 + tegra241_cmdqv_setup_all_vcmdq(cmdqv, errp);
344 } else {
345 + tegra241_cmdqv_free_all_vcmdq(cmdqv);
346 cmdqv->vintf_status &= ~R_VINTF0_STATUS_ENABLE_OK_MASK;
347 }
348 break;
@@ -341,6 +448,7 @@ out:
448 static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
449 uint32_t value)
450 {
451 + Error *local_err = NULL;
452 int index;
453
454 switch (offset) {
@@ -348,18 +456,39 @@ static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
456 cmdqv->config = value;
457 if (value & R_CONFIG_CMDQV_EN_MASK) {
458 cmdqv->status |= R_STATUS_CMDQV_ENABLED_MASK;
459 + /*
460 + * VCMDQs whose BASE was programmed before CMDQV was enabled
461 + * need their hw_queue allocated now.
462 + */
463 + tegra241_cmdqv_setup_all_vcmdq(cmdqv, &local_err);
464 } else {
465 + tegra241_cmdqv_free_all_vcmdq(cmdqv);
466 cmdqv->status &= ~R_STATUS_CMDQV_ENABLED_MASK;
467 }
468 break;
469 case A_VI_INT_MASK_0 ... A_VI_INT_MASK_1:
470 cmdqv->vi_int_mask[(offset - A_VI_INT_MASK_0) / 4] = value;
471 break;
358 - case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1:
359 - cmdqv->cmdq_alloc_map[(offset - A_CMDQ_ALLOC_MAP_0) / 4] = value;
472 + case A_CMDQ_ALLOC_MAP_0 ... A_CMDQ_ALLOC_MAP_1: {
473 + int idx = (offset - A_CMDQ_ALLOC_MAP_0) / 4;
474 + bool was_alloc = cmdqv->cmdq_alloc_map[idx] &
475 + R_CMDQ_ALLOC_MAP_0_ALLOC_MASK;
476 + bool now_alloc = value & R_CMDQ_ALLOC_MAP_0_ALLOC_MASK;
477 +
478 + cmdqv->cmdq_alloc_map[idx] = value;
479 + /*
480 + * If the VCMDQ was already programmed (BASE) before mapping, fire
481 + * setup on the ALLOC 0->1 transition; tear down on 1->0.
482 + */
483 + if (!was_alloc && now_alloc) {
484 + tegra241_cmdqv_setup_vcmdq(cmdqv, idx, &local_err);
485 + } else if (was_alloc && !now_alloc) {
486 + tegra241_cmdqv_free_vcmdq(cmdqv, idx);
487 + }
488 break;
489 + }
490 case A_VINTF0_CONFIG ... A_VINTF0_LVCMDQ_ERR_MAP_3:
362 - tegra241_cmdqv_config_vintf_write(cmdqv, offset, value);
491 + tegra241_cmdqv_config_vintf_write(cmdqv, offset, value, &local_err);
492 break;
493 case A_VI_VCMDQ0_CONS_INDX ... A_VI_VCMDQ1_GERRORN:
494 /*
@@ -387,17 +516,23 @@ static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
516 offset -= CMDQV_VINTF_PAGE1_BASE - CMDQV_VCMDQ_PAGE1_BASE;
517 index = (offset - CMDQV_VCMDQ_PAGE1_BASE) / CMDQV_VCMDQ_STRIDE;
518 tegra241_cmdqv_write_vcmdq_page1(cmdqv,
390 - offset - index * CMDQV_VCMDQ_STRIDE, index, value, false);
519 + offset - index * CMDQV_VCMDQ_STRIDE, index, value, false,
520 + &local_err);
521 break;
522 case A_VCMDQ0_BASE_L ... A_VCMDQ1_CONS_INDX_BASE_DRAM_H:
523 index = (offset - CMDQV_VCMDQ_PAGE1_BASE) / CMDQV_VCMDQ_STRIDE;
524 tegra241_cmdqv_write_vcmdq_page1(cmdqv,
395 - offset - index * CMDQV_VCMDQ_STRIDE, index, value, true);
525 + offset - index * CMDQV_VCMDQ_STRIDE, index, value, true,
526 + &local_err);
527 break;
528 default:
529 qemu_log_mask(LOG_UNIMP, "%s unhandled write access at 0x%" PRIx64 "\n",
530 __func__, offset);
531 }
532 +
533 + if (local_err) {
534 + error_report_err(local_err);
535 + }
536 }
537
538 /*
@@ -407,6 +542,7 @@ static void tegra241_cmdqv_writel_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
542 static void tegra241_cmdqv_writell_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
543 uint64_t value)
544 {
545 + Error *local_err = NULL;
546 int index;
547
548 switch (offset) {
@@ -419,18 +555,24 @@ static void tegra241_cmdqv_writell_mmio(Tegra241CMDQV *cmdqv, hwaddr offset,
555 offset -= CMDQV_VINTF_PAGE1_BASE - CMDQV_VCMDQ_PAGE1_BASE;
556 index = (offset - CMDQV_VCMDQ_PAGE1_BASE) / CMDQV_VCMDQ_STRIDE;
557 tegra241_cmdqv_write_vcmdq_page1_64(cmdqv,
422 - offset - index * CMDQV_VCMDQ_STRIDE, index, value, false);
558 + offset - index * CMDQV_VCMDQ_STRIDE, index, value, false,
559 + &local_err);
560 break;
561 case A_VCMDQ0_BASE_L ... A_VCMDQ1_CONS_INDX_BASE_DRAM_H:
562 index = (offset - CMDQV_VCMDQ_PAGE1_BASE) / CMDQV_VCMDQ_STRIDE;
563 tegra241_cmdqv_write_vcmdq_page1_64(cmdqv,
427 - offset - index * CMDQV_VCMDQ_STRIDE, index, value, true);
564 + offset - index * CMDQV_VCMDQ_STRIDE, index, value, true,
565 + &local_err);
566 break;
567 default:
568 qemu_log_mask(LOG_UNIMP,
569 "%s unhandled 64-bit write at 0x%" PRIx64 " (WI)\n",
570 __func__, offset);
571 }
572 +
573 + if (local_err) {
574 + error_report_err(local_err);
575 + }
576 }
577
578 static void tegra241_cmdqv_write_mmio(void *opaque, hwaddr offset,
@@ -535,6 +677,13 @@ free_viommu:
677
678 static void tegra241_cmdqv_reset(SMMUv3State *s)
679 {
680 + Tegra241CMDQV *cmdqv = s->s_accel->cmdqv;
681 +
682 + if (!cmdqv) {
683 + return;
684 + }
685 +
686 + tegra241_cmdqv_free_all_vcmdq(cmdqv);
687 }
688
689 static const MemoryRegionOps mmio_cmdqv_ops = {
hw/arm/tegra241-cmdqv.h
+11
@@ -47,6 +47,7 @@ typedef struct Tegra241CMDQV {
47 MemoryRegion mmio_cmdqv;
48 qemu_irq irq;
49 IOMMUFDVeventq *veventq;
50 + IOMMUFDHWqueue *vcmdq[TEGRA241_CMDQV_MAX_CMDQ];
51 void *vintf_page0;
52
53 /* CMDQ-V Config page register cache */
@@ -364,6 +365,16 @@ SMMU_CMDQV_VI_VCMDQi_BASE_H_(1)
365 SMMU_CMDQV_VI_VCMDQi_CONS_INDX_BASE_DRAM_L_(1)
366 SMMU_CMDQV_VI_VCMDQi_CONS_INDX_BASE_DRAM_H_(1)
367
368 +static inline bool tegra241_cmdqv_enabled(Tegra241CMDQV *cmdqv)
369 +{
370 + return cmdqv->status & R_STATUS_CMDQV_ENABLED_MASK;
371 +}
372 +
373 +static inline bool tegra241_vintf_enabled(Tegra241CMDQV *cmdqv)
374 +{
375 + return cmdqv->vintf_status & R_VINTF0_STATUS_ENABLE_OK_MASK;
376 +}
377 +
378 const SMMUv3AccelCmdqvOps *tegra241_cmdqv_get_ops(void);
379
380 #endif /* HW_ARM_TEGRA241_CMDQV_H */