ac6-formation, un département d'Ac6 SAS
EN
EnglishFrench
go-up

ac6 ac6-formation

Writing an embedded Linux driver: from devicetree to sysfs, in the right layer

Most badly written Linux drivers are not bad because of a coding error, but because they were written in the wrong layer. Before the first line, one question matters: does a subsystem already exist for this kind of hardware.

Choosing the layer, first

Linux provides subsystems for almost every device family. Writing a temperature sensor driver as a plain character device with home-made ioctl calls throws away everything the kernel already offers and forces a proprietary interface on user space.

Hardware typeSubsystemInterface you get
Sensor, ADCIIO/sys/bus/iio/, buffers, timestamps
LED, backlightLED, backlight/sys/class/leds/
Button, keypad, touchInput/dev/input/event*
Charger, batteryPower supply/sys/class/power_supply/
Real-time clockRTC/dev/rtc0, hwclock
WatchdogWatchdog/dev/watchdog
Bus controllerI2C, SPI adapterstandard bus
Non-volatile memoryNVMEM/sys/bus/nvmem/

A thirty-line IIO driver gives you timestamping, buffering, triggers and an interface every tool understands. The same sensor as a character device is three hundred lines and nobody else can use it.

A character device stays legitimate for genuinely specific hardware with no existing family.

Matching through devicetree

On an embedded platform, the kernel binds a devicetree node to a driver through the compatible property.

On the devicetree side:

&i2c1 {
    temp0: temp-sensor@48 {
        compatible = "acme,temp42";
        reg = <0x48>;
        interrupt-parent = <&gpio2>;
        interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
        vdd-supply = <&reg_3v3>;
    };
};

On the driver side:

static const struct of_device_id temp42_of_match[] = {
    { .compatible = "acme,temp42" },
    { }
};
MODULE_DEVICE_TABLE(of, temp42_of_match);

Forgetting MODULE_DEVICE_TABLE is the most frequent silent mistake: the driver compiles, loads by hand with insmod, but is never loaded automatically, because the alias that lets udev make the connection does not exist.

probe and remove, the symmetry that matters

probe() is called when a matching device appears. It acquires resources, initialises the hardware and registers the device with its subsystem.

The absolute rule is symmetry: everything acquired in probe must be released in remove, in reverse order. And crucially, every error path in probe must undo what has already been done.

That is exactly the class of bug the devm_ helpers remove:

static int temp42_probe(struct i2c_client *client)
{
    struct temp42 *st;

    st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
    if (!st)
        return -ENOMEM;

    st->vdd = devm_regulator_get(&client->dev, "vdd");
    if (IS_ERR(st->vdd))
        return dev_err_probe(&client->dev, PTR_ERR(st->vdd),
                             "vdd regulator unavailable\n");

    return devm_iio_device_register(&client->dev, indio_dev);
}

Three things to take from that fragment. devm_ allocations are freed automatically when the device goes away, which often makes remove unnecessary. dev_err_probe handles -EPROBE_DEFER cleanly, without flooding the logs when a dependency is not ready yet. And returning directly on error, with no goto ladder, becomes possible and readable.

Deferred probing, to understand once and for all

-EPROBE_DEFER is not an error. It is the kernel saying "this device depends on a resource that does not exist yet, I will retry later". It happens constantly on embedded platforms, where probe order is not deterministic: your sensor needs a regulator whose driver is not loaded yet.

The trap is treating that code as a failure and logging an error. Boot then fills with alarming messages although everything eventually works. dev_err_probe exists for exactly this: it logs at debug level for that case and at error level for the rest.

Context mistakes, the ones that panic

This is where the kernel does not forgive.

Sleeping in atomic context. Inside an interrupt handler, under a spinlock, or in a tasklet, any function that may sleep is forbidden: msleep, mutex_lock, kmalloc(GFP_KERNEL), regmap_read over I2C. Practical rule: if you do not know whether a function can sleep, it probably can.

The right allocation flag. GFP_KERNEL may sleep, GFP_ATOMIC does not but draws on a limited reserve. Using GFP_ATOMIC everywhere out of caution exhausts that reserve and destabilises the system under load.

Two-stage interrupts. A sensor on I2C cannot be read from an interrupt handler, since the I2C read sleeps. The correct pattern is devm_request_threaded_irq with a quick handler that only acknowledges, and a threaded handler that reads.

Enable CONFIG_DEBUG_ATOMIC_SLEEP during development: it turns these faults into explicit messages rather than random hangs.

What to expose to user space

The kernel has conventions, and following them is the difference between an acceptable driver and one nobody can use.

One value per sysfs file, in the subsystem's standard unit. No home-made format. ioctl only when no existing interface fits. And never an undocumented binary format in a sysfs file.

If your driver is ever meant to reach mainline, these conventions are not negotiable, and following them from the start beats rewriting everything later.

References

Going further

These reflexes are built by writing a complete driver on real hardware, deliberately failing in probe, and watching what the kernel objects to. That is what our Linux Drivers and Linux USB Drivers courses cover.