blob: 4442fe1fbf233712165910af97335d0c2ea84f1d [file] [log] [blame]
/*
* Intel IOMMU unit test.
*
* Copyright (C) 2016 Red Hat, Inc.
*
* Authors:
* Peter Xu <peterx@redhat.com>,
*
* 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");
irq_enable();
/* 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();
}