// SPDX-License-Identifier: Apache-2.0 // // pcitest -- prove that a driver could live out here. // // docs/plan/hybrid.md mechanisms 2 and 4, exercised end to end from a process: // enumerate the bus, claim a device, map its registers, read them, and get // memory the device could be pointed at. // // It is deliberately a test and not a driver. The question it answers is "does // the mechanism work", which has to be answered before the first real eviction // -- otherwise a virtio-gpu that fails in userspace has two candidate causes // and no way to tell them apart. // // pcitest list the bus // pcitest claim that device, map BAR0, read it, allocate DMA #include "types.h" #include "user.h" #include "fcntl.h" #include "posix.h" #include "pciuser.h" static void puts(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } static void puthex(unsigned long long v, int digits) { static const char h[] = "0123456789abcdef"; char b[17]; for (int i = digits - 1; i >= 0; i--) { b[i] = h[v & 0xF]; v >>= 4; } write(1, b, digits); } static void putn(int v) { char b[12]; int i = 0; if (v < 0) { write(1, "-", 1); v = -v; } if (v == 0) { write(1, "0", 1); return; } while (v > 0) { b[i++] = '0' + (v % 10); v /= 10; } while (i-- > 0) write(1, &b[i], 1); } static int num(const char *s) { int v = 0; while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0'); return v; } // What the class byte means, for the handful that matter here. Not a full // table: a listing that says "class 02" is only marginally better than one // that says nothing, and a full one is 200 lines of data nobody reads. static const char * class_name(unsigned char c) { switch (c) { case 0x01: return "storage"; case 0x02: return "network"; case 0x03: return "display"; case 0x04: return "multimedia"; case 0x06: return "bridge"; case 0x0c: return "serial bus"; default: return "other"; } } int main(int argc, char *argv[]) { int fd = open("/dev/pci", O_RDWR); if (fd < 0) { puts("pcitest: cannot open /dev/pci\n"); exit(); } int count = 0; if (ioctl(fd, PCIIOC_COUNT, &count) < 0) { puts("pcitest: PCIIOC_COUNT failed\n"); exit(); } if (argc == 1) { puts(" # bdf vendor:device class driver\n"); for (int i = 0; i < count; i++) { struct pciuser_info in; in.index = (unsigned int)i; if (ioctl(fd, PCIIOC_INFO, &in) < 0) continue; puts(" "); putn(i); puts(" "); puthex(in.bus, 2); puts(":"); puthex(in.slot, 2); puts("."); puthex(in.func, 1); puts(" "); puthex(in.vendor, 4); puts(":"); puthex(in.device, 4); puts(" "); puts(class_name(in.class)); puts(" "); puts(in.driver[0] ? in.driver : "-"); puts("\n"); } puts("\npcitest to claim one and read its registers\n"); exit(); } int idx = num(argv[1]); struct pciuser_info in; in.index = (unsigned int)idx; if (ioctl(fd, PCIIOC_INFO, &in) < 0) { puts("pcitest: no device "); putn(idx); puts("\n"); exit(); } puts("device "); putn(idx); puts(": "); puthex(in.vendor, 4); puts(":"); puthex(in.device, 4); puts(" ("); puts(class_name(in.class)); puts(")"); if (in.driver[0]) { puts(", held by "); puts(in.driver); } puts("\n"); if (ioctl(fd, PCIIOC_CLAIM, &idx) < 0) { // EBUSY is the expected answer for anything the kernel drives, and it // is the right answer until a driver can be told to let go. Saying so // beats a bare failure. puts(" claim refused -- the kernel is driving it, or someone else is\n"); exit(); } puts(" claimed\n"); // The first memory BAR. A device with none is not one a driver reaches // this way, and saying that is more useful than mapping nothing. int b = -1; for (int i = 0; i < PCIUSER_MAXBAR; i++) if (in.bar[i].base && (in.bar[i].flags & PCIUSER_BAR_MMIO)) { b = i; break; } if (b < 0) { puts(" no memory BAR to map\n"); exit(); } puts(" BAR"); putn(b); puts(" at 0x"); puthex(in.bar[b].base, 16); puts(", "); putn((int)(in.bar[b].size / 1024)); puts(" KB\n"); unsigned long long len = in.bar[b].size; if (len > 4096) len = 4096; // a page is enough to prove the point volatile unsigned int *regs = (volatile unsigned int *)mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, in.bar[b].base); if (regs == 0 || (long)regs == -1) { puts(" mmap of the BAR failed\n"); exit(); } puts(" mapped at 0x"); puthex((unsigned long long)regs, 16); puts("\n"); puts(" regs[0..3] ="); for (int i = 0; i < 4; i++) { puts(" 0x"); puthex(regs[i], 8); } puts("\n"); // All-ones across the board means the read did not reach the device: an // unmapped PCI read returns 0xFFFFFFFF rather than faulting, so this is // the difference between "mapped the wrong thing" and "it works", and it // is worth checking rather than eyeballing. if (regs[0] == 0xFFFFFFFFu && regs[1] == 0xFFFFFFFFu) { puts(" all ones -- the mapping does not reach the device\n"); } else { puts(" MMIO reaches the device\n"); } struct pciuser_dma dma; dma.size = 8192; if (ioctl(fd, PCIIOC_DMA, &dma) < 0) { puts(" DMA allocation failed\n"); exit(); } puts(" DMA 8 KB at virtual 0x"); puthex(dma.uaddr, 16); puts(", physical 0x"); puthex(dma.paddr, 16); puts("\n"); // Write through the mapping and read it back: proves the memory is really // there and really writable, which a physical address alone does not. volatile unsigned int *buf = (volatile unsigned int *)dma.uaddr; buf[0] = 0x5C405; buf[1] = 0xD1DD1E; if (buf[0] == 0x5C405u && buf[1] == 0xD1DD1Eu) puts(" DMA buffer reads back what was written\n"); else puts(" DMA buffer did not read back correctly\n"); puts("\nmechanisms 2 and 4 work: a driver could live out here.\n"); exit(); }