SchOS/docs/plan/gpu.md
Nexoniarz cb3d28e3d6 docs: README, plan, guides and the site describe what is built now
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
2026-10-26 08:08:05 +01:00

6.6 KiB
Raw Blame History

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:

  1. Threads. kthread_run, workqueues with real worker threads, gpu-sched's scheduler threads. Map them to pthreads in the driver process.
  2. Locks that lock. mutex, spinlock, rwsem, ww_mutex (TTM and dma-resv depend on wound/wait semantics), completions and wait queues, on futexes.
  3. Interrupts as interrupts. A thread blocked in read() on /dev/pci calls the handler and any threaded handler, instead of the poll loop.
  4. Timers. timer_list, hrtimer, delayed_work and jiffies, from a timer thread.
  5. 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_DMA arena has to grow to hundreds of megabytes, with struct page (vmemmap) coverage for all of it.
  6. Firmware. request_firmware reads /lib/firmware/amdgpu/*.bin, from Alpine's linux-firmware-amdgpu package.
  7. 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/netdev uses 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_RIGHTS passes 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.

  1. One module set. Build Linux 6.18 from source, once, with DRM=m, DRM_AMDGPU=m and BTF on. That gives drm.ko, drm_kms_helper.ko and the rest, plus a matching vmlinux BTF. make ldls-modules does the build, and packaging it as an apk makes it installable.
  2. 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.
  3. 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 dmesg shows amdgpu's "SMU is initialized" and the panel stays lit.
  4. KMS. /dev/dri/card0 with ioctl forwarding and the mmap path. Done when modetest (Alpine libdrm-tests) sets a mode and draws a frame.
  5. Rendering. renderD128, GEM mmap, dma-buf, syncobj. Done when eglinfo names radeonsi and glmark2-es2-drm and vkcube (RADV) run.
  6. 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/dri machinery, 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.