SchOS/docs/architecture/udev.md
Nexoniarz 8d836c66b2 PCI and USB devices in the /sys/devices tree, with uevents and USB hot-plug
PCI devices live at /sys/devices/pci0000:00/<addr> and USB devices under the
xHCI controller that carries their bus, below that bus's root hub usbN;
/sys/bus/pci/devices and /sys/bus/usb/devices are symlinks into it, and every
device has its subsystem and driver links, which is how udev learns SUBSYSTEM
and DRIVER. Writing to a device's uevent now announces it with MODALIAS,
PCI_ID, PRODUCT and the rest, so udevadm trigger covers them, and the USB core
sends add when a device is attached and remove as it goes. The root hub is
announced when the controller comes up, before anything is plugged in.

Checked under QEMU: lspci and udevadm info through the links, udevadm monitor
showing the kernel and udev add event for device_add usb-kbd/usb-mouse and
the remove event for device_del, and a triggered add for usb1 and 1-5.

Known limits: the bus-to-controller mapping is a convention (bus N is the Nth
xHCI in PCI order); PCI bridges are not parents; USB interfaces and
/dev/bus/usb nodes are absent; busybox's lsusb reads /dev/bus/usb and prints
nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 15:51:28 +01:00

99 lines
5.4 KiB
Markdown

# udev
Alpine's `eudev` runs on SchOS as an ordinary process, started by `schinit`
(`etc/schinit/udevd.job`, with `udev-trigger.job` replaying the devices that
were there first). It is what lets Xorg *find* its keyboard and mouse instead of
being told, and the road to a Wayland compositor starts here: libinput and
libseat both ask udev what exists.
The kernel's half is three pieces, all small, none of them specific to udev.
## The device table: `kernel/dev/kdev.c`
The devices the kernel implements itself — input event nodes, the memory
devices, terminals, the framebuffer, the disks — are listed once, with their
class, name, `/dev` name and parent. Everything below is generated from it.
The device numbers are the ones `stat()` reports for the node, not SchOS's
internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in
`kernel/sys/sys_fs.c`), and udev compares what it reads in `/sys` with what
`stat()` says about the node. `dev_stat_numbers()` is the one mapping both use.
## `/sys`: `kernel/fs/sysfs.c`
```
/sys/devices/pci0000:00/0000:00:1f.2/ vendor device class ... uevent subsystem -> ../../../bus/pci driver
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3 idVendor idProduct ... uevent subsystem -> ../../../../../bus/usb
/sys/bus/pci/devices/0000:00:1f.2 -> ../../../devices/pci0000:00/0000:00:1f.2
/sys/bus/usb/devices/1-3 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-3
/sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties
/sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input
/sys/class/input/event0 -> ../../devices/virtual/input/input0/event0
/sys/dev/char/13:64 ... -> ../../devices/virtual/...
```
Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is
readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and
writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the
kernel announce the device again. PCI and USB devices announce the same way:
`MODALIAS`, `PCI_ID`, `PCI_CLASS`, `PCI_SLOT_NAME` for a PCI device, `PRODUCT`,
`TYPE`, `BUSNUM`, `DEVNUM` for a USB one.
**PCI devices** are flat under `pci0000:00` however the bridges are wired: the
kernel keeps no bridge tree, so a device behind a bridge is not nested inside
it. **USB devices** sit under the xHCI controller that carries their bus, below
that bus's root hub `usbN`; a hub's children are its siblings, not its
children. The bus-to-controller mapping is a convention (bus N is the Nth xHCI
in PCI order), since the USB core numbers a bus without recording which
controller it came from.
**Hot-plug is real**: the USB core announces `add` when a device is attached
and `remove` as it detaches, and `usbN` is announced when its controller comes
up. With `udevadm monitor`, plugging in a mouse prints the kernel event and
then the same event after udev's rules have run. PCI has no hot-plug here.
The input capability files are what `input_id` reads to decide
`ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever
the machine — every keyboard on `event0`, every mouse on `event1`
(`kernel/dev/input.c`) — so each has a fixed, plausible capability set.
## `/dev`: `kernel/fs/devfs.c`
devtmpfs is no longer read-only: udev creates `/dev/char/MAJ:MIN`, `/dev/fd`,
`/dev/input/by-*` and the like. A small in-memory overlay (192 entries) holds
directories and symlinks, listed after the kernel's own nodes in whichever
directory they are in. Device nodes themselves are still the kernel's: `mknod`
and regular files in `/dev` are refused.
## The sockets: `kernel/net/unix.c`
- **`AF_UNIX` `SOCK_DGRAM` and `SOCK_SEQPACKET`** keep message boundaries.
udevd's control socket is `SEQPACKET`, and its manager and workers talk over
a `DGRAM` socketpair. A message carries its sender's credentials with it
(several workers share one end), a zero-length datagram is a datagram, and a
datagram socket never reads end-of-stream.
- **`AF_NETLINK` / `NETLINK_KOBJECT_UEVENT`** sockets bind to multicast groups:
group 1 is the kernel's events, group 2 is what udevd re-announces once its
rules have run. A datagram can also be addressed to a port id — udevd hands
an event to an idle worker that way, and the worker believes it only from the
manager's port. The kernel's messages arrive as credentials with uid 0, pid 0.
A queue that is full drops the message, as Linux does, and `udevadm trigger`
is how a reader recovers.
## What is not there
- USB interfaces (`1-3:1.0`), `/dev/bus/usb` nodes, and PCI bridges as parents.
A USB keyboard works because every keyboard is `event0`; udev now knows the
device arrived but not which interface became the keyboard.
- `hwdb` (`/etc/udev/hwdb.bin`): `udevadm hwdb --update` has not been run in the
image, so `60-input-id` logs "returned non-zero" for the hwdb import. The
properties that matter come from `input_id`.
- Block device partitions, `by-id` / `by-uuid` links, and a `/sys/block`.
- `libinput` and `libseat` (next; see [../plan/roadmap.md](../plan/roadmap.md)).
## Testing
Boot the ISO and run `udevd --debug &`, then `udevadm trigger; udevadm settle`.
`/run/udev/data/c7:0` should contain
`ID_INPUT_KEYBOARD=1`, and `udevadm info /dev/input/event0` should print the
device. Under X, `/var/log/Xorg.0.log` says `config/udev: Adding input device`.