keventd: add class/<subsystem>/<name> and driver/<name> conditions

dev/<X> only fires when a device gets a /dev node, which leaves a lot of
embedded-relevant hardware uncoverable: DSA switch cores, IIO sensors,
LEDs, backlight, PHYs, regulators -- all live purely under sysfs.

Two new condition namespaces:

  class/<subsystem>/<sysname>  asserted on every sysfs class device add
                                (e.g. <class/leds/blue>)
  driver/<name>                 asserted while the driver is bound to at
                                least one device (e.g. <driver/mt7530>)

A driver can bind to several devices, so driver/ conditions are
refcounted: asserted on first bind, cleared when the last device is
unbound.

dev_cond() is generalized into a static cond_emit(prefix, rel, set) so
class_cond() and driver_cond() share the same mkpath + symlink/erase
code.  The name avoids colliding with src/cond.h's public cond_path()
helper (unrelated function that returns a condition's filesystem path).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-16 22:03:34 +02:00
1 parent e4d9193f06
commit f3461ce2ba
6 files changed
+174 -13

No files matched your search

+12 -1
View File
@@ -334,11 +334,17 @@ static void handle_uevent(char *buf, size_t len)
/* Create symlinks */
symlink_add(&ev);
/* class/<subsystem>/<sysname> -- covers sysfs-only
* devices that don't get a /dev node. */
class_cond(&ev, 1);
}
break;
case ACT_REMOVE:
if (!passive) {
class_cond(&ev, 0);
/* Remove symlinks first */
symlink_del(&ev);
@@ -356,8 +362,13 @@ static void handle_uevent(char *buf, size_t len)
break;
case ACT_BIND:
if (!passive)
driver_cond(&ev, 1);
break;
case ACT_UNBIND:
/* Driver bind/unbind - could trigger conditions */
if (!passive)
driver_cond(&ev, 0);
break;
default:
+3
View File
@@ -149,6 +149,9 @@ int devnode_add (struct uevent *ev);
int devnode_del (struct uevent *ev);
int netdev_add (struct uevent *ev);
void class_cond (const struct uevent *ev, int set);
void driver_cond (const struct uevent *ev, int set);
int symlink_add (struct uevent *ev);
int symlink_del (struct uevent *ev);
void symlink_write_db (const char *devpath, FILE *fp);
+95 -11
View File
@@ -58,37 +58,121 @@
void logit(int prio, const char *fmt, ...);
/*
* Set/clear a device condition by creating/removing a symlink.
* Set/clear a Finit condition by creating/removing a symlink.
* keventd is a standalone daemon, so we manipulate the filesystem directly
* rather than using Finit's internal cond_set()/cond_clear() API.
*
* prefix is the namespace path fragment (COND_DEV, COND_CLASS,
* COND_DRIVER, ...) ending in '/'. rel is the relative path under that
* namespace (e.g. "sda", "input/event0", "net/eth0", "mt7530").
*/
static void dev_cond(const char *devname, int set)
static void cond_emit(const char *prefix, const char *rel, int set)
{
char cond[PATH_MAX];
char *dir;
if (!devname)
if (!prefix || !rel || !*rel)
return;
snprintf(cond, sizeof(cond), "%s%s", _PATH_CONDDEV, devname);
snprintf(cond, sizeof(cond), "%s%s%s", _PATH_COND, prefix, rel);
/* Create parent directory if needed (e.g., dev/input/) */
/* mkpath is idempotent (EEXIST-tolerant) so just always call it. */
dir = strdupa(cond);
dir = dirname(dir);
if (strcmp(dir, _PATH_CONDDEV)) {
if (mkpath(dir, 0755) && errno != EEXIST)
logit(LOG_WARNING, "Failed creating condition dir %s", dir);
}
if (mkpath(dir, 0755) && errno != EEXIST)
logit(LOG_WARNING, "Failed creating condition dir %s", dir);
if (set) {
if (symlink(_PATH_RECONF, cond) && errno != EEXIST)
logit(LOG_WARNING, "Failed setting dev/%s condition", devname);
logit(LOG_WARNING, "Failed setting %s%s condition", prefix, rel);
} else {
if (erase(cond) && errno != ENOENT)
logit(LOG_WARNING, "Failed clearing dev/%s condition", devname);
logit(LOG_WARNING, "Failed clearing %s%s condition", prefix, rel);
}
}
static void dev_cond(const char *devname, int set)
{
cond_emit(COND_DEV, devname, set);
}
/*
* class/<subsystem>/<sysname> -- fires for any sysfs class device add,
* regardless of whether the device gets a /dev node. Lets services
* depend on sysfs-only hardware (DSA switch ports, LEDs, IIO sensors,
* PHYs, etc.).
*/
void class_cond(const struct uevent *ev, int set)
{
const char *sysname;
char rel[256];
if (!ev->subsystem || !ev->devpath)
return;
sysname = strrchr(ev->devpath, '/');
sysname = sysname ? sysname + 1 : ev->devpath;
if (!*sysname)
return;
snprintf(rel, sizeof(rel), "%s/%s", ev->subsystem, sysname);
cond_emit(COND_CLASS, rel, set);
}
/*
* driver/<name> -- asserted while the driver is bound to at least one
* device, from the kernel's bind/unbind uevents. Lets services depend
* on a specific driver having probed its hardware (e.g. <driver/mt7530>)
* even when no class device or /dev node marks the moment.
*
* A driver can bind to several devices, so the condition is refcounted:
* asserted on the first bind, cleared when the last device is unbound.
*/
struct drv_bind {
TAILQ_ENTRY(drv_bind) link;
char *name;
size_t count;
};
static TAILQ_HEAD(, drv_bind) drv_binds = TAILQ_HEAD_INITIALIZER(drv_binds);
void driver_cond(const struct uevent *ev, int set)
{
struct drv_bind *db;
if (!ev->driver)
return;
TAILQ_FOREACH(db, &drv_binds, link) {
if (!strcmp(db->name, ev->driver))
break;
}
if (set) {
if (!db) {
db = calloc(1, sizeof(*db));
if (!db)
goto emit;
db->name = strdup(ev->driver);
if (!db->name) {
free(db);
goto emit;
}
TAILQ_INSERT_TAIL(&drv_binds, db, link);
}
if (db->count++)
return;
} else if (db) {
if (--db->count)
return;
TAILQ_REMOVE(&drv_binds, db, link);
free(db->name);
free(db);
}
emit:
cond_emit(COND_DRIVER, ev->driver, set);
}
/* Symlink tracking for cleanup on device removal */
static TAILQ_HEAD(, dev_symlink) symlinks = TAILQ_HEAD_INITIALIZER(symlinks);