diff --git a/docs/branding/wallpaper1.png b/docs/branding/wallpaper1.png new file mode 100644 index 0000000..94f6467 Binary files /dev/null and b/docs/branding/wallpaper1.png differ diff --git a/docs/licensing.md b/docs/licensing.md index d7c08f7..8a641da 100644 --- a/docs/licensing.md +++ b/docs/licensing.md @@ -63,6 +63,11 @@ SchOS does not. ever, no matter how convenient. This is why the USB stack was written from the specification rather than ported. +**"Cannot be adopted" is not the same as "cannot be run."** A driver the *user* +obtains and loads on their own machine is their act, not a distribution by us — +see §4 and [plan/ldls.md](plan/ldls.md), which is the layer built on exactly +that distinction and which ships no Linux code of any kind. + --- ## 3. NetBSD in particular diff --git a/docs/plan/1.0.md b/docs/plan/1.0.md index d9e00fa..f91e868 100644 --- a/docs/plan/1.0.md +++ b/docs/plan/1.0.md @@ -95,9 +95,17 @@ The galling ones. The work exists, is tested, and nothing uses it. |---|---| | **`foundations.md`** | Item 1 built. Items 2 (a shareable memory object), 3 (`AF_UNIX` in the filesystem namespace), 4 and 5 open. **Wayland stops at item 2.** | | ~~**`hybrid.md`**~~ | **Closed.** All four mechanisms built; H1, H2 and H3 done. H4 — the USB eviction — is *withdrawn* rather than outstanding: USB is the bus peripherals arrive on, not a peripheral, and an eviction that has to leave an identical driver behind has moved nothing. See [hybrid.md](hybrid.md), "Decided: USB stays in the kernel". | +| **`ldls.md`** | Not started. The plan exists; L0, the loader, is the first thing that would. | | **Schcom v0** | 715 lines that composite, over a wire format that is superseded. Frozen: it runs, nothing more is added, and it is kept only because it is the one thing in the tree that has ever put a client's pixels on screen. | | **`debugger.md`** | D0 through D4 built and described in [../architecture/debugging.md](../architecture/debugging.md). Only the network transport is open — a userspace daemon bridging TCP to `/dev/dbg`, which is deliberately last. | +**The row LDLS spends.** `hybrid.md` closed above — H3 built, H4 withdrawn — +and [ldls.md](ldls.md) takes the slot it freed. Worth being honest that this is +the largest thing ever to enter this register, and that it is **explicitly not +part of 1.0**: nothing in the acceptance test needs a foreign driver. It is +here because the mechanisms it uses are finished and idle, and because the +alternative to it is writing a driver for every device that exists. + **The row the debugger spent.** Adding one requires closing one, and *A released PCI device* closed when H2 landed. It is worth saying plainly that the debugger is not a capability of the OS the way every other row here is: nothing in it diff --git a/docs/plan/README.md b/docs/plan/README.md index 1c18b32..d0bef00 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -24,7 +24,7 @@ Taken together, because they turn out to need the same foundation. | | | |---|---| | **[wayland.md](wayland.md)** | The GUI stack becomes Wayland. SchWM and SchDE are **deleted** — actually gone from the tree, not marked; Schcom survives and changes its wire format. | -| **[hybrid.md](hybrid.md)** | The kernel becomes hybrid — drivers move out into userspace processes, starting with the GPU. **Partly built**: all four mechanisms exist and the GPU driver already runs outside the kernel. | +| ~~**hybrid.md**~~ | **Built.** All four mechanisms exist, the GPU driver runs outside the kernel, it survives being killed, and the display mode can be changed from a process. H4 (USB) was *withdrawn* rather than left open — USB is the bus peripherals arrive on, not a peripheral. | | ~~init.md~~ | **Built.** PID 1 is now a role the kernel refills; the description moved to [../architecture/init.md](../architecture/init.md). | And the one they all wait on: @@ -39,6 +39,12 @@ And the one that says which parts of the above are actually being worked on: |---|---| | **[1.0.md](1.0.md)** | The finish line. Four gates, and everything else explicitly deferred. | +And the one the hybrid mechanisms were built for: + +| | | +|---|---| +| **[ldls.md](ldls.md)** | The Linux Driver Layer for SchOS. Someone else's drivers, in a process, reached through `/dev/pci` — because no project can write a driver for every device that exists. **Not started.** | + And one that is a tool rather than a capability: | | | @@ -99,8 +105,9 @@ requirements, and building the interface against it is what keeps it a general mechanism rather than a hole shaped like something foreign. The compositor is still a demanding consumer — it is no longer the *only* one available. -Four mechanisms, all built. One eviction done, one to go, then the foreign -driver host. +Four mechanisms, all built. One eviction done, the second withdrawn with its +reasons recorded, and the foreign driver host — [ldls.md](ldls.md) — is what +they were for. ### 4. `wayland.md` — with the GPU driver either side of the seam diff --git a/docs/plan/ldls.md b/docs/plan/ldls.md new file mode 100644 index 0000000..05c216a --- /dev/null +++ b/docs/plan/ldls.md @@ -0,0 +1,214 @@ +# LDLS — the Linux Driver Layer for SchOS + +SchOS cannot write a driver for every device that exists. No project can. The +only route to the hardware support an established system has is to run someone +else's drivers, and in practice "someone else's drivers" means Linux's. + +[hybrid.md](hybrid.md) built the mechanisms for this and said so in as many +words — MMIO, DMA, interrupts and config space to a process, with SchOS's own +drivers as the first consumers so the interface would be general rather than a +hole shaped like something foreign. `vgpud` proved it. **LDLS is what those +mechanisms were built for.** + +--- + +## The rules + +These are the spine. Everything below is an implementation of them, and +anything that violates one is wrong however well it works. + +### 1. Nothing GPL ever enters an image we build + +The driver is **the user's**, obtained by them, loaded on their machine. SchOS +ships the layer and never the thing it loads. This is not a new position — it +was decided on 2026-08-08 and is written up in +[../architecture/devices.md](../architecture/devices.md) and +[../licensing.md](../licensing.md) §2: + +> Apache-2.0 code and GPL-2.0-only code cannot be combined into one +> distributable work. It is not a matter of taste or of how carefully the files +> are kept apart in the tree. + +`RAMDISK_EXCLUDE` and `NOTPORTS` are the existing mechanisms that keep this +true mechanically rather than by intention, and LDLS must stay inside them. + +### 2. It runs in a process, never in the kernel + +Two independent reasons, and either alone would decide it. + +**Legally**, a module linked into our kernel is the strongest derivative-work +argument that exists — it is the case the FSF has always made. A module +combined with our shim *inside a user process, on the user's machine, at their +direction* is a different thing at arm's length, and it is the position +FreeBSD has shipped from for about a decade: a BSD-licensed `linuxkpi` in base, +GPL `drm-kmod` installed by the user from ports. + +**Practically**, this is foreign code. A driver we did not write, cannot fully +audit and did not choose the quality of, running with the machine's memory in +reach, is exactly the code that should not be able to take the machine down. +Crash isolation is not a nicety here; it is the reason. + +### 3. Facts, not expression + +The shim has to present Linux's internal API, and that API is expressed in GPL +headers. The line LDLS holds: + +| | | +|---|---| +| A symbol exists, and takes these arguments | **fact** — reproduce it | +| A struct has these fields in this order at these offsets | **fact** — a compiled driver indexes them, so there is no other way to express it | +| The comment above it | **expression** — never | +| The macro body, the inline function body, the implementation | **expression** — never | + +Every KPI file records where its shape came from. Not decoration: it is the +difference between a clean-room reimplementation and a transcription, and the +only time anyone will ask is the time it matters. + +This is the same discipline [../licensing.md](../licensing.md) §5 already +describes — *"the xHCI, USB, PCI, fbdev and ELF specifications were all read +and implemented directly, which is why those drivers have no licence +entanglement at all"* — applied to a case where the specification is thinner. + +> **Not legal advice, and it should be checked by someone whose job that is +> before anything ships.** The reasoning rests on interfaces being outside +> copyright (EU Software Directive Art. 1(2)) and on *Google v. Oracle* (2021), +> and neither is a blanket permission. + +### 4. One kernel version at a time, pinned and named + +Linux states in writing that it has no stable in-kernel ABI — +`Documentation/process/stable-api-nonsense.rst` exists to say so. Believing +them is cheaper than being surprised by them. + +So LDLS targets **one version**, says which, and is re-pinned deliberately. +FreeBSD's LinuxKPI does exactly this per drm-kmod release. A layer that claims +to support "Linux drivers" without a version is claiming something nobody has +ever delivered. + +### 5. The unresolved-symbol list is the specification + +Do not guess the API surface. **Load a real module and let the loader say what +is missing.** That list is the work queue, it is exact, and it is available +before a single line of shim is written. + +--- + +## What it is made of + +Four pieces, of which three are ordinary and one is the project. + +### The loader — ours, clean, and small + +A `.ko` is a relocatable ELF64 object. Loading one is: lay out the allocatable +sections, apply the relocations, resolve the undefined symbols against a table, +and find the entry point. SchOS already parses ELF in `kernel/fs/exec.c`, for a +different object type but with the same plumbing. + +There is no licence question here at all — ELF is a published specification and +so is the relocation set. + +### The KPI — the shim, and the actual work + +Our Apache-2.0 reimplementation of the Linux symbols a given driver needs, +organised under Linux's own header names so it is navigable: `kpi/slab.h`, +`kpi/pci.h`, `kpi/netdevice.h`. Rule 3 governs every file. + +### The back end — already built + +`/dev/pci`: `PCIIOC_INFO`, `PCIIOC_CLAIM`, `PCIIOC_CFG`, `mmap` of a BAR, +`PCIIOC_DMA`, `PCIIOC_IRQ`. A Linux driver's `pci_iomap`, `dma_alloc_coherent` +and `request_irq` land on these, which is the whole reason they were specified +in terms of PCI devices rather than in terms of drivers. + +### The class bridge — the one new kernel interface + +This is the piece that does not exist, and it is the interesting one. + +A hosted driver produces packets in a process. SchOS's network stack consumes +them through `struct netdev_ops` in the kernel — `transmit`, `receive`, `mac`, +`link_up`, four entries and nothing else. So what is missing is a **netdev +whose backend is a process**: the kernel publishes the vtable, the four calls +are forwarded over a descriptor, and lwIP never learns that the driver is not +in the kernel. + +It is the exact analogue of what `display_ops` was for the GPU, and +`netdev.h`'s own opening comment already anticipates the shape — *"the stack +should ask for the network interfaces and get whatever is bound … exactly as a +compositor asks for the display and does not care which driver answers."* + +One device, one process, claimed and released, on the `/dev/pci` discipline. + +--- + +## Milestones + +| | | | +|---|---|---| +| **L0** | **The loader.** Parse a real `.ko`, lay it out, relocate it, and **print every unresolved symbol**. Nothing executes. The output is the measured API surface. | | +| **L1** | **Stubs.** Every symbol resolves, to a function that logs its name and stops. `init_module` runs and dies at the first real call — which yields the *call order*, the second thing you need and the other thing you cannot guess. | | +| **L2** | **The hardware.** `probe()` receives a working `struct pci_dev`, maps its BARs, allocates coherent DMA and registers an interrupt, all through `/dev/pci`. The driver reaches its device. | | +| **L3** | **A packet.** The process-backed netdev, wired to the driver's transmit and receive paths. **`ping` over a Linux driver.** This is the milestone that proves the whole idea and the one that cannot be skipped. | | +| **L4** | **Kill it.** The host process dies, the kernel clears bus mastering and reclaims the DMA, a new host starts and the interface comes back. | free — H2 built all of this | + +L4 is listed for completeness rather than as work. `pciuser_stop()`, +`pciuser_release_all()` and `KeepAlive` already do it, and that they apply +unchanged to a foreign driver is the point of having built them against our +own. + +### The first target: `8139too`, not `r8169` + +The obvious choice is a Realtek `r8169`, because that silicon is everywhere. +It is the wrong first choice for one decisive reason: **QEMU cannot emulate +it**, so L0 through L3 would have no way to be tested, and a driver layer +developed against hardware you cannot run is a layer nobody can debug. + +QEMU emulates `rtl8139`. `8139too` is small, self-contained, needs no firmware +blob, and uses the thin `mii` helpers rather than dragging in phylib — which +`r8169` does, and which is a substantial surface on its own. + +So: **`8139too` to build the layer, `r8169` to prove it was general.** The +second is the interesting one and it is much cheaper once the first works. + +This is the `/dev/pci` lesson applied backwards. There, a native driver was the +first consumer so the interface would not be shaped like something foreign. +Here the interface is fixed by Linux, and what is chosen instead is the +*smallest real consumer that can be tested* — because the risk is no longer +designing the wrong interface, it is building a hundred stubs nobody has ever +run. + +--- + +## What it will not do + +- **It is not a security boundary, and here that is worse than usual.** There + is no IOMMU, so a hosted driver can point its device at any physical address. + For SchOS's own drivers that isolates bugs and not intent, which + [hybrid.md](hybrid.md) says at length. For *foreign* drivers the distinction + between bug and intent is one we are in no position to make at all. This is + the single strongest argument for an IOMMU that this project has produced, + and it should be recorded as such rather than waved past. +- **No answer yet on `EXPORT_SYMBOL_GPL`.** Linux marks much of its internal + API GPL-only and modules carry `MODULE_LICENSE`. A layer that supplies those + symbols while asserting a licence it does not hold would be a deliberate + circumvention, and LDLS must not do that. What it should do instead — + refuse such modules, or simply not claim to be Linux and let the user decide + what they load on their own machine — is **undecided**, and is the first + thing to take real advice on. +- **No firmware loading.** `request_firmware` is a stub that fails. Many + drivers need a blob, most blobs are redistributable only under their own + terms, and that is a second licensing problem which is not this one. +- **No module-to-module dependencies.** One object, no `depmod`, no symbol + exports between hosted modules. +- **No sysfs, procfs or debugfs.** Drivers that only *report* through them will + work; drivers that are *configured* through them will not. +- **No unload.** `module_exit` is never called; the way to stop a driver is to + kill the process, which H2 made a supported operation rather than a failure. +- **No sleeping-context fidelity.** Linux drivers assume a kernel's rules about + what may sleep, what runs in interrupt context and what `spin_lock_irqsave` + costs. A process has different rules, and where they differ the shim will be + wrong in ways that show up as rare races rather than as failures to build. + This is the deepest technical risk in the whole plan. +- **It will not support "Linux drivers".** It will support *one driver, on one + kernel version, that has been tried*. Anything beyond that is a claim, and + the list of what has actually been run belongs in the architecture document + when there is one.