299 lines
6.9 KiB
Rust
299 lines
6.9 KiB
Rust
// kernel/src/hal/rtc.rs
|
|
use crate::hal::io::{io_port_rb, io_port_wb};
|
|
use core::sync::atomic::{AtomicU64, Ordering};
|
|
use crate::log_info;
|
|
|
|
const RTC_INDEX_PORT: u16 = 0x70;
|
|
const RTC_TARGET_PORT: u16 = 0x71;
|
|
const WITH_NMI_DISABLED: u8 = 0x80;
|
|
|
|
const RTC_REG_SEC: u8 = 0x00;
|
|
const RTC_REG_MIN: u8 = 0x02;
|
|
const RTC_REG_HRS: u8 = 0x04;
|
|
const RTC_REG_WDY: u8 = 0x06;
|
|
const RTC_REG_DAY: u8 = 0x07;
|
|
const RTC_REG_MTH: u8 = 0x08;
|
|
const RTC_REG_YRS: u8 = 0x09;
|
|
|
|
const RTC_REG_A: u8 = 0x0A;
|
|
const RTC_REG_B: u8 = 0x0B;
|
|
const RTC_REG_C: u8 = 0x0C;
|
|
|
|
const RTC_UPDATE_IN_PROGRESS: u8 = 0x80;
|
|
const RTC_BIN_ENCODED_BIT: u8 = 0x04;
|
|
const RTC_24HRS_ENCODED_BIT: u8 = 0x02;
|
|
|
|
const RTC_TIMER_ON: u8 = 0x40;
|
|
const RTC_FREQUENCY_1024HZ: u8 = 0b110;
|
|
const RTC_DIVIDER_33KHZ: u8 = 0b010 << 4;
|
|
|
|
const RTC_CURRENT_CENTURY: u16 = 2000;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct DateTime {
|
|
pub year: u16,
|
|
pub month: u8,
|
|
pub day: u8,
|
|
pub weekday: u8,
|
|
pub hour: u8,
|
|
pub minute: u8,
|
|
pub second: u8,
|
|
}
|
|
|
|
impl DateTime {
|
|
pub fn format(&self) -> alloc::string::String {
|
|
alloc::format!(
|
|
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
|
|
self.year, self.month, self.day,
|
|
self.hour, self.minute, self.second
|
|
)
|
|
}
|
|
|
|
pub fn weekday_name(&self) -> &'static str {
|
|
match self.weekday {
|
|
1 => "Sunday",
|
|
2 => "Monday",
|
|
3 => "Tuesday",
|
|
4 => "Wednesday",
|
|
5 => "Thursday",
|
|
6 => "Friday",
|
|
7 => "Saturday",
|
|
_ => "Unknown",
|
|
}
|
|
}
|
|
|
|
pub fn month_name(&self) -> &'static str {
|
|
match self.month {
|
|
1 => "January",
|
|
2 => "February",
|
|
3 => "March",
|
|
4 => "April",
|
|
5 => "May",
|
|
6 => "June",
|
|
7 => "July",
|
|
8 => "August",
|
|
9 => "September",
|
|
10 => "October",
|
|
11 => "November",
|
|
12 => "December",
|
|
_ => "Unknown",
|
|
}
|
|
}
|
|
}
|
|
|
|
// RTC Configuration
|
|
static mut RTC_CONFIG: RtcConfig = RtcConfig::new();
|
|
|
|
// Tick Counter
|
|
static RTC_TICK_COUNT: AtomicU64 = AtomicU64::new(0);
|
|
|
|
struct RtcConfig {
|
|
binary_mode: bool,
|
|
hour_24_mode: bool,
|
|
}
|
|
|
|
impl RtcConfig {
|
|
const fn new() -> Self {
|
|
Self {
|
|
binary_mode: false,
|
|
hour_24_mode: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Read CMOS register
|
|
#[inline]
|
|
pub unsafe fn read_register(reg: u8) -> u8 {
|
|
io_port_wb(RTC_INDEX_PORT, reg | WITH_NMI_DISABLED);
|
|
io_port_rb(RTC_TARGET_PORT)
|
|
}
|
|
|
|
/// Write to CMOS register
|
|
#[inline]
|
|
pub unsafe fn write_register(reg: u8, value: u8) {
|
|
io_port_wb(RTC_INDEX_PORT, reg | WITH_NMI_DISABLED);
|
|
io_port_wb(RTC_TARGET_PORT, value);
|
|
}
|
|
|
|
/// Check if RTC is updating
|
|
#[inline]
|
|
unsafe fn is_updating() -> bool {
|
|
(read_register(RTC_REG_A) & RTC_UPDATE_IN_PROGRESS) != 0
|
|
}
|
|
|
|
/// Wait for RTC update to complete
|
|
unsafe fn wait_for_update() {
|
|
while is_updating() {
|
|
core::hint::spin_loop();
|
|
}
|
|
}
|
|
|
|
/// Convert BCD to binary
|
|
#[inline]
|
|
fn bcd_to_binary(bcd: u8) -> u8 {
|
|
(bcd & 0x0F) + ((bcd >> 4) * 10)
|
|
}
|
|
|
|
pub fn init() {
|
|
unsafe {
|
|
// Read configuration
|
|
let status_b = read_register(RTC_REG_B);
|
|
RTC_CONFIG.binary_mode = (status_b & RTC_BIN_ENCODED_BIT) != 0;
|
|
RTC_CONFIG.hour_24_mode = (status_b & RTC_24HRS_ENCODED_BIT) != 0;
|
|
|
|
// Configuring frequencies and dividers
|
|
let mut reg_a = read_register(RTC_REG_A);
|
|
reg_a = (reg_a & 0xF0) | RTC_DIVIDER_33KHZ | RTC_FREQUENCY_1024HZ;
|
|
write_register(RTC_REG_A, reg_a);
|
|
|
|
// 清除中斷
|
|
read_register(RTC_REG_C);
|
|
|
|
// 確保 timer 關閉
|
|
disable_timer();
|
|
|
|
// 再次清除
|
|
read_register(RTC_REG_C);
|
|
}
|
|
|
|
log_info!("RTC initialized (lock-free design)");
|
|
}
|
|
|
|
pub fn get_time() -> DateTime {
|
|
unsafe {
|
|
loop {
|
|
// Wait for RTC to be ready
|
|
wait_for_update();
|
|
|
|
// Read twice quickly
|
|
let time1 = read_time_raw();
|
|
let time2 = read_time_raw();
|
|
|
|
// If the two reads are consistent, return the result
|
|
if time1 == time2 {
|
|
return time1;
|
|
}
|
|
|
|
// Retry if inconsistent (maybe just rollover in seconds)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Directly read the RTC register
|
|
unsafe fn read_time_raw() -> DateTime {
|
|
let config = &RTC_CONFIG;
|
|
|
|
let mut second = read_register(RTC_REG_SEC);
|
|
let mut minute = read_register(RTC_REG_MIN);
|
|
let mut hour = read_register(RTC_REG_HRS);
|
|
let mut day = read_register(RTC_REG_DAY);
|
|
let mut month = read_register(RTC_REG_MTH);
|
|
let mut year = read_register(RTC_REG_YRS);
|
|
let weekday = read_register(RTC_REG_WDY);
|
|
|
|
if !config.binary_mode {
|
|
second = bcd_to_binary(second);
|
|
minute = bcd_to_binary(minute);
|
|
day = bcd_to_binary(day);
|
|
month = bcd_to_binary(month);
|
|
year = bcd_to_binary(year);
|
|
}
|
|
|
|
let pm_bit = hour & 0x80;
|
|
hour = if config.binary_mode {
|
|
hour & 0x7F
|
|
} else {
|
|
bcd_to_binary(hour & 0x7F)
|
|
};
|
|
|
|
if !config.hour_24_mode && pm_bit != 0 {
|
|
hour = (hour + 12) % 24;
|
|
}
|
|
|
|
DateTime {
|
|
year: RTC_CURRENT_CENTURY + year as u16,
|
|
month,
|
|
day,
|
|
weekday,
|
|
hour,
|
|
minute,
|
|
second,
|
|
}
|
|
}
|
|
|
|
/// Enable RTC periodic interrupt (1024Hz)
|
|
pub fn enable_timer() {
|
|
unsafe {
|
|
// Close first
|
|
disable_timer();
|
|
read_register(RTC_REG_C);
|
|
|
|
// Setting the frequency
|
|
let mut reg_a = read_register(RTC_REG_A);
|
|
reg_a = (reg_a & 0xF0) | RTC_DIVIDER_33KHZ | RTC_FREQUENCY_1024HZ;
|
|
write_register(RTC_REG_A, reg_a);
|
|
|
|
// Enable periodic interrupts
|
|
let mut reg_b = read_register(RTC_REG_B);
|
|
reg_b |= RTC_TIMER_ON;
|
|
write_register(RTC_REG_B, reg_b);
|
|
|
|
// Clear interrupt flag
|
|
read_register(RTC_REG_C);
|
|
}
|
|
|
|
log_info!("RTC timer enabled at 1024Hz");
|
|
}
|
|
|
|
/// Disable RTC periodic interrupt
|
|
pub fn disable_timer() {
|
|
unsafe {
|
|
let mut reg_b = read_register(RTC_REG_B);
|
|
reg_b &= !RTC_TIMER_ON;
|
|
write_register(RTC_REG_B, reg_b);
|
|
|
|
read_register(RTC_REG_C);
|
|
}
|
|
}
|
|
|
|
/// RTC interrupt handler
|
|
///
|
|
/// This is the only function called in interrupt context.
|
|
/// Clears the interrupt flag
|
|
/// Increments the tick counter
|
|
#[inline]
|
|
pub fn handle_interrupt() {
|
|
unsafe {
|
|
read_register(RTC_REG_C);
|
|
}
|
|
|
|
RTC_TICK_COUNT.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Get RTC tick count (lock-free)
|
|
#[inline]
|
|
pub fn get_tick_count() -> u64 {
|
|
RTC_TICK_COUNT.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Reset tick count
|
|
#[inline]
|
|
pub fn reset_tick_count() {
|
|
RTC_TICK_COUNT.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Print current time information
|
|
pub fn print_info() {
|
|
let time = get_time();
|
|
|
|
log_info!(
|
|
"{}, {} {}, {} - {:02}:{:02}:{:02}",
|
|
time.weekday_name(),
|
|
time.month_name(),
|
|
time.day,
|
|
time.year,
|
|
time.hour,
|
|
time.minute,
|
|
time.second
|
|
);
|
|
} |