Skip to content

Day 95 - ioremap() Internals

Today's Goal

Today's objective is to understand how Linux maps MMIO (Memory-Mapped I/O) physical addresses into the kernel virtual address space.

Unlike normal RAM allocation, hardware registers already exist at fixed physical addresses. Drivers cannot safely access these physical addresses directly, so they must first create a kernel virtual mapping using ioremap().

In this lab, we implement a simplified MMIO mapping manager to simulate the behavior of Linux ioremap() and understand the relationship between physical addresses, virtual addresses, and hardware registers.


Why ioremap()?

Modern operating systems use virtual memory.

Kernel drivers generally operate on virtual addresses, while hardware peripherals expose physical addresses.

For example, a GPIO controller may reside at:

Physical Address

0xFE200000
GPIO Registers

A driver cannot simply dereference this physical address.

Instead, Linux creates a kernel virtual mapping:

Physical Address
ioremap()
Kernel Virtual Address

After mapping, the driver accesses the device through the returned virtual address using APIs such as readl() and writel().


Linux Architecture

In Linux, ioremap() creates a page-table mapping.

The CPU MMU automatically translates virtual addresses into physical addresses whenever the driver accesses an MMIO register.

Driver

Virtual Address
     MMU
Physical Address
Hardware Register

The driver never performs this translation itself.


Simulator Architecture

Since a userspace program cannot create kernel page-table mappings, this lab implements a simplified MMIO mapping simulator.

Instead of modifying page tables, the simulator maintains its own mapping table and backing storage.

                 +----------------------+
                 |    io_region         |
                 |----------------------|
Physical Address | 0x10000000           |
Size             | 0x1000               |
Storage          | malloc() buffer      |
                 +----------+-----------+
                            |
                            |
                 +----------v-----------+
                 |    io_mapping        |
                 |----------------------|
Virtual Address  | 0x20000000           |
Physical Address | 0x10000000           |
Size             | 0x100                |
                 +----------+-----------+
                            |
                            |
                     io_mapping_translate()
                            |
                   Backing Storage Pointer

The simulator therefore separates:

  • Physical MMIO regions
  • Virtual mappings
  • Address translation

This closely resembles the responsibilities of the Linux MMU while remaining entirely in userspace.


MMIO Region Management

The simulator first registers available MMIO regions.

io_region_register(0x10000000, 0x1000);

Each region represents a simulated hardware device.

Internally, every region owns a block of backing storage allocated from heap memory.

Unlike Linux, this storage does not exist in real hardware. It only simulates hardware registers for userspace testing.


Virtual Mapping

Drivers create virtual mappings through ioremap().

base = ioremap(0x10000000, 0x100);

Instead of returning a heap pointer, the simulator allocates a simulated kernel virtual address.

For example:

Physical Address     Virtual Address

0x10000000   --->    0x20000000
0x10000800   --->    0x20001000

Virtual addresses are allocated in page-sized increments to resemble Linux kernel mappings.


Address Translation

The simulated virtual address cannot be dereferenced directly.

Instead, every MMIO access is translated into the corresponding backing storage address.

Virtual Address
io_mapping_translate()
Backing Storage

This translation performs the role that the MMU would normally provide inside the Linux kernel.


Mapping Lifecycle

The simulator enforces the same ownership rules as Linux.

io_region_register()
ioremap()
MMIO Access
iounmap()
io_region_unregister()

A region cannot be removed while active mappings still exist.


Lab Summary

Lab 1 — Region Registration

Validated:

  • Region registration
  • Overlap detection
  • Adjacent regions
  • Zero-sized regions
  • Address overflow

Lab 2 — ioremap()

Validated:

  • Virtual address allocation
  • Page alignment
  • Partial mappings
  • Multiple mappings
  • Mapping boundary checking

Lab 3 — Address Translation

Validated:

  • Virtual-to-storage translation
  • Mapping offsets
  • Physical region offsets
  • Shared backing storage
  • Translation boundary checking

Lab 4 — Mapping Lifecycle

Validated:

  • Region busy protection
  • Multiple active mappings
  • Double iounmap()
  • Invalid iounmap()
  • Manager cleanup
  • Reinitialization

Key Takeaways

  • Hardware peripherals are accessed through MMIO rather than normal RAM allocation.
  • ioremap() maps MMIO physical addresses into the kernel virtual address space.
  • Linux relies on the MMU to translate virtual addresses into physical addresses.
  • The simulator replaces MMU translation with io_mapping_translate().
  • Backing storage is only used by the simulator to emulate hardware registers in userspace.
  • Separating region management, virtual mappings, and address translation closely mirrors the Linux MMIO architecture.

  • ioremap()
  • iounmap()
  • readb()
  • readw()
  • readl()
  • writeb()
  • writew()
  • writel()