RTC Driver Implementation

Relevant source files

This document covers the core implementation details of the RISC-V Goldfish RTC driver, including the main Rtc struct, its methods, and the underlying hardware abstraction mechanisms. The implementation provides a clean interface for reading and writing Unix timestamps to Goldfish RTC hardware through memory-mapped I/O operations.

For complete API documentation and usage examples, see API Reference. For detailed hardware register mapping and MMIO specifics, see Hardware Interface. For in-depth time conversion algorithms, see Time Conversion.

Core Driver Structure

The RTC driver is implemented as a single Rtc struct that encapsulates all functionality for interfacing with the Goldfish RTC hardware. The driver follows a minimalist design pattern suitable for embedded and bare-metal environments.

flowchart TD
subgraph subGraph2["Hardware Constants"]
    RtcTimeLow["RTC_TIME_LOW = 0x00"]
    RtcTimeHigh["RTC_TIME_HIGH = 0x04"]
    NsecPerSec["NSEC_PER_SEC = 1_000_000_000"]
end
subgraph subGraph1["Internal Methods"]
    ReadMethod["read(reg: usize) -> u32"]
    WriteMethod["write(reg: usize, value: u32)"]
end
subgraph subGraph0["Rtc Struct Implementation"]
    RtcStruct["Rtc { base_address: usize }"]
    NewMethod["new(base_address: usize) -> Self"]
    GetMethod["get_unix_timestamp() -> u64"]
    SetMethod["set_unix_timestamp(unix_time: u64)"]
end

GetMethod --> NsecPerSec
GetMethod --> ReadMethod
NewMethod --> RtcStruct
ReadMethod --> RtcTimeHigh
ReadMethod --> RtcTimeLow
SetMethod --> NsecPerSec
SetMethod --> WriteMethod
WriteMethod --> RtcTimeHigh
WriteMethod --> RtcTimeLow

Sources: src/lib.rs(L11 - L50) 

Memory-Mapped I/O Layer

The driver implements a low-level hardware abstraction through unsafe memory operations. All hardware access is channeled through two core methods that handle volatile memory operations to prevent compiler optimizations from interfering with hardware communication.

MethodPurposeSafetyReturn Type
read(reg: usize)Read 32-bit value from hardware registerUnsafe - requires valid base addressu32
write(reg: usize, value: u32)Write 32-bit value to hardware registerUnsafe - requires valid base address()
flowchart TD
subgraph subGraph3["Physical Hardware"]
    RtcHardware["Goldfish RTC Registers"]
end
subgraph subGraph2["Hardware Layer"]
    VolatileRead["core::ptr::read_volatile()"]
    VolatileWrite["core::ptr::write_volatile()"]
    BaseAddr["(base_address + reg) as *const/*mut u32"]
end
subgraph subGraph1["Abstraction Layer"]
    ReadReg["read(reg)"]
    WriteReg["write(reg, value)"]
end
subgraph subGraph0["Software Layer"]
    GetTimestamp["get_unix_timestamp()"]
    SetTimestamp["set_unix_timestamp()"]
end

BaseAddr --> RtcHardware
GetTimestamp --> ReadReg
ReadReg --> VolatileRead
SetTimestamp --> WriteReg
VolatileRead --> BaseAddr
VolatileWrite --> BaseAddr
WriteReg --> VolatileWrite

Sources: src/lib.rs(L17 - L24) 

Driver Initialization and Configuration

The Rtc::new() constructor provides the single entry point for driver instantiation. The method requires a base memory address that corresponds to the hardware device's memory-mapped location, typically obtained from device tree parsing during system initialization.

flowchart TD
subgraph subGraph1["Usage Pattern"]
    CreateInstance["let rtc = Rtc::new(0x101000)"]
    GetTime["rtc.get_unix_timestamp()"]
    SetTime["rtc.set_unix_timestamp(epoch)"]
end
subgraph subGraph0["Initialization Flow"]
    DeviceTree["Device Treertc@101000"]
    BaseAddr["base_addr = 0x101000"]
    NewCall["Rtc::new(base_addr)"]
    RtcInstance["Rtc { base_address: 0x101000 }"]
end

BaseAddr --> NewCall
CreateInstance --> GetTime
CreateInstance --> SetTime
DeviceTree --> BaseAddr
NewCall --> RtcInstance
RtcInstance --> CreateInstance

The constructor performs no hardware validation or initialization - it simply stores the provided base address for future memory operations. This design assumes that hardware initialization and memory mapping have been handled by the system's device management layer.

Sources: src/lib.rs(L27 - L33)  README.md(L10 - L16) 

64-bit Register Handling

The Goldfish RTC hardware stores time as a 64-bit nanosecond value split across two consecutive 32-bit registers. The driver handles this split through careful register sequencing and bit manipulation to maintain data integrity during multi-register operations.

flowchart TD
subgraph subGraph1["64-bit Write Operation"]
    subgraph subGraph0["64-bit Read Operation"]
        InputUnix["unix_time: u64"]
        ConvertNanos["unix_time * NSEC_PER_SEC"]
        SplitHigh["(time_nanos >> 32) as u32"]
        SplitLow["time_nanos as u32"]
        WriteHigh["write(RTC_TIME_HIGH, high)"]
        WriteLow["write(RTC_TIME_LOW, low)"]
        ReadLow["read(RTC_TIME_LOW) -> u32"]
        CombineBits["(high << 32) | low"]
        ReadHigh["read(RTC_TIME_HIGH) -> u32"]
        ConvertTime["nanoseconds / NSEC_PER_SEC"]
        ReturnUnix["return unix_timestamp"]
    end
end

CombineBits --> ConvertTime
ConvertNanos --> SplitHigh
ConvertNanos --> SplitLow
ConvertTime --> ReturnUnix
InputUnix --> ConvertNanos
ReadHigh --> CombineBits
ReadLow --> CombineBits
SplitHigh --> WriteHigh
SplitLow --> WriteLow

The write operation follows a specific sequence where the high register is written first, followed by the low register. This ordering ensures atomic updates from the hardware perspective and prevents temporal inconsistencies during timestamp updates.

Sources: src/lib.rs(L36 - L49) 

Error Handling and Safety Model

The driver implementation uses Rust's unsafe blocks to perform direct memory access while maintaining memory safety through controlled access patterns. The safety model relies on several assumptions:

  • The provided base_address points to valid, mapped memory
  • The memory region corresponds to actual Goldfish RTC hardware
  • Concurrent access is managed by the calling code
  • The hardware registers maintain their expected layout
flowchart TD
subgraph subGraph1["Caller Responsibilities"]
    ValidAddr["Provide valid base_address"]
    MemoryMap["Ensure memory mapping"]
    Concurrency["Handle concurrent access"]
end
subgraph subGraph0["Safety Boundaries"]
    SafeAPI["Safe Public API"]
    UnsafeInternal["Unsafe Internal Methods"]
    HardwareLayer["Hardware Memory"]
end

Concurrency --> SafeAPI
MemoryMap --> SafeAPI
SafeAPI --> UnsafeInternal
UnsafeInternal --> HardwareLayer
ValidAddr --> SafeAPI

The driver does not perform runtime validation of memory addresses or hardware responses, prioritizing performance and simplicity for embedded use cases where system-level guarantees about hardware presence and memory mapping are typically established during boot.

Sources: src/lib.rs(L17 - L24)