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
6.6 KiB
The GPU, through Linux's own driver
The laptop this is for (ASUS TUF A15 FA507NU) has a Radeon 680M in the CPU and an RTX 4050 beside it. SchOS draws everything with the CPU on an EFI framebuffer. Rendering a browser page that way keeps every core busy, and the machine runs hot doing it.
Writing a Radeon driver is not on the table: amdgpu is around four million lines
before its register headers. The plan is to run Linux's amdgpu unmodified,
with its firmware, in a process under LDLS (../architecture/ldls.md),
and to give Mesa the /dev/dri it expects. Licensing is not an obstacle:
SchOS is GPL-2.0-or-later, and the user has said that Linux code and firmware
blobs may be shipped. That supersedes LDLS's rule 1.
Why LDLS and not an in-kernel linuxkpi
FreeBSD's drm-kmod compiles Linux's DRM source into kernel modules against a
shim (linuxkpi). SchOS has already chosen the other route, and for good reasons
(devices.md): GPU drivers live outside the kernel, a crashed driver
falls back to the framebuffer instead of taking the machine down, and LDLS
loads unmodified .ko files against the running kernel's BTF instead of
needing a source port per Linux release. vgpud already drives a GPU from a
process, and LDLS already runs 8139too and e1000 that way.
What it costs: the numbers
Measured against Linux 6.18.54 (the host's kernel and modules), with
modprobe --show-depends, nm -u and the export tables:
| Driver | Modules in the closure | Symbols needed from the core kernel | LDLS provides today |
|---|---|---|---|
| r8169 | 12 (libphy, mdio-bus, realtek PHY, ...) | 330 | 70 |
| amdgpu | 15 (ttm, gpu-sched, drm_buddy, drm_exec, drm_display_helper, ...) | 1,166 | 76 |
Of amdgpu's 1,166 symbols, 314 are the DRM core (drm_*). NixOS builds
drm.ko and drm_kms_helper.ko into the kernel, so a module set that has them
as modules is needed; see step 0. A further 45 are dma-fence, dma-buf and
dma-resv. The rest is ordinary kernel: memory, locks, workqueues, timers, PCI,
ACPI, firmware loading, i2c, backlight.
What LDLS lacks, and must grow
LDLS today assumes one thread, no-op locks and a polled interrupt handler (ldls.md, "What it costs"). amdgpu cannot work under that:
- Threads.
kthread_run, workqueues with real worker threads,gpu-sched's scheduler threads. Map them to pthreads in the driver process. - Locks that lock. mutex, spinlock, rwsem,
ww_mutex(TTM and dma-resv depend on wound/wait semantics), completions and wait queues, on futexes. - Interrupts as interrupts. A thread blocked in
read()on/dev/pcicalls the handler and any threaded handler, instead of the poll loop. - Timers.
timer_list,hrtimer,delayed_workandjiffies, from a timer thread. - Memory with physical addresses. TTM allocates system pages for GTT and
maps them for the GPU. Without an IOMMU, DMA address equals physical
address, so the
PCIIOC_DMAarena has to grow to hundreds of megabytes, withstruct page(vmemmap) coverage for all of it. - Firmware.
request_firmwarereads/lib/firmware/amdgpu/*.bin, from Alpine'slinux-firmware-amdgpupackage. - ACPI. On an APU the VBIOS comes from the VFCT table, and amdgpu calls
ATIF/ATCS. A small
/dev/acpi(read a table, evaluate a method through uACPI) covers both.
Each of these is general LDLS work, not amdgpu work. Every later driver needs the same pieces: e1000e, iwlwifi, nouveau.
The new kernel piece: /dev/dri served by a process
Mesa and Xorg open /dev/dri/card0 and /dev/dri/renderD128, then use
ioctl, mmap and poll, and pass buffers to each other as dma-buf file
descriptors. The kernel has to forward each of those to the driver process:
- open, ioctl, poll, close go to the server process with the client's
identity attached, the same pattern
/dev/netdevuses for NICs, extended to ioctls with their argument buffers. - mmap is the part the kernel must do itself. The server replies with physical ranges (VRAM through the BAR, or GTT pages from the arena), and the kernel maps those into the client's address space.
- dma-buf becomes a kernel file object naming a buffer the server owns.
SCM_RIGHTSpasses it between processes (already built; see networking.md), and X's DRI3 needs exactly that. - sysfs:
/sys/class/drm/card0,/sys/dev/char/226:*, and the vendor/device files that libdrm and Mesa read to pick a driver.
Order
Each step ends with something you can run.
- One module set. Build Linux 6.18 from source, once, with
DRM=m,DRM_AMDGPU=mand BTF on. That givesdrm.ko,drm_kms_helper.koand the rest, plus a matchingvmlinuxBTF.make ldls-modulesdoes the build, and packaging it as an apk makes it installable. - LDLS concurrency and memory (lacks 1–5 above), proven on r8169.
The real Linux r8169 brings PHY tuning, the firmware patch, ASPM handling
and every RTL8168 variant. It is the laptop NIC, so this step also fixes the
network properly. Done when the laptop's ethernet runs at gigabit speed
through
ldls0. - amdgpu probes. The driver process loads firmware, brings up the IP
blocks and takes the display from the EFI framebuffer. The kernel console
must hand over the screen and get it back if the process dies. Done when
dmesgshows amdgpu's "SMU is initialized" and the panel stays lit. - KMS.
/dev/dri/card0with ioctl forwarding and the mmap path. Done whenmodetest(Alpinelibdrm-tests) sets a mode and draws a frame. - Rendering.
renderD128, GEM mmap, dma-buf, syncobj. Done wheneglinfonames radeonsi andglmark2-es2-drmandvkcube(RADV) run. - The desktop. Xorg's modesetting driver with glamor, then Epiphany with GL compositing. Done when the fans stay quiet while a page scrolls.
The RTX 4050
Later, and in two parts:
- Powered off, now. Without a driver the dGPU sits in D0 and draws power.
Putting it in D3cold through its ACPI power resources (
_PR3/_OFF) is a few dozen lines in the kernel and helps the heat and the battery today. - Driven, after amdgpu. nouveau with GSP firmware (Ada needs it), through
the same LDLS and
/dev/drimachinery, for PRIME render offload.
Not GPU, but also why the laptop runs hot
Until now, idle CPUs halted at C1 and never reached the deep states listed in
ACPI _CST. That kept every core powered at an idle shell prompt. It is fixed
alongside this plan: the scheduler's idle path now enters _CST's C2 (CC6 on
Zen). P-state control (AMD CPPC) is still missing, so the cores run at the
firmware's default clock under load.