/* * Intel IOMMU unit test. * * Copyright (C) 2016 Red Hat, Inc. * * Authors: * Peter Xu , * * This work is licensed under the terms of the GNU LGPL, version 2 or * later. */ #include "intel-iommu.h" #include "pci-edu.h" #include "x86/apic.h" #include "vm.h" #include "alloc_page.h" #define VTD_TEST_DMAR_4B ("DMAR 4B memcpy test") #define VTD_TEST_IR_MSI ("IR MSI") #define VTD_TEST_IR_IOAPIC ("IR IOAPIC") static struct pci_edu_dev edu_dev; static void vtd_test_dmar(void) { struct pci_edu_dev *dev = &edu_dev; void *page = alloc_page(); report_prefix_push("vtd_dmar"); #define DMA_TEST_WORD (0x12345678) /* Modify the first 4 bytes of the page */ *(uint32_t *)page = DMA_TEST_WORD; /* * Map the newly allocated page into IOVA address 0 (size 4K) * of the device address space. Root entry and context entry * will be automatically created when needed. */ vtd_map_range(dev->pci_dev.bdf, 0, virt_to_phys(page), PAGE_SIZE); /* * DMA the first 4 bytes of the page to EDU device buffer * offset 0. */ edu_dma(dev, 0, 4, 0, false); /* * DMA the first 4 bytes of EDU device buffer into the page * with offset 4 (so it'll be using 4-7 bytes). */ edu_dma(dev, 4, 4, 0, true); /* * Check data match between 0-3 bytes and 4-7 bytes of the * page. */ report(*((uint32_t *)page + 1) == DMA_TEST_WORD, VTD_TEST_DMAR_4B); free_page(page); report_prefix_pop(); } static volatile bool edu_intr_recved; static void edu_isr(isr_regs_t *regs) { edu_intr_recved = true; eoi(); edu_reg_writel(&edu_dev, EDU_REG_INTR_ACK, edu_reg_readl(&edu_dev, EDU_REG_INTR_STATUS)); } static void vtd_test_ir(void) { #define VTD_TEST_VECTOR_IOAPIC (0xed) #define VTD_TEST_VECTOR_MSI (0xee) struct pci_edu_dev *dev = &edu_dev; struct pci_dev *pci_dev = &dev->pci_dev; report_prefix_push("vtd_ir"); sti(); /* This will enable INTx */ pci_msi_set_enable(pci_dev, false); vtd_setup_ioapic_irq(pci_dev, VTD_TEST_VECTOR_IOAPIC, 0, TRIGGER_EDGE); handle_irq(VTD_TEST_VECTOR_IOAPIC, edu_isr); edu_intr_recved = false; wmb(); /* Manually trigger INTR */ edu_reg_writel(dev, EDU_REG_INTR_RAISE, 1); while (!edu_intr_recved) cpu_relax(); /* Clear INTR bits */ edu_reg_writel(dev, EDU_REG_INTR_RAISE, 0); /* We are good as long as we reach here */ report(edu_intr_recved == true, VTD_TEST_IR_IOAPIC); /* * Setup EDU PCI device MSI, using interrupt remapping. By * default, EDU device is using INTx. */ if (!vtd_setup_msi(pci_dev, VTD_TEST_VECTOR_MSI, 0)) { printf("edu device does not support MSI, skip test\n"); report_skip(VTD_TEST_IR_MSI); return; } handle_irq(VTD_TEST_VECTOR_MSI, edu_isr); edu_intr_recved = false; wmb(); /* Manually trigger INTR */ edu_reg_writel(dev, EDU_REG_INTR_RAISE, 1); while (!edu_intr_recved) cpu_relax(); /* We are good as long as we reach here */ report(edu_intr_recved == true, VTD_TEST_IR_MSI); report_prefix_pop(); } int main(int argc, char *argv[]) { setup_vm(); vtd_init(); report_prefix_push("vtd_init"); report(vtd_readl(DMAR_FSTS_REG) == 0, "fault status check"); report(vtd_readl(DMAR_GSTS_REG) & VTD_GCMD_QI, "QI enablement"); report(vtd_readl(DMAR_GSTS_REG) & VTD_GCMD_ROOT, "DMAR table setup"); report(vtd_readl(DMAR_GSTS_REG) & VTD_GCMD_IR_TABLE, "IR table setup"); report(vtd_readl(DMAR_GSTS_REG) & VTD_GCMD_DMAR, "DMAR enablement"); report(vtd_readl(DMAR_GSTS_REG) & VTD_GCMD_IR, "IR enablement"); report(vtd_readq(DMAR_CAP_REG) & VTD_CAP_SAGAW, "DMAR support 39 bits address width"); report(vtd_readq(DMAR_CAP_REG) & VTD_CAP_SLLPS, "DMAR support huge pages"); report_prefix_pop(); if (!edu_init(&edu_dev)) { printf("Please specify \"-device edu\" to do " "further IOMMU tests.\n"); report_skip(VTD_TEST_DMAR_4B); report_skip(VTD_TEST_IR_IOAPIC); report_skip(VTD_TEST_IR_MSI); } else { printf("Found EDU device:\n"); pci_dev_print(&edu_dev.pci_dev); vtd_test_dmar(); vtd_test_ir(); } return report_summary(); }