// SPDX-License-Identifier: Apache-2.0 // // /dev/pci -- device access from userspace. // // docs/plan/hybrid.md, mechanisms 2 and 4: MMIO and DMA to a process. This is // the interface a driver that does not live in the kernel talks to, and it is // deliberately about *PCI devices*, not about any particular driver model. // // That generality is the point, and it is worth being explicit about why. // The first users of this will be SchOS's own drivers, evicted one at a time // -- virtio-gpu, then xHCI -- and the interface has to be judged by them. // Something that hosts foreign drivers later is then a consumer of a general // mechanism rather than a hole shaped like one, which is both the honest // design and the only one that stays honest under examination. // // What a driver needs from a kernel, and all it needs: // // - to find its device, and read what the bus says about it INFO // - to be given it, exclusively CLAIM // - to reach its registers mmap(BAR) // - memory it can hand to the device DMA // - to be told when the device wants attention IRQ + read() // // There is no IOMMU. A process holding a DMA buffer can point the device at // any physical address, so this isolates a driver against its own *bugs* and // not against its own *intent*. docs/plan/hybrid.md says so at length and it // remains true here: a crashed driver that restarts instead of taking the // machine down is the whole benefit, and it is not a security boundary. #ifndef PCIUSER_H #define PCIUSER_H #define PCIUSER_MAJOR 9 #define PCIUSER_MAXBAR 6 struct pciuser_bar { unsigned long long base; // physical address, 0 when the BAR is unused unsigned long long size; unsigned int flags; // PCIUSER_BAR_* unsigned int pad; }; #define PCIUSER_BAR_MMIO 0x01 // memory space; mappable #define PCIUSER_BAR_IO 0x02 // I/O port space; not mappable #define PCIUSER_BAR_PREFETCH 0x04 // One device, as the bus describes it. `index` is the only field the caller // fills in; everything else comes back. struct pciuser_info { unsigned int index; // in: 0 .. PCIIOC_COUNT-1 unsigned char bus, slot, func; unsigned char header_type; unsigned short vendor, device; unsigned char class, subclass, prog_if, revision; unsigned char irq; // interrupt line, 0xFF when unrouted unsigned char claimed; // 1 when a userspace process holds it unsigned short pad; // The name of whatever owns it: an in-kernel driver, "userspace" when a // process has claimed it, or empty when nothing has. char driver[16]; struct pciuser_bar bar[PCIUSER_MAXBAR]; }; // A DMA buffer. The kernel allocates it from the DMA arena, maps it into the // caller, and reports the physical address the *device* must be given -- which // is the entire reason a driver cannot do this for itself with mmap. // A dword of PCI configuration space. A virtio device says where its registers // live in vendor capabilities, which are here and nowhere else -- so without // this a driver cannot find its own device. struct pciuser_cfg { unsigned int index; // in unsigned int offset; // in: dword-aligned, 0..0xFC unsigned int value; // out unsigned int pad; }; struct pciuser_dma { unsigned long long size; // in: bytes, rounded up to a page unsigned long long uaddr; // out: where it landed in this process unsigned long long paddr; // out: what to program into the device }; #define PCIIOC_COUNT 0x9000 // out: int, how many devices the bus has #define PCIIOC_INFO 0x9001 // struct pciuser_info #define PCIIOC_CLAIM 0x9002 // in: int index -- take it from the kernel #define PCIIOC_RELEASE 0x9003 // in: int index #define PCIIOC_DMA 0x9004 // struct pciuser_dma #define PCIIOC_CFG 0x9006 // struct pciuser_cfg -- read config space #define PCIIOC_IRQ 0x9005 // in: int index -- deliver this device's // interrupts to this process // Interrupts arrive by *reading* this device, which is how Linux's uio does it // and for the same reason: a descriptor composes with poll, and a driver that // stops reading falls behind rather than losing events. // // ioctl(fd, PCIIOC_IRQ, &index); // for (;;) { // unsigned long long n; // read(fd, &n, sizeof n); // blocks until the device interrupts // ... service the device, and ACK IT AT THE DEVICE ... // write(fd, &n, sizeof n); // re-arm // } // // **The write is not optional.** A PCI interrupt line is level-triggered: it // stays asserted until the driver clears the cause *at the device*, and the // kernel cannot do that because clearing it is device-specific -- it is a // different register on every chip. So the kernel masks the line when the // interrupt arrives and leaves it masked until the write says the device has // been dealt with. // // Without that, EOI is signalled on a line that is still asserted and the // interrupt re-fires immediately, for ever. That is not hypothetical: it // happened in this kernel, at 72,000 interrupts a second, and what it looked // like from userspace was a clock running seven times slow. // See docs/history/kernel.md section 13. // // One device per process. A driver drives one thing; if you need two, run two. // Registers are reached with mmap() on this device, where the *offset is the // physical address of the BAR*: // // ioctl(fd, PCIIOC_INFO, &info); // ioctl(fd, PCIIOC_CLAIM, &info.index); // regs = mmap(0, info.bar[0].size, PROT_READ|PROT_WRITE, MAP_SHARED, // fd, info.bar[0].base); // // The kernel refuses an offset that is not inside a BAR of a device this // process has claimed, which is what stops this being /dev/mem with extra // steps. #endif // PCIUSER_H