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
6.9 KiB
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_UNIXSOCK_DGRAMandSOCK_SEQPACKETkeep message boundaries. udevd's control socket isSEQPACKET, and its manager and workers talk over aDGRAMsocketpair. 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_UEVENTsockets 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, andudevadm triggeris 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 anAF_UNIXsocket as far assendmsgwas concerned. The endconnect()queues for the listener was allocated with its domain left at zero, andsendmsgrefusesSCM_RIGHTSon 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.
sendmsgnow does too.
What is not there
- USB interfaces (
1-3:1.0),/dev/bus/usbnodes, and PCI bridges as parents. A USB keyboard works because every keyboard isevent0; udev now knows the device arrived but not which interface became the keyboard. hwdb(/etc/udev/hwdb.bin):udevadm hwdb --updatehas not been run in the image, so60-input-idlogs "returned non-zero" for the hwdb import. The properties that matter come frominput_id.- Block device partitions,
by-id/by-uuidlinks, and a/sys/block. - A compositor. libinput and libseat are in place, DRM is not; see ../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.