| 1 | /* |
| 2 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | */ |
| 4 | |
| 5 | #include "qemu/osdep.h" |
| 6 | #include "libqtest.h" |
| 7 | #include "hw/i386/amd_iommu.h" |
| 8 | |
| 9 | #define CMDBUF_ADDR 0x200000 |
| 10 | #define CMDBUF_LEN_FIELD 8 |
| 11 | #define CMDBUF_ENTRIES (1U << CMDBUF_LEN_FIELD) |
| 12 | |
| 13 | static inline uint64_t amdvi_reg_readq(QTestState *s, uint64_t offset) |
| 14 | { |
| 15 | return qtest_readq(s, AMDVI_BASE_ADDR + offset); |
| 16 | } |
| 17 | |
| 18 | static inline void amdvi_reg_writeq(QTestState *s, uint64_t offset, |
| 19 | uint64_t val) |
| 20 | { |
| 21 | qtest_writeq(s, AMDVI_BASE_ADDR + offset, val); |
| 22 | } |
| 23 | |
| 24 | static void test_cmdbuf_head_wrap(void) |
| 25 | { |
| 26 | QTestState *s; |
| 27 | uint64_t head; |
| 28 | int i; |
| 29 | /* 16 bytes per command */ |
| 30 | struct { |
| 31 | uint64_t qw0; |
| 32 | uint64_t qw1; |
| 33 | } cmdbuf[CMDBUF_ENTRIES]; |
| 34 | |
| 35 | if (!qtest_has_machine("q35")) { |
| 36 | g_test_skip("q35 machine not available"); |
| 37 | return; |
| 38 | } |
| 39 | |
| 40 | s = qtest_init("-M q35 -device amd-iommu"); |
| 41 | |
| 42 | /* fill the command buffer with COMPLETION_WAIT (no-op) commands */ |
| 43 | for (i = 0; i < CMDBUF_ENTRIES; i++) { |
| 44 | cmdbuf[i].qw0 = (uint64_t)AMDVI_CMD_COMPLETION_WAIT << 60; |
| 45 | cmdbuf[i].qw1 = 0; |
| 46 | } |
| 47 | qtest_memwrite(s, CMDBUF_ADDR, cmdbuf, sizeof(cmdbuf)); |
| 48 | |
| 49 | /* point the IOMMU at the command buffer and set its length */ |
| 50 | amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_BASE, |
| 51 | CMDBUF_ADDR | ((uint64_t)CMDBUF_LEN_FIELD << 56)); |
| 52 | |
| 53 | /* enable the IOMMU and its command buffer processor */ |
| 54 | amdvi_reg_writeq(s, AMDVI_MMIO_CONTROL, |
| 55 | AMDVI_MMIO_CONTROL_AMDVIEN | AMDVI_MMIO_CONTROL_CMDBUFLEN); |
| 56 | |
| 57 | /* advance tail to the last entry, consuming all but the final entry */ |
| 58 | amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_TAIL, |
| 59 | (CMDBUF_ENTRIES - 1) * AMDVI_COMMAND_SIZE); |
| 60 | |
| 61 | /* wrap tail to 0, consuming the final entry and completing the buffer */ |
| 62 | amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_TAIL, 0); |
| 63 | |
| 64 | /* after consuming all entries the IOMMU must wrap CmdHeadPtr to 0 */ |
| 65 | head = amdvi_reg_readq(s, AMDVI_MMIO_COMMAND_HEAD); |
| 66 | g_assert((head & AMDVI_MMIO_CMDBUF_HEAD_MASK) == 0); |
| 67 | |
| 68 | qtest_quit(s); |
| 69 | } |
| 70 | |
| 71 | int main(int argc, char **argv) |
| 72 | { |
| 73 | g_test_init(&argc, &argv, NULL); |
| 74 | qtest_add_func("/q35/amd-iommu/cmdbuf-head-wrap", test_cmdbuf_head_wrap); |
| 75 | return g_test_run(); |
| 76 | } |