SchOS/include/dma.h
Nexoniarz eacb13ce35 dma: best fit, and say which number is the problem
The arena hands out contiguous memory -- that is the only reason it exists --
so the number that matters is not how much is free but how big the largest
piece is. First fit spends that number cheaply: a 64 KB ring taken from the
middle of a 16 MB arena leaves two 8 MB extents, and a framebuffer wanting 9 MB
then cannot be had on an arena three quarters empty.

dma_alloc() is best fit now: the tightest extent that holds the request, which
leaves the large ones whole for the requests that have no alternative. O(n)
over a table with a handful of entries, and it cannot fragment worse than first
fit for any request.

And the failure says both numbers, because they point at different fixes and
the old message only had one of them:

    dma_alloc: no room for 9216000 bytes -- 12591104 free,
               largest contiguous piece 8495104 (`schdbg dma` for the map)

"12 MB free" alone reads as out-of-memory on a machine that has plenty; the gap
between the two is the whole story. A fragmented arena is cured by whoever is
holding the block in the middle letting go, a full one by DMA_ARENA_BYTES.

Also fixes a real bug found while reading it: when the extent table was full,
the tail-split path did `continue` while still holding the lock and having
already committed the extent to the caller -- so it carried on scanning, and
could return a second block or fall through to the failure path having handed
one out. It now returns the block it committed, and says so.

**This does not close the report it came from.** vgpud failing to get a
framebuffer was not reproduced here: at 1280x800 the arena has 8.1 MB
contiguous against a 4 MB request. What is fixed is that the next occurrence
explains itself instead of saying "not enough DMA memory" on an empty arena.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 21:28:07 +02:00

59 lines
2.9 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.
// 16 MB, not 4.
//
// 4 MB was one framebuffer at 1280x800, and that 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): for the moment either side of the
// handover both drivers exist, and the incoming one has to allocate its
// framebuffer before the outgoing one is gone.
//
// It is carved off the top of physical memory at boot and never grows, so the
// cost of being wrong in this direction is 12 MB and the cost of being wrong
// in the other is a driver that cannot start.
#define DMA_ARENA_BYTES (16*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);