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
125 lines
6.6 KiB
Markdown
125 lines
6.6 KiB
Markdown
# 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](../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](../architecture/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](../architecture/networking.md#descriptor-passing)), 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.
|
||
|
||
0. **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.
|
||
1. **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`.
|
||
2. **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.
|
||
3. **KMS.** `/dev/dri/card0` with ioctl forwarding and the mmap path. Done
|
||
when `modetest` (Alpine `libdrm-tests`) sets a mode and draws a frame.
|
||
4. **Rendering.** `renderD128`, GEM mmap, dma-buf, syncobj. Done when
|
||
`eglinfo` names radeonsi and `glmark2-es2-drm` and `vkcube` (RADV) run.
|
||
5. **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.
|