From 1334f446cc594c912028514850be9759b54e8a2c Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 08:59:55 +0100 Subject: [PATCH 1/2] config/drivers.conf: which drivers are compiled in y or n per optional driver (virtio-gpu, stdvga, e1000, rtl8139, native r8169, AC'97, AHCI, NVMe), all y except the native r8169, overridable with make DRV_X=n. Only drivers nothing else calls can be optional; the registry #ifdefs them and the Makefile leaves their objects out. Built with every one off (links, none of their symbols present), with r8169 on, and by default. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- Makefile | 36 ++++++++++++++++++----- config/drivers.conf | 45 ++++++++++++++++++++++++++++ docs/architecture/devices.md | 11 +++++++ docs/guides/adding-a-driver.md | 6 +++- docs/guides/building.md | 3 ++ kernel/drivers/registry.c | 54 +++++++++++++++++++++++++++++----- 6 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 config/drivers.conf diff --git a/Makefile b/Makefile index 69dc846..dadd556 100644 --- a/Makefile +++ b/Makefile @@ -38,17 +38,34 @@ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ # already eight files with four called usb_something, and each driver added made # the directory harder to read. The compile rule uses `mkdir -p $(dir $@)`, so # the depth costs nothing. +# Which of them are compiled in is config/drivers.conf: y or n per driver, y by +# default, overridable on the command line (make DRV_E1000=n). Only the drivers +# that nothing else calls can be left out; the rest are always linked. +-include config/drivers.conf +DRV_LIST = VGA_PCI VIRTIO_GPU E1000 RTL8139 R8169 AC97 AHCI NVME +DRV_DEFAULT_R8169 = n +$(foreach d,$(DRV_LIST),$(eval DRV_$(d) ?= $(if $(DRV_DEFAULT_$(d)),$(DRV_DEFAULT_$(d)),y))) +$(foreach d,$(DRV_LIST),$(if $(filter y n,$(DRV_$(d))),,$(error config/drivers.conf: DRV_$(d) must be y or n, not '$(DRV_$(d))'))) +DRV_DEFS = $(foreach d,$(DRV_LIST),$(if $(filter y,$(DRV_$(d))),-DCONFIG_DRV_$(d)=1)) +# Rewritten only when the choice changes, so registry.o is rebuilt then and not otherwise. +DRV_STAMP = $(BUILD)/kernel/drivers/.config +$(DRV_STAMP): FORCE + @mkdir -p $(dir $@) + @echo '$(DRV_DEFS)' | cmp -s - $@ || echo '$(DRV_DEFS)' > $@ +$(BUILD)/kernel/drivers/registry.o: $(DRV_STAMP) +$(BUILD)/kernel/drivers/registry.o: CFLAGS += $(DRV_DEFS) + KERN_DRIVERS = $(BUILD)/kernel/drivers/registry.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_fb.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/r8169/r8169.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/rtl8139/rtl8139.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/audio/ac97.o +KERN_DRIVERS += $(if $(filter y,$(DRV_VGA_PCI)),$(BUILD)/kernel/drivers/gpu/vga/vga_pci.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_VIRTIO_GPU)),$(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_E1000)),$(BUILD)/kernel/drivers/net/e1000/e1000.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_R8169)),$(BUILD)/kernel/drivers/net/r8169/r8169.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_RTL8139)),$(BUILD)/kernel/drivers/net/rtl8139/rtl8139.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_AC97)),$(BUILD)/kernel/drivers/audio/ac97.o) KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/storage/ahci/ahci.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/storage/nvme/nvme.o +KERN_DRIVERS += $(if $(filter y,$(DRV_AHCI)),$(BUILD)/kernel/drivers/storage/ahci/ahci.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_NVME)),$(BUILD)/kernel/drivers/storage/nvme/nvme.o) KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o @@ -1825,3 +1842,6 @@ distclean: clean # build output: Alpine's usr/lib/sysctl.d is a directory, and including it # stopped every make with "Is a directory". -include $(shell find $(BUILD) \( -path $(BUILD)/aroot -o -path $(BUILD)/iso -o -path $(BUILD)/ports \) -prune -o -type f -name '*.d' -print 2>/dev/null) + +FORCE: +.PHONY: FORCE diff --git a/config/drivers.conf b/config/drivers.conf new file mode 100644 index 0000000..4446135 --- /dev/null +++ b/config/drivers.conf @@ -0,0 +1,45 @@ +# Which drivers are compiled into the kernel. +# +# y compiled in, and enabled: it is offered every device it can drive +# n not compiled in: the code is not linked and the kernel never knows it +# +# Everything is y unless it says otherwise. Change a line, or override one for +# a single build: +# +# make DRV_E1000=n DRV_NVME=n +# +# A changed value rebuilds only kernel/drivers/registry.o and the link. +# +# These are the drivers that can be left out cleanly: nothing else in the kernel +# calls them, they are only offered a device by the registry +# (kernel/drivers/registry.c). The others cannot be, because the rest of the +# kernel calls into them directly, and are not listed: +# +# vga_fb the boot framebuffer: the console and the crash screen draw on it +# xhci, usb core, usb hid / storage / hub +# the keyboard and the root disk can arrive over USB, bio.c and +# the hotplug thread call in, and so does the I2C-HID touchpad +# PS/2, IDE, the 16550 UART +# fixed-address legacy devices (kernel/dev/) +# +# Linux's own drivers, run in a process by ldlsd, are not here: they are files in +# the image (make ldls-r8169), not code in the kernel. + +# --- display -------------------------------------------------------------- +DRV_VGA_PCI = y # the stdvga/Bochs card (the framebuffer driver stays regardless) +DRV_VIRTIO_GPU = y # QEMU's virtio-gpu: mode setting and an explicit flush + +# --- network ---------------------------------------------------------------- +DRV_E1000 = y # Intel 8254x and 8257x +DRV_RTL8139 = y # Realtek 8139 (the one Realtek part QEMU emulates) +# The native RTL8168/8111 driver. Off by default because the laptop's NIC is +# driven by Linux's r8169 under LDLS (etc/schinit/ldls-r8169.job), and a driver +# here would claim the device first. Turn it on for a machine without that job. +DRV_R8169 = n + +# --- storage ---------------------------------------------------------------- +DRV_AHCI = y # SATA +DRV_NVME = y # NVMe SSDs + +# --- audio ------------------------------------------------------------------ +DRV_AC97 = y # Intel AC'97 (QEMU's and old boards') diff --git a/docs/architecture/devices.md b/docs/architecture/devices.md index 43cb767..ae85ba7 100644 --- a/docs/architecture/devices.md +++ b/docs/architecture/devices.md @@ -9,6 +9,17 @@ Drivers that live in a process rather than in the kernel are described below --- +## Which drivers are compiled in + +`config/drivers.conf` lists the drivers that can be left out of a kernel, `y` (compiled in and offered every device it can +drive) or `n` (not linked at all), all `y` unless a line says otherwise — the one exception is the native `r8169`, `n` +because Linux's runs in its place under LDLS. Override one for a build with `make DRV_E1000=n DRV_NVME=n`. A changed +choice rebuilds only `registry.o` and the link. The list is short on purpose: a driver is optional only if nothing else in +the kernel calls it (display: `vga_pci`, `virtio_gpu`; network: `e1000`, `rtl8139`, `r8169`; storage: `ahci`, `nvme`; +audio: `ac97`). `vga_fb`, the USB stack and the legacy PS/2, IDE and UART devices are always there, because the console, the +keyboard and the root disk can depend on them. A kernel with every optional driver off builds and links; whether such a +kernel is useful on any particular machine is the person who turned them off to say. + ## What stays in the kernel, and what may leave "Hybrid" is used to mean everything from "a monolith with a nice device model" to diff --git a/docs/guides/adding-a-driver.md b/docs/guides/adding-a-driver.md index 1feac7b..8dd8c01 100644 --- a/docs/guides/adding-a-driver.md +++ b/docs/guides/adding-a-driver.md @@ -61,6 +61,10 @@ ours. 2. Declare it in `kernel/drivers/registry.c` and add it to the array. 3. Add the object to `KERN_DRIVERS` in the `Makefile`. +3½. If nothing else in the kernel calls it, make it optional: a `DRV_` line in + `config/drivers.conf`, the name in `DRV_LIST` in the Makefile, the object behind + `$(filter y,$(DRV_))`, and an `#ifdef CONFIG_DRV_` around its line in `registry.c`. + A driver other code calls (`vga_fb`, the USB stack) cannot be optional. 4. Fill in the **class vtable** for what it is, and point `.ops` at it. That is the whole of what the rest of the kernel sees: @@ -113,7 +117,7 @@ That is data; the logic stays ours. - [ ] One directory under `kernel/drivers///` - [ ] Exports a `struct pci_driver` or `struct usb_driver` - [ ] Declared in `kernel/drivers/registry.c` **and** added to its array -- [ ] Object added to `KERN_DRIVERS` in the `Makefile` +- [ ] Object added to `KERN_DRIVERS` in the `Makefile`, and an option in `config/drivers.conf` if it can be left out - [ ] Class vtable filled in and `.ops` pointed at it - [ ] `.attached` implemented if it can drive more than one card - [ ] `devclass` set, if it is a USB driver diff --git a/docs/guides/building.md b/docs/guides/building.md index e21c6b3..91ce833 100644 --- a/docs/guides/building.md +++ b/docs/guides/building.md @@ -17,6 +17,9 @@ hundred megabytes, kept in `dist/`) and compiles Xorg's dependencies not at all: packages are Alpine's binaries, pinned in `ports/alpine/world.lock`. See [packages](../architecture/packages.md). +Which drivers go into the kernel is `config/drivers.conf` (all of the optional ones by default); +`make DRV_E1000=n` leaves one out for a build. See [devices](../architecture/devices.md). + ## Prerequisites | Need | Why | Windows | Linux | macOS | BSD | diff --git a/kernel/drivers/registry.c b/kernel/drivers/registry.c index 15d9be5..e9616ba 100644 --- a/kernel/drivers/registry.c +++ b/kernel/drivers/registry.c @@ -12,9 +12,10 @@ // a driver silently dropped by the linker looks exactly like one that was // never written. At this size, being able to read the list wins. // -// To add a driver: write kernel/drivers/.c exporting a struct -// pci_driver, declare it here, and add it to the array and to the Makefile's -// KERN_DRIVERS. +// To add a driver: write kernel/drivers///.c exporting a +// struct pci_driver, declare it here, add it to the array and to the Makefile's +// KERN_DRIVERS -- and, if nothing else in the kernel calls it, give it a +// DRV_ option in config/drivers.conf so it can be left out. #include "core/types.h" #include "core/defs.h" @@ -28,14 +29,34 @@ #include "dev/usb.h" #include "dev/device.h" +// Which of these exist in this kernel is config/drivers.conf: the Makefile +// leaves a disabled driver's object out and defines CONFIG_DRV_ for the +// ones it keeps. The ones with no option (xHCI) are always here. +#ifdef CONFIG_DRV_VGA_PCI extern const struct pci_driver vga_pci_driver; +#endif extern const struct pci_driver xhci_driver; +#ifdef CONFIG_DRV_VIRTIO_GPU extern const struct pci_driver virtio_gpu_driver; +#endif +#ifdef CONFIG_DRV_E1000 extern const struct pci_driver e1000_driver; +#endif +#ifdef CONFIG_DRV_AC97 extern const struct pci_driver ac97_driver; +#endif +#ifdef CONFIG_DRV_RTL8139 extern const struct pci_driver rtl8139_driver; +#endif +#ifdef CONFIG_DRV_R8169 +extern const struct pci_driver r8169_driver; +#endif +#ifdef CONFIG_DRV_AHCI extern const struct pci_driver ahci_driver; +#endif +#ifdef CONFIG_DRV_NVME extern const struct pci_driver nvme_driver; +#endif // Order matters here in one respect: virtio-gpu is offered the bus before the // VBE framebuffer driver, so that on a machine with both, the device with real @@ -43,23 +64,42 @@ extern const struct pci_driver nvme_driver; // claims the stdvga card underneath, which is what the boot splash and the // text console are drawn on. static const struct pci_driver *drivers[] = { +#ifdef CONFIG_DRV_VIRTIO_GPU &virtio_gpu_driver, +#endif +#ifdef CONFIG_DRV_VGA_PCI &vga_pci_driver, +#endif +#ifdef CONFIG_DRV_E1000 &e1000_driver, - // &r8169_driver -- the RTL8168 is driven by Linux's own r8169 through - // LDLS (etc/schinit/ldls-r8169.job), which a kernel driver here would - // claim first. The native driver stays in the tree as the fallback. +#endif + // The RTL8168 is driven by Linux's own r8169 through LDLS + // (etc/schinit/ldls-r8169.job), which a kernel driver here would claim + // first -- so DRV_R8169 is off by default. The native driver stays in the + // tree as the fallback for a machine without that job. +#ifdef CONFIG_DRV_R8169 + &r8169_driver, +#endif +#ifdef CONFIG_DRV_RTL8139 &rtl8139_driver, +#endif &xhci_driver, +#ifdef CONFIG_DRV_AHCI &ahci_driver, +#endif +#ifdef CONFIG_DRV_NVME &nvme_driver, +#endif +#ifdef CONFIG_DRV_AC97 &ac97_driver, +#endif + 0, // keeps the array non-empty when everything optional is off }; void pci_bind_all(void) { - for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) + for (unsigned i = 0; drivers[i]; i++) pci_bind(drivers[i]); } From 6e78bb0d5e1bbec183ac05c9b3b5df467597b44f Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 09:20:22 +0100 Subject: [PATCH 2/2] X runs without -dumbSched; the ISO ships r8169 and the licences Tried in QEMU on a built ISO: startx brings up Window Maker, xclock and xeyes connect and disconnect, an xterm runs and exits, Xorg stays up -- the pthread_join crash is gone with threads. -dumbSched is removed from .xserverrc. make iso now stages Linux's r8169 set first when this machine has the modules (ldls-r8169-if-present), and sysroot-install puts LICENSE, LICENSE.MIT and NOTICE in /usr/share/licenses/schos. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- Makefile | 16 +++++++++++++++- docs/architecture/desktop.md | 11 ++++++----- tools/posix/mkaroot.sh | 14 +++++++------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index dadd556..9504c30 100644 --- a/Makefile +++ b/Makefile @@ -804,6 +804,7 @@ projects: # CFLAGS and make cannot see that. # ----------------------------------------------------------------------- iso: + @$(MAKE) --no-print-directory ldls-r8169-if-present $(MAKE) RAMROOT=1 schos.iso # One El Torito entry, pointing at a FAT image holding the loader and kernel. @@ -1427,6 +1428,15 @@ ldls-r8169: *) cp $$f $(SYSROOT)/lib/firmware/rtl_nic/;; esac; done @echo "staged Linux r8169 ($(shell uname -r)) into $(SYSROOT)" +# `make iso` runs this first: the ISO ships Linux's r8169 set (NOTICE), so stage it whenever +# this machine has the modules and the BTF to stage it from, and say so when it does not. +ldls-r8169-if-present: + @if ls $(LDLS_KDIR)/drivers/net/ethernet/realtek/r8169.ko* >/dev/null 2>&1 && [ -r /sys/kernel/btf/vmlinux ]; then \ + $(MAKE) --no-print-directory ldls-r8169; \ + else \ + echo "iso: no Linux r8169 modules or BTF on this machine ($(LDLS_KDIR)) -- this ISO will not carry them"; \ + fi + ldls-unstage: rm -rf $(SYSROOT)/lib/modules @echo "removed" @@ -1497,6 +1507,10 @@ etc-install: $(SCHINIT_JOBS) $(ETC_PLAIN) $(ETC_ZSH) $(LOGO_ASCII) $(SRV_FILES) @mkdir -p $(SYSROOT)/usr/share/hwdata @rm -f $(SYSROOT)/usr/share/usb.ids @for f in usb.ids pci.ids; do [ -f dist/$$f ] && cp -p dist/$$f $(SYSROOT)/usr/share/hwdata/; done; true + @# The licences travel with the image: SchOS's, xv6's, and NOTICE, which carries the + @# third-party ones -- including Linux's r8169 modules, whose source offer it records. + @mkdir -p $(SYSROOT)/usr/share/licenses/schos + @cp -p LICENSE LICENSE.MIT NOTICE $(SYSROOT)/usr/share/licenses/schos/ @echo "installed $(words $(ETC_PLAIN)) /etc file(s), $(words $(ETC_ZSH)) zsh file(s) and $(words $(SCHINIT_JOBS)) schinit manifest(s)" # The mount points. Created here rather than by init, because a mkdir on a @@ -1844,4 +1858,4 @@ distclean: clean -include $(shell find $(BUILD) \( -path $(BUILD)/aroot -o -path $(BUILD)/iso -o -path $(BUILD)/ports \) -prune -o -type f -name '*.d' -print 2>/dev/null) FORCE: -.PHONY: FORCE +.PHONY: FORCE ldls-r8169-if-present diff --git a/docs/architecture/desktop.md b/docs/architecture/desktop.md index df56968..8be437c 100644 --- a/docs/architecture/desktop.md +++ b/docs/architecture/desktop.md @@ -40,11 +40,12 @@ the real program is for. ## The four files - **`/root/.xserverrc`** starts `X` on the VT `startx` was started from (startx works that - out only when `uname -s` says Linux, and SchOS's says SchIFT) with **`-dumbSched`**. Xorg - normally reads input on a thread of its own; when SchOS had no threads the server crashed - the first time a client disconnected, in `pthread_join` on a thread that was never made, and - `-dumbSched` is Xorg's own switch for input on the main loop. Threads exist now; the switch - is still on, and nobody has run the server without it since. + out only when `uname -s` says Linux, and SchOS's says SchIFT). It used to add **`-dumbSched`**, + Xorg's switch for reading input on the main loop, because Xorg normally reads input on a + thread of its own and, when SchOS had no threads, the server crashed the first time a client + disconnected, in `pthread_join` on a thread that was never made. Threads exist now and the switch + is gone: under QEMU, X starts with Window Maker, `xclock` and `xeyes` connect and disconnect, an + `xterm` runs and exits, and the server stays up. - **`/root/.xinitrc`** is `exec wmaker`. Without it xinit falls back to twm and an xterm, which are not in the image. - **`/etc/X11/xorg.conf.d/10-schos.conf`** names the input devices. Alpine's Xorg asks udev for diff --git a/tools/posix/mkaroot.sh b/tools/posix/mkaroot.sh index c0eb688..dcfb269 100644 --- a/tools/posix/mkaroot.sh +++ b/tools/posix/mkaroot.sh @@ -97,12 +97,12 @@ mkdir -p "$P/etc/profile.d" grep -v '^export \(PATH\|HOME\|TERM\)=' "$P/etc/profile" > "$P/etc/profile.d/schos.sh" rm -f "$P/etc/profile" -# How startx starts the X server for root: Alpine's xserverrc, plus -# -dumbSched. Xorg reads input on a thread of its own; when SchOS had no threads -# the server crashed on its way out the first time a client disconnected, in -# pthread_join on a thread that was never made. -dumbSched is Xorg's own switch -# for input on the main loop. SchOS has threads now (clone, futex), so this may -# no longer be needed -- it has simply not been tried without it. The file is +# How startx starts the X server for root: Alpine's xserverrc. (It used to add +# -dumbSched, Xorg's switch for reading input on the main loop, because when SchOS +# had no threads the server crashed on its way out the first time a client +# disconnected, in pthread_join on a thread that was never made. SchOS has threads +# now; X was run without the switch under QEMU -- clients connecting and +# disconnecting, an xterm starting and exiting -- and it stays up.) The file is # root's own rather than /etc/X11/xinit/xserverrc, which belongs to the xinit # package. mkdir -p "$P/root" @@ -111,7 +111,7 @@ cat > "$P/root/.xserverrc" <<'XEOF' # The VT X runs on is the one startx was started from. startx works that out # only when `uname -s` says Linux, and SchOS's says SchIFT. vt=$(tty | sed -n 's|^/dev/tty\([0-9][0-9]*\)$|vt\1|p') -exec /usr/bin/X -nolisten tcp -dumbSched $vt "$@" +exec /usr/bin/X -nolisten tcp $vt "$@" XEOF # What startx runs inside it: Window Maker, which world installs. Without this