the number of different uevent
actions that can be emitted by the kernel
these days is limited, and seems to be fixed.
from include/linux/kobject.h
:
40 /*
41 * The actions here must match the index to the string array
42 * in lib/kobject_uevent.c
43 *
44 * Do not add new actions here without checking with the driver-core
45 * maintainers. Action strings are not meant to express subsystem
46 * or device specific properties. In most cases you want to send a
47 * kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
48 * specific variables added to the event environment.
49 */
50 enum kobject_action {
51 KOBJ_ADD,
52 KOBJ_REMOVE,
53 KOBJ_CHANGE,
54 KOBJ_MOVE,
55 KOBJ_ONLINE,
56 KOBJ_OFFLINE,
57 KOBJ_MAX
58 };
devs are effectively “gently nudged” towards using KOBJ_ADD+KOBJ_ONLINE
and KOBJ_OFFLINE+KOBJ_REMOVE
on start and stop, respectively, of
whatever they’re representing by their kobject
-s, with KOBJ_CHANGE
acting
as the wildcard/void*
of uevents
. whatever else you want to tell
userspace - you’re pretty much stuck with KOBJ_CHANGE
.
using just one action for all the interesting events might seem a little limiting at first. but there are two ways in which you can “enrich” the “context” of the events you’re emitting:
make sure that by the time you’re emitting the event you set some
kobject
attr of interest to the new value, and letudev
rule match the subtype of theuevent
based on the value of that attr:SYSFS{attribute}=="some_value"
this is simpler implementation-wise (less code to write in kernel, trivial to set up in a
udev
rule), but has the disadvantage of being slightly slower and not atomic. i.e. if thekobject
attr might be changing reasonably fast, by the timeudev
will get to match the contents of the correspondingsysfs
file to theudev
rule, the attr might have changed to, e.g., whatever your nextuevent
wanted to communicate to userspace. on the upside, it’s easier to debug -sysfs
files are there for userspace scripts to peek at, whereasuevent
env is ephemeral, if yourudev
rule was wrong - you just missed theuevent
.when emitting
uevent
-s, you can add your ownKEY=val
to the env. while the performance aspect of choosing this is usually frowned upon, the atomicity argument in favor of this approach is perfectly valid and rather important. the tradeoff is pretty much the opposite of the above - slightly more to write on the kernel side, atomicity, and the env is ephemeral. the latter obstacle can be partially overcome at dev time by makingudevadm
spill the beans on incominguevent
-s to stdout:udevadm monitor --kernel --property
you can also add
--subsystem-match=
and--tag-match=
to narrow down what you want to see.
since the former approach is rather self explanatory - just maintain
kobject
attrs neatly prior to emitting uevent
-s, the rest of the text
deals with the latter approach: directly enriching uevent
env.
a simple example comes dock/undock notifications, which just emit the
events with the specified env in one go. instead of the plain vanilla
kobject_uevent()
, dock is using the underlying kobject_uevent_env()
.
from lib/kobject_uevent.c
:
321 /**
322 * kobject_uevent - notify userspace by sending an uevent
323 *
324 * @action: action that is happening
325 * @kobj: struct kobject that the action is happening to
326 *
327 * Returns 0 if kobject_uevent() is completed with success or the
328 * corresponding error when it fails.
329 */
330 int kobject_uevent(struct kobject *kobj, enum kobject_action action)
331 {
332 return kobject_uevent_env(kobj, action, NULL);
333 }
334 EXPORT_SYMBOL_GPL(kobject_uevent);
120 /**
121 * kobject_uevent_env - send an uevent with environmental data
122 *
123 * @action: action that is happening
124 * @kobj: struct kobject that the action is happening to
125 * @envp_ext: pointer to environmental data
126 *
127 * Returns 0 if kobject_uevent_env() is completed with success or the
128 * corresponding error when it fails.
129 */
130 int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
131 char *envp_ext[])
from drivers/acpi/dock.c
:
392 static void dock_event(struct dock_station *ds, u32 event, int num)
393 {
394 struct device *dev = &ds->dock_device->dev;
395 char event_string[13];
396 char *envp[] = { event_string, NULL };
397 struct dock_dependent_device *dd;
398
399 if (num == UNDOCK_EVENT)
400 sprintf(event_string, "EVENT=undock");
401 else
402 sprintf(event_string, "EVENT=dock");
403
404 /*
405 * Indicate that the status of the dock station has
406 * changed.
407 */
408 if (num == DOCK_EVENT)
409 kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
410
411 list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list)
412 if (dd->ops && dd->ops->uevent)
413 dd->ops->uevent(dd->handle, event, dd->context);
414
415 if (num != DOCK_EVENT)
416 kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
417 }
a little more complex example comes from GFS2 that installs custom
struct kset_uevent_ops
to enrich the env of every event it emits. see
the overview at:
Documentation/filesystems/gfs2-uevents.txt
and impl, including some handy inspiration of start/stop events as well:
fs/gfs2/sys.c
from Documentation/kobject.txt
:
If a kset wishes to control the uevent operations of the kobjects
associated with it, it can use the struct kset_uevent_ops to handle it:
struct kset_uevent_ops {
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
[...]
The name function will be called to override the default name of the kset
that the uevent sends to userspace. By default, the name will be the same
as the kset itself, but this function, if present, can override that name.
The uevent function will be called when the uevent is about to be sent to
userspace to allow more environment variables to be added to the uevent.
and it’s the uevent()
that GFS2 overrides to add several common KEY=val
entries to the env of all the events it emits.
from fs/gfs2/sys.c
:
630 static int gfs2_uevent(struct kset *kset, struct kobject *kobj,
631 struct kobj_uevent_env *env)
632 {
633 struct gfs2_sbd *sdp = container_of(kobj, struct gfs2_sbd, sd_kobj);
634 struct super_block *s = sdp->sd_vfs;
635 const u8 *uuid = s->s_uuid;
636
637 add_uevent_var(env, "LOCKTABLE=%s", sdp->sd_table_name);
638 add_uevent_var(env, "LOCKPROTO=%s", sdp->sd_proto_name);
639 if (!test_bit(SDF_NOJOURNALID, &sdp->sd_flags))
640 add_uevent_var(env, "JOURNALID=%d", sdp->sd_lockstruct.ls_jid);
641 if (gfs2_uuid_valid(uuid))
642 add_uevent_var(env, "UUID=%pUB", uuid);
643 return 0;
644 }
645
646 static const struct kset_uevent_ops gfs2_uevent_ops = {
647 .uevent = gfs2_uevent,
648 };
649
650 int gfs2_sys_init(void)
651 {
652 gfs2_kset = kset_create_and_add("gfs2", &gfs2_uevent_ops, fs_kobj);
653 if (!gfs2_kset)
654 return -ENOMEM;
655 return 0;
656 }
657
658 void gfs2_sys_uninit(void)
659 {
660 kset_unregister(gfs2_kset);
661 }