master
c 318 lines 8.83 KB
Raw
1 /*
2 * A test device for IOMMU
3 *
4 * Copyright (c) 2026 Phytium Technology
5 *
6 * Author:
7 * Tao Tang <tangtao1634@phytium.com.cn>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "qemu/osdep.h"
13 #include "system/address-spaces.h"
14 #include "system/memory.h"
15 #include "trace.h"
16 #include "hw/pci/pci_device.h"
17 #include "hw/core/qdev-properties.h"
18 #include "qom/object.h"
19 #include "hw/misc/iommu-testdev.h"
20
21 #define TYPE_IOMMU_TESTDEV "iommu-testdev"
22 OBJECT_DECLARE_SIMPLE_TYPE(IOMMUTestDevState, IOMMU_TESTDEV)
23
24 struct IOMMUTestDevState {
25 PCIDevice parent_obj;
26 MemoryRegion bar0;
27 uint64_t dma_vaddr;
28 uint64_t dma_paddr;
29 uint32_t dma_len;
30 uint32_t dma_result;
31 bool dma_armed; /* armed until a trigger consumes the request */
32
33 AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
34 uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 valid */
35 };
36
37 static bool iommu_testdev_attrs_inconsistent(uint32_t cfg)
38 {
39 uint32_t space;
40 bool secure;
41
42 if (!ITD_ATTRS_GET_SPACE_VALID(cfg)) {
43 return false;
44 }
45
46 space = ITD_ATTRS_GET_SPACE(cfg);
47 secure = ITD_ATTRS_GET_SECURE(cfg);
48
49 if (space == ITD_ATTRS_SPACE_SECURE || space == ITD_ATTRS_SPACE_NONSECURE) {
50 return secure != (space == ITD_ATTRS_SPACE_SECURE);
51 }
52
53 return false;
54 }
55
56 static void iommu_testdev_maybe_run_dma(IOMMUTestDevState *s)
57 {
58 uint32_t expected_val, actual_val;
59 g_autofree uint8_t *write_buf = NULL;
60 g_autofree uint8_t *read_buf = NULL;
61 MemTxResult write_res, read_res;
62 MemTxAttrs attrs = {};
63 AddressSpace *as;
64 bool space_valid;
65
66 if (!s->dma_armed) {
67 s->dma_result = ITD_DMA_ERR_NOT_ARMED;
68 trace_iommu_testdev_dma_result(s->dma_result);
69 return;
70 }
71 trace_iommu_testdev_dma_start();
72
73 if (!s->dma_len) {
74 s->dma_result = ITD_DMA_ERR_BAD_LEN;
75 goto out;
76 }
77
78 write_buf = g_malloc(s->dma_len);
79 read_buf = g_malloc(s->dma_len);
80
81 /* Initialize MemTxAttrs from generic register. */
82 attrs.secure = ITD_ATTRS_GET_SECURE(s->dma_attrs_cfg);
83
84 space_valid = ITD_ATTRS_GET_SPACE_VALID(s->dma_attrs_cfg);
85 if (space_valid) {
86 /* The 'space' field in MemTxAttrs is ARM-specific. */
87 attrs.space = ITD_ATTRS_GET_SPACE(s->dma_attrs_cfg);
88 } else {
89 /* Default to Non-Secure when space is not valid. */
90 attrs.space = ITD_ATTRS_SPACE_NONSECURE;
91 }
92
93 if (iommu_testdev_attrs_inconsistent(s->dma_attrs_cfg)) {
94 s->dma_result = ITD_DMA_ERR_BAD_ATTRS;
95 goto out;
96 }
97
98 as = s->dma_as;
99
100 /* Step 1: Write ITD_DMA_WRITE_VAL to DMA address */
101 trace_iommu_testdev_dma_write(s->dma_vaddr, s->dma_len);
102
103 for (int i = 0; i < s->dma_len; i++) {
104 /* Data is written in little-endian order */
105 write_buf[i] = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
106 }
107 write_res = dma_memory_write(as, s->dma_vaddr, write_buf,
108 s->dma_len, attrs);
109
110 if (write_res != MEMTX_OK) {
111 s->dma_result = ITD_DMA_ERR_TX_FAIL;
112 goto out;
113 }
114
115 /* Step 2: Read back from the same DMA address */
116 trace_iommu_testdev_dma_read(s->dma_vaddr, s->dma_len);
117
118 read_res = address_space_read(&address_space_memory, s->dma_paddr,
119 attrs, read_buf, s->dma_len);
120
121 if (read_res != MEMTX_OK) {
122 s->dma_result = ITD_DMA_ERR_RD_FAIL;
123 goto out;
124 }
125
126 /* Step 3: Verify the read data matches what we wrote */
127 for (int i = 0; i < s->dma_len; i += 4) {
128 int remaining_bytes = MIN(4, s->dma_len - i);
129
130 expected_val = 0;
131 actual_val = 0;
132
133 for (int j = 0; j < remaining_bytes; j++) {
134 expected_val |= ((uint32_t)write_buf[i + j]) << (j * 8);
135 actual_val |= ((uint32_t)read_buf[i + j]) << (j * 8);
136 }
137
138 trace_iommu_testdev_dma_verify(expected_val, actual_val);
139
140 if (expected_val != actual_val) {
141 s->dma_result = ITD_DMA_ERR_MISMATCH;
142 goto out;
143 }
144 }
145
146 /* All checks passed */
147 s->dma_result = 0;
148 out:
149 trace_iommu_testdev_dma_result(s->dma_result);
150 s->dma_armed = false;
151 }
152
153 static uint64_t iommu_testdev_mmio_read(void *opaque, hwaddr addr,
154 unsigned size)
155 {
156 IOMMUTestDevState *s = opaque;
157 uint64_t value = 0;
158
159 switch (addr) {
160 case ITD_REG_DMA_TRIGGERING:
161 /*
162 * This lets tests poll ITD_REG_DMA_RESULT to observe BUSY before
163 * consuming the DMA.
164 */
165 iommu_testdev_maybe_run_dma(s);
166 value = 0;
167 break;
168 case ITD_REG_DMA_GVA_LO:
169 value = (uint32_t)(s->dma_vaddr & 0xffffffffu);
170 break;
171 case ITD_REG_DMA_GVA_HI:
172 value = (uint32_t)(s->dma_vaddr >> 32);
173 break;
174 case ITD_REG_DMA_GPA_LO:
175 value = (uint32_t)(s->dma_paddr & 0xffffffffu);
176 break;
177 case ITD_REG_DMA_GPA_HI:
178 value = (uint32_t)(s->dma_paddr >> 32);
179 break;
180 case ITD_REG_DMA_LEN:
181 value = s->dma_len;
182 break;
183 case ITD_REG_DMA_RESULT:
184 value = s->dma_result;
185 break;
186 case ITD_REG_DMA_ATTRS:
187 value = s->dma_attrs_cfg;
188 break;
189 default:
190 value = 0;
191 break;
192 }
193
194 trace_iommu_testdev_mmio_read(addr, value, size);
195 return value;
196 }
197
198 static void iommu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
199 unsigned size)
200 {
201 IOMMUTestDevState *s = opaque;
202 uint32_t data = val;
203
204 trace_iommu_testdev_mmio_write(addr, val, size);
205
206 switch (addr) {
207 case ITD_REG_DMA_GVA_LO:
208 s->dma_vaddr = (s->dma_vaddr & ~0xffffffffull) | data;
209 break;
210 case ITD_REG_DMA_GVA_HI:
211 s->dma_vaddr = (s->dma_vaddr & 0xffffffffull) |
212 ((uint64_t)data << 32);
213 break;
214 case ITD_REG_DMA_GPA_LO:
215 s->dma_paddr = (s->dma_paddr & ~0xffffffffull) | data;
216 break;
217 case ITD_REG_DMA_GPA_HI:
218 s->dma_paddr = (s->dma_paddr & 0xffffffffull) |
219 ((uint64_t)data << 32);
220 break;
221 case ITD_REG_DMA_LEN:
222 s->dma_len = data;
223 break;
224 case ITD_REG_DMA_RESULT:
225 s->dma_result = data;
226 break;
227 case ITD_REG_DMA_DBELL:
228 if (data & ITD_DMA_DBELL_ARM) {
229 /* Arm the DMA operation; repeated arm is idempotent. */
230 s->dma_armed = true;
231 s->dma_result = ITD_DMA_RESULT_BUSY;
232 trace_iommu_testdev_dma_armed(true);
233 } else {
234 /* Disarm the DMA operation */
235 s->dma_armed = false;
236 s->dma_result = ITD_DMA_RESULT_IDLE;
237 trace_iommu_testdev_dma_armed(false);
238 }
239 break;
240 case ITD_REG_DMA_ATTRS:
241 s->dma_attrs_cfg = data;
242 break;
243 default:
244 break;
245 }
246 }
247
248 static const MemoryRegionOps iommu_testdev_mmio_ops = {
249 .read = iommu_testdev_mmio_read,
250 .write = iommu_testdev_mmio_write,
251 .endianness = DEVICE_LITTLE_ENDIAN,
252 .valid = {
253 .min_access_size = 4,
254 .max_access_size = 4,
255 },
256 };
257
258 static void iommu_testdev_realize(PCIDevice *pdev, Error **errp)
259 {
260 IOMMUTestDevState *s = IOMMU_TESTDEV(pdev);
261
262 s->dma_vaddr = 0;
263 s->dma_paddr = 0;
264 s->dma_len = 0;
265 s->dma_result = ITD_DMA_RESULT_IDLE;
266 s->dma_armed = false;
267 s->dma_attrs_cfg = ITD_ATTRS_SET_SPACE(0, ITD_ATTRS_SPACE_NONSECURE);
268 s->dma_as = pci_device_iommu_address_space(pdev);
269
270 memory_region_init_io(&s->bar0, OBJECT(pdev), &iommu_testdev_mmio_ops, s,
271 TYPE_IOMMU_TESTDEV ".bar0", BAR0_SIZE);
272 pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
273 }
274
275 static void iommu_testdev_reset(DeviceState *dev)
276 {
277 IOMMUTestDevState *s = IOMMU_TESTDEV(dev);
278
279 s->dma_vaddr = 0;
280 s->dma_paddr = 0;
281 s->dma_len = 0;
282 s->dma_result = ITD_DMA_RESULT_IDLE;
283 s->dma_armed = false;
284 s->dma_attrs_cfg = ITD_ATTRS_SET_SPACE(0, ITD_ATTRS_SPACE_NONSECURE);
285 }
286
287 static void iommu_testdev_class_init(ObjectClass *klass, const void *data)
288 {
289 DeviceClass *dc = DEVICE_CLASS(klass);
290 PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
291
292 pc->realize = iommu_testdev_realize;
293 pc->vendor_id = IOMMU_TESTDEV_VENDOR_ID;
294 pc->device_id = IOMMU_TESTDEV_DEVICE_ID;
295 pc->revision = 0;
296 pc->class_id = PCI_CLASS_OTHERS;
297 dc->desc = "A test device for IOMMU";
298 set_bit(DEVICE_CATEGORY_MISC, dc->categories);
299 device_class_set_legacy_reset(dc, iommu_testdev_reset);
300 }
301
302 static const TypeInfo iommu_testdev_info = {
303 .name = TYPE_IOMMU_TESTDEV,
304 .parent = TYPE_PCI_DEVICE,
305 .instance_size = sizeof(IOMMUTestDevState),
306 .class_init = iommu_testdev_class_init,
307 .interfaces = (const InterfaceInfo[]) {
308 { INTERFACE_CONVENTIONAL_PCI_DEVICE },
309 { }
310 },
311 };
312
313 static void iommu_testdev_register_types(void)
314 {
315 type_register_static(&iommu_testdev_info);
316 }
317
318 type_init(iommu_testdev_register_types);