| /* |
| * QEMU "pci-testdev" PCI test device |
| * |
| * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev <agordeev@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include "pci.h" |
| #include "asm/io.h" |
| |
| struct pci_testdev_ops { |
| u8 (*io_readb)(const volatile void *addr); |
| u16 (*io_readw)(const volatile void *addr); |
| u32 (*io_readl)(const volatile void *addr); |
| void (*io_writeb)(u8 value, volatile void *addr); |
| void (*io_writew)(u16 value, volatile void *addr); |
| void (*io_writel)(u32 value, volatile void *addr); |
| }; |
| |
| static u8 pio_readb(const volatile void *addr) |
| { |
| return inb((unsigned long)addr); |
| } |
| |
| static u16 pio_readw(const volatile void *addr) |
| { |
| return inw((unsigned long)addr); |
| } |
| |
| static u32 pio_readl(const volatile void *addr) |
| { |
| return inl((unsigned long)addr); |
| } |
| |
| static void pio_writeb(u8 value, volatile void *addr) |
| { |
| outb(value, (unsigned long)addr); |
| } |
| |
| static void pio_writew(u16 value, volatile void *addr) |
| { |
| outw(value, (unsigned long)addr); |
| } |
| |
| static void pio_writel(u32 value, volatile void *addr) |
| { |
| outl(value, (unsigned long)addr); |
| } |
| |
| static struct pci_testdev_ops pci_testdev_io_ops = { |
| .io_readb = pio_readb, |
| .io_readw = pio_readw, |
| .io_readl = pio_readl, |
| .io_writeb = pio_writeb, |
| .io_writew = pio_writew, |
| .io_writel = pio_writel |
| }; |
| |
| static u8 mmio_readb(const volatile void *addr) |
| { |
| return *(const volatile u8 __force *)addr; |
| } |
| |
| static u16 mmio_readw(const volatile void *addr) |
| { |
| return *(const volatile u16 __force *)addr; |
| } |
| |
| static u32 mmio_readl(const volatile void *addr) |
| { |
| return *(const volatile u32 __force *)addr; |
| } |
| |
| static void mmio_writeb(u8 value, volatile void *addr) |
| { |
| *(volatile u8 __force *)addr = value; |
| } |
| |
| static void mmio_writew(u16 value, volatile void *addr) |
| { |
| *(volatile u16 __force *)addr = value; |
| } |
| |
| static void mmio_writel(u32 value, volatile void *addr) |
| { |
| *(volatile u32 __force *)addr = value; |
| } |
| |
| static struct pci_testdev_ops pci_testdev_mem_ops = { |
| .io_readb = mmio_readb, |
| .io_readw = mmio_readw, |
| .io_readl = mmio_readl, |
| .io_writeb = mmio_writeb, |
| .io_writew = mmio_writew, |
| .io_writel = mmio_writel |
| }; |
| |
| static bool pci_testdev_one(struct pci_test_dev_hdr *test, |
| int test_nr, |
| struct pci_testdev_ops *ops) |
| { |
| u8 width; |
| u32 count, sig, off; |
| const int nr_writes = 16; |
| int i; |
| |
| ops->io_writeb(test_nr, &test->test); |
| count = ops->io_readl(&test->count); |
| if (count != 0) |
| return false; |
| |
| width = ops->io_readb(&test->width); |
| if (width != 1 && width != 2 && width != 4) |
| return false; |
| |
| sig = ops->io_readl(&test->data); |
| off = ops->io_readl(&test->offset); |
| |
| for (i = 0; i < nr_writes; i++) { |
| switch (width) { |
| case 1: ops->io_writeb(sig, (void *)test + off); break; |
| case 2: ops->io_writew(sig, (void *)test + off); break; |
| case 4: ops->io_writel(sig, (void *)test + off); break; |
| } |
| } |
| |
| count = ops->io_readl(&test->count); |
| if (!count) |
| return true; |
| |
| return (int)count == nr_writes; |
| } |
| |
| static void pci_testdev_print(struct pci_test_dev_hdr *test, |
| struct pci_testdev_ops *ops) |
| { |
| bool io = (ops == &pci_testdev_io_ops); |
| int i; |
| |
| printf("pci-testdev %3s: ", io ? "io" : "mem"); |
| for (i = 0;; ++i) { |
| char c = ops->io_readb(&test->name[i]); |
| if (!c) |
| break; |
| printf("%c", c); |
| } |
| printf("\n"); |
| } |
| |
| static int pci_testdev_all(struct pci_test_dev_hdr *test, |
| struct pci_testdev_ops *ops) |
| { |
| int i; |
| |
| for (i = 0;; i++) { |
| if (!pci_testdev_one(test, i, ops)) |
| break; |
| pci_testdev_print(test, ops); |
| } |
| |
| return i; |
| } |
| |
| int pci_testdev(void) |
| { |
| struct pci_dev pci_dev; |
| pcidevaddr_t dev; |
| phys_addr_t addr; |
| void __iomem *mem, *io; |
| int nr_tests = 0; |
| bool ret; |
| |
| dev = pci_find_dev(PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_REDHAT_TEST); |
| if (dev == PCIDEVADDR_INVALID) { |
| printf("'pci-testdev' device is not found, " |
| "check QEMU '-device pci-testdev' parameter\n"); |
| return -1; |
| } |
| pci_dev_init(&pci_dev, dev); |
| |
| ret = pci_bar_is_valid(&pci_dev, 0) && pci_bar_is_valid(&pci_dev, 1); |
| assert(ret); |
| |
| addr = pci_bar_get_addr(&pci_dev, 0); |
| mem = ioremap(addr, PAGE_SIZE); |
| |
| addr = pci_bar_get_addr(&pci_dev, 1); |
| #if defined(__i386__) || defined(__x86_64__) |
| io = (void *)(unsigned long)addr; |
| #else |
| io = ioremap(addr, PAGE_SIZE); |
| #endif |
| |
| nr_tests += pci_testdev_all(mem, &pci_testdev_mem_ops); |
| nr_tests += pci_testdev_all(io, &pci_testdev_io_ops); |
| |
| return nr_tests; |
| } |