System Architecture
Relevant source files
Purpose and Scope
This document explains the high-level architecture of the arm_pl031 crate, focusing on how the driver abstracts ARM PL031 RTC hardware through layered components, safety boundaries, and feature integration. It covers the structural relationships between the core Rtc
driver, hardware register interface, and optional feature extensions.
For implementation details of specific components, see Core Driver Implementation. For practical usage guidance, see Getting Started.
Overall Architecture Overview
The arm_pl031 crate implements a layered architecture that provides safe, high-level access to ARM PL031 RTC hardware while maintaining clear separation between abstraction levels.
flowchart TD subgraph subGraph3["Safety Boundary"] UNSAFE_OPS["unsafe operations"] end subgraph subGraph2["Hardware Layer"] MMIO["Memory-Mapped I/O"] PL031_HW["ARM PL031 Hardware"] end subgraph subGraph1["arm_pl031 Crate"] RTC["Rtc struct"] REGISTERS["Registers struct"] CHRONO_MOD["chrono module"] end subgraph subGraph0["Application Layer"] APP["Application Code"] CHRONO_API["chrono::DateTime API"] UNIX_API["Unix Timestamp API"] end APP --> CHRONO_API APP --> UNIX_API CHRONO_API --> CHRONO_MOD CHRONO_MOD --> RTC MMIO --> PL031_HW REGISTERS --> MMIO REGISTERS --> UNSAFE_OPS RTC --> REGISTERS UNIX_API --> RTC UNSAFE_OPS --> MMIO
Architecture Diagram: Overall System Layers
This architecture provides multiple abstraction levels, allowing developers to choose between high-level DateTime
operations or low-level timestamp manipulation while maintaining memory safety through controlled unsafe boundaries.
Sources: src/lib.rs(L1 - L159)
Hardware Abstraction Layers
The driver implements a three-tier abstraction model that isolates hardware specifics while providing flexible access patterns.
flowchart TD subgraph subGraph2["Hardware Interface"] READ_VOLATILE["read_volatile()"] WRITE_VOLATILE["write_volatile()"] ADDR_OF["addr_of!()"] ADDR_OF_MUT["addr_of_mut!()"] end subgraph subGraph1["Register Abstraction"] DR["dr: u32"] LR["lr: u32"] MR["mr: u32"] IMSC["imsc: u8"] MIS["mis: u8"] ICR["icr: u8"] end subgraph subGraph0["Safe Rust Interface"] GET_TIME["get_unix_timestamp()"] SET_TIME["set_unix_timestamp()"] INTERRUPTS["enable_interrupt()"] MATCH_OPS["set_match_timestamp()"] end DR --> READ_VOLATILE GET_TIME --> DR IMSC --> WRITE_VOLATILE INTERRUPTS --> IMSC LR --> WRITE_VOLATILE MATCH_OPS --> MR MR --> WRITE_VOLATILE READ_VOLATILE --> ADDR_OF SET_TIME --> LR WRITE_VOLATILE --> ADDR_OF_MUT
Architecture Diagram: Hardware Abstraction Tiers
Each tier serves a specific purpose:
- Safe Interface: Provides memory-safe methods with clear semantics
- Register Abstraction: Maps PL031 registers to Rust types with proper layout
- Hardware Interface: Handles volatile memory operations with explicit safety documentation
Sources: src/lib.rs(L15 - L39) src/lib.rs(L62 - L121)
Component Architecture and Relationships
The core architecture centers around two primary structures that encapsulate hardware interaction and provide the driver interface.
Component | Purpose | Safety Level | Key Responsibilities |
---|---|---|---|
Rtc | Driver interface | Safe wrapper | Time operations, interrupt management |
Registers | Hardware layout | Memory representation | Register field mapping, memory alignment |
chronomodule | Feature extension | Safe wrapper | DateTime conversion, high-level API |
flowchart TD subgraph subGraph2["Memory Operations"] PTR_DEREF["(*self.registers)"] VOLATILE_READ["addr_of!(field).read_volatile()"] VOLATILE_WRITE["addr_of_mut!(field).write_volatile(value)"] end subgraph subGraph1["Register Layout Structure"] REG_STRUCT["#[repr(C, align(4))] Registers"] DR_FIELD["dr: u32"] MR_FIELD["mr: u32"] LR_FIELD["lr: u32"] CONTROL_FIELDS["cr, imsc, ris, mis, icr: u8"] RESERVED["_reserved0..4: [u8; 3]"] end subgraph subGraph0["Rtc Driver Structure"] RTC_STRUCT["Rtc { registers: *mut Registers }"] NEW_METHOD["unsafe fn new(base_address: *mut u32)"] GET_TIMESTAMP["fn get_unix_timestamp() -> u32"] SET_TIMESTAMP["fn set_unix_timestamp(unix_time: u32)"] INTERRUPT_METHODS["interrupt management methods"] end GET_TIMESTAMP --> PTR_DEREF INTERRUPT_METHODS --> PTR_DEREF NEW_METHOD --> RTC_STRUCT PTR_DEREF --> VOLATILE_READ PTR_DEREF --> VOLATILE_WRITE RTC_STRUCT --> REG_STRUCT SET_TIMESTAMP --> PTR_DEREF VOLATILE_READ --> DR_FIELD VOLATILE_WRITE --> LR_FIELD VOLATILE_WRITE --> MR_FIELD
Architecture Diagram: Core Component Relationships
The Rtc
struct maintains a single pointer to a Registers
structure, which provides C-compatible memory layout matching the PL031 hardware specification. All hardware access flows through volatile pointer operations to ensure proper memory semantics.
Sources: src/lib.rs(L15 - L44) src/lib.rs(L46 - L61)
Safety Model and Boundaries
The driver implements a clear safety model that contains all unsafe operations at the hardware interface boundary while providing safe abstractions for application code.
flowchart TD subgraph subGraph2["Hardware Zone"] MMIO_ACCESS["Direct MMIO register access"] DEVICE_MEMORY["PL031 device memory"] end subgraph subGraph1["Unsafe Boundary"] UNSAFE_NEW["unsafe fn new()"] SAFETY_INVARIANTS["Safety invariants and documentation"] VOLATILE_OPS["Volatile memory operations"] end subgraph subGraph0["Safe Zone"] SAFE_METHODS["Public safe methods"] TIMESTAMP_OPS["get_unix_timestamp, set_unix_timestamp"] INTERRUPT_OPS["enable_interrupt, clear_interrupt"] MATCH_OPS["set_match_timestamp, matched"] end subgraph subGraph3["Concurrency Safety"] SEND_IMPL["unsafe impl Send for Rtc"] THREAD_SAFETY["Multi-thread access guarantees"] SYNC_IMPL["unsafe impl Sync for Rtc"] end UNSAFE_BOUNDARY["UNSAFE_BOUNDARY"] INTERRUPT_OPS --> VOLATILE_OPS MATCH_OPS --> VOLATILE_OPS MMIO_ACCESS --> DEVICE_MEMORY SAFE_METHODS --> UNSAFE_BOUNDARY SEND_IMPL --> THREAD_SAFETY SYNC_IMPL --> THREAD_SAFETY TIMESTAMP_OPS --> VOLATILE_OPS UNSAFE_NEW --> SAFETY_INVARIANTS VOLATILE_OPS --> MMIO_ACCESS
Architecture Diagram: Safety Boundaries and Concurrency Model
Safety Requirements
The driver enforces specific safety requirements at the boundary:
- Memory Safety: The
base_address
parameter inRtc::new()
must point to valid PL031 MMIO registers - Alignment: Hardware addresses must be 4-byte aligned as enforced by
#[repr(C, align(4))]
- Exclusive Access: No other aliases to the device memory region are permitted
- Device Mapping: Memory must be mapped as device memory, not normal cacheable memory
Concurrency Safety
The driver provides thread-safety through carefully designed Send
and Sync
implementations:
Send
: Safe because device memory access works from any thread contextSync
: Safe because shared read access to device registers is inherently safe, and mutation requires&mut self
Sources: src/lib.rs(L51 - L60) src/lib.rs(L123 - L128)
Feature Integration Architecture
The crate supports optional feature integration through Cargo feature flags, with the chrono integration serving as the primary example of the extensibility model.
Architecture Diagram: Feature Integration and Build Configuration
Feature Design Principles
- Minimal Core: The base driver remains lightweight with only essential functionality
- Optional Extensions: Advanced features like DateTime support are opt-in through feature flags
- No-std Compatibility: All features maintain compatibility with embedded environments
- Layered Integration: Feature modules build upon the core API without modifying its interface
The chrono integration demonstrates this pattern by providing higher-level DateTime<Utc>
operations while delegating to the core get_unix_timestamp()
and set_unix_timestamp()
methods.
Sources: src/lib.rs(L10 - L11) src/lib.rs(L3)