include: one folder per subsystem, and includes that name it

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
This commit is contained in:
Nexoniarz 2026-10-23 15:05:12 +02:00
commit a1c965da1e
267 changed files with 1560 additions and 1440 deletions

73
include/core/dma.h Normal file
View file

@ -0,0 +1,73 @@
#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/plan/hybrid.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);