Hardware Interface and MMIO

Relevant source files

This document details how the ARM PL031 RTC driver interfaces with the physical hardware through Memory-Mapped I/O (MMIO) operations. It covers the register layout, volatile access patterns, memory safety boundaries, and the hardware abstraction layer implementation.

For information about specific register operations and their meanings, see Register Operations. For details about the overall driver architecture, see Driver Architecture and Design.

MMIO Register Layout

The PL031 hardware interface is accessed through a well-defined memory-mapped register layout represented by the Registers struct. This struct provides a direct mapping to the hardware register space with proper alignment and padding.

PL031 Register Memory Layout

flowchart TD
subgraph subGraph0["MMIO Address Space"]
    BASE["Base Address(4-byte aligned)"]
    DR["DR (0x00)Data Register32-bit"]
    MR["MR (0x04)Match Register32-bit"]
    LR["LR (0x08)Load Register32-bit"]
    CR["CR (0x0C)Control Register8-bit + 3 reserved"]
    IMSC["IMSC (0x10)Interrupt Mask8-bit + 3 reserved"]
    RIS["RIS (0x14)Raw Interrupt Status8-bit + 3 reserved"]
    MIS["MIS (0x18)Masked Interrupt Status8-bit + 3 reserved"]
    ICR["ICR (0x1C)Interrupt Clear8-bit + 3 reserved"]
end

BASE --> DR
CR --> IMSC
DR --> MR
IMSC --> RIS
LR --> CR
MIS --> ICR
MR --> LR
RIS --> MIS

The Registers struct enforces proper memory layout through careful use of padding and alignment directives. Each register is positioned according to the PL031 specification, with reserved bytes maintaining proper spacing between hardware registers.

Sources: src/lib.rs(L15 - L39) 

Hardware Access Abstraction

The driver implements a three-layer abstraction for hardware access, ensuring both safety and performance while maintaining direct hardware control.

Hardware Access Layers

flowchart TD
subgraph subGraph1["Hardware Layer"]
    MMIO["Memory-Mapped I/ODevice Memory Region"]
    REGS["PL031 Hardware RegistersPhysical Hardware"]
end
subgraph subGraph0["Software Layers"]
    API["Public API Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"]
    UNSAFE["Unsafe Hardware Accessaddr_of!()read_volatile()write_volatile()"]
    PTR["Raw Pointer Operations(*self.registers).dr(*self.registers).mr"]
end

API --> UNSAFE
MMIO --> REGS
PTR --> MMIO
UNSAFE --> PTR

The Rtc struct maintains a single *mut Registers pointer that serves as the bridge between safe Rust code and raw hardware access. All hardware operations go through volatile memory operations to ensure proper interaction with device memory.

Sources: src/lib.rs(L42 - L44)  src/lib.rs(L56 - L60) 

Volatile Memory Operations

All hardware register access uses volatile operations to prevent compiler optimizations that could interfere with hardware behavior. The driver employs a consistent pattern for both read and write operations.

MMIO Operation Patterns

sequenceDiagram
    participant PublicAPI as "Public API"
    participant UnsafeBlock as "Unsafe Block"
    participant addr_ofaddr_of_mut as "addr_of!/addr_of_mut!"
    participant read_volatilewrite_volatile as "read_volatile/write_volatile"
    participant PL031Hardware as "PL031 Hardware"

    Note over PublicAPI,PL031Hardware: Read Operation Pattern
    PublicAPI ->> UnsafeBlock: Call hardware access
    UnsafeBlock ->> addr_ofaddr_of_mut: addr_of!((*registers).field)
    addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .read_volatile()
    read_volatilewrite_volatile ->> PL031Hardware: Load from device memory
    PL031Hardware -->> read_volatilewrite_volatile: Register value
    read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Raw value
    addr_ofaddr_of_mut -->> UnsafeBlock: Typed value
    UnsafeBlock -->> PublicAPI: Safe result
    Note over PublicAPI,PL031Hardware: Write Operation Pattern
    PublicAPI ->> UnsafeBlock: Call hardware access
    UnsafeBlock ->> addr_ofaddr_of_mut: addr_of_mut!((*registers).field)
    addr_ofaddr_of_mut ->> read_volatilewrite_volatile: .write_volatile(value)
    read_volatilewrite_volatile ->> PL031Hardware: Store to device memory
    PL031Hardware -->> read_volatilewrite_volatile: Write acknowledgment
    read_volatilewrite_volatile -->> addr_ofaddr_of_mut: Write complete
    addr_ofaddr_of_mut -->> UnsafeBlock: Operation complete
    UnsafeBlock -->> PublicAPI: Safe completion

The driver uses addr_of! and addr_of_mut! macros to safely obtain field addresses from the raw pointer without creating intermediate references, avoiding undefined behavior while maintaining direct hardware access.

Sources: src/lib.rs(L63 - L67)  src/lib.rs(L70 - L74)  src/lib.rs(L78 - L82) 

Memory Safety Model

The hardware interface implements a carefully designed safety model that isolates unsafe operations while providing safe public APIs. The safety boundaries are clearly defined and documented.

Operation TypeSafety LevelAccess PatternUsage
ConstructionUnsafeRtc::new()Requires valid MMIO base address
Register ReadSafeget_unix_timestamp()Volatile read through safe wrapper
Register WriteSafeset_unix_timestamp()Volatile write through safe wrapper
Interrupt ControlSafeenable_interrupt()Masked volatile operations

The Rtc struct implements Send and Sync traits with careful safety justification, allowing the driver to be used in multi-threaded contexts while maintaining memory safety guarantees.

Safety Boundary Implementation

flowchart TD
subgraph subGraph2["Hardware Zone"]
    DEVICE_MEM["Device Memory"]
    PL031_HW["PL031 Hardware"]
end
subgraph subGraph1["Unsafe Zone"]
    UNSAFE_BLOCKS["unsafe blocks"]
    RAW_PTR["*mut Registers"]
    VOLATILE_OPS["volatile operations"]
end
subgraph subGraph0["Safe Zone"]
    SAFE_API["Safe Public Methods"]
    SAFE_REFS["&self / &mut self"]
end

RAW_PTR --> DEVICE_MEM
SAFE_API --> UNSAFE_BLOCKS
SAFE_REFS --> RAW_PTR
UNSAFE_BLOCKS --> VOLATILE_OPS
VOLATILE_OPS --> PL031_HW

Sources: src/lib.rs(L51 - L60)  src/lib.rs(L123 - L128) 

Address Space Configuration

The hardware interface requires proper address space configuration to function correctly. The base address must point to a valid PL031 device memory region with appropriate mapping characteristics.

Address Space Requirements

flowchart TD
subgraph subGraph2["Safety Requirements"]
    EXCLUSIVE["Exclusive AccessNo other aliases"]
    VALID["Valid MappingProcess address space"]
    DEVICE["Device Memory TypeProper memory attributes"]
end
subgraph subGraph1["Physical Hardware"]
    PL031_DEV["PL031 DeviceHardware Registers"]
    MMIO_REGION["MMIO Region32-byte space"]
end
subgraph subGraph0["Virtual Address Space"]
    BASE_ADDR["Base Address(from device tree)"]
    ALIGN["4-byte AlignmentRequired"]
    MAPPING["Device Memory MappingNon-cacheable"]
end

ALIGN --> MAPPING
BASE_ADDR --> ALIGN
BASE_ADDR --> EXCLUSIVE
MAPPING --> PL031_DEV
MAPPING --> VALID
MMIO_REGION --> DEVICE
PL031_DEV --> MMIO_REGION

The driver expects the base address to be obtained from platform-specific sources such as device tree configuration, and requires the caller to ensure proper memory mapping with device memory characteristics.

Sources: src/lib.rs(L47 - L60)