Skip to content

Common Embedded Linux Driver Pitfalls

These are recurring issues in embedded Linux driver work. Most are simple once understood, but they can cost a great deal of debugging time when they first appear.

1. Memory-access mistakes

Never dereference a physical address directly

Code like this may appear to work on bare metal, but it is invalid in a Linux driver's virtual address space:

unsigned int *reg = (unsigned int *)0x10000000;
*reg = 0x01;

Map the I/O range first and use the I/O accessors:

void __iomem *reg_base = ioremap(0x10000000, 0x1000);
if (!reg_base) {
    pr_err("ioremap failed\n");
    return -ENOMEM;
}
writel(0x01, reg_base);
/* ... */
iounmap(reg_base);

Every ioremap() needs its matching iounmap(), normally in the driver's remove() path.

2. Concurrency and locking

Initialize locks before use

A lock embedded in a device structure is not ready until it is initialized:

static int device_probe(struct platform_device *pdev)
{
    struct my_device *dev;

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

    spin_lock_init(&dev->lock);
    return 0;
}

The same principle applies to mutexes, semaphores, completions, and every other synchronization primitive.

Do not sleep while holding a spinlock

This is a deadlock risk:

spin_lock(&dev->lock);
msleep(100);
spin_unlock(&dev->lock);

Use a mutex when the protected work may sleep, or split the operation so sleeping happens after releasing the spinlock.

3. Device model and probe

A missing probe is usually a matching problem

probe() runs only when a device and a driver match. For a platform driver, verify the device-tree node or the device registration first:

cat /proc/modules | grep my_driver
cat /sys/bus/platform/devices
dmesg | grep my_device

Be careful with managed resources

devm_* helpers release their resources automatically when the device is removed. Do not leave raw global pointers to managed memory after its device disappears:

int *temp = devm_kzalloc(dev, sizeof(*temp), GFP_KERNEL);
g_temp = temp;  /* unsafe after driver removal */

If data really must outlive the device, give it an explicit lifetime and manage it manually.

4. Interrupts

Match request_irq() with free_irq()

If probe fails after an IRQ was requested, or when the driver is removed, release it:

static int device_remove(struct platform_device *pdev)
{
    struct my_device *dev = platform_get_drvdata(pdev);

    free_irq(dev->irq, dev);
    return 0;
}

Treat probe() and remove() as mirrored paths: every acquired resource needs a matching release.

Never sleep in a hard-IRQ handler

Interrupt handlers cannot take a mutex or call sleeping functions. Keep them short, use a spinlock only when needed, and defer longer work to a workqueue:

static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;

    spin_lock(&dev->lock);
    queue_work(dev->workqueue, &dev->work);
    spin_unlock(&dev->lock);
    return IRQ_HANDLED;
}

5. Device tree

compatible must match exactly

A one-character mismatch prevents binding:

my_device@10000000 {
    compatible = "mycompany,my-device-v1";
    reg = <0x10000000 0x1000>;
};
static const struct of_device_id my_of_match_table[] = {
    { .compatible = "mycompany,my-device-v1" },
    { }
};

When using of_find_compatible_node(), balance a successful lookup with of_node_put() when the node is no longer needed.

6. Removal and sysfs cleanup

Do not free the device structure while IRQs, I/O mappings, or sysfs files can still access it. Remove user-visible attributes first, then release resources in the reverse order in which they were acquired.

static int device_remove(struct platform_device *pdev)
{
    struct my_device *dev = platform_get_drvdata(pdev);

    device_remove_file(&pdev->dev, &dev_attr_value);
    free_irq(dev->irq, dev);
    iounmap(dev->reg_base);
    kfree(dev);
    return 0;
}

7. A short debug checklist

lsmod | grep driver_name
ls -la /dev/my_device
cat /sys/bus/platform/devices
cat /proc/device-tree/my_device/compatible
dmesg -w
cat /proc/interrupts

Takeaway

Use paired resource management, check every error path, understand execution context, keep DTS and match tables consistent, and make remove() the deliberate mirror of probe(). These habits prevent many of the hardest-to-reproduce driver failures.