Chapter 38: Auto-creating /dev/ nodes¶
Privilege boundary: $ means normal user. # or sudo means root and can change host or target state. After a privileged command, verify the expected device, service, or file appears before continuing. Roll back by undoing the config change or stopping the service you just enabled.
What:
class_create+device_create, the two calls that let your driver tell the kernel “I have a new device. Please broadcast a hot-plug event so user-space creates/dev/<name>for me.” With these in place you nevermknodby hand again.Why: real drivers don’t burden users with manual
mknodsteps after everyinsmod. Modern Linux uses the uevent mechanism, the kernel broadcasts a netlink message describing the new device, and a user-space agent (udev on workstations, mdev on embedded) reacts by creating the right file in/dev/, setting permissions, and possibly running scripts. Your driver’s only responsibility is to register the device and let the framework do the rest. udev: the user-space device manager that reacts to kernel device events and creates policy-driven /dev nodes.Focus: the relationship between
/sys/class/...and/dev/.... The class hierarchy in sysfs is the source of truth, that’s where the kernel describes what devices exist. The/dev/tree is a shadow of sysfs maintained by the hot-plug agent. Get this picture right and most “why is my device file missing?” debugging becomes trivial.
38.1 The hot-plug pipeline¶
When your driver calls device_create(...), this happens:
driver: device_create(class, NULL, devid, NULL, "hello")
│
▼
kernel: creates /sys/class/hello/hello/ with attributes:
├─ dev (the "240:0" string)
├─ uevent (writable trigger)
└─ subsystem (symlink back up)
│
▼
kernel: emits a uevent of type "add"
ACTION=add
DEVNAME=hello
MAJOR=240
MINOR=0
SUBSYSTEM=hello
│
▼ (netlink broadcast)
udev/mdev: receives the event, looks up rules
│
▼
udev/mdev: mknod /dev/hello c 240 0
chmod 0660 /dev/hello
chown root:plugdev /dev/hello
The driver call (device_create) doesn’t itself create /dev/hello. It creates the sysfs entry at /sys/class/hello/hello/, which the kernel uses to broadcast the uevent. The actual /dev/hello is created by the listener.
This is different from how you might imagine it. The kernel does not maintain /dev/. It publishes events. User-space decides what to do with them. Different listeners can make wildly different choices (udev creates rich-permission nodes with named symlinks. Mdev creates minimal nodes. Both work).
(Aside: there is a fallback. If CONFIG_DEVTMPFS=y and the kernel mounts devtmpfs on /dev/, Ch 32, the kernel itself auto-creates the device node, no user-space listener required. Then udev/mdev’s job becomes just refining permissions and creating symlinks. We’ll assume devtmpfs is on, which it is in 99% of modern setups.)
38.2 Adding to the chardev driver¶
Take the Ch 37 driver and add a class, a device, and matching cleanup, about a dozen lines.
#include <linux/device.h>
struct hello_dev {
struct cdev cdev;
dev_t devid;
struct class *class;
struct device *device;
char *buffer;
size_t buf_len;
struct mutex lock;
};
In hello_init, after cdev_add succeeds:
hd->class = class_create("hello");
if (IS_ERR(hd->class)) {
err = PTR_ERR(hd->class);
goto del_cdev;
}
hd->device = device_create(hd->class, NULL, hd->devid, NULL, "hello");
if (IS_ERR(hd->device)) {
err = PTR_ERR(hd->device);
goto destroy_class;
}
And the new cleanup labels:
destroy_class:
class_destroy(hd->class);
del_cdev:
cdev_del(&hd->cdev);
In hello_exit, balance them (in reverse order):
device_destroy(hd->class, hd->devid);
class_destroy(hd->class);
cdev_del(&hd->cdev);
unregister_chrdev_region(hd->devid, 1);
That’s it. Build, load:
[root@pa-mini:~]# insmod hello_chrdev.ko
[root@pa-mini:~]# ls -l /dev/hello
crw-rw---- 1 root root 240, 0 May 24 09:30 /dev/hello
[root@pa-mini:~]# echo "ping" > /dev/hello
[root@pa-mini:~]# cat /dev/hello
ping
[root@pa-mini:~]# rmmod hello_chrdev
[root@pa-mini:~]# ls -l /dev/hello
ls: cannot access '/dev/hello': No such file or directory
No mknod step. The file appears at load and disappears at unload.
38.3 Anatomy of class and device¶
class_create¶
struct class *class_create(struct module *owner, const char *name);
Creates a directory /sys/class/<name>/. A class is a group of devices that share a role, LED, RTC, GPIO chip, network interface, sound card. The class directory holds one entry per device in that group. It also publishes group-level attributes that udev/mdev rules can match on.
MCU bridge: Think of Linux GPIO like the same pin set/reset block you used on STM32, but accessed through a kernel subsystem that owns numbering, direction, interrupts, and user-space exposure. GPIO: General-Purpose Input/Output, a pin controlled as a digital input, output, or interrupt source.
The kernel ships dozens of standard classes:
[root@pa-mini:~]# ls /sys/class/
backlight gpio leds power_supply rtc
block i2c-adapter mdio_bus pwm sound
bluetooth input net regulator spi_master
...
When you create your own class ("hello"), /sys/class/hello/ appears. New custom-driver chardevs that don’t fit an existing class do this, make a class with the driver’s name. Drivers that fit an existing class skip class_create and register with the subsystem framework directly. For example, an LED driver belongs in leds and an RTC in rtc. Ch 44–48 cover these subsystems.
device_create¶
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
class: which class this device belongs to.parent: the device’s parent in the device hierarchy.NULLis fine for top-level chardev. Real subsystem drivers set this to the platform device or USB device that hosts them, so sysfs reflects the bus topology.devt: thedev_t(major:minor). The kernel writesMAJOR:MINORinto the device’sdevattribute, which is what udev/mdev reads.drvdata: avoid *stored in thedevice’sdriver_datafield. Use it (ordev_set_drvdatalater) to attach your own state.fmt, ...:printf-style device name. Becomes the directory name in/sys/class/<class>/<name>/and (via the uevent’sDEVNAME) the filename in/dev/.
A few naming conventions to know:
For a single-instance device, use
"hello".For multiple instances, use a format like
"hello%d", index. The result is/dev/hello0,/dev/hello1, etc.For sub-devices, use a slash:
"input/event%d"puts the result at/dev/input/eventN.
Inspect what got created:
[root@pa-mini:~]# ls /sys/class/hello/hello/
dev power/ subsystem uevent
[root@pa-mini:~]# cat /sys/class/hello/hello/dev
240:0
[root@pa-mini:~]# cat /sys/class/hello/hello/uevent
DEVNAME=hello
DEVTYPE=
MAJOR=240
MINOR=0
SUBSYSTEM=hello
The uevent file is special: reading it prints the current state, writing to it re-broadcasts the event. Writing echo add > uevent re-triggers the event. This is useful for replaying events on a system that booted before udev was running.
38.4 Picking permissions and ownership¶
By default, udev/mdev creates /dev/hello with permissions 0600 (root-only). That’s safe but unhelpful, your test programs running as a regular user can’t open the device.
Three places to set device permissions, in order of preference:
A. Udev rule (cleanest)¶
Create /etc/udev/rules.d/99-hello.rules:
KERNEL=="hello", MODE="0660", GROUP="plugdev"
Reload:
[root@pa-mini:~]# udevadm control --reload
[root@pa-mini:~]# udevadm trigger --subsystem-match=hello
Now /dev/hello is mode 0660, owned by root:plugdev. Add your user to the plugdev group and they can read/write it.
B. Mdev rule (embedded with BusyBox)¶
/etc/mdev.conf:
hello 0:plugdev 0660
mdev’s syntax is positional, not key=value: name, owner:group, mode. Mdev applies these on every new device event. (Re-trigger by mdev -s to apply to already-created files.)
C. Devnode callback in the driver¶
For permissions that must be a property of the driver (because user-space rule files might not exist), set a devnode callback on the class:
static char *hello_devnode(struct device *dev, umode_t *mode)
{
if (mode)
*mode = 0660; /* readable by group */
return NULL;
}
/* In init: */
hd->class->devnode = hello_devnode;
The kernel’s devtmpfs runs devnode when creating the device and respects the returned mode. This is the most reliable but least configurable approach, most production systems use udev rules instead.
38.5 Multiple devices in one driver¶
If your driver controls N identical devices (e.g., 4 LEDs, 8 GPIO chips), you want a single driver creating multiple /dev/ nodes. The pattern:
#define N_DEVICES 4
static struct hello_dev *hds[N_DEVICES];
static struct class *hello_class;
static dev_t hello_base_devid;
static int __init hello_init(void)
{
int i, err;
err = alloc_chrdev_region(&hello_base_devid, 0, N_DEVICES, "hello");
if (err) return err;
hello_class = class_create("hello");
if (IS_ERR(hello_class)) {
err = PTR_ERR(hello_class);
goto unreg;
}
for (i = 0; i < N_DEVICES; i++) {
hds[i] = kzalloc(sizeof(*hds[i]), GFP_KERNEL);
if (!hds[i]) { err = -ENOMEM; goto unwind; }
hds[i]->devid = MKDEV(MAJOR(hello_base_devid), i);
cdev_init(&hds[i]->cdev, &hello_fops);
hds[i]->cdev.owner = THIS_MODULE;
err = cdev_add(&hds[i]->cdev, hds[i]->devid, 1);
if (err) goto unwind;
device_create(hello_class, NULL, hds[i]->devid, NULL, "hello%d", i);
}
return 0;
unwind:
while (--i >= 0) {
device_destroy(hello_class, hds[i]->devid);
cdev_del(&hds[i]->cdev);
kfree(hds[i]);
}
class_destroy(hello_class);
unreg:
unregister_chrdev_region(hello_base_devid, N_DEVICES);
return err;
}
After load:
[root@pa-mini:~]# ls /dev/hello*
/dev/hello0 /dev/hello1 /dev/hello2 /dev/hello3
Each is an independent chardev sharing the same file_operations. Use iminor(filp->f_inode) to figure out which one a given open is referring to.
38.6 Exposing custom sysfs attributes¶
A device file is one way to talk to your driver. Sysfs attributes are another, and they’re often better for set-once configuration or one-shot commands.
Add a state attribute:
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "loaded\n"); /* bounds-checked since 5.10; prefer over sprintf */
}
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
pr_info("hello: state set to %.*s", (int)count, buf);
return count;
}
static DEVICE_ATTR_RW(state); /* creates `state` read+write attr */
/* In init, after device_create succeeds: */
err = device_create_file(hd->device, &dev_attr_state);
Now:
[root@pa-mini:~]# cat /sys/class/hello/hello/state
loaded
[root@pa-mini:~]# echo run > /sys/class/hello/hello/state
[root@pa-mini:~]# dmesg | tail -1
hello: state set to run
sysfs files are limited to PAGE_SIZE (4 KB on i.MX6ULL) per show callback, but that’s plenty for status / configuration. They’re great for things like:
Read-only stats (
bytes_processed,irq_count).One-shot commands (
echo reset > /sys/class/.../control).Toggles (
echo 1 > /sys/class/leds/led0/brightness).
For things like streaming data, stick to the chardev read/write. Sysfs is for control, not bandwidth.
For multiple attributes, group them and use device_create_file per attribute, or sysfs_create_group(&dev->kobj, &attr_group) for atomic group creation/deletion.
38.7 Lab¶
Add
class_create+device_createto the Ch 37 driver. Verify/dev/helloappears at load and disappears at unload.Switch to multi-device. Modify your driver to create 4 instances (
/dev/hello0…/dev/hello3). Each instance has its own buffer.Make it user-accessible. Write a udev rule that gives your devices mode 0660 in group
plugdev. Verify a non-root user cancat /dev/hello0.Add a sysfs attribute. Create
state_show/state_storeto expose a runtime knob, say, an integer that throttles your write speed.Inspect the uevent. Open two terminals on the target. In one, run
udevadm monitor(ormdev -dif using mdev). In the other,insmodandrmmod. Watch the events fire.Use the
devnodecallback to set 0660 mode in the driver itself. Verify/dev/hellocomes up with the right mode even without a udev rule.
38.8 Pitfalls¶
Calling
device_createbeforecdev_add. Device node appears butopenon it returns-ENXIObecause no cdev is registered for that major. Alwayscdev_addfirst, thendevice_create. Cleanup in reverse order.Forgetting
device_destroyin cleanup. The/dev/node lingers (kernel side), udev/mdev never gets the “remove” event. Next load creates/dev/helloagain, double-registered. Eventually you’ll trip over name conflicts.IS_ERR(class)vsclass == NULL.class_createreturns anERR_PTRon failure, notNULL. Test withIS_ERR(class)and recover withPTR_ERR(class). Same fordevice_create.Race between insmod and udev. If your user-space code does
insmod hello.ko && cat /dev/hello, the second part may run before udev has created/dev/hello. Mitigation: useudevadm settlebetweeninsmodand the access, or have your user-space code retry with a short delay.Class name collisions. Two drivers trying to register a class with the same name fail loudly. Use unique names. If you’re adding a driver to an existing subsystem, use that subsystem’s
registerAPI (e.g.,led_classdev_register) instead of creating a competing class.Sysfs attribute permissions.
DEVICE_ATTR_RWcreates 0644 attributes. For root-only writes, useDEVICE_ATTR(name, 0640, show, store)or one ofDEVICE_ATTR_RO,DEVICE_ATTR_WO,DEVICE_ATTR_ADMIN_RO.Returning >PAGE_SIZE from a
showcallback. Silently truncated by sysfs. Don’t use sysfs to stream large data. Use a chardev ordebugfsinstead.
38.9 Going deeper¶
Documentation/driver-api/driver-model/overview.rst: the device model is much richer than what we use here. Classes are one slice of it.Documentation/admin-guide/devices.rst: udev/mdev rule syntax in detail.drivers/leds/led-class.c: a real subsystem class, fully fleshed out. Read it once.Documentation/filesystems/sysfs.rst: the sysfs design rationale. Explains why each file is limited to PAGE_SIZE and how the kobject hierarchy works.
Next chapter: Chapter 39: Platform drivers + device tree. With manual device registration understood, we move to the real way Linux drivers describe themselves: a
platform_driverthat gets matched to a device-treecompatiblestring, withprobe/removedoing whatmodule_init/module_exitdid up until now.