How to ioremap() in Linux Kernel: Mapping Device Registers the Modern Way-Linux Device Driver Training

ioremap() in Linux Kernel: Mapping Device Registers the Modern Way

Free Linux Kernel Programming Course • Hardware I/O Memory • Lecture 3

← Previous Lecture    Next Lecture →

What You Will Learn

  • Why a physical register address cannot be used directly inside kernel code
  • How ioremap() in the Linux kernel maps physical I/O memory into a usable kernel virtual address
  • The current recommended API: devm_platform_ioremap_resource()
  • How to safely read and write mapped registers with readl()/writel()
  • How to correctly unmap a region with iounmap()

Prerequisites

  • Lecture 1: MMIO vs PMIO in Linux Device Drivers
  • Lecture 2: request_mem_region() in Linux Drivers

Why You Can’t Just Use a Physical Address in Kernel Code

A running kernel operates entirely in virtual address space; the MMU translates every virtual address a driver uses into the corresponding physical address behind the scenes. A device’s register block, however, is described by its physical (or bus) address in the device tree or PCI configuration space. Before a driver can safely read or write those registers, it must ask the kernel to create a virtual mapping for that physical range. This is exactly the job of ioremap() in the Linux kernel.

Conceptual View: What ioremap() Does
Physical Address
(device register block)
ioremap() →
Kernel Virtual Address
(vmalloc region)

The Core API

#include <asm/io.h>

void __iomem *ioremap(phys_addr_t offset, size_t size);
void iounmap(volatile void __iomem *addr);

offset is the physical (bus) start address of the register block, and size is its length in bytes. The return value is a kernel virtual address of type void __iomem *. The __iomem annotation carries no runtime effect — it is stripped at compile time — but it lets static analysis tools (like sparse) flag any place where you accidentally treat this pointer like ordinary memory.

The Modern, Recommended Way: devm_platform_ioremap_resource()

Current kernels combine “find the resource,” “reserve it,” and “map it” into a single managed call. This is now the standard pattern used across the vast majority of platform drivers in the mainline tree:

static int mychip_probe(struct platform_device *pdev)
{
    struct mychip_dev *chip;
    void __iomem *base;

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

    base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(base))
        return PTR_ERR(base);

    chip->regs = base;
    platform_set_drvdata(pdev, chip);

    return 0;
}

This single call replaces the older three-step sequence of platform_get_resource(), devm_request_mem_region(), and ioremap(). There is nothing to unmap manually — the mapping is released automatically when the device is removed.

Reading and Writing Mapped Registers Safely

Once you have a mapped void __iomem * base, you never dereference it like a normal pointer. Instead, use the dedicated accessor functions, which guarantee correct ordering and access width:

#define MYCHIP_CTRL_OFFSET   0x00
#define MYCHIP_STATUS_OFFSET 0x04

/* Enable the device */
writel(0x1, chip->regs + MYCHIP_CTRL_OFFSET);

/* Read back status */
u32 status = readl(chip->regs + MYCHIP_STATUS_OFFSET);

Accessor Functions Cheat Sheet

Function Width Purpose
readb() / writeb() 8-bit Byte-wide register access
readw() / writew() 16-bit Half-word register access
readl() / writel() 32-bit Most common register width
readq() / writeq() 64-bit 64-bit capable platforms only

Manual Cleanup: iounmap()

If you use the classic, non-managed ioremap() call, you must pair it with an explicit iounmap() when your driver is removed or when an error path unwinds. Forgetting this leaks a chunk of the kernel’s vmalloc virtual address range every time the driver is loaded and unloaded, which can eventually exhaust available virtual address space on a long-running embedded system.

void __iomem *base = ioremap(phys_addr, size);
if (!base)
    return -ENOMEM;

/* ... use base via readl()/writel() ... */

iounmap(base);

Real-World Use Cases

  • Mapping a UART controller’s register block to configure baud rate and control interrupts
  • Mapping a GPIO controller’s register bank for a gpiolib-based driver
  • Mapping a display or DMA controller’s control registers in a framebuffer driver
  • Mapping PCIe BAR regions in a network interface card driver

Common Mistakes and Troubleshooting

  • Dereferencing the __iomem pointer directly instead of using readl()/writel() — undefined behavior on many architectures.
  • Not checking for a NULL or IS_ERR() return from ioremap() variants before use.
  • Forgetting iounmap() when using the non-managed API, leaking kernel virtual address space over repeated load/unload cycles.
  • Wrong access width — using readb() on a register that hardware expects to be accessed as a full 32-bit word.

Best Practices

  • Prefer devm_platform_ioremap_resource() in new platform driver code
  • Always check the return value with IS_ERR() (managed API) or NULL check (classic API)
  • Always use readX()/writeX() accessors, never raw pointer dereference
  • Match the access width to what the hardware datasheet specifies for each register

Performance and Security Considerations

Mapped I/O memory is typically uncached, so every readl()/writel() call reaches the hardware directly — minimize unnecessary repeated register reads in hot code paths. Because this mapping exposes real hardware control surfaces, never allow the mapped kernel virtual address to be exposed to user space without a carefully audited interface such as a dedicated ioctl or sysfs attribute.

Summary / Key Takeaways

  • ioremap() in the Linux kernel maps a physical register range into a usable kernel virtual address
  • devm_platform_ioremap_resource() is the current recommended, all-in-one approach
  • Always access mapped registers through readX()/writeX(), never as plain memory
  • Pair every manual ioremap() with a matching iounmap() to avoid leaking virtual address space

Conclusion

With MMIO concepts, resource reservation, and ioremap()-based mapping covered across these three lectures, you now have the complete foundation needed to safely access hardware registers from a Linux device driver on any modern kernel. This wraps up the Hardware I/O Memory series of this free Linux kernel programming course — future lectures in this free Linux device drivers course will build on this foundation with interrupt handling and DMA.

Frequently Asked Questions

Q1. What does ioremap() do in the Linux kernel?

It maps a physical (bus) address range belonging to a hardware device into a usable kernel virtual address, typically within the vmalloc region, so a driver can safely access it.

Q2. What is the difference between ioremap() and devm_platform_ioremap_resource()?

ioremap() only performs the mapping and requires a manual iounmap() later, while devm_platform_ioremap_resource() combines resource lookup, reservation, and mapping into one managed call that is automatically cleaned up.

Q3. Why can’t I dereference the pointer returned by ioremap() directly?

The __iomem pointer must be accessed only through readX()/writeX() functions, which guarantee correct ordering, access width, and prevent compiler optimizations that would be unsafe for hardware registers.

Q4. What happens if I forget to call iounmap()?

The kernel virtual address range stays reserved (leaked), which can gradually exhaust the vmalloc address space if the driver is repeatedly loaded and unloaded.

Q5. Which accessor function should I use — readb, readw, readl, or readq?

Match the width to what the hardware datasheet specifies for that register; using the wrong width can produce incorrect reads or undefined hardware behavior.

Q6. Is ioremap() used on Raspberry Pi and similar ARM boards?

Yes, ioremap() and its managed variants are used across essentially all ARM-based single-board computers to map peripheral register blocks such as GPIO, UART, and timers.

Q7. Does ioremap() work the same way on x86 systems?

The API is the same across architectures; the difference is mainly in how the underlying resource is discovered — device tree on most embedded ARM boards versus ACPI/PCI configuration on typical x86 systems.

Continue Your Free Linux Kernel Programming Course

This lecture is part of EmbeddedPathashala’s free Linux device drivers and kernel programming course.

Explore More Free Lectures

← Previous Lecture    Next Lecture →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *