README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
73 lines
3.7 KiB
C
73 lines
3.7 KiB
C
#pragma once
|
|
|
|
// Physically contiguous memory for devices that read and write it themselves.
|
|
//
|
|
// kalloc() hands out 4 KB pages from a free list, in whatever order they were
|
|
// released. That is exactly what a page table wants and exactly what a device
|
|
// cannot use: an xHCI command ring, a NIC's descriptor array or a virtio
|
|
// queue is a single physically contiguous object that the hardware walks
|
|
// without any idea that paging exists. Two kalloc'd pages are adjacent in the
|
|
// kernel's address space only by luck.
|
|
//
|
|
// So DMA memory comes from an arena carved out of the top of physical memory
|
|
// before kalloc ever sees it, and is handed out contiguous and aligned.
|
|
//
|
|
// On x86 this memory needs no special cacheability. The PCI bus is cache
|
|
// coherent: a device's write invalidates the corresponding cache line, and a
|
|
// CPU write is snooped out of the store buffer before the device sees stale
|
|
// data. Architectures without that guarantee need explicit cache maintenance
|
|
// around every descriptor access, and porting this file is where that would
|
|
// go. What is *not* free even here is ordering -- the compiler and the CPU
|
|
// will happily reorder stores to a ring, so a driver still needs a barrier
|
|
// between filling a descriptor and telling the device about it.
|
|
|
|
// 64 MB, and the arithmetic is worth writing down because it has been got
|
|
// wrong twice.
|
|
//
|
|
// 4 MB was one framebuffer at 1280x800, which was fine while exactly one driver
|
|
// wanted one. It stopped being fine the day a display driver could be handed to
|
|
// userspace (docs/architecture/devices.md): either side of that handover **both drivers
|
|
// exist**, and the incoming one has to allocate its framebuffer before the
|
|
// outgoing one lets go of its own. So the real requirement is two framebuffers
|
|
// at once, not one.
|
|
//
|
|
// 16 MB was chosen for two at 1280x800 (4 MB each) and that is what it holds.
|
|
// It does not hold two of anything larger, and virtio-gpu's size is **the size
|
|
// of the host's window**, which the user resizes. A 1920x982 window is 7365 KB
|
|
// a copy, so the handover needs 14.4 MB of a 16 MB arena and then fails on the
|
|
// rings and the alignment:
|
|
//
|
|
// vgpud: wanted 7365 KB of DMA memory for a 1920x982 framebuffer
|
|
// vgpud: not enough contiguous DMA memory
|
|
// schinitd: restarting os.schos.vgpud (and again, and again)
|
|
//
|
|
// 64 MB is two framebuffers at 2560x1440 with room to spare, which covers any
|
|
// window somebody is likely to drag out on a desktop. It is carved off the top
|
|
// of physical memory at boot and never grows, so it is 64 MB this kernel cannot
|
|
// use for anything else -- 6% of the 1 GB this is usually run with, against a
|
|
// display driver that otherwise cannot start at all.
|
|
#define DMA_ARENA_BYTES (64*1024*1024)
|
|
|
|
void dma_init(void *vstart, void *vend);
|
|
|
|
// Zeroed, physically contiguous, at least `align`-aligned (align must be a
|
|
// power of two). Returns a kernel virtual address, or 0.
|
|
void *dma_alloc(uint64 size, uint64 align);
|
|
|
|
// `size` must be the size the allocation was made with, as in Linux's
|
|
// dma_free_coherent(). Tracking it here would mean a second table whose only
|
|
// job is to remember something the caller already knows.
|
|
void dma_free(void *p, uint64 size);
|
|
|
|
// The address to give the device. Kernel memory is direct-mapped, so this is
|
|
// just V2P -- but drivers should say what they mean, because on a machine
|
|
// with an IOMMU these stop being the same number.
|
|
addr_t dma_phys(const void *p);
|
|
|
|
uint64 dma_free_bytes(void);
|
|
|
|
// The biggest single block still available. When an allocation fails, the
|
|
// difference between this and dma_free_bytes() is the difference between a
|
|
// full arena and a fragmented one -- see the message in dma_alloc().
|
|
uint64 dma_largest_bytes(void);
|
|
uint64 dma_total_bytes(void);
|