Alpine's libinput, libseat, seatd, xf86-input-libinput and usbutils join the image, and seatd runs from schinit. libinput lists the keyboard and pointer as an ordinary user, Xorg now drives both through libinput, and a program linked to libseat running as schos opens seat0 through seatd, is handed /dev/input/event0 and reads its name through the descriptor. Two kernel bugs stood in the way, and both would have stopped a Wayland compositor too. The socket connect() queues for a listener was allocated with its domain at zero, so sendmsg refused SCM_RIGHTS on the accepted end: a server could receive descriptors and never send one. And sendmsg treated a control buffer longer than its messages as an error, where Linux takes a following header too short to be one as the end; seatd passes a zeroed CMSG_SPACE array with one descriptor at the front. Found with /proc/strace and a temporary kinfo in sendmsg's EINVAL sites. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
125 lines
6.9 KiB
Markdown
125 lines
6.9 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.
|
|
|
|
## libinput and libseat
|
|
|
|
`libinput` (Alpine) reads the evdev nodes udev tagged and `xf86-input-libinput`
|
|
gives Xorg the same library; `libinput list-devices` as an ordinary user shows
|
|
`SchOS keyboard` and `SchOS pointer`. Xorg's `40-libinput.conf` now claims both,
|
|
and the log says `Using input driver 'libinput'`.
|
|
|
|
`seatd` (job `etc/schinit/seatd.job`, socket `/run/seatd.sock`, group `video`)
|
|
and `libseat` let a program that is not root take devices from the system. The
|
|
client asks seatd for `seat0`; seatd takes the VT (`VT_SETMODE`, `KDSETMODE`,
|
|
`KDSKBMODE`, all there already), opens the device as root and passes the
|
|
descriptor back over the socket with `SCM_RIGHTS`. Checked as `schos` against
|
|
`/dev/input/event0` and `event1` with a small program linked to libseat, reading
|
|
the device's name through the descriptor it was handed.
|
|
|
|
Getting there found two kernel bugs, both in descriptor passing:
|
|
|
|
- **A socket returned by `accept()` was not an `AF_UNIX` socket as far as
|
|
`sendmsg` was concerned.** The end `connect()` queues for the listener was
|
|
allocated with its domain left at zero, and `sendmsg` refuses `SCM_RIGHTS` on
|
|
anything else. So a *server* could receive descriptors and never send one:
|
|
seatd, and equally a Wayland compositor handing a client a buffer.
|
|
- **A control buffer longer than its messages was an error.** seatd passes a
|
|
zeroed array with one descriptor at the front; Linux treats a following header
|
|
too short to be one as the end. `sendmsg` now does too.
|
|
|
|
## 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`.
|
|
- A compositor. libinput and libseat are in place, DRM is not; 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`.
|