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}