Skip to main content

ax_arm_pl031/
lib.rs

1//! System Real Time Clock (RTC) Drivers for aarch64 based on PL031.
2
3#![cfg_attr(not(test), no_std)]
4#![deny(
5    clippy::missing_safety_doc,
6    clippy::undocumented_unsafe_blocks,
7    unsafe_op_in_unsafe_fn
8)]
9
10#[cfg(feature = "chrono")]
11mod chrono;
12
13use core::ptr::{addr_of, addr_of_mut};
14
15#[derive(Default)]
16#[repr(C, align(4))]
17struct Registers {
18    /// Data register
19    dr: u32,
20    /// Match register
21    mr: u32,
22    /// Load register
23    lr: u32,
24    /// Control register
25    cr: u8,
26    _reserved0: [u8; 3],
27    /// Interrupt Mask Set or Clear register
28    imsc: u8,
29    _reserved1: [u8; 3],
30    /// Raw Interrupt Status
31    ris: u8,
32    _reserved2: [u8; 3],
33    /// Masked Interrupt Status
34    mis: u8,
35    _reserved3: [u8; 3],
36    /// Interrupt Clear Register
37    icr: u8,
38    _reserved4: [u8; 3],
39}
40
41/// The System Real Time Clock structure for aarch64 based on PL031.
42pub struct Rtc {
43    registers: *mut Registers,
44}
45
46impl Rtc {
47    /// Constructs a new instance of the RTC driver for a PL031 device at the given base address.
48    ///
49    /// The base address may be obtained from the device tree.
50    ///
51    /// # Safety
52    ///
53    /// The given base address must point to the MMIO control registers of a PL031 device, which
54    /// must be mapped into the address space of the process as device memory and not have any other
55    /// aliases. It must be aligned to a 4 byte boundary.
56    pub unsafe fn new(base_address: *mut u32) -> Self {
57        Rtc {
58            registers: base_address as _,
59        }
60    }
61
62    /// Returns the current time in seconds since UNIX epoch.
63    pub fn get_unix_timestamp(&self) -> u32 {
64        // SAFETY: We know that self.registers points to the control registers
65        // of a PL031 device which is appropriately mapped.
66        unsafe { addr_of!((*self.registers).dr).read_volatile() }
67    }
68
69    /// Sets the current time in seconds since UNIX epoch.
70    pub fn set_unix_timestamp(&mut self, unix_time: u32) {
71        // SAFETY: We know that self.registers points to the control registers
72        // of a PL031 device which is appropriately mapped.
73        unsafe { addr_of_mut!((*self.registers).lr).write_volatile(unix_time) }
74    }
75
76    /// Writes a match value. When the RTC value matches this then an interrupt
77    /// will be generated (if it is enabled).
78    pub fn set_match_timestamp(&mut self, match_timestamp: u32) {
79        // SAFETY: We know that self.registers points to the control registers
80        // of a PL031 device which is appropriately mapped.
81        unsafe { addr_of_mut!((*self.registers).mr).write_volatile(match_timestamp) }
82    }
83
84    /// Returns whether the match register matches the RTC value, whether or not
85    /// the interrupt is enabled.
86    pub fn matched(&self) -> bool {
87        // SAFETY: We know that self.registers points to the control registers
88        // of a PL031 device which is appropriately mapped.
89        let ris = unsafe { addr_of!((*self.registers).ris).read_volatile() };
90        (ris & 0x01) != 0
91    }
92
93    /// Returns whether there is currently an interrupt pending.
94    ///
95    /// This should be true if and only if `matched` returns true and the
96    /// interrupt is masked.
97    pub fn interrupt_pending(&self) -> bool {
98        // SAFETY: We know that self.registers points to the control registers
99        // of a PL031 device which is appropriately mapped.
100        let ris = unsafe { addr_of!((*self.registers).mis).read_volatile() };
101        (ris & 0x01) != 0
102    }
103
104    /// Sets or clears the interrupt mask.
105    ///
106    /// When the mask is true the interrupt is enabled; when it is false the
107    /// interrupt is disabled.
108    pub fn enable_interrupt(&mut self, mask: bool) {
109        let imsc = if mask { 0x01 } else { 0x00 };
110        // SAFETY: We know that self.registers points to the control registers
111        // of a PL031 device which is appropriately mapped.
112        unsafe { addr_of_mut!((*self.registers).imsc).write_volatile(imsc) }
113    }
114
115    /// Clears a pending interrupt, if any.
116    pub fn clear_interrupt(&mut self) {
117        // SAFETY: We know that self.registers points to the control registers
118        // of a PL031 device which is appropriately mapped.
119        unsafe { addr_of_mut!((*self.registers).icr).write_volatile(0x01) }
120    }
121}
122
123// SAFETY: `Rtc` just contains a pointer to device memory, which can be accessed from any context.
124unsafe impl Send for Rtc {}
125
126// SAFETY: An `&Rtc` only allows reading device registers, which can safety be done from multiple
127// places at once.
128unsafe impl Sync for Rtc {}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_get_timestamp() {
136        let mut registers = Registers {
137            dr: 12345678,
138            ..Registers::default()
139        };
140
141        // SAFETY: The pointer is constructed from a reference so it must be valid
142        let rtc = unsafe { Rtc::new(&mut registers as *mut Registers as _) };
143
144        assert_eq!(rtc.get_unix_timestamp(), 12345678);
145    }
146
147    #[test]
148    fn test_set_timestamp() {
149        let mut registers = Registers::default();
150
151        // SAFETY: The pointer is constructed from a reference so it must be valid
152        let mut rtc = unsafe { Rtc::new(&mut registers as *mut Registers as _) };
153
154        rtc.set_unix_timestamp(424242);
155        drop(rtc);
156        assert_eq!(registers.lr, 424242);
157    }
158}