veridian_kernel/arch/x86_64/
rtc.rs1use core::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
8
9static BOOT_EPOCH: AtomicU64 = AtomicU64::new(0);
11
12static BOOT_TSC: AtomicU64 = AtomicU64::new(0);
14
15static TIMEZONE_OFFSET: AtomicI64 = AtomicI64::new(0);
17
18static NTP_CORRECTION_MS: AtomicI64 = AtomicI64::new(0);
21
22static ALARM_ENABLED: AtomicBool = AtomicBool::new(false);
24
25static ALARM_CALLBACK: AtomicU64 = AtomicU64::new(0);
27
28pub const RTC_ALM_SET: u32 = 0x01;
30pub const RTC_AIE_ON: u32 = 0x02;
32pub const RTC_AIE_OFF: u32 = 0x03;
34
35#[derive(Debug, Clone, Copy)]
37pub struct RtcTime {
38 pub year: u16,
39 pub month: u8,
40 pub day: u8,
41 pub hour: u8,
42 pub minute: u8,
43 pub second: u8,
44}
45
46pub fn init() {
48 let time = read_rtc();
49 let epoch = rtc_to_epoch(&time);
50 BOOT_EPOCH.store(epoch, Ordering::Relaxed);
51 BOOT_TSC.store(crate::arch::timer::read_hw_timestamp(), Ordering::Relaxed);
52 crate::println!(
53 "[RTC] Boot time: {:04}-{:02}-{:02} {:02}:{:02}:{:02} (epoch={})",
54 time.year,
55 time.month,
56 time.day,
57 time.hour,
58 time.minute,
59 time.second,
60 epoch,
61 );
62}
63
64pub fn current_epoch_secs() -> u64 {
69 let boot = BOOT_EPOCH.load(Ordering::Relaxed);
70 let boot_tsc = BOOT_TSC.load(Ordering::Relaxed);
71 let now_tsc = crate::arch::timer::read_hw_timestamp();
72 let elapsed_ticks = now_tsc.saturating_sub(boot_tsc);
73 let elapsed_secs = elapsed_ticks / 1_000_000_000;
75 let base = boot + elapsed_secs;
76
77 let ntp_ms = NTP_CORRECTION_MS.load(Ordering::Relaxed);
79 let ntp_secs = ntp_ms / 1000;
80 let corrected = (base as i64).saturating_add(ntp_secs);
81
82 let tz_offset = TIMEZONE_OFFSET.load(Ordering::Relaxed);
84 let adjusted = corrected.saturating_add(tz_offset);
85
86 if adjusted < 0 {
88 0
89 } else {
90 adjusted as u64
91 }
92}
93
94pub fn read_rtc() -> RtcTime {
96 wait_for_update();
98
99 let mut second = read_cmos(0x00);
100 let mut minute = read_cmos(0x02);
101 let mut hour = read_cmos(0x04);
102 let mut day = read_cmos(0x07);
103 let mut month = read_cmos(0x08);
104 let mut year = read_cmos(0x09);
105 let century = read_cmos(0x32); let reg_b = read_cmos(0x0B);
107
108 if reg_b & 0x04 == 0 {
110 second = bcd_to_bin(second);
111 minute = bcd_to_bin(minute);
112 hour = bcd_to_bin(hour & 0x7F) | (hour & 0x80); day = bcd_to_bin(day);
114 month = bcd_to_bin(month);
115 year = bcd_to_bin(year);
116 }
117
118 if reg_b & 0x02 == 0 && hour & 0x80 != 0 {
120 hour = ((hour & 0x7F) + 12) % 24;
121 }
122
123 let century_val = if century > 0 {
124 if reg_b & 0x04 == 0 {
125 bcd_to_bin(century)
126 } else {
127 century
128 }
129 } else {
130 20 };
132
133 let full_year = (century_val as u16) * 100 + (year as u16);
134
135 RtcTime {
136 year: full_year,
137 month,
138 day,
139 hour,
140 minute,
141 second,
142 }
143}
144
145fn rtc_to_epoch(t: &RtcTime) -> u64 {
147 let mut days: u64 = 0;
149 for y in 1970..t.year {
150 days += if is_leap(y) { 366 } else { 365 };
151 }
152 let month_days: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
154 for m in 1..t.month {
155 if m == 2 && is_leap(t.year) {
156 days += 29;
157 } else if (m as usize) <= 12 {
158 days += month_days[(m - 1) as usize] as u64;
159 }
160 }
161 days += (t.day as u64).saturating_sub(1);
162
163 days * 86400 + (t.hour as u64) * 3600 + (t.minute as u64) * 60 + (t.second as u64)
164}
165
166fn is_leap(y: u16) -> bool {
167 (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
168}
169
170fn wait_for_update() {
172 for _ in 0..10_000 {
174 if read_cmos(0x0A) & 0x80 == 0 {
175 return;
176 }
177 core::hint::spin_loop();
178 }
179}
180
181#[cfg(target_os = "none")]
183fn read_cmos(reg: u8) -> u8 {
184 unsafe {
188 crate::arch::x86_64::outb(0x70, reg);
189 crate::arch::x86_64::inb(0x71)
190 }
191}
192
193#[cfg(not(target_os = "none"))]
195fn read_cmos(_reg: u8) -> u8 {
196 0
197}
198
199fn bcd_to_bin(bcd: u8) -> u8 {
201 (bcd >> 4) * 10 + (bcd & 0x0F)
202}
203
204#[allow(dead_code)]
206fn bin_to_bcd(val: u8) -> u8 {
207 ((val / 10) << 4) | (val % 10)
208}
209
210#[cfg(all(target_arch = "x86_64", target_os = "none"))]
219pub fn set_alarm(hour: u8, minute: u8, second: u8) {
220 let reg_b = read_cmos(0x0B);
221 let is_bcd = reg_b & 0x04 == 0;
222
223 let (s, m, h) = if is_bcd {
224 (bin_to_bcd(second), bin_to_bcd(minute), bin_to_bcd(hour))
225 } else {
226 (second, minute, hour)
227 };
228
229 write_cmos(0x01, s); write_cmos(0x03, m); write_cmos(0x05, h); let new_b = reg_b | 0x20;
235 write_cmos(0x0B, new_b);
236 ALARM_ENABLED.store(true, Ordering::Relaxed);
237
238 crate::println!(
239 "[RTC] Alarm set for {:02}:{:02}:{:02}",
240 hour,
241 minute,
242 second,
243 );
244}
245
246#[cfg(all(target_arch = "x86_64", target_os = "none"))]
248pub fn disable_alarm() {
249 let reg_b = read_cmos(0x0B);
250 write_cmos(0x0B, reg_b & !0x20); ALARM_ENABLED.store(false, Ordering::Relaxed);
252}
253
254#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
256pub fn set_alarm(_hour: u8, _minute: u8, _second: u8) {}
257
258#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
260pub fn disable_alarm() {
261 ALARM_ENABLED.store(false, Ordering::Relaxed);
262}
263
264pub fn set_alarm_callback(callback: fn()) {
266 ALARM_CALLBACK.store(callback as usize as u64, Ordering::Relaxed);
267}
268
269#[cfg(all(target_arch = "x86_64", target_os = "none"))]
278pub fn rtc_interrupt_handler() {
279 let status = read_cmos(0x0C);
281
282 if status & 0x20 != 0 && ALARM_ENABLED.load(Ordering::Relaxed) {
284 let cb_ptr = ALARM_CALLBACK.load(Ordering::Relaxed);
285 if cb_ptr != 0 {
286 let callback: fn() = unsafe { core::mem::transmute(cb_ptr as usize) };
289 callback();
290 }
291 }
292}
293
294#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
296pub fn rtc_interrupt_handler() {}
297
298pub fn set_timezone_offset(seconds: i64) {
307 TIMEZONE_OFFSET.store(seconds, Ordering::Relaxed);
308}
309
310pub fn get_timezone_offset() -> i64 {
312 TIMEZONE_OFFSET.load(Ordering::Relaxed)
313}
314
315pub fn set_time_correction(offset_ms: i64) {
328 NTP_CORRECTION_MS.store(offset_ms, Ordering::Relaxed);
329}
330
331pub fn get_time_correction() -> i64 {
333 NTP_CORRECTION_MS.load(Ordering::Relaxed)
334}
335
336#[derive(Debug, Clone, Copy)]
342pub struct RtcAlarm {
343 pub hour: u8,
344 pub minute: u8,
345 pub second: u8,
346}
347
348pub fn rtc_read() -> RtcTime {
352 read_rtc()
353}
354
355pub fn rtc_ioctl(cmd: u32, arg: u64) -> i32 {
364 match cmd {
365 RTC_ALM_SET => {
366 let alarm = unsafe { &*(arg as *const RtcAlarm) };
371 if alarm.hour > 23 || alarm.minute > 59 || alarm.second > 59 {
372 return -1;
373 }
374 set_alarm(alarm.hour, alarm.minute, alarm.second);
375 0
376 }
377 RTC_AIE_ON => {
378 ALARM_ENABLED.store(true, Ordering::Relaxed);
379 0
380 }
381 RTC_AIE_OFF => {
382 disable_alarm();
383 0
384 }
385 _ => -1, }
387}
388
389#[cfg(all(target_arch = "x86_64", target_os = "none"))]
395fn write_cmos(reg: u8, val: u8) {
396 unsafe {
398 crate::arch::x86_64::outb(0x70, reg);
399 crate::arch::x86_64::outb(0x71, val);
400 }
401}
402
403#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
405#[allow(dead_code)]
406fn write_cmos(_reg: u8, _val: u8) {}
407
408#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn test_bcd_conversions() {
418 assert_eq!(bcd_to_bin(0x59), 59);
420 assert_eq!(bcd_to_bin(0x23), 23);
421 assert_eq!(bcd_to_bin(0x00), 0);
422
423 assert_eq!(bin_to_bcd(59), 0x59);
425 assert_eq!(bin_to_bcd(23), 0x23);
426 assert_eq!(bin_to_bcd(0), 0x00);
427
428 for val in 0..=99u8 {
430 assert_eq!(bcd_to_bin(bin_to_bcd(val)), val);
431 }
432 }
433
434 #[test]
435 fn test_timezone_offset() {
436 set_timezone_offset(0);
438 assert_eq!(get_timezone_offset(), 0);
439
440 set_timezone_offset(-18000);
442 assert_eq!(get_timezone_offset(), -18000);
443
444 set_timezone_offset(3600);
446 assert_eq!(get_timezone_offset(), 3600);
447
448 set_timezone_offset(0);
450 }
451
452 #[test]
453 fn test_ntp_correction() {
454 set_time_correction(0);
456 assert_eq!(get_time_correction(), 0);
457
458 set_time_correction(500);
460 assert_eq!(get_time_correction(), 500);
461
462 set_time_correction(-200);
464 assert_eq!(get_time_correction(), -200);
465
466 set_time_correction(0);
468 }
469
470 #[test]
471 fn test_rtc_to_epoch_known_dates() {
472 let t = RtcTime {
474 year: 1970,
475 month: 1,
476 day: 1,
477 hour: 0,
478 minute: 0,
479 second: 0,
480 };
481 assert_eq!(rtc_to_epoch(&t), 0);
482
483 let t2 = RtcTime {
485 year: 2000,
486 month: 1,
487 day: 1,
488 hour: 0,
489 minute: 0,
490 second: 0,
491 };
492 assert_eq!(rtc_to_epoch(&t2), 946684800);
493
494 let t3 = RtcTime {
496 year: 2026,
497 month: 3,
498 day: 5,
499 hour: 12,
500 minute: 30,
501 second: 45,
502 };
503 let epoch3 = rtc_to_epoch(&t3);
504 assert!(epoch3 > 1_735_689_600);
506 }
507
508 #[test]
509 fn test_rtc_read_returns_valid_time() {
510 let time = rtc_read();
511 assert!(time.month <= 12);
514 assert!(time.day <= 31);
515 assert!(time.hour <= 23);
516 assert!(time.minute <= 59);
517 assert!(time.second <= 59);
518 }
519
520 #[test]
521 fn test_rtc_ioctl_invalid_command() {
522 assert_eq!(rtc_ioctl(0xFF, 0), -1);
523 }
524
525 #[test]
526 fn test_rtc_ioctl_alarm_set() {
527 let alarm = RtcAlarm {
528 hour: 14,
529 minute: 30,
530 second: 0,
531 };
532 let result = rtc_ioctl(RTC_ALM_SET, &alarm as *const RtcAlarm as u64);
533 assert_eq!(result, 0);
534
535 let bad_alarm = RtcAlarm {
537 hour: 25, minute: 0,
539 second: 0,
540 };
541 let result2 = rtc_ioctl(RTC_ALM_SET, &bad_alarm as *const RtcAlarm as u64);
542 assert_eq!(result2, -1);
543 }
544
545 #[test]
546 fn test_rtc_ioctl_alarm_enable_disable() {
547 assert_eq!(rtc_ioctl(RTC_AIE_ON, 0), 0);
548 assert!(ALARM_ENABLED.load(Ordering::Relaxed));
549
550 assert_eq!(rtc_ioctl(RTC_AIE_OFF, 0), 0);
551 assert!(!ALARM_ENABLED.load(Ordering::Relaxed));
552 }
553
554 #[test]
555 fn test_alarm_callback_registration() {
556 fn dummy_callback() {}
557 set_alarm_callback(dummy_callback);
558 let stored = ALARM_CALLBACK.load(Ordering::Relaxed);
559 assert_ne!(stored, 0);
560 ALARM_CALLBACK.store(0, Ordering::Relaxed);
562 }
563}