Files
finit/doc/keventd.md
T
Joachim Wiberg 67d61a8282 keventd: add udev rules engine
Transform keventd from a power-supply monitor + basic hotplug handler into
a full udev-compatible device manager.

Rules engine (rules.c):
- Full .rules file parser covering all udev key types: ACTION, KERNEL,
  SUBSYSTEM, DEVPATH, ENV, ATTR, SYSCTL, TAG, RESULT, PROGRAM, TEST,
  parent-chain KERNELS/SUBSYSTEMS/ATTRS/DRIVERS, and more
- Pattern matching: plain string, fnmatch glob, and pipe-separated alternatives
- Operators: ==, !=, =, +=, -=, :=
- Assignments: NAME=, MODE=, OWNER=, GROUP=, SYMLINK+=, ENV{k}=, TAG+=, RUN+=
- IMPORT{program|file|builtin|parent|cmdline|db}=
- PROGRAM= with stdout capture for subsequent RESULT== matching
- GOTO=/LABEL= flow control
- Loads *.rules from /lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d
  and an optional extra directory (-r DIR); reloads on SIGHUP

Builtin framework (builtin.c):
- kmod:     load module by MODALIAS or explicit alias
- hwdb:     match device against *.hwdb text files in udev hwdb dirs; builds
	    correct lookup key per subsystem — evdev:input:b*v*p*e* for input,
	    usb:v*p* for USB, raw modalias for PCI/platform
- path_id:  build stable ID_PATH / ID_PATH_TAG from sysfs topology (PCI, USB,
	    ATA, NVMe, platform, ACPI, virtio)
- usb_id:   read idVendor/idProduct/bcdDevice/serial from sysfs; look up
	    ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE from usb.ids
	    (hwdata package) when available; silent fallback when absent
- input_id: classify input devices (keyboard, mouse, joystick, touchscreen,
	    touchpad) from evdev capability bitmasks in sysfs
- net_id:   generate predictable names — MAC-based enx<mac> and PCI-slot-based
	    enp<bus>s<dev>[f<func>]
- blkid:    probe filesystem type, UUID, and label via libblkid; sets ID_FS_*
	    and ID_PART_TABLE_* properties

Network interface renaming (uevent.c):
- netdev_add() renames interfaces via SIOCSIFNAME when a NAME= rule matched,
  then sets the Finit dev/ condition on the final name; and any setup using
  persistent interface naming via udev rules

Device node and symlink improvements (uevent.c):
- NAME=, MODE=, OWNER=, GROUP= overrides from matched rules applied at
  mknod/chown time, falling back to the built-in permission table
- SYMLINK+= links from rules applied alongside built-in by-id/by-path links

Device property database (udevdb.c):
- Persist per-device E:/S:/I: records to /run/udev/data/ on ADD/CHANGE,
  delete on REMOVE; IMPORT{db}= restores saved properties into event env

Build:
- libblkid (util-linux) is now required for keventd

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:31 +02:00

10 KiB

Bundled Device Manager

The kernel event daemon keventd is a built-in device manager bundled with Finit. It replaces the need for external device managers like mdev, mdevd, or udevd on systems where a lighter-weight solution is preferred, particularly on embedded systems.

It is enabled by default since Finit v5. To disable it and use an external device manager instead:

./configure --without-keventd

Features

When started, keventd listens on a NETLINK_KOBJECT_UEVENT socket for kernel events and handles:

  • Device node creation: creates and removes /dev nodes with correct permissions on device add/remove events
  • udev rules: matches events against rules read from /lib/udev/rules.d/, /run/udev/rules.d/, and /etc/udev/rules.d/, with a curated ruleset derived from eudev installed by default
  • Persistent symlinks: creates by-id and by-path symlinks under /dev/disk/, /dev/input/, and elsewhere, for stable device naming
  • Firmware loading: responds to kernel firmware requests by searching /lib/firmware/ and writing firmware data to sysfs
  • Module loading: parses MODALIAS from uevents and spawns modprobe to load the appropriate kernel module
  • Coldplug: with the -c flag, walks /sys/devices and triggers add events for all devices present at boot
  • Power supply monitoring: tracks AC power status and provides the sys/pwr/ac condition
  • Device conditions: sets dev/* conditions in the Finit condition system when device nodes appear or disappear
  • Netlink rebroadcast: rebroadcasts processed uevents to netlink group 0x4 for libudev-zero consumers (enabled by default)

Device Nodes

On receiving an add event with MAJOR, MINOR, and DEVNAME fields, keventd creates the corresponding device node in /dev using mknod(). Parent directories are created automatically (e.g., /dev/input/ for /dev/input/event0).

On remove events, the device node and its associated symlinks and conditions are cleaned up.

Default Permissions

keventd applies permissions based on built-in rules that match on device subsystem and name:

Subsystem Pattern Mode Owner:Group
block sd*, vd*, nvme*, mmcblk*, loop*, dm-, md 0660 root:disk
tty tty[0-9]* 0620 root:tty
tty ttyS*, ttyUSB*, ttyACM* 0660 root:dialout
input event*, mouse*, mice 0660 root:root
sound * 0660 root:audio
video4linux * 0660 root:video
drm card*, render* 0660 root:video
(any) null, zero, full, random, urandom 0666 root:root
(any) console 0600 root:root
(default) 0660 root:root

Symlinks come from two places. A handful are built into keventd and are created for every device in the subsystem, whatever rules are loaded.

For block devices, under /dev/disk/:

  • by-id: based on the device serial number and model, read from sysfs attributes (/sys/.../device/vendor, model, serial)
  • by-path: based on the device topology path

For input devices, under /dev/input/:

  • by-id: based on the device name from sysfs
  • by-path: based on the physical device path

The rest come from SYMLINK+= in the udev rules, so what you get depends on the ruleset installed.

Built-in and rule-provided symlinks alike are tracked internally and removed when the corresponding device is unplugged.

Firmware Loading

When a kernel driver requests firmware (via request_firmware()), the kernel sends a uevent with a FIRMWARE= field. keventd handles this by:

  1. Searching for the firmware file in order:
    • /lib/firmware/updates/<kernel-version>/<name>
    • /lib/firmware/updates/<name>
    • /lib/firmware/<kernel-version>/<name>
    • /lib/firmware/<name>
  2. Writing 1 to /sys/<devpath>/loading to signal start
  3. Copying the firmware data to /sys/<devpath>/data
  4. Writing 0 to /sys/<devpath>/loading on success (or -1 on failure)

This is particularly important early in boot when drivers for graphics cards, network adapters, and other hardware need firmware before they can operate.

Module Loading

When a device add event includes a MODALIAS field, keventd spawns modprobe -bq <modalias> to load the matching kernel module. Module loading is done asynchronously (keventd does not wait for modprobe to complete) to avoid blocking other event processing.

Coldplug

To handle devices that were present before keventd started, it supports a coldplug mode activated with the -c flag. This walks the entire /sys/devices tree and writes add to each uevent file, causing the kernel to re-emit add events for all existing devices.

This replaces the separate coldplug script previously used with mdev.

The Linux kernel sends uevents to netlink multicast group 1 (bit 0) of NETLINK_KOBJECT_UEVENT. Only the device manager listens on this raw kernel group. Userspace consumers — applications using libudev — expect to receive processed events on a separate netlink group.

systemd/udevd established the convention of rebroadcasting processed events to a separate group, and libudev-zero, a daemonless drop-in replacement for libudev, listens on group 0x4 for these events. Without a device manager rebroadcasting, graphical applications, Wayland/X11 compositors, libinput, and anything else using libudev to monitor device hotplug will never see any events.

keventd rebroadcasts by default to netlink group 4 (0x4). A second netlink socket is created at startup, and after each uevent has been fully processed (device nodes created, symlinks set up, modules loaded), the original raw event is sent to the configured group(s). Rebroadcasting after processing ensures that device nodes and symlinks already exist by the time consumers receive the notification.

Use -g GROUP to override the default group mask, or -G to disable rebroadcast entirely. Bit 0 of the group mask is always forced off to prevent a feedback loop with the kernel's own multicast group.

Background

The netlink uevent architecture uses separate multicast groups to isolate the kernel-to-device-manager channel from the device-manager-to- application channel:

Group Bit Purpose
1 0 Kernel events (device manager listens here)
4 2 Processed events (libudev consumers listen)

This two-group design was established by systemd/udevd and is the de facto standard. mdevd implements the same mechanism via its -O flag, and keventd follows the same convention.

For more details, see:

  • libudev-zero — daemonless replacement for libudev
  • mdevd — mdev-compatible device manager with rebroadcast support

Conditions

keventd provides conditions in two namespaces:

Device Conditions (dev/)

When a device node is created, keventd asserts a corresponding condition in /run/finit/cond/dev/. This allows services to wait for specific devices:

service mdadm {
    description = "RAID monitor"
    runlevel    = "2345"
    conditions  = { "dev/sda" }
    command     = "/usr/sbin/mdadm --monitor /dev/md0"
}

service gps-daemon {
    description = "GPS daemon"
    runlevel    = "2345"
    conditions  = { "dev/ttyUSB0" }
    command     = "/usr/sbin/gps-daemon"
}

When the device is removed, the condition is cleared and Finit stops the dependent services.

Power Supply Conditions (sys/pwr/)

keventd monitors the power_supply subsystem and provides:

  • sys/pwr/ac — asserted when AC power is connected

This is useful for preventing power-hungry services from running on battery:

service cron {
    description = "Cron daemon"
    runlevel    = "2345"
    conditions  = { "sys/pwr/ac", "pid/syslogd" }
    command     = "cron -f"
}

Usage

keventd [-cdGhnpv] [-g GROUP] [-r DIR]

Options:
  -c        Run coldplug at startup
  -d        Enable debug mode (foreground, verbose)
  -g GROUP  Override netlink rebroadcast group (default: 4)
  -G        Disable netlink rebroadcast entirely
  -h        Show help text
  -n        Run in foreground (no daemon)
  -p        Passive mode: power supply events only
  -r DIR    Extra rules directory
  -v        Show version

In normal operation, Finit starts keventd automatically via its system configuration. The -d flag is useful for debugging device issues -- it runs keventd in the foreground and logs all received uevents.

Debug logging can also be toggled at runtime by sending SIGUSR1:

kill -USR1 $(pidof keventd)

Integration with Finit

keventd is a standalone daemon started by Finit as an internal service. It communicates with Finit exclusively through the filesystem-based condition system — creating and removing symlinks in /run/finit/cond/.

This means keventd can also be tested independently:

# Run in debug mode to see all kernel events
keventd -d

# Run with coldplug to populate /dev from scratch
keventd -c -n

# Run without rebroadcast (e.g., headless embedded system)
keventd -c -G

Only one device manager should be active at a time. This is settled at build time: with the hotplug plugin enabled, Finit starts keventd in passive mode (-p), leaving device management to the udevd, mdevd, or mdev service from system/10-hotplug.conf and monitoring only power supply events. Without the hotplug plugin, keventd runs as the system device manager (keventd -c).