Skip to content

MMIO Register Access

Overview

Memory-Mapped I/O (MMIO) is the primary mechanism used by Linux platform drivers to access hardware registers.

Instead of using dedicated I/O instructions, peripheral registers are mapped into the CPU address space and accessed through memory operations.

CPU
Address
MMIO Register

Device Tree MMIO Resource

A Device Tree node describes a register block through the reg property.

Example:

mydev@10000000 {
    compatible = "demo,mydev";
    reg = <0x0 0x10000000 0x1000>;
};

Meaning:

Base Address = 0x10000000
Size         = 0x1000

Resource Flow

Linux converts Device Tree MMIO information into a platform resource.

Device Tree
    reg
platform_get_resource()
struct resource
start/end/size

Example:

struct resource *res;

res = platform_get_resource(
        pdev,
        IORESOURCE_MEM,
        0);

Device Tree Address Translation

The address stored in reg is not always a CPU physical address.

Address translation may occur through the parent bus ranges property.

Device Tree Address
ranges
CPU Physical Address

Example:

Device Tree

gpio@7d508500


CPU Physical

107d508500

Kernel resources and /proc/iomem reflect the translated address.


MMIO Mapping

Before a driver can access registers, the MMIO resource must be mapped into kernel virtual address space.

Traditional Method

base = ioremap(
            res->start,
            resource_size(res));

Cleanup:

iounmap(base);

Managed Method

base = devm_ioremap_resource(
            &pdev->dev,
            res);

Benefits:

Ownership Check
+
ioremap()
+
Automatic Cleanup

Modern platform drivers should prefer:

devm_ioremap_resource()

__iomem

Mapped MMIO addresses use:

void __iomem *base;

Example:

struct mydev {
    void __iomem *base;
};

Purpose:

Address-space annotation

Used by:

Sparse
Kernel static analysis

It does not change pointer size.


Register Access Model

Hardware registers are accessed through:

Base Address
+
Register Offset

Example:

Offset      Register

0x00        CTRL
0x04        STATUS
0x08        DATA
0x0C        IRQ_STATUS

Definitions:

#define REG_CTRL        0x00
#define REG_STATUS      0x04
#define REG_DATA        0x08
#define REG_IRQ_STATUS  0x0c

Access:

readl(base + REG_STATUS);

writel(val,
       base + REG_CTRL);

readl() and writel()

Linux drivers should use:

readl()
writel()

instead of:

*(u32 *)addr

Reasons:

Architecture Abstraction
Memory Ordering
Endian Handling
Bus Semantics

Typical usage:

u32 val;

val = readl(base + REG_STATUS);

writel(val,
       base + REG_CTRL);

Read-Modify-Write

Most drivers update only part of a register.

Pattern:

Read
Modify
Write

Example:

reg = readl(base + REG_CTRL);

reg |= BIT(0);

writel(reg,
       base + REG_CTRL);

BIT()

Single-bit definitions:

#define CTRL_ENABLE    BIT(0)
#define CTRL_IRQ_EN    BIT(1)
#define CTRL_RESET     BIT(2)
#define CTRL_DMA_EN    BIT(3)

Enable:

reg |= CTRL_ENABLE;

Disable:

reg &= ~CTRL_ENABLE;

Toggle:

reg ^= CTRL_ENABLE;

Register Fields

Registers often contain multi-bit fields.

Example:

Bit[1:0]     MODE
Bit[4:2]     DIV
Bit[7:5]     DRIVE

GENMASK()

Generate field mask.

GENMASK(4, 2)

Result:

0x1c

FIELD_PREP()

Prepare a value for a field.

FIELD_PREP(CTRL_DIV_MASK, 5)

Equivalent to:

5 shifted into field position

FIELD_GET()

Extract field value.

FIELD_GET(CTRL_DIV_MASK, reg)

Returns:

Field value

Example:

#define CTRL_MODE_MASK   GENMASK(1, 0)
#define CTRL_DIV_MASK    GENMASK(4, 2)
#define CTRL_DRV_MASK    GENMASK(7, 5)

reg |= FIELD_PREP(CTRL_MODE_MASK, 2);
reg |= FIELD_PREP(CTRL_DIV_MASK, 5);
reg |= FIELD_PREP(CTRL_DRV_MASK, 3);

mode = FIELD_GET(CTRL_MODE_MASK, reg);
div  = FIELD_GET(CTRL_DIV_MASK, reg);
drv  = FIELD_GET(CTRL_DRV_MASK, reg);

Typical Platform Driver Flow

Device Tree reg
platform_get_resource()
struct resource
devm_ioremap_resource()
void __iomem *base
Register Offset
readl()/writel()
BIT()
Read-Modify-Write
GENMASK()
FIELD_PREP()
FIELD_GET()
  • Linux Driver Model Fundamentals
  • Platform Driver and Device Tree Binding
  • GPIO Controller Architecture
  • Linux GPIO Driver (gpiod)
  • platform_get_resource()
  • devm_ioremap_resource()
  • ioremap()
  • iounmap()
  • readl()
  • writel()
  • BIT()
  • GENMASK()
  • FIELD_PREP()
  • FIELD_GET()