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:
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:
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:
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:
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.