#pragma once // Port I/O, from a process. // // A Linux driver does not always ask. `ioread32` is a function call LDLS can // implement, but `outl` is an *instruction*, compiled into the module, and the // first foreign driver to need one found this the hard way: // // e1000_io_write+0xd: out %eax,(%dx) -> trap 13 err 0, rdx=0xc000 // // That is not a driver doing something exotic. e1000's own reset path picks it // deliberately -- "these controllers can't ack the 64-bit write when issuing // the reset, so use IO-mapping as a workaround" -- so on QEMU's 82540EM there // is no route to a working NIC that avoids it. Any layer that runs somebody // else's drivers has to let a process reach the ports of the device it holds. // // **Which ports, and no others.** x86 offers two ways to grant this and they // are very far apart. IOPL=3 is one flag and hands over all 65536 ports -- the // interrupt controller, the timer, the disk. The TSS I/O permission bitmap is // per port, and the ports a process gets are exactly the I/O BARs of the PCI // devices it has claimed. The second costs a few hundred lines and is the only // one worth having: a foreign driver already has DMA with no IOMMU, and the // answer to "this is not a security boundary" is not to move it further. // // It is the same bargain as kpi_io_ok() in ldls/kpi/io.c, one level down and // enforced by hardware rather than by the driver's host being polite. #include "types.h" struct proc; // Ranges per process. A PCI device has at most six BARs and a process may hold // several devices; eight is past anything seen and small enough to scan. #define NIOPORT 8 struct ioport_range { uint16 base; uint16 len; }; // Grant / revoke. Called from /dev/pci when a process claims or releases a // device, and from exit() by way of pciuser_release_all(). // // Granting a range twice is not an error -- a driver may claim, release and // claim again -- and revoking one that was never granted is not either. void ioperm_grant(struct proc *p, uint16 base, uint16 len); void ioperm_revoke_all(struct proc *p); // Install this process's ranges in the running CPU's TSS. Called from // switchuvm() with interrupts off. void ioperm_switch(struct proc *p); // The per-CPU TSS, which now has a bitmap attached to it. seginit() asks for // this CPU's and points the GDT descriptor at it. void *ioperm_tss(int cpu, uint64 *limit); // The same block, for everyone after seginit(). void *ioperm_tss_of(int cpu); // Where RSP0 lives, so switchuvm() does not have to know the layout. void ioperm_set_rsp0(void *tss, addr_t stktop);