hybrid: /dev/pci -- a driver can live outside the kernel now
docs/plan/hybrid.md mechanisms 2 and 4: MMIO and DMA to a process.
A process can enumerate the bus, claim a device exclusively, map its BARs and
get memory the device can be pointed at. Proven end to end against an e1000e
-- the exact device class we cannot drive and would want a foreign driver for:
device 7: 8086:10d3 (network)
claimed
BAR0 at 0x81060000, 128 KB
regs[0..3] = 0x00140261 0x00000000 0x00080283 0x00000000
MMIO reaches the device
DMA 8 KB at virtual 0x40001000, physical 0x3f8eb000
Those are the e1000e's CTRL and STATUS with full-duplex and link-up set, which
is how you tell a real mapping from one reading 0xFFFFFFFF off an unmapped
address. Claiming a device the kernel drives is refused -- two writers of one
register set looks like hardware that has started lying -- and everything a
process held is released when it exits, because a driver that crashed must not
keep its hardware away from its replacement.
mmap's offset is the BAR's physical address, checked against the BARs of
devices *this process has claimed*. Without that check the device is /dev/mem
with extra steps and a process could map the kernel's own memory. Mappings are
write-through: a cached write to a doorbell arrives whenever the cache feels
like it, which from a driver's side is indistinguishable from hardware that
ignores you.
The interface is about PCI devices, not about any driver model, and its first
users will be SchOS's own drivers when virtio-gpu is evicted. That ordering is
the design rather than an accident: an interface whose first consumers are
native drivers is a general mechanism something foreign can use later, instead
of a hole shaped like something foreign.
Also: PROT_* and MAP_* moved to include/posix.h. They were defined only in
sys_mem.c, so cmd/fbinfo carried a private copy and this wanted a third --
two copies of a number the kernel compares against, where a mismatch shows up
as a mapping with the wrong permissions rather than an error.
No IOMMU, so this isolates a driver against its own bugs and not against its
own DMA. The plan says so at length and it is still true.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
6c70040f57
commit
24f79194ab
14 changed files with 678 additions and 11 deletions
|
|
@ -12,15 +12,13 @@
|
|||
#include "file.h"
|
||||
#include "syscall.h"
|
||||
#include "errno.h"
|
||||
#include "posix.h"
|
||||
#include "pciuser.h"
|
||||
|
||||
extern int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm);
|
||||
|
||||
#define MAP_SHARED 0x01
|
||||
#define MAP_PRIVATE 0x02
|
||||
#define MAP_FIXED 0x10
|
||||
#define MAP_ANONYMOUS 0x20
|
||||
#define PROT_NONE 0
|
||||
#define PROT_WRITE 0x2
|
||||
/* MAP_* and PROT_* now live in include/posix.h, so userspace and the kernel
|
||||
compare the same numbers. */
|
||||
|
||||
// Reject user mapping requests that fall outside user space. Without this,
|
||||
// MAP_FIXED at an arbitrary address could create user-accessible PTEs over
|
||||
|
|
@ -158,6 +156,29 @@ sys_mmap(void)
|
|||
return va;
|
||||
}
|
||||
|
||||
// /dev/pci: the offset is the physical address of a BAR.
|
||||
//
|
||||
// Checked against the BARs of the devices *this process has claimed*,
|
||||
// which is what stops it being /dev/mem with extra steps -- without that
|
||||
// test a process could map the kernel's own memory and call it a driver.
|
||||
if (f && f->type == FD_INODE && f->ip->type == T_DEV &&
|
||||
f->ip->major == PCIUSER_MAJOR) {
|
||||
uint64 phys = PGROUNDDOWN(offset);
|
||||
uint64 len = PGROUNDUP(length);
|
||||
if (!pciuser_mmio_ok(phys, len))
|
||||
return -EACCES;
|
||||
// PTE_PWT: device registers are not memory, and a cached write to a
|
||||
// doorbell arrives whenever the cache feels like it, which for a
|
||||
// driver is indistinguishable from hardware that ignores you.
|
||||
if (mappages(myproc()->pgdir, (void*)va, len, phys,
|
||||
PTE_W | PTE_U | PTE_PWT) < 0)
|
||||
return -ENOMEM;
|
||||
v->addr = va; v->length = len; v->used = 1;
|
||||
v->no_free = 1; // device memory is not ours to free
|
||||
v->shmid = -1;
|
||||
return va;
|
||||
}
|
||||
|
||||
if (f && f->type == FD_INODE && f->ip->type == T_DEV && f->ip->major == 2) {
|
||||
// Map the framebuffer where it actually is. This used to map a
|
||||
// hardcoded 0xfd000000 for a hardcoded length, so a request for more
|
||||
|
|
|
|||
Loading…
Reference in a new issue