Skip to content

Day 96 - MMIO Access APIs

Today's Goal

After implementing ioremap() and iounmap(), today's goal is to understand how Linux kernel drivers access memory-mapped I/O (MMIO) registers.

Unlike normal memory access, device registers must be accessed through dedicated MMIO APIs such as readb(), readw(), readl(), writeb(), writew(), and writel(). These APIs provide a consistent interface for reading and writing device registers while preserving the semantics required by different CPU architectures.

To understand their behavior, I extended the MMIO simulator from Day95 and implemented Linux-style MMIO access APIs.


What I Learned

  • The purpose of readb(), readw(), readl(), writeb(), writew(), and writel()
  • The difference between normal memory access and MMIO access
  • Why Linux drivers use dedicated MMIO APIs instead of directly dereferencing pointers
  • How Little Endian affects register layout in memory
  • Accessing registers using base address plus register offsets
  • Validating MMIO mapping boundaries before each access
  • Managing multiple independent MMIO mappings simultaneously

Implementation

Implemented a Linux-style MMIO access layer.

utils/memory/io/
├── io_mapping.c
├── io_mapping.h
├── ioremap.c
├── ioremap.h
├── mmio_access.c
└── mmio_access.h

Responsibilities:

  • io_mapping
  • Maintain MMIO mapping information
  • Translate virtual addresses into backing memory
  • Validate mapping boundaries

  • ioremap

  • Create virtual mappings
  • Remove mappings

  • mmio_access

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

The simulator assumes a Little Endian architecture, matching Raspberry Pi, ARM64, and x86 systems.


Labs Completed

Lab 1 — 8-bit MMIO Access

Implemented and verified:

  • writeb()
  • readb()

Lab 2 — 16-bit MMIO Access

Implemented and verified:

  • writew()
  • readw()

Lab 3 — 32-bit MMIO Access

Implemented and verified:

  • writel()
  • readl()

Lab 4 — Little Endian Memory Layout

Verified the byte layout of a 32-bit register.

Value

0x12345678

Memory

+0  78
+1  56
+2  34
+3  12

This demonstrates how a 32-bit register value is stored in Little Endian systems.


Lab 5 — Register Layout with Offsets

Implemented Linux-style register access using register offsets.

base + REG_CTRL
base + REG_STATUS
base + REG_DATA

Verified that each register can be accessed independently.


Lab 6 — Boundary Validation

Validated that every MMIO access remains inside the mapped region.

Verified:

  • last valid 32-bit access succeeds
  • out-of-range access is rejected
  • no partial write occurs

Lab 7 — Multiple MMIO Mappings

Created two independent MMIO mappings.

Verified:

  • different physical addresses map to different virtual addresses
  • each mapping owns independent backing memory
  • accesses to one mapping do not affect the other

Summary

Today I completed a Linux-style MMIO access layer on top of the ioremap() simulator.

The implementation now supports:

  • Linux MMIO access APIs
  • 8-bit, 16-bit, and 32-bit register access
  • Little Endian register layout
  • Register offset access
  • Boundary validation
  • Multiple independent MMIO mappings

With Day95 and Day96 completed, the simulator now models the complete MMIO workflow used by Linux drivers:

Physical Address
ioremap()
Virtual MMIO Address
readb()/readw()/readl()
writeb()/writew()/writel()

This mirrors the programming model used by real Linux platform drivers when accessing hardware registers.