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
*
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
}
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] =
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;
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
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;
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) {
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
/*
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
/*
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) {
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,
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 = {