Hello! Welcome to your next lesson in the Embedded Interview Preparation Bootcamp.
In our last session, we demystified the Linux platform driver model. You learned how the Device Tree describes hardware, how the compatible string creates the crucial link between a device and its driver, and how to use the probe function to retrieve hardware resources like memory addresses and IRQ numbers.
However, the manual resource handling we used—calling platform_get_resource, ioremap, and request_irq in probe, and then manually undoing everything in remove—has a significant drawback. It requires meticulous bookkeeping, especially in the probe function, where a failure at any step requires a complex chain of goto statements to unwind all previously acquired resources. This is tedious and a common source of resource-leak bugs.
Today, we will learn the modern, robust solution to this problem.
Your learning outcome for this lesson is to: Implement a platform driver that uses devm_* managed functions for automatic resource cleanup (e.g., memory, IRQs).
1. The Problem with Manual Resource Management
Let's consider a typical probe function that manually acquires several resources:
// A typical (but fragile) probe function
static int my_driver_probe(struct platform_device *pdev) {
struct my_driver_data *data;
struct resource *mem_res;
void __iomem *regs;
int irq;
int ret;
// 1. Allocate private data structure
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
// 2. Get and map memory-mapped I/O
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
ret = -EINVAL;
goto err_free_data;
}
regs = ioremap(mem_res->start, resource_size(mem_res));
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
goto err_free_data;
}
data->regs = regs;
// 3. Get and request IRQ
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto err_unmap_regs;
}
ret = request_irq(irq, my_irq_handler, 0, "my_device", data);
if (ret) {
goto err_unmap_regs;
}
data->irq = irq;
// ... initialization continues ...
dev_info(&pdev->dev, "Probe successful!\n");
return 0;
// Error-unwinding chain
err_unmap_regs:
iounmap(data->regs);
err_free_data:
kfree(data);
return ret;
}
static int my_driver_remove(struct platform_device *pdev) {
struct my_driver_data *data = platform_get_drvdata(pdev);
free_irq(data->irq, data);
iounmap(data->regs);
kfree(data);
return 0;
}
Notice the goto cascade required for error handling. If request_irq fails, we must jump to a label that unmaps the registers and then falls through to free the private data. This becomes exponentially more complex with each new resource. The remove function essentially duplicates this cleanup logic. This pattern is fragile and difficult to maintain.
2. Device-Managed Resources: The devm_* API
The kernel provides a much cleaner solution: device-managed resources. The core idea is to tie the lifetime of a resource directly to the struct device it is allocated for.
When you use a devm_* variant of an allocation function (e.g., devm_kzalloc instead of kzalloc), the kernel does two things:
- It allocates the resource as requested.
- It registers a corresponding "release" action with the device.
When the device is later detached from the driver (either through a call to remove or because probe fails and returns an error), the kernel automatically walks the list of registered resources and frees them in the reverse order of allocation.
This has two profound benefits:
- Simplified
probe(): Error handling becomes a simplereturn error_code;. No moregotochains for cleanup. - Simplified
remove(): The function often becomes empty, as the framework handles all the cleanup.
This is analogous to the RAII (Resource Acquisition Is Initialization) principle in C++, where an object's destructor automatically releases the resources it holds. Here, the "object" is the struct device, and the "destructor" is the driver core's detach process.
How It Works Internally
Let's visualize the process for devm_kzalloc.

3. Refactoring a Driver with devm_*
Let's refactor our previous example using the devm_* API. The most common managed functions have direct counterparts to the manual ones.
| Manual Function | Device-Managed (devm_*) Equivalent |
|---|---|
kzalloc, kmalloc |
devm_kzalloc, devm_kmalloc |
request_irq |
devm_request_irq |
platform_get_resource + ioremap |
devm_ioremap_resource |
gpiod_get |
devm_gpiod_get |
clk_get |
devm_clk_get |
Here is the clean, modern version of our driver:
// A robust probe function using devm_*
static int my_driver_probe(struct platform_device *pdev) {
struct my_driver_data *data;
void __iomem *regs;
int irq;
int ret;
// 1. Allocate private data structure (managed)
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
// 2. Get and map memory-mapped I/O (managed)
// devm_ioremap_resource combines platform_get_resource and ioremap
regs = devm_ioremap_resource(&pdev->dev, 0);
if (IS_ERR(regs)) {
// If this fails, devm_kzalloc'd memory is auto-freed!
return PTR_ERR(regs);
}
data->regs = regs;
// 3. Get and request IRQ (managed)
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, my_irq_handler, 0, "my_device", data);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq);
// If this fails, regs and data are auto-freed!
return ret;
}
data->irq = irq;
dev_info(&pdev->dev, "Probe successful!\n");
return 0; // No error cleanup path needed!
}
// The remove function is now wonderfully simple.
static int my_driver_remove(struct platform_device *pdev) {
// All resources allocated with devm_* are automatically freed
// by the kernel after this function returns.
dev_info(&pdev->dev, "Driver removed.\n");
return 0;
}
The difference is night and day. The code is linear, readable, and fundamentally more robust.
4. Analyzing Real-World Examples
Let's examine how this pattern is used in professional drivers. We will look at two excellent examples that demonstrate slightly different use cases.
First, let's study a complete driver that requests a GPIO and an IRQ.
Implementing IRQs on the Nitrogen93
The following article from Ezurio provides a perfect, self-contained GPIO interrupt driver that is written using modern best practices, including extensive use of devm_* functions.
Read the brief 'Overview' and then carefully study the 'Kernel Driver Source Code' (gpio-irq-demo.c). Pay close attention to the gpio_irq_probe and gpio_irq_remove functions. Identify every call that starts with devm_ and notice how simple the remove function is as a result.
From that code, you should have identified:
devm_kzalloc: Allocates thegpio_irq_devprivate data structure.devm_gpiod_get: Acquires the GPIO descriptor from the device tree.devm_request_irq: Registers the interrupt handler.gpio_irq_remove: This function only contains a call topm_runtime_disable. It doesn't need to free the IRQ, the GPIO descriptor, or the private data, because the framework handles it all.
Next, let's look at a snippet that deals with memory-mapped registers, which is extremely common in SoC drivers.
[PDF] Introduction to Linux kernel driver programming
This presentation slide from Bootlin shows a code snippet from a real I2C controller driver. It's a classic example of mapping hardware registers in a managed way.
Focus on slide 22, titled 'Usage of the platform bus'. Analyze the omap_i2c_probe function. Specifically, note the sequence of platform_get_resource followed by devm_ioremap_resource.
This example shows another common pattern: devm_ioremap_resource. It's a helper that combines fetching the IORESOURCE_MEM resource and mapping it into the kernel's virtual address space in one managed step. This is the preferred way to map device registers.
Conclusion
Mastering device-managed resources is a key differentiator for an experienced engineer. It demonstrates that you not only know how to write a functional driver, but how to write a robust, maintainable, and modern one. In an interview, being able to contrast the manual and managed approaches shows a deep understanding of kernel development best practices.
Key Takeaways:
- Managed Resources: The
devm_*family of functions allocates resources (memory, IRQs, clocks, GPIOs) and ties their lifetime to astruct device. - Automatic Cleanup: The kernel automatically frees all managed resources when a driver is unbound from its device, either during a normal
removeor uponprobefailure. - Simplified Code: This pattern eliminates complex error-handling
gotochains inprobeand often reduces theremovefunction to being empty. - Core Functions:
devm_kzalloc,devm_request_irq, anddevm_ioremap_resourceare fundamental tools for building robust platform drivers.
Preview of the Next Lesson:
We now have a solid, safe framework for acquiring and managing an interrupt request line. But what should we actually do inside the interrupt handler? Interrupt context is highly constrained—you cannot sleep, and you must be extremely fast. In the next lesson, we will dive into implementing kernel interrupt handling using request_irq and partitioning the handler into a fast "top half" and a deferrable "bottom half" (using a workqueue).