pub const PL031_BASE: u64 = 0x0B00_1000;
pub const PL031_SIZE: u64 = 0x1000;
pub const PL031_FDT_SPI: u32 = 32;
const RTC_DR: u64 = 0x000;
const RTC_MR: u64 = 0x004;
const RTC_LR: u64 = 0x008;
const RTC_CR: u64 = 0x00C;
const RTC_IMSC: u64 = 0x010;
const RTC_RIS: u64 = 0x014;
const RTC_MIS: u64 = 0x018;
const RTC_ICR: u64 = 0x01C;
const AMBA_ID_BASE: u64 = 0xFE0;
const AMBA_IDS: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0D, 0xF0, 0x05, 0xB1];
pub struct Pl031 {
tick_offset: u32,
lr: u32,
mr: u32,
imsc: u32,
}
impl Pl031 {
pub fn new() -> Self {
Self {
tick_offset: 0,
lr: 0,
mr: 0,
imsc: 0,
}
}
pub fn contains(&self, addr: u64) -> bool {
(PL031_BASE..PL031_BASE + PL031_SIZE).contains(&addr)
}
fn host_secs() -> u32 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs() as u32)
}
pub fn read(&self, addr: u64, _size: usize) -> u64 {
let offset = addr - PL031_BASE;
let value = match offset {
RTC_DR => Self::host_secs().wrapping_add(self.tick_offset),
RTC_MR => self.mr,
RTC_LR => self.lr,
RTC_CR => 1,
RTC_IMSC => self.imsc,
RTC_RIS | RTC_MIS => 0,
_ if (AMBA_ID_BASE..AMBA_ID_BASE + 0x20).contains(&offset) => {
u32::from(AMBA_IDS[((offset - AMBA_ID_BASE) >> 2) as usize])
}
_ => 0,
};
u64::from(value)
}
pub fn write(&mut self, addr: u64, _size: usize, value: u64) {
let offset = addr - PL031_BASE;
let value = value as u32;
match offset {
RTC_LR => {
self.tick_offset = value.wrapping_sub(Self::host_secs());
self.lr = value;
}
RTC_MR => self.mr = value,
RTC_IMSC => self.imsc = value & 1,
RTC_CR | RTC_ICR => {}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn read32(rtc: &Pl031, offset: u64) -> u32 {
rtc.read(PL031_BASE + offset, 4) as u32
}
#[test]
fn dr_reports_host_wall_clock() {
let rtc = Pl031::new();
let before = Pl031::host_secs();
let dr = read32(&rtc, RTC_DR);
let after = Pl031::host_secs();
assert!(
(before..=after).contains(&dr),
"DR {dr} not in [{before}, {after}]"
);
}
#[test]
fn lr_write_offsets_dr_and_reads_back() {
let mut rtc = Pl031::new();
let target = 0x1000_0000u32;
rtc.write(PL031_BASE + RTC_LR, 4, u64::from(target));
assert_eq!(read32(&rtc, RTC_LR), target);
let dr = read32(&rtc, RTC_DR);
assert!(
dr.wrapping_sub(target) <= 2,
"DR {dr} not near loaded {target}"
);
}
#[test]
fn control_and_interrupt_registers() {
let mut rtc = Pl031::new();
assert_eq!(read32(&rtc, RTC_CR), 1, "RTC reads as enabled");
rtc.write(PL031_BASE + RTC_CR, 4, 0); assert_eq!(read32(&rtc, RTC_CR), 1);
rtc.write(PL031_BASE + RTC_IMSC, 4, 1);
assert_eq!(read32(&rtc, RTC_IMSC), 1);
assert_eq!(read32(&rtc, RTC_RIS), 0);
assert_eq!(read32(&rtc, RTC_MIS), 0);
rtc.write(PL031_BASE + RTC_MR, 4, 42);
assert_eq!(read32(&rtc, RTC_MR), 42);
}
#[test]
fn amba_primecell_ids_match_pl031() {
let rtc = Pl031::new();
let ids: Vec<u32> = (0..8).map(|i| read32(&rtc, AMBA_ID_BASE + i * 4)).collect();
assert_eq!(ids, vec![0x31, 0x10, 0x14, 0x00, 0x0D, 0xF0, 0x05, 0xB1]);
}
}