EXAMPLE(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

✨ EXAMPLE(9) ✨

okay so this is the EXAMPLE(9) page from the Kernel API Manual, and it's honestly kind of a big deal β€” it's the whole "how do I split my driver into cute little pieces" tutorial. let's get into it bestie 🫢


πŸ’… EXAMPLE

so here's the vibe: auxiliary devices get created and registered by a subsystem-level core device that needs to break up its functionality into smaller fragments. think of it like the main character device deciding it has too much going on and delegating πŸŽ€

one way to extend the scope of an auxiliary_device is to encapsulate it inside a domain-specific structure defined by the parent device. that structure holds the auxiliary_device plus any associated shared data/callbacks you need to establish the connection back to the parent. it's giving "found family" honestly.

an example is:

struct foo {
     struct auxiliary_device auxdev;
     void (*connect)(struct auxiliary_device *auxdev);
     void (*disconnect)(struct auxiliary_device *auxdev);
     void *data;
};

so then the parent device registers the auxiliary_device by calling auxiliary_device_init, and then auxiliary_device_add, passing it the pointer to the auxdev member of that structure up there. the parent also provides a name for the auxiliary_device that, combined with the parent's KBUILD_MODNAME, creates a match_name that's used for matching and binding with a driver. match made in heaven, no cap ✨

whenever an auxiliary_driver gets registered, based on that match_name, the auxiliary_driver's probe is invoked for the matching devices. and listen β€” the auxiliary_driver can also be encapsulated inside custom drivers that make the core device's functionality extensible by adding extra domain-specific ops, like this:

struct my_ops {
        void (*send)(struct auxiliary_device *auxdev);
        void (*receive)(struct auxiliary_device *auxdev);
};

struct my_driver {
        struct auxiliary_driver auxiliary_drv;
        const struct my_ops ops;
};

an example of this type of usage is:

const struct auxiliary_device_id my_auxiliary_id_table[] = {
        { .name = "foo_mod.foo_dev" },
        { },
};

const struct my_ops my_custom_ops = {
        .send = my_tx,
        .receive = my_rx,
};

const struct my_driver my_drv = {
        .auxiliary_drv = {
                .name = "myauxiliarydrv",
                .id_table = my_auxiliary_id_table,
                .probe = my_probe,
                .remove = my_remove,
                .shutdown = my_shutdown,
        },
        .ops = my_custom_ops,
};
⚠️ okay but please note β€” this custom ops approach is valid, but it is hard to implement right without global locks per-device to protect from auxiliary_drv removal during a call to those ops. and on top of that, this implementation lacks proper module dependency, which causes load/unload races between auxiliary parent and device modules. it's giving "chaotic" and not in the fun way 😭

the most easiest way to provide these ops reliably without needing to have a lock is to EXPORT_SYMBOL*() them and rely on the already existing modules infrastructure for validity and correct dependencies chains. slay, let the kernel do the work for you πŸ‘‘


πŸ¦‹ SEE ALSO


πŸ’– that's the whole page, bestie! this one came from ./drivers/base and it's dated October 2026 β€” go forth and split your functionality into smaller fragments, ily, bye! πŸŒΈπŸ’…

πŸ“¦ where this page comes from