Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
267 lines
9.7 KiB
C
267 lines
9.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// 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 <index> claim that device, map BAR0, read it, allocate DMA
|
|
// pcitest <index> x the same, then assert an interrupt and exit WITHOUT
|
|
// acking it -- which is what a driver that crashed looks
|
|
// like from the kernel's side, and the case that used to
|
|
// leave a level-triggered line asserted with no handler
|
|
|
|
#include "core/types.h"
|
|
#include "uapi/user.h"
|
|
#include "uapi/fcntl.h"
|
|
#include "uapi/posix.h"
|
|
#include "dev/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 irq class bm 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;
|
|
|
|
// Bus mastering, straight out of the command register.
|
|
//
|
|
// Worth a column of its own because it is the one bit that says
|
|
// whether a device can still touch memory, and the whole lifecycle
|
|
// turns on it: claiming a device sets it, releasing one clears it,
|
|
// and a driver that died leaves a device that must not be left
|
|
// reading buffers the kernel is about to hand to somebody else.
|
|
struct pciuser_cfg cfg;
|
|
cfg.index = (unsigned int)i;
|
|
cfg.offset = 0x04; // PCI_COMMAND
|
|
cfg.value = 0;
|
|
// Three states, not two. A read that failed is not the same as a
|
|
// bit that is clear, and printing them the same way is how a
|
|
// column stops being evidence -- which is exactly what happened
|
|
// the first time this was run.
|
|
const char *bm = "? ";
|
|
if (ioctl(fd, PCIIOC_CFG, &cfg) == 0)
|
|
bm = (cfg.value & 0x0004) ? "on " : "-- ";
|
|
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(" ");
|
|
if (in.irq == 0xFF) puts(" - "); else { putn(in.irq); puts(" "); }
|
|
puts(class_name(in.class));
|
|
puts(" ");
|
|
puts(bm);
|
|
puts(in.driver[0] ? in.driver : "-");
|
|
puts("\n");
|
|
}
|
|
puts("\npcitest <n> 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");
|
|
|
|
// Mechanism 3. Ask for the device's interrupts, then make it interrupt.
|
|
//
|
|
// The e1000 family has an Interrupt Cause Set register at 0x00C8: writing
|
|
// a cause bit asserts the line exactly as the hardware would, which is the
|
|
// only way to test interrupt delivery without traffic arriving from
|
|
// somewhere. Reading ICR at 0x00C0 clears it, which is the ack the kernel
|
|
// cannot do for us.
|
|
if (in.vendor != 0x8086 || in.class != 0x02) {
|
|
puts("\nmechanisms 2 and 4 work: a driver could live out here.\n");
|
|
puts("(interrupts untested -- that part knows only Intel NICs)\n");
|
|
exit();
|
|
}
|
|
|
|
if (ioctl(fd, PCIIOC_IRQ, &idx) < 0) {
|
|
puts(" interrupts refused -- the line is shared with a kernel driver\n");
|
|
puts("\nmechanisms 2 and 4 work: a driver could live out here.\n");
|
|
exit();
|
|
}
|
|
puts(" interrupts routed here (line "); putn(in.irq); puts(")\n");
|
|
|
|
volatile unsigned int *IMS = ®s[0x00D0 / 4]; // interrupt mask set
|
|
volatile unsigned int *ICS = ®s[0x00C8 / 4]; // interrupt cause set
|
|
volatile unsigned int *ICR = ®s[0x00C0 / 4]; // cause read, clears
|
|
|
|
*IMS = 0x00000004; // LSC: link status change, a cause with no traffic
|
|
(void)*ICR; // clear anything already pending
|
|
*ICS = 0x00000004; // assert it
|
|
|
|
// Crash simulation: assert the cause and leave, without reading or
|
|
// acking. The line is asserted and this process is about to stop existing.
|
|
if (argc > 2 && argv[2][0] == 'x') {
|
|
puts(" interrupt asserted; exiting WITHOUT acking (simulated crash)\n");
|
|
exit();
|
|
}
|
|
|
|
unsigned long long n = 0;
|
|
if (read(fd, &n, sizeof n) != (int)sizeof n) {
|
|
puts(" read() did not return an interrupt\n");
|
|
exit();
|
|
}
|
|
puts(" interrupt delivered, count = "); putn((int)n); puts("\n");
|
|
|
|
unsigned int cause = *ICR; // ack at the device
|
|
puts(" ICR = 0x"); puthex(cause, 8); puts("\n");
|
|
write(fd, &n, sizeof n); // re-arm
|
|
|
|
puts("\nall three mechanisms work: a driver could live out here.\n");
|
|
exit();
|
|
}
|