Memory Safety and Concurrency

Relevant source files

This page covers the memory safety model and concurrency characteristics of the ARM PL031 RTC driver. It explains how the driver maintains safety while performing low-level hardware operations, the boundaries between safe and unsafe code, and the thread safety guarantees provided by the implementation.

For information about the core driver architecture, see Driver Architecture and Design. For details about hardware register operations, see Register Operations.

Safety Model and Unsafe Boundaries

The arm_pl031 driver implements a layered safety model that encapsulates all unsafe hardware operations within well-defined boundaries while providing safe APIs to consumers.

Unsafe Code Isolation

The driver concentrates all unsafe operations within the core Rtc implementation, creating a clear safety boundary:

flowchart TD
subgraph subGraph2["Hardware Layer"]
    REGISTERS["PL031 Registers*mut Registers"]
    DEVICE_MEMORY["Device MemoryMMIO Region"]
end
subgraph subGraph1["Unsafe Boundary"]
    CONSTRUCTOR["unsafe fn new()Raw pointer validation"]
    MMIO_OPS["MMIO Operationsread_volatile()write_volatile()"]
end
subgraph subGraph0["Safe API Layer"]
    PUBLIC_API["Public Methodsget_unix_timestamp()set_unix_timestamp()enable_interrupt()"]
end

CONSTRUCTOR --> REGISTERS
MMIO_OPS --> REGISTERS
PUBLIC_API --> CONSTRUCTOR
PUBLIC_API --> MMIO_OPS
REGISTERS --> DEVICE_MEMORY

Sources: src/lib.rs(L46 - L60)  src/lib.rs(L63 - L120) 

Constructor Safety Requirements

The unsafe fn new() constructor establishes the fundamental safety contract for the entire driver. The caller must guarantee several critical invariants:

Safety RequirementDescriptionValidation
Valid MMIO MappingBase address points to mapped PL031 registersCaller responsibility
Exclusive AccessNo other aliases to the memory regionCaller responsibility
Device Memory TypeMemory mapped as device memory, not normal memoryCaller responsibility
AlignmentAddress aligned to 4-byte boundaryEnforced by#[repr(C, align(4))]

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

Hardware Register Access Safety

The driver uses specific patterns to ensure safe access to hardware registers while maintaining proper volatile semantics.

Volatile Access Pattern

All register operations use volatile access through addr_of! and addr_of_mut! macros to prevent undefined behavior:

flowchart TD
subgraph Hardware["Hardware"]
    DR_REGISTER["DR RegisterData Register"]
end
subgraph subGraph1["Unsafe Block"]
    ADDR_OF["addr_of!((*self.registers).dr)"]
    READ_VOLATILE["read_volatile()"]
end
subgraph subGraph0["Safe Method Call"]
    METHOD["get_unix_timestamp()"]
end

ADDR_OF --> READ_VOLATILE
METHOD --> ADDR_OF
READ_VOLATILE --> DR_REGISTER

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

Register Layout Safety

The Registers struct uses precise memory layout control to match hardware expectations:

flowchart TD
subgraph subGraph1["Layout Attributes"]
    REPR_C["#[repr(C)]C-compatible layout"]
    ALIGN_4["#[repr(align(4))]4-byte alignment"]
end
subgraph subGraph0["Memory Layout"]
    DR["dr: u32Offset 0x00"]
    MR["mr: u32Offset 0x04"]
    LR["lr: u32Offset 0x08"]
    CR["cr: u8 + paddingOffset 0x0C"]
    IMSC["imsc: u8 + paddingOffset 0x10"]
    RIS["ris: u8 + paddingOffset 0x14"]
    MIS["mis: u8 + paddingOffset 0x18"]
    ICR["icr: u8 + paddingOffset 0x1C"]
end

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

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

Thread Safety and Concurrency Model

The driver implements explicit thread safety through manual Send and Sync trait implementations with documented safety reasoning.

Send Implementation

The Send trait implementation allows the Rtc instance to be transferred between threads:

flowchart TD
subgraph Hardware["Hardware"]
    DEVICE["PL031 DeviceDevice Memory"]
end
subgraph subGraph1["Thread B"]
    RTC_B["Rtc instance"]
    ACCESS_B["Hardware access"]
end
subgraph subGraph0["Thread A"]
    RTC_A["Rtc instance"]
    MOVE["move rtc"]
end

ACCESS_B --> DEVICE
MOVE --> RTC_B
RTC_A --> MOVE
RTC_B --> ACCESS_B

The implementation is safe because device memory can be accessed from any thread context.

Sources: src/lib.rs(L123 - L124) 

Sync Implementation

The Sync trait implementation allows shared references across threads:

flowchart TD
subgraph Hardware["Hardware"]
    REGISTERS["PL031 RegistersRead-only operations"]
end
subgraph subGraph2["Shared Instance"]
    RTC_SHARED["Rtc instance"]
end
subgraph subGraph1["Thread 2"]
    REF2["&Rtc"]
    READ2["matched()"]
end
subgraph subGraph0["Thread 1"]
    REF1["&Rtc"]
    READ1["get_unix_timestamp()"]
end

READ1 --> REGISTERS
READ2 --> REGISTERS
REF1 --> RTC_SHARED
REF2 --> RTC_SHARED

The implementation is safe because shared references only allow read operations on device registers, which can be performed concurrently.

Sources: src/lib.rs(L126 - L128) 

Mutation Safety

Methods requiring &mut self ensure exclusive access for write operations:

Method CategoryReference TypeThread SafetyHardware Impact
Read Operations&selfConcurrent safeRead-only register access
Write Operations&mut selfExclusive accessRegister modification
Interrupt Management&mut selfExclusive accessControl register writes

Sources: src/lib.rs(L70 - L120) 

Memory Safety Guarantees

The driver provides several layers of memory safety guarantees through its design:

Pointer Validity

Once constructed with a valid base address, all register accesses are guaranteed to be within the mapped MMIO region:

flowchart TD
subgraph subGraph1["Runtime Guarantees"]
    NO_UB["No undefined behaviorAll accesses valid"]
    ATOMIC_OPS["Atomic register operationsSingle instruction access"]
    CONSISTENT_STATE["Consistent hardware stateProper ordering"]
end
subgraph subGraph0["Safety Invariants"]
    VALID_PTR["Valid base pointerEstablished in new()"]
    BOUNDED_ACCESS["Bounded register accessWithin Registers struct"]
    VOLATILE_OPS["Volatile operationsProper device memory handling"]
end

BOUNDED_ACCESS --> ATOMIC_OPS
VALID_PTR --> NO_UB
VOLATILE_OPS --> CONSISTENT_STATE

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

Strict Safety Lints

The crate enforces strict safety requirements through compiler lints:

  • clippy::missing_safety_doc: All unsafe functions must have safety documentation
  • clippy::undocumented_unsafe_blocks: All unsafe blocks must have safety comments
  • unsafe_op_in_unsafe_fn: Unsafe operations in unsafe functions must be explicit

Sources: src/lib.rs(L4 - L8) 

This comprehensive safety model ensures that despite the low-level hardware interaction, the driver maintains Rust's memory safety guarantees while providing efficient concurrent access to the RTC hardware.