add rtc hardware drivers and service

This commit is contained in:
bunnie 2025-11-24 18:28:00 +08:00
parent cd8a312b8d
commit d30d889774
3 changed files with 330 additions and 0 deletions

View file

@ -39,6 +39,8 @@ pub mod mbox;
#[cfg(not(feature = "hosted-baosec"))]
pub mod rram;
// no signature checking in the kernel
#[cfg(not(feature = "hosted-baosec"))]
pub mod rtc;
#[cfg(all(not(feature = "hosted-baosec"), not(feature = "kernel"),))]
pub mod sigcheck;
#[cfg(not(feature = "hosted-baosec"))]

146
libs/bao1x-hal/src/rtc.rs Normal file
View file

@ -0,0 +1,146 @@
use utralib::*;
/*
The RTC is a weird bit of IP. It's a proprietary ARM block called the "PL031".
Because we don't have the source code for it, we couldn't extract a standard UTRA
header for the block. However, apparently ~ChatGPT~ knows about it, so I was able
to ask it to help generate the following user guide.
Query: "give me a C header file for the PL031 RTC block".
Here is a concise C header for the ARM PrimeCell PL031 RTC block. Field offsets follow the ARM TRM. Adjust types or volatile rules as needed for your environment.
#ifndef PL031_H
#define PL031_H
#include <stdint.h>
#define PL031_BASE <insert location>
/* Register offsets */
#define PL031_DR 0x00 /* Data register */
#define PL031_MR 0x04 /* Match register */
#define PL031_LR 0x08 /* Load register */
#define PL031_CR 0x0C /* Control register */
#define PL031_IMSC 0x10 /* Interrupt mask set/clear */
#define PL031_RIS 0x14 /* Raw interrupt status */
#define PL031_MIS 0x18 /* Masked interrupt status */
#define PL031_ICR 0x1C /* Interrupt clear */
#define PL031_PERIPH_ID 0xFE0
#define PL031_PCELL_ID 0xFF0
/* Bit definitions */
#define PL031_CR_EN (1U << 0)
#define PL031_IMSC_MIS (1U << 0)
#define PL031_RIS_RIS (1U << 0)
#define PL031_MIS_MIS (1U << 0)
#define PL031_ICR_CLR (1U << 0)
/* Access helpers */
static inline void pl031_write(uint32_t offset, uint32_t val) {
*(volatile uint32_t *)(PL031_BASE + offset) = val;
}
static inline uint32_t pl031_read(uint32_t offset) {
return *(volatile uint32_t *)(PL031_BASE + offset);
}
#endif
LR (Load register, 0x08)
Used to set the starting epoch. On the next second boundary, LR is copied into DR and becomes the running counter.
MR (Match register, 0x04)
Stores a compare value. When DR equals MR, the block asserts its interrupt. Software typically uses this for scheduled wakeups or periodic tasks.
CR (Control register, 0x0C)
Bit 0 enables or disables the RTC. When disabled, the counter stops incrementing.
IMSC (Interrupt mask set or clear, 0x10)
Controls whether the match event generates an interrupt to the CPU. Bit 0 is the mask.
RIS (Raw interrupt status, 0x14)
Reflects the unmasked interrupt state. Set when DR equals MR.
MIS (Masked interrupt status, 0x18)
Reflects the final interrupt state after masking. This is what software normally checks.
ICR (Interrupt clear, 0x1C)
Write 1 to bit 0 to clear the match interrupt so the next event can fire.
Query: "how would i actually use this to keep time"
To use the PL031 to keep time, treat it as a free running 1 Hz counter. The hardware increments the DR register once per second. The workflow has three steps.
1. Initialize the counter
Write an initial epoch value to LR, then enable the block.
/* Set initial epoch time, for example UNIX time */
pl031_write(PL031_LR, initial_epoch_seconds);
/* Enable RTC */
pl031_write(PL031_CR, PL031_CR_EN);
The PL031 copies LR into DR on the next tick, then DR increments once every second.
2. Read the current time
Just read DR whenever you need seconds since the epoch you chose.
uint32_t now = pl031_read(PL031_DR);
If you want a fuller timestamp, convert now to a struct tm equivalent in software.
3. Generate periodic events
If your system needs periodic wakeups or scheduling:
uint32_t t = pl031_read(PL031_DR);
pl031_write(PL031_MR, t + interval_seconds);
pl031_write(PL031_IMSC, PL031_IMSC_MIS); /* unmask interrupt */
On interrupt, clear it and program the next match.
pl031_write(PL031_ICR, PL031_ICR_CLR);
Considerations
If your system sleeps, the PL031 keeps running as long as its clock domain stays powered.
If resets occur, reinitialize LR and CR.
If subsecond precision is required, this peripheral cannot supply it; layer a software counter from a higher frequency timer.
~Good enough for me!~
*/
// data register
pub const DR: Register = Register::new(0, 0xFFFF_FFFF);
// match register
pub const MR: Register = Register::new(1, 0xFFFF_FFFF);
// load register
pub const LR: Register = Register::new(2, 0xFFFF_FFFF);
// control register
pub const CR: Register = Register::new(3, 1);
pub const CR_EN: Field = Field::new(1, 0, CR);
// interrupt mask set/clear
pub const IMSC: Register = Register::new(4, 1);
pub const IMSC_MASK: Field = Field::new(1, 0, IMSC);
// raw interrupt status
pub const RIS: Register = Register::new(5, 1);
pub const RIS_STATUS: Field = Field::new(1, 0, RIS);
// masked interrupt status
pub const MIS: Register = Register::new(6, 1);
pub const MIS_STATUS: Field = Field::new(1, 0, MIS);
// interrupt clear
pub const ICR: Register = Register::new(7, 1);
pub const ICR_CLEAR: Field = Field::new(1, 0, ICR);
// IP ID registers
pub const PERI_ID: Register = Register::new(1016, 0xffff_ffff);
pub const PRIME_ID: Register = Register::new(1020, 0xffff_ffff);
pub const HW_RTC_BASE: usize = 0x40061000;
pub const RTC_NUMREGS: usize = 10;

View file

@ -0,0 +1,182 @@
use bao1x_hal::rtc::*;
use utralib::*;
use crate::api::TimeOp;
/// This is a "well known name" used by `libstd` to connect to the time server
/// Anyone who wants to check if time has been initialized would use this name.
pub const TIME_SERVER_PUBLIC: &'static [u8; 16] = b"timeserverpublic";
pub fn start_rtc_service() {
let _ = std::thread::spawn({
move || {
rtc_service();
}
});
}
fn rtc_service() -> ! {
// the public SID is well known and accessible by anyone who uses `libstd`
let pub_sid =
xous::create_server_with_address(&TIME_SERVER_PUBLIC).expect("Couldn't create Ticktimer server");
let tt = ticktimer::Ticktimer::new().unwrap();
let rtc_range = xous::map_memory(
xous::MemoryAddress::new(bao1x_hal::rtc::HW_RTC_BASE),
None,
4096,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map RTC range");
let rtc = CSR::new(rtc_range.as_mut_ptr() as *mut u32);
// this loop allows us to fail slightly more gracefully in the case that there is an RTC hardware
// failure
let mut start_rtc_secs = rtc.r(DR);
let mut start_tt_ms = tt.elapsed_ms();
let mut utc_offset_ms = 0;
let mut tz_offset_ms = 0;
let mut msg_opt = None;
let mut return_type = 0;
loop {
xous::reply_and_receive_next_legacy(pub_sid, &mut msg_opt, &mut return_type).unwrap();
let msg = msg_opt.as_mut().unwrap();
let opcode: Option<TimeOp> = num_traits::FromPrimitive::from_usize(msg.body.id());
log::debug!("{:?}", opcode);
match opcode {
Some(TimeOp::HwSync) => {
start_rtc_secs = rtc.r(DR);
start_tt_ms = tt.elapsed_ms();
}
Some(TimeOp::GetUtcTimeMs) => {
if let Some(scalar) = msg.body.scalar_message_mut() {
let t = start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64
+ utc_offset_ms;
if t < 0 {
// the offset has some error in it, perhaps due to an RTC reset. reset the offset!
log::warn!(
"Time was negative, recovering from time setting error by clearing utc offset to 0"
);
utc_offset_ms = 0;
}
log::trace!("utc ms {}", t);
// `Scalar2` return type
return_type = 2;
scalar.arg1 = (t as u64 & 0xFFFF_FFFF) as usize;
scalar.arg2 = (((t as u64) >> 32) & 0xFFFF_FFFF) as usize;
}
}
Some(TimeOp::GetLocalTimeMs) => {
if let Some(scalar) = msg.body.scalar_message_mut() {
log::trace!(
"current offset {}",
(start_rtc_secs as i64 * 1000i64 + (tt.elapsed_ms() - start_tt_ms) as i64) / 1000
);
let t = start_rtc_secs as i64 * 1000i64
+ (tt.elapsed_ms() - start_tt_ms) as i64
+ utc_offset_ms
+ tz_offset_ms;
if t < 0 {
log::warn!(
"Time was negative, recovering from time setting error by clearing utc and timezone offsets to 0."
);
utc_offset_ms = 0;
tz_offset_ms = 0;
}
log::trace!("local since epoch {}", t / 1000);
return_type = 2;
scalar.arg1 = (((t as u64) >> 32) & 0xFFFF_FFFF) as usize;
scalar.arg2 = (t as u64 & 0xFFFF_FFFF) as usize;
}
}
/*
Set with:
log::info!("Got NTP time: {}.{}", time.sec(), time.sec_fraction());
let current_time = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()
+ chrono::Duration::seconds(time.sec() as i64);
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetUtcTimeMs.to_usize().unwrap(),
((current_time.timestamp_millis() as u64) >> 32) as usize,
(current_time.timestamp_millis() as u64 & 0xFFFF_FFFF) as usize,
0,
0,
),
)
.expect("couldn't set time");
*/
Some(TimeOp::SetUtcTimeMs) => {
if let Some(scalar) = msg.body.scalar_message() {
let utc_hi_ms = scalar.arg1;
let utc_lo_ms = scalar.arg2;
let utc_time_ms = (utc_hi_ms as i64) << 32 | (utc_lo_ms as i64);
start_rtc_secs = rtc.r(DR);
start_tt_ms = tt.elapsed_ms();
log::info!("utc_time: {}", utc_time_ms / 1000);
log::info!("rtc_secs: {}", start_rtc_secs);
log::info!("start_tt_ms: {}", start_tt_ms);
let offset = utc_time_ms - (start_rtc_secs as i64) * 1000;
utc_offset_ms = offset;
}
}
/*
`tz` is a float that represents the current UTC time offset (e.g. -8 for singapore)
see simple_kilofloat_parse() in dns/src/time.rs for an efficient string parser if needed
let tz_offset_ms = (tz * 3600) as i64;
xous::send_message(
timeserver_cid,
Message::new_scalar(
crate::time::TimeOp::SetTzOffsetMs.to_usize().unwrap(),
(tz_offset_ms >> 32) as usize,
(tz_offset_ms & 0xFFFF_FFFF) as usize,
0,
0,
),
)
.expect("couldn't set timezone");
*/
Some(TimeOp::SetTzOffsetMs) => {
if let Some(scalar) = msg.body.scalar_message() {
let tz_hi_ms = scalar.arg1;
let tz_lo_ms = scalar.arg2;
let tz_ms = ((tz_hi_ms as i64) << 32) | (tz_lo_ms as i64);
// sanity check with very broad bounds: I don't know of any time zones that are more
// than +/2 days from UTC 86400 seconds in a day, times 1000
// milliseconds, times 2 days
if tz_ms < -(86400 * 1000 * 2) || tz_ms > 86400 * 1000 * 2 {
log::warn!("Requested timezone offset {} is out of bounds, ignoring!", tz_ms);
continue;
} else {
tz_offset_ms = tz_ms;
}
}
}
Some(TimeOp::WallClockTimeInit) => {
if let Some(scalar) = msg.body.scalar_message_mut() {
return_type = 2;
if utc_offset_ms == 0 {
scalar.arg1 = 0;
} else {
scalar.arg2 = 1;
}
}
}
// Calling this allows the caller to store the offsets used to compute the current
// time from the RTC clock setting. The caller would restore time through a pair
// of calls to SetUtcTimeMs with arg1/arg2, and SetTzOffsetMs with arg3/arg4.
Some(TimeOp::GetState) => {
if let Some(scalar) = msg.body.scalar_message_mut() {
scalar.arg1 = (utc_offset_ms & 0xFFFF_FFFFF) as usize;
scalar.arg2 = ((utc_offset_ms >> 32) & 0xFFFF_FFFF) as usize;
scalar.arg3 = (tz_offset_ms & 0xFFFF_FFFF) as usize;
scalar.arg4 = ((tz_offset_ms >> 32) & 0xFFFF_FFFF) as usize;
}
}
None => log::error!("Time server public thread received unknown opcode: {:?}", msg),
}
}
}