Test whether the AMD IOMMU behaves correctly when a device is in passthrough
mode: that is, DTE[V] = 1, DTE[TV] = 1, and DTE[IR] and DTE[IW] are some
combination of 0 and 1. The edu device is used as an initiator of DMA
transactions.
We are looking for, in order:
- when IR = 1 and IW = 1, no IO page faults
- when IR = 1 and IW = 0, an IO page fault on device write
- when IR = 0 and IW = 1, an IO page fault on read
- when IR = 1 and IW = 1, no IO page fault (back to nodma region)
- when IR = 0 and IV = 0, an IO page fault on device read/write
- after reset, the device to behave as if IR = 1 and IW = 1 (nodma)
Signed-off-by: Daniel Paziyski <[email protected]>
---
tests/qtest/amd-iommu-test.c | 230 +++++++++++++++++++++++++++++++++++
1 file changed, 230 insertions(+)
diff --git a/tests/qtest/amd-iommu-test.c b/tests/qtest/amd-iommu-test.c
index fb28511588..79a65d9cbf 100644
--- a/tests/qtest/amd-iommu-test.c
+++ b/tests/qtest/amd-iommu-test.c
@@ -4,11 +4,23 @@
#include "qemu/osdep.h"
#include "libqtest.h"
+#include "libqos/pci-pc.h"
#include "hw/i386/amd_iommu.h"
#define CMDBUF_ADDR 0x200000
#define CMDBUF_LEN_FIELD 8
#define CMDBUF_ENTRIES (1U << CMDBUF_LEN_FIELD)
+#define CMDBUF_SIZE (CMDBUF_ENTRIES * AMDVI_COMMAND_SIZE)
+
+#define DEVTAB_ADDR (CMDBUF_ADDR + CMDBUF_SIZE)
+#define DEVTAB_LEN_FIELD 0x1ff
+#define DEVTAB_ENTRIES (((DEVTAB_LEN_FIELD + 1) << 12U) /
AMDVI_DEVTAB_ENTRY_SIZE)
+#define DEVTAB_SIZE ((DEVTAB_LEN_FIELD + 1) << 12U)
+
+#define EVLOG_ADDR (DEVTAB_ADDR + DEVTAB_SIZE)
+#define EVLOG_LEN_FIELD 0x0f
+#define EVLOG_ENTRIES ((4096U << (EVLOG_LEN_FIELD - 8)) / AMDVI_EVENT_LEN)
+#define EVLOG_SIZE (4096U << (EVLOG_LEN_FIELD - 8))
static inline uint64_t amdvi_reg_readq(QTestState *s, uint64_t offset)
{
@@ -21,6 +33,34 @@ static inline void amdvi_reg_writeq(QTestState *s, uint64_t
offset,
qtest_writeq(s, AMDVI_BASE_ADDR + offset, val);
}
+static void amdvi_invalidate_devtab_entry(QTestState *s, uint16_t bdf)
+{
+ uint64_t tail = amdvi_reg_readq(s, AMDVI_MMIO_COMMAND_TAIL);
+
+ qtest_writel(s, CMDBUF_ADDR + tail, bdf);
+ qtest_writel(s, CMDBUF_ADDR + tail + 4, 0x20000000);
+ qtest_writel(s, CMDBUF_ADDR + tail + 8, 0);
+ qtest_writel(s, CMDBUF_ADDR + tail + 12, 0);
+
+ g_assert_true(amdvi_reg_readq(s, AMDVI_MMIO_STATUS)
+ & AMDVI_MMIO_STATUS_CMDBUF_RUN);
+
+ tail = (tail + AMDVI_COMMAND_SIZE) % CMDBUF_SIZE;
+ amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_TAIL, tail);
+
+ for (;;) {
+ g_assert_true(amdvi_reg_readq(s, AMDVI_MMIO_STATUS) &
+ AMDVI_MMIO_STATUS_CMDBUF_RUN);
+ if ((amdvi_reg_readq(s, AMDVI_MMIO_COMMAND_HEAD)
+ & AMDVI_MMIO_CMDBUF_HEAD_MASK) == tail) {
+ break;
+ }
+ }
+
+ g_assert_true(amdvi_reg_readq(s, AMDVI_MMIO_STATUS)
+ & AMDVI_MMIO_STATUS_CMDBUF_RUN);
+}
+
static void test_cmdbuf_head_wrap(void)
{
QTestState *s;
@@ -68,9 +108,199 @@ static void test_cmdbuf_head_wrap(void)
qtest_quit(s);
}
+static void test_dte_mode_0_edu_dma(QTestState *s,
+ QPCIDevice *edu, QPCIBar edu_bar,
+ hwaddr edu_dma_src, hwaddr edu_dma_dst,
+ bool edu_to_ram,
+ bool expect_fail)
+{
+ bool log;
+ uint64_t head, tail, addr;
+
+ qpci_io_writeq(edu, edu_bar, 0x80, edu_dma_src);
+ qpci_io_writeq(edu, edu_bar, 0x88, edu_dma_dst);
+ qpci_io_writeq(edu, edu_bar, 0x90, 1);
+ qpci_io_writeq(edu, edu_bar, 0x98, 0x01 | (edu_to_ram ? 0x02 : 0x00));
+
+ while (qpci_io_readq(edu, edu_bar, 0x98) & 0x01) {
+ /* edu uses a timer for scheduling the DMA operation */
+ qtest_clock_step_next(s);
+ }
+
+ log = amdvi_reg_readq(s, AMDVI_MMIO_STATUS) & AMDVI_MMIO_STATUS_EVENT_INT;
+ if (expect_fail) {
+ g_assert_true(log);
+ amdvi_reg_writeq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_EVENT_INT);
+ } else {
+ g_assert_false(log);
+ }
+
+ if (expect_fail) {
+ head = amdvi_reg_readq(s, AMDVI_MMIO_EVENT_HEAD) &
+ AMDVI_MMIO_EVTLOG_HEAD_MASK;
+ tail = amdvi_reg_readq(s, AMDVI_MMIO_EVENT_TAIL) &
+ AMDVI_MMIO_EVTLOG_TAIL_MASK;
+
+ /* make sure only one event has occurred */
+ g_assert_true(((head + AMDVI_EVENT_LEN) % EVLOG_SIZE) ==
+ (tail % EVLOG_SIZE));
+
+ addr = qtest_readq(s, EVLOG_ADDR + head + 8);
+
+ if (edu_to_ram) {
+ g_assert_true(addr == edu_dma_dst);
+ } else {
+ g_assert_true(addr == edu_dma_src);
+ }
+
+ head = (head + AMDVI_EVENT_LEN) % EVLOG_SIZE;
+ amdvi_reg_writeq(s, AMDVI_MMIO_EVENT_HEAD, head);
+ }
+}
+
+static void test_dte_mode_0_iommu_setup(QTestState *s)
+{
+ /* command buffer */
+ amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_BASE,
+ CMDBUF_ADDR | ((uint64_t)CMDBUF_LEN_FIELD << 56));
+ amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_HEAD, 0);
+ amdvi_reg_writeq(s, AMDVI_MMIO_COMMAND_TAIL, 0);
+
+ /* event log */
+ amdvi_reg_writeq(s, AMDVI_MMIO_EVENT_BASE,
+ EVLOG_ADDR | ((uint64_t)EVLOG_LEN_FIELD << 56));
+ amdvi_reg_writeq(s, AMDVI_MMIO_EVENT_HEAD, 0);
+ amdvi_reg_writeq(s, AMDVI_MMIO_EVENT_TAIL, 0);
+
+ /* device table */
+ amdvi_reg_writeq(s, AMDVI_MMIO_DEVICE_TABLE,
+ DEVTAB_ADDR | DEVTAB_LEN_FIELD);
+
+ /* enable */
+ amdvi_reg_writeq(s, AMDVI_MMIO_CONTROL, AMDVI_MMIO_CONTROL_AMDVIEN |
+ AMDVI_MMIO_CONTROL_CMDBUFLEN |
+ AMDVI_MMIO_CONTROL_EVENTLOGEN |
+ AMDVI_MMIO_CONTROL_EVENTINTEN);
+}
+
+static void test_dte_mode_0(void)
+{
+ QTestState *s;
+ QPCIBus *bus;
+ QPCIDevice *edu;
+ QPCIBar edu_bar;
+ const unsigned int edu_devfn = PCI_DEVFN(0x14, 0),
+ edu_devtab_off = edu_devfn * AMDVI_DEVTAB_ENTRY_SIZE;
+ const hwaddr edu_dma_test_buf = 0x1000, edu_dma_dev_buf = 0x40000;
+
+ if (!qtest_has_machine("q35")) {
+ g_test_skip("q35 machine not available");
+ }
+
+ if (!qtest_has_device("edu")) {
+ g_test_skip("edu device not available");
+ }
+
+ s = qtest_init("-M q35 -device amd-iommu,dma-remap=on,dma-translation=on,"
+ "device-iotlb=on -device
edu,addr=14.0,dma_mask=0xffffffff");
+ bus = qpci_new_pc(s, NULL);
+
+ test_dte_mode_0_iommu_setup(s);
+
+ /* get bar of EDU device */
+ edu = qpci_device_find(bus, edu_devfn);
+ g_assert_nonnull(edu);
+
+ edu_bar = qpci_iomap(edu, 0, NULL);
+ qpci_device_enable(edu);
+
+ g_test_message("testing DMA passthrough, IR = 1, IW = 1");
+ qtest_writeq(s, DEVTAB_ADDR + edu_devtab_off,
+ AMDVI_DEV_PERM_READ | AMDVI_DEV_PERM_WRITE |
+ AMDVI_DEV_TRANSLATION_VALID | AMDVI_DEV_VALID);
+ amdvi_invalidate_devtab_entry(s, edu_devfn);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, false);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, false);
+
+ g_test_message("testing DMA passthrough, IR = 1, IW = 0");
+ qtest_writeq(s, DEVTAB_ADDR + edu_devtab_off,
+ AMDVI_DEV_PERM_READ |
+ AMDVI_DEV_TRANSLATION_VALID | AMDVI_DEV_VALID);
+ amdvi_invalidate_devtab_entry(s, edu_devfn);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, false);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, true);
+
+ g_test_message("testing DMA passthrough, IR = 0, IW = 1");
+ qtest_writeq(s, DEVTAB_ADDR + edu_devtab_off,
+ AMDVI_DEV_PERM_WRITE |
+ AMDVI_DEV_TRANSLATION_VALID | AMDVI_DEV_VALID);
+ amdvi_invalidate_devtab_entry(s, edu_devfn);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, true);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, false);
+
+ /* make sure the nodma memory region is activated again properly */
+ g_test_message("testing DMA passthrough, IR = 1, IW = 1 (reset to nodma)");
+ qtest_writeq(s, DEVTAB_ADDR + edu_devtab_off,
+ AMDVI_DEV_PERM_READ | AMDVI_DEV_PERM_WRITE |
+ AMDVI_DEV_TRANSLATION_VALID | AMDVI_DEV_VALID);
+ amdvi_invalidate_devtab_entry(s, edu_devfn);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, false);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, false);
+
+ g_test_message("testing DMA passthrough, IR = 0, IW = 0");
+ qtest_writeq(s, DEVTAB_ADDR + edu_devtab_off,
+ AMDVI_DEV_TRANSLATION_VALID | AMDVI_DEV_VALID);
+ amdvi_invalidate_devtab_entry(s, edu_devfn);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, true);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, true);
+
+ /* make sure that after reset, the device switches to nodma */
+ qtest_system_reset(s);
+ test_dte_mode_0_iommu_setup(s);
+
+ g_test_message("testing DMA after reset");
+ edu_bar = qpci_iomap(edu, 0, NULL);
+ qpci_device_enable(edu);
+
+ g_test_message("testing read");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_test_buf, edu_dma_dev_buf,
+ false, false);
+ g_test_message("testing write");
+ test_dte_mode_0_edu_dma(s, edu, edu_bar, edu_dma_dev_buf, edu_dma_test_buf,
+ true, false);
+
+ qtest_quit(s);
+}
+
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
qtest_add_func("/q35/amd-iommu/cmdbuf-head-wrap", test_cmdbuf_head_wrap);
+ qtest_add_func("/q35/amd-iommu/dte-mode-0", test_dte_mode_0);
return g_test_run();
}
--
2.55.0