Register Operations

Relevant source files

This page provides comprehensive documentation of the PL031 register operations implemented in the ARM PL031 RTC driver. It covers the register memory layout, volatile access patterns, and the meaning of each hardware register. For information about the broader hardware interface and MMIO concepts, see Hardware Interface and MMIO. For details about interrupt-specific register usage, see Interrupt Handling.

Register Layout and Memory Mapping

The PL031 device exposes its functionality through a set of memory-mapped registers. The driver defines these registers using a C-compatible structure that matches the hardware layout exactly.

Memory Layout Structure

The register layout is defined in the Registers struct which uses precise memory alignment and padding to match the PL031 hardware specification:

flowchart TD
subgraph subGraph0["Registers Struct Layout"]
    DR["dr: u32Data RegisterOffset: 0x00"]
    MR["mr: u32Match RegisterOffset: 0x04"]
    LR["lr: u32Load RegisterOffset: 0x08"]
    CR["cr: u8 + paddingControl RegisterOffset: 0x0C"]
    IMSC["imsc: u8 + paddingInterrupt MaskOffset: 0x10"]
    RIS["ris: u8 + paddingRaw Interrupt StatusOffset: 0x14"]
    MIS["mis: u8 + paddingMasked Interrupt StatusOffset: 0x18"]
    ICR["icr: u8 + paddingInterrupt ClearOffset: 0x1C"]
end

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

The structure uses #[repr(C, align(4))] to ensure proper memory layout and alignment with the hardware expectations. Reserved fields with specific padding ensure that each register appears at the correct memory offset.

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

Register Access Patterns

All register operations use volatile memory access to prevent compiler optimizations that could interfere with hardware communication:

flowchart TD
subgraph subGraph1["Write Operations"]
    subgraph subGraph0["Read Operations"]
        WRITE_START["Function Call"]
        WRITE_PTR["addr_of_mut!((*registers).field)"]
        WRITE_VOLATILE["write_volatile(value)"]
        WRITE_COMPLETE["Hardware Updated"]
        READ_START["Function Call"]
        READ_PTR["addr_of!((*registers).field)"]
        READ_VOLATILE["read_volatile()"]
        READ_RESULT["Return Value"]
    end
end

READ_PTR --> READ_VOLATILE
READ_START --> READ_PTR
READ_VOLATILE --> READ_RESULT
WRITE_PTR --> WRITE_VOLATILE
WRITE_START --> WRITE_PTR
WRITE_VOLATILE --> WRITE_COMPLETE

Sources: src/lib.rs(L64 - L66)  src/lib.rs(L70 - L74)  src/lib.rs(L78 - L82) 

Individual Register Operations

Data Register (DR)

The Data Register provides read-only access to the current RTC value as a 32-bit Unix timestamp.

FieldTypeAccessPurpose
dru32Read-onlyCurrent time in seconds since Unix epoch

The get_unix_timestamp() method reads this register:

sequenceDiagram
    participant Application as "Application"
    participant Rtcget_unix_timestamp as "Rtc::get_unix_timestamp()"
    participant PL031Hardware as "PL031 Hardware"

    Application ->> Rtcget_unix_timestamp: "get_unix_timestamp()"
    Rtcget_unix_timestamp ->> PL031Hardware: "addr_of!((*registers).dr).read_volatile()"
    PL031Hardware -->> Rtcget_unix_timestamp: "u32 timestamp"
    Rtcget_unix_timestamp -->> Application: "u32 timestamp"

Sources: src/lib.rs(L18)  src/lib.rs(L62 - L67) 

Load Register (LR)

The Load Register allows setting the current RTC time by writing a 32-bit Unix timestamp.

FieldTypeAccessPurpose
lru32Write-onlySets current time in seconds since Unix epoch

The set_unix_timestamp() method writes to this register:

Sources: src/lib.rs(L22)  src/lib.rs(L69 - L74) 

Match Register (MR)

The Match Register stores a timestamp value that triggers an interrupt when the RTC time matches it.

FieldTypeAccessPurpose
mru32Write-onlyAlarm time for interrupt generation

The set_match_timestamp() method configures this register:

Sources: src/lib.rs(L20)  src/lib.rs(L76 - L82) 

Interrupt Control Registers

The PL031 provides several registers for interrupt management:

RegisterFieldTypePurpose
IMSCimscu8Interrupt mask control (enable/disable)
RISrisu8Raw interrupt status (always shows match state)
MISmisu8Masked interrupt status (shows pending interrupts)
ICRicru8Interrupt clear (write to acknowledge)
flowchart TD
subgraph subGraph0["Interrupt Register Flow"]
    MATCH["Hardware Match Event"]
    RIS_SET["RIS[0] = 1"]
    IMSC_CHECK["Check IMSC[0]"]
    MIS_SET["MIS[0] = RIS[0] & IMSC[0]"]
    IRQ["Interrupt Signal"]
    ICR_WRITE["Write ICR[0] = 1"]
    CLEAR["Clear RIS[0] and MIS[0]"]
end

ICR_WRITE --> CLEAR
IMSC_CHECK --> MIS_SET
IRQ --> ICR_WRITE
MATCH --> RIS_SET
MIS_SET --> IRQ
RIS_SET --> IMSC_CHECK

Sources: src/lib.rs(L27 - L38)  src/lib.rs(L104 - L113)  src/lib.rs(L115 - L120) 

Volatile Access Safety

All register operations are performed within unsafe blocks due to the raw pointer dereference required for MMIO access. The driver ensures safety through several mechanisms:

Memory Safety Guarantees

  • The Rtc::new() constructor requires the caller to provide safety guarantees about the base address
  • All register access uses addr_of! and addr_of_mut! macros to create properly aligned pointers
  • Volatile operations prevent compiler optimizations that could reorder or eliminate hardware accesses
flowchart TD
subgraph subGraph1["Safety Documentation"]
    DOC_NEW["Safety doc for new()"]
    DOC_BLOCKS["Safety comments in blocks"]
end
subgraph subGraph0["Safety Boundaries"]
    UNSAFE_NEW["unsafe fn new(base_address)"]
    SAFE_METHODS["Safe public methods"]
    UNSAFE_BLOCKS["Unsafe register access blocks"]
    HARDWARE["Hardware registers"]
end

DOC_BLOCKS --> UNSAFE_BLOCKS
DOC_NEW --> UNSAFE_NEW
SAFE_METHODS --> UNSAFE_BLOCKS
UNSAFE_BLOCKS --> HARDWARE
UNSAFE_NEW --> SAFE_METHODS

Sources: src/lib.rs(L51 - L60)  src/lib.rs(L64 - L66)  src/lib.rs(L72 - L74) 

Register Field Access Patterns

The driver uses consistent patterns for accessing register fields:

OperationPatternExample
Read u32addr_of!((*registers).field).read_volatile()DR, RIS, MIS
Write u32addr_of_mut!((*registers).field).write_volatile(value)LR, MR
Read u8addr_of!((*registers).field).read_volatile()Status registers
Write u8addr_of_mut!((*registers).field).write_volatile(value)Control registers

All register operations include safety comments explaining why the unsafe operation is valid within the context of the driver's safety guarantees.

Sources: src/lib.rs(L64 - L74)  src/lib.rs(L88 - L90)  src/lib.rs(L99 - L101)  src/lib.rs(L111 - L112)  src/lib.rs(L118 - L119)