mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
add rtc hardware drivers and service
This commit is contained in:
parent
cd8a312b8d
commit
d30d889774
3 changed files with 330 additions and 0 deletions
|
|
@ -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
146
libs/bao1x-hal/src/rtc.rs
Normal 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;
|
||||
182
services/bao1x-hal-service/src/servers/rtc.rs
Normal file
182
services/bao1x-hal-service/src/servers/rtc.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue