devm_ioremap in Linux Kernel Driver: Managed I/O Memory Mapping Explained
Free Linux Kernel Programming Course — Chapter 3: Working with Hardware I/O Memory
← Previous Lecture | Next Lecture →
If you are writing a Linux device driver that talks to a memory-mapped peripheral, sooner or later you will reach for devm_ioremap in a Linux kernel driver. This single call has quietly replaced a whole family of older, error-prone APIs, and understanding why it exists — and how it fits into the kernel’s resource management model — is one of the most practical skills you can pick up as a beginner driver author. In this lesson we break the topic down from first principles, using only original explanations and diagrams, so that it makes sense whether you are following along on a Raspberry Pi, a BeagleBone, or any other ARM/embedded Linux board running a recent 6.x kernel.
Key APIs covered in this lesson
What You Will Learn
- Why a peripheral’s physical address cannot be touched directly by the CPU and must be mapped into virtual memory
- How the classic
ioremap()/request_mem_region()pair works, and why it is considered legacy today - What the devres (managed device resource) framework does behind the scenes
- How
devm_ioremap(),devm_ioremap_resource()anddevm_platform_ioremap_resource()differ from each other - A complete, modern probe() function that safely maps I/O memory in a handful of lines
- Common mistakes, debugging tips, and best practices for production drivers
Prerequisites
- Basic C programming and comfort reading kernel-style function signatures
- A working cross-compilation or native build setup for a recent Linux kernel (5.x or 6.x)
- Familiarity with the idea of a platform driver and its
probe()/remove()hooks - Some exposure to physical vs virtual addressing is helpful but not mandatory — we recap it below
Physical Memory vs Virtual Memory: Why We Need ioremap at All
Every peripheral on an embedded board — a GPIO controller, a UART, an I2C block — is wired onto the system bus and exposed at a fixed physical address. The CPU running Linux, however, never operates directly on physical addresses once paging is enabled; every access from kernel or user code goes through the MMU and lands on a virtual address that the page tables translate into a physical one. This is true for RAM and it is equally true for the register banks of your hardware. So before a driver can read or write a device register, that register’s physical address range has to be mapped into the kernel’s virtual address space. That mapping step is what ioremap() family functions perform.
|
Physical Address Space
Peripheral registers
e.g. GPIO controller Physical RAM
|
→ devm_ioremap() |
Kernel Virtual Address Space
Mapped register block
accessible via readl()/writel() Linear-mapped RAM
|
The Classic Approach: request_mem_region() and ioremap()
Before the devres framework existed, driver authors mapped I/O memory by hand, in two separate steps. First they reserved the physical range so no other driver could claim it, then they mapped it into virtual memory:
struct resource *region;
void __iomem *base;
region = request_mem_region(phys_addr, size, "my_device");
if (!region)
return -EBUSY;
base = ioremap(phys_addr, size);
if (!base) {
release_mem_region(phys_addr, size);
return -ENOMEM;
}
/* ... use base with readl()/writel() ... */
/* on driver removal, both steps must be undone in reverse order */
iounmap(base);
release_mem_region(phys_addr, size);
This works, but notice how much bookkeeping the driver author is responsible for. Both the region reservation and the mapping must be released in the correct order, on every error path and inside remove(). Miss one exit path — and there are usually several in a real probe function — and you leak a resource or, worse, leave a stale mapping that a future driver bind can trip over.
Why This Older Style Is Considered Deprecated
The core problem is not that ioremap() is unsafe by itself — it is that pairing it with manual cleanup scales badly. A typical probe() function might allocate memory, request several register regions, obtain a clock, register an interrupt handler and register the driver with a subsystem, all before returning success. If any one of these steps fails halfway through, the driver has to carefully unwind everything it already did, in reverse order. This unwind logic is where a large share of real-world kernel bugs — use-after-free, double-free, resource leaks — actually come from. The kernel community’s answer to this class of bug is the devres (managed device resource) framework, and the devm_ioremap Linux kernel driver family of functions is the I/O-memory-specific part of it.
How the devres Framework Works
Every struct device in the kernel carries a private list of “managed resources.” When a driver calls a devm_*() function, the kernel does two things: it performs the requested action (allocating memory, mapping registers, requesting an IRQ, and so on), and it registers a small release callback on that device’s resource list. Later, when the device is unbound from the driver — whether because of a clean rmmod, a hot-unplug, or because probe() itself returned an error — the kernel walks that list in reverse order and calls every release callback automatically. The driver author never has to write matching cleanup code for resources obtained through devm_*() calls.
| probe() runs devm_ioremap() called |
→ | Mapping stored on device’s devres list |
→ | Driver runs normally uses readl()/writel() |
→ | Unbind / error path devres auto-unmaps |
devm_ioremap(), devm_ioremap_resource() and devm_platform_ioremap_resource()
The devres family for I/O memory has grown a few useful layers, and picking the right one saves you lines of boilerplate:
| Function | What it does for you |
|---|---|
devm_ioremap() |
Managed version of ioremap() only. You must still reserve the region yourself. |
devm_ioremap_resource() |
Validates a struct resource *, requests the memory region, and maps it — all three steps, managed, in one call. |
devm_platform_ioremap_resource() |
Same as above, but also fetches the resource from the platform device for you by index, so you don’t even need to call the resource-lookup function separately. |
For the vast majority of platform drivers written today, devm_platform_ioremap_resource() is the one you want, because it collapses resource lookup, region reservation and mapping into a single managed call.
Practical Example: A Modern probe() Function
Here is a minimal, original example of how a platform driver’s probe() function looks when it is written the modern, managed way, targeting a recent kernel:
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/module.h>
struct mydev_priv {
void __iomem *base;
};
static int mydev_probe(struct platform_device *pdev)
{
struct mydev_priv *priv;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* One call: look up resource 0, request it, and ioremap it.
* Everything is released automatically on detach or on
* any later probe() failure. */
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "mydev mapped successfully\n");
return 0;
}
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "example,mydev" },
{ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
};
module_platform_driver(mydev_driver);
MODULE_LICENSE("GPL");
Notice that there is no remove() callback at all in this example, and no manual iounmap() anywhere. Both the private structure (via devm_kzalloc()) and the register mapping (via devm_platform_ioremap_resource()) are cleaned up automatically by the devres framework when the device is unbound.
Common Mistakes and Troubleshooting
- Forgetting to check
IS_ERR(). The managed ioremap functions return an encoded error pointer, notNULL, on failure. Checking againstNULLwill silently pass a bad pointer forward. - Mixing managed and unmanaged calls. Pairing
devm_ioremap()with a manualiounmap()inremove()causes a double-unmap. If you opted into devres, commit to it for that resource. - Using the wrong resource index. The index passed to
devm_platform_ioremap_resource()must match the order of theregentries in the device tree node; an off-by-one here is a very common source of a garbagebasepointer. - Assuming devm_* resources vanish immediately on error. They are released when the device unbinds — which does happen right after a failed
probe()returns, but it’s worth knowing the mechanism rather than just trusting it blindly while debugging.
Best Practices
- Default to
devm_platform_ioremap_resource()for new platform drivers unless you have a specific reason to manage the mapping’s lifetime yourself. - Reserve manual
ioremap()/iounmap()for the rare cases where a mapping’s lifetime is shorter than the device’s binding, such as a temporary mapping used only during a firmware upload. - Always propagate the real error code from
PTR_ERR()instead of hard-coding-EINVALor similar — it makes diagnosing probe failures in the field much easier. - Keep the private driver struct itself allocated with
devm_kzalloc()so its lifetime is tied to the device as well.
Performance Considerations
The devres bookkeeping adds a small, one-time cost at probe time — allocating a tiny release-record structure and linking it onto the device’s list. This has no measurable effect on the actual hot-path performance of register reads and writes done through the mapped pointer afterwards; readl()/writel() perform exactly the same regardless of how the mapping was created. In other words, choosing the managed API is essentially free at runtime and only saves work at driver load/unload time.
Security Considerations
Automatic cleanup closes off a real attack surface: a leaked or stale I/O mapping left behind after a botched unbind can, in some scenarios, be raced by a second driver claiming the same physical range, or simply leave a window that a kernel fuzzer can trigger a use-after-free through. Letting devres own the mapping’s lifetime removes an entire class of manual-cleanup bugs from your driver’s error paths, which is exactly why the mainline kernel has been steadily converting older drivers over to the managed calls.
Real-World Use Cases
Almost any SoC peripheral driver you will encounter in the mainline kernel today — GPIO controllers, I2C/SPI controllers, UARTs, DMA engines, display controllers — maps its register block using some member of this devm_ioremap Linux kernel driver family. It is the standard way a platform driver gets access to its hardware, whether that driver is discovered via Device Tree, ACPI, or classic board files.
Summary / Key Takeaways
- I/O memory must be mapped from physical to virtual address space before a driver can access it.
- The classic
request_mem_region()+ioremap()pair requires careful manual cleanup on every exit path. - The devres framework automatically releases resources obtained through any
devm_*()call when the device unbinds. devm_platform_ioremap_resource()is the modern, one-line way to look up, reserve, and map a platform device’s register block.- Always check the return value with
IS_ERR(), never againstNULL.
Conclusion
The move from manual ioremap()/iounmap() pairs to the managed devm_ioremap Linux kernel driver APIs is one of the clearest examples of how the kernel community has hardened driver code over the years, without changing the underlying hardware access model at all. Once you understand that devres is simply “a per-device list of cleanup callbacks that fire automatically on unbind,” the whole family of devm_*() functions across memory, IRQs, clocks and GPIOs starts to make a lot more sense. For new drivers, reach for devm_platform_ioremap_resource() first, and only step down to the lower-level calls when you have a genuine reason to manage a mapping’s lifetime by hand.
Frequently Asked Questions
1. What is the difference between ioremap() and devm_ioremap()?
ioremap() maps physical I/O memory into the kernel’s virtual address space but leaves you responsible for calling iounmap() yourself. devm_ioremap() does the same mapping but registers it with the devres framework so it is unmapped automatically when the device is unbound.
2. Is ioremap() actually removed from the kernel?
No, ioremap() and iounmap() still exist and are used internally by the managed wrappers and in a few low-level contexts. What is discouraged is calling them directly in ordinary platform driver probe()/remove() code when a managed alternative is available.
3. When should I still use plain ioremap() instead of a devm_* variant?
When the mapping’s lifetime genuinely does not match the device’s binding lifetime — for example, a short-lived mapping used only once during a firmware download and torn down long before remove() runs.
4. What does devm_platform_ioremap_resource() actually do internally?
It looks up the memory resource at the given index on the platform device, then calls the equivalent of devm_ioremap_resource(), which validates the resource, requests the memory region, and maps it — all as managed operations.
5. Why does devm_platform_ioremap_resource() return an error pointer instead of NULL?
Returning an encoded error via ERR_PTR() lets the function communicate exactly why the mapping failed (bad resource, region already taken, mapping failure) instead of collapsing every failure into a single NULL. Always unwrap it with IS_ERR() and PTR_ERR().
6. Do I still need request_mem_region() if I use devm_ioremap_resource()?
No. devm_ioremap_resource() performs the region request internally, so calling request_mem_region() separately would be redundant and would actually fail since the region is already reserved.
7. What happens if probe() fails after devm_platform_ioremap_resource() succeeds?
The devres framework still unwinds the mapping automatically, because a failed probe() triggers the same device-unbind cleanup path as a normal driver removal.
8. Does using devm_* APIs affect runtime performance of register access?
No. Once the mapping exists, reading and writing through it with readl()/writel() behaves identically regardless of whether the mapping was created with a managed or unmanaged call.
9. Can I mix devm_kzalloc() with a manually kfree()’d structure?
You can, but it defeats the purpose — if you allocate with devm_kzalloc(), let devres free it too. Manually freeing a devm-managed allocation will cause a double-free when the device unbinds.
10. Is this relevant on x86 systems too, or only ARM/embedded boards?
The devres framework and the devm_ioremap family are architecture-independent; they are used just as heavily by PCI and platform drivers on x86 as they are on ARM SoC drivers, even though resource discovery differs (ACPI/PCI enumeration vs Device Tree).

1 Comment