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 Type | Safety Level | Access Pattern | Usage |
---|---|---|---|
Construction | Unsafe | Rtc::new() | Requires valid MMIO base address |
Register Read | Safe | get_unix_timestamp() | Volatile read through safe wrapper |
Register Write | Safe | set_unix_timestamp() | Volatile write through safe wrapper |
Interrupt Control | Safe | enable_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)