SchOS/docs/plan/gpu.md
Nexoniarz 4ad49ed5f2 Linux's own r8169 through LDLS, deep idle, and the GPU plan
The RTL8168 is now driven by Linux's r8169, unmodified, in a process.
`make ldls-r8169` stages it from the running kernel together with libphy,
mdio-bus, mdio_devres and the Realtek PHY driver, the kernel's BTF and
the RTL PHY firmware, and etc/schinit/ldls-r8169.job starts it at boot
with its log in /var/log/ldls-r8169.log. The native driver is out of the
registry; its code, now with Linux's 8168H setup, stays as the fallback.

LDLS grows what that needed:
- ldlsd loads a comma-separated module set in dependency order, each
  linking against the ones before it, with BTF read before any
  init_module runs;
- a driver core (bus match, driver probe), devres, kernel printf, real
  udelay/msleep/ktime and 1000 Hz jiffies, phylib's bitmaps and
  link_mode_params, firmware from /lib/firmware, and the symbols whose
  stub zero was a lie (list validity, try_module_get, dev_err_probe,
  eth_platform_get_mac_address);
- pdev->bus is a real pci_bus, and cmovcc from per-CPU memory is
  rewritten like the other %gs forms.
In QEMU all five modules load, link and initialise, and r8169 registers
and finds no device. Probe and traffic are untested until real hardware.

The kernel turns ASPM off on Realtek NIC links at boot, as r8169 does
through pci_disable_link_state, which a process cannot.

Idle CPUs enter ACPI _CST's C2 (CC6 on Zen) by port read or MWAIT when
the APIC timer keeps running, instead of only ever halting at C1.

docs/plan/gpu.md: amdgpu and nouveau through LDLS, with measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 14:26:16 +01:00

125 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
([hybrid.md](hybrid.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, foundations.md
item 1), 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.