# Drivers How hardware gets found, claimed and used, and where drivers that are not part of the build will attach. --- ## 1. The device model There is one list of everything attached, whatever bus found it: `include/device.h` and `kernel/dev/device.c`, readable at `/proc/devices`. ``` name bus class driver device kbd0 platform input ps2kbd PS/2 keyboard mouse0 platform input ps2mouse PS/2 mouse uart0 platform other uart8250 16550 serial port ide0 platform storage ide ATA disk controller vga0 pci display vga display controller ``` Before this there were two registries in `kernel/drivers/registry.c` — an array of PCI drivers and an array of USB drivers — with nothing that knew about both. That is sufficient for *binding* and useless for the three things wanted next. **Enumeration.** "What is in this machine, and what claimed it" had no answer covering a USB disk and a PCI display at once. A device manager is exactly that question. **Classes.** A compositor should ask for *the display* and not care whether that is the VBE framebuffer or virtio-gpu. Without a class every consumer hardcodes a driver, and adding the second driver means editing the consumers — which is how a framebuffer address ended up hardcoded in three places here once already. **A boundary for drivers that are not compiled in.** They have to attach somewhere that is not an array fixed at link time. Two vocabularies are kept deliberately separate: a device's **bus** is provenance (`platform`, `pci`, `usb`), its **class** is capability (`display`, `input`, `storage`, `net`, `bus`). A USB disk and a SATA disk are both `storage` despite sharing no numbering at all. Where a driver claims a device, the driver's opinion wins over the descriptor's: `struct usb_driver` has a `devclass` field separate from the USB interface class, because the descriptor says what the device *claims* to be and the driver knows what it *made* of it. It is a flat list with a bus tag, **not** a device tree. Nothing yet needs to walk a hierarchy, and a tree nobody traverses is just a harder list. Hot-unplug would need more; the `in_use` flag is the hook. Platform devices are registered in one place (`platform_register_devices()` in `registry.c`) rather than inside each driver's init, for the same reason the driver arrays are written out by hand: a list you can read beats a list assembled by side effects. --- ## 2. Where this is going: a hybrid model The intended shape is the NeXTSTEP/macOS one rather than Linux's — a device is an object with a class and an ops vtable, and consumers talk to the class. Two things often get bundled together as "loadable drivers" and they are separable: | | | |---|---| | **Loadable modules** | A stable driver ABI, drivers as files loaded at runtime (`.sys`, `.kext`). A linker/loader job on top of the model above. | | **Drivers out of kernel** | Drivers as user-space processes (macOS DriverKit, which is why kexts are being retired). An IPC and memory-mapping job; the `AF_UNIX` and shared-memory work it needs already exists. | The second is more in the spirit of the SchAPI direction and is slower and larger. Neither is built yet. What exists is the attachment point they both need. --- ## 3. Linux drivers **Decided 2026-08-08.** Linux drivers are not shipped with SchOS and never will be. The user may install one on their own machine, through a device manager or equivalent, and SchOS will load it. The licensing reasoning matters and is worth stating exactly, because it is easy to get wrong in both directions: - **Distribution is what triggers the GPL.** Shipping a GPL-2.0-only driver in an Apache-2.0 image is what cannot be done — see [licensing.md](licensing.md) §2. A user obtaining a driver themselves and loading it on their own machine is their act, not ours, and is not a distribution by us. - **Implementing a loader is not copying.** Writing support for a module format and a driver ABI from published documentation produces our own work. It is a clean-room implementation, and it stays one only if it is written from documentation rather than transcribed from Linux source. Reading Linux to understand *hardware* is fine; translating its code is not, whatever the identifiers say. So the rule for this subsystem is the same as everywhere else in the tree: the loader is ours and Apache-2.0, what it loads is the user's business, and nothing GPL ever enters an image we build. `RAMDISK_EXCLUDE` and `NOTPORTS` are the existing mechanisms for keeping that true mechanically rather than by intention. --- ## 4. Adding a driver today 1. Write `kernel/drivers///.c` exporting a `struct pci_driver` or `struct usb_driver`. One directory per driver, so a driver that grows a register header, a second source file or a firmware blob has somewhere to put it: kernel/drivers/ registry.c gpu/vga/ gpu/virtio_gpu/ net/e1000/ bus/xhci/ usb/core/ usb/hid/ usb/storage/ usb/hub/ 2. Declare it in `kernel/drivers/registry.c` and add it to the array. 3. Add the object to `KERN_DRIVERS` in the `Makefile`. Registration in the device model is automatic — `pci_bind()` and `usb_bind_all()` publish whatever they claim, so a driver cannot forget to. Set `devclass` on a USB driver, or the device lands in `other`. The best source for a new driver is the **specification**, not another kernel: xHCI, AHCI, virtio, NVMe and the Intel datasheets are public documents with no licence on what you write from them. `kernel/drivers/bus/xhci/xhci.c` came from the spec, and so did `net/e1000/` and `gpu/virtio_gpu/`. Where a *table* of constants is genuinely faster to transcribe than to retype from a PDF -- device IDs, register offsets -- taking it from a BSD tree is fine after the licence check, and goes in `NOTICE`. That is data; the logic stays ours.