Skip to content

Day96 - MMIO Access APIs

Objective

In this lab, we implement Linux-style MMIO access APIs on top of the ioremap() simulator built in Day95.

The goal is to understand how Linux drivers access hardware registers using readb(), readw(), readl(), writeb(), writew(), and writel() instead of directly dereferencing pointers.


Background

After a device's physical registers are mapped into the kernel virtual address space using ioremap(), Linux drivers access registers through dedicated MMIO access APIs.

Typical Linux driver code looks like this:

void __iomem *base;

base = ioremap(PHYS_ADDR, SIZE);

writel(CTRL_ENABLE, base + REG_CTRL);

status = readl(base + REG_STATUS);

These APIs provide a portable interface for accessing device registers across different CPU architectures.


Lab Architecture

                 Physical Device
              0x3F200000 - 0x3F2000FF
                  ioremap()
              Virtual MMIO Address
                 0x20000000
        readb()/readw()/readl()
       writeb()/writew()/writel()
               Backing Memory

APIs Implemented

API Purpose
readb() Read an 8-bit MMIO register
readw() Read a 16-bit MMIO register
readl() Read a 32-bit MMIO register
writeb() Write an 8-bit MMIO register
writew() Write a 16-bit MMIO register
writel() Write a 32-bit MMIO register

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


Lab 1 — 8-bit MMIO Access

Implement and verify:

  • writeb()
  • readb()

Expected result:

[CASE1] 8-bit MMIO access
...
Result : PASS

Lab 2 — 16-bit MMIO Access

Implement and verify:

  • writew()
  • readw()

Expected result:

[CASE2] 16-bit MMIO access
...
Result : PASS

Lab 3 — 32-bit MMIO Access

Implement and verify:

  • writel()
  • readl()

Expected result:

[CASE3] 32-bit MMIO access
...
Result : PASS

Lab 4 — Little Endian Memory Layout

Write the following value:

0x12345678

Verify the bytes stored in memory:

Address +0 : 0x78
Address +1 : 0x56
Address +2 : 0x34
Address +3 : 0x12

Expected result:

[CASE4] Little-endian memory layout
...
Result : PASS

Lab 5 — Register Layout with Offsets

Implement register accesses using register offsets.

#define REG_CTRL_OFFSET      0x20
#define REG_STATUS_OFFSET    0x24
#define REG_DATA_OFFSET      0x28

Example:

writel(1, base + REG_CTRL_OFFSET);

status = readl(base + REG_STATUS_OFFSET);

data = readl(base + REG_DATA_OFFSET);

Expected result:

[CASE5] Register layout with offsets
...
Result : PASS

Lab 6 — Boundary Validation

Verify that every MMIO access stays within the mapped region.

Test:

  • last valid 32-bit access
  • invalid access beyond the mapping boundary

Expected result:

[CASE6] Boundary validation
...
Result : PASS

Lab 7 — Multiple MMIO Mappings

Create two independent MMIO mappings.

Verify:

  • different physical addresses
  • different virtual addresses
  • independent register values

Expected result:

[CASE7] Multiple mappings
...
Result : PASS

Summary

In this lab, we implemented Linux-style MMIO access APIs and verified:

  • 8-bit, 16-bit, and 32-bit register access
  • Little Endian register layout
  • Register access using offsets
  • Mapping boundary validation
  • Multiple independent MMIO mappings

The completed MMIO workflow is now:

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

This is the same programming model used by Linux platform drivers to access hardware registers.