use core::hint::spin_loop;
use hermit_entry::boot_info::PlatformInfo;
use hermit_sync::{without_interrupts, OnceCell};
use time::OffsetDateTime;
use x86::io::*;
use crate::arch::x86_64::kernel::{boot_info, processor};
const CMOS_COMMAND_PORT: u16 = 0x70;
const CMOS_DATA_PORT: u16 = 0x71;
const CMOS_DISABLE_NMI: u8 = 1 << 7;
const CMOS_SECOND_REGISTER: u8 = 0x00;
const CMOS_MINUTE_REGISTER: u8 = 0x02;
const CMOS_HOUR_REGISTER: u8 = 0x04;
const CMOS_DAY_REGISTER: u8 = 0x07;
const CMOS_MONTH_REGISTER: u8 = 0x08;
const CMOS_YEAR_REGISTER: u8 = 0x09;
const CMOS_STATUS_REGISTER_A: u8 = 0x0A;
const CMOS_STATUS_REGISTER_B: u8 = 0x0B;
const CMOS_UPDATE_IN_PROGRESS_FLAG: u8 = 1 << 7;
const CMOS_24_HOUR_FORMAT_FLAG: u8 = 1 << 1;
const CMOS_BINARY_FORMAT_FLAG: u8 = 1 << 2;
const CMOS_12_HOUR_PM_FLAG: u8 = 0x80;
struct Rtc {
cmos_format: u8,
}
impl Rtc {
fn new() -> Self {
Self {
cmos_format: Self::read_cmos_register(CMOS_STATUS_REGISTER_B),
}
}
const fn is_24_hour_format(&self) -> bool {
self.cmos_format & CMOS_24_HOUR_FORMAT_FLAG > 0
}
const fn is_binary_format(&self) -> bool {
self.cmos_format & CMOS_BINARY_FORMAT_FLAG > 0
}
const fn time_is_pm(hour: u8) -> bool {
hour & CMOS_12_HOUR_PM_FLAG > 0
}
const fn convert_bcd_value(value: u8) -> u8 {
((value / 16) * 10) + (value & 0xF)
}
fn microseconds_from_date(
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> u64 {
let (m, y) = if month > 2 {
(u64::from(month - 2), u64::from(year))
} else {
(u64::from(month + 12 - 2), u64::from(year - 1))
};
let days_since_epoch =
(y / 4 - y / 100 + y / 400 + 367 * m / 12 + u64::from(day)) + y * 365 - 719_499;
let hours_since_epoch = days_since_epoch * 24 + u64::from(hour);
let minutes_since_epoch = hours_since_epoch * 60 + u64::from(minute);
let seconds_since_epoch = minutes_since_epoch * 60 + u64::from(second);
seconds_since_epoch * 1_000_000u64
}
fn read_cmos_register(register: u8) -> u8 {
unsafe {
outb(CMOS_COMMAND_PORT, CMOS_DISABLE_NMI | register);
inb(CMOS_DATA_PORT)
}
}
fn read_datetime_register(&self, register: u8) -> u8 {
let value = Self::read_cmos_register(register);
if self.is_binary_format() {
value
} else {
Self::convert_bcd_value(value)
}
}
fn read_all_values(&self) -> u64 {
let year = u16::from(self.read_datetime_register(CMOS_YEAR_REGISTER)) + 2000;
let month = self.read_datetime_register(CMOS_MONTH_REGISTER);
let day = self.read_datetime_register(CMOS_DAY_REGISTER);
let mut hour = Self::read_cmos_register(CMOS_HOUR_REGISTER);
let mut is_pm = false;
if !self.is_24_hour_format() {
is_pm = Self::time_is_pm(hour);
hour &= !CMOS_12_HOUR_PM_FLAG;
}
if !self.is_binary_format() {
hour = Self::convert_bcd_value(hour);
}
if !self.is_24_hour_format() {
if hour == 12 {
hour = 0;
}
if is_pm {
hour += 12;
}
}
let minute = self.read_datetime_register(CMOS_MINUTE_REGISTER);
let second = self.read_datetime_register(CMOS_SECOND_REGISTER);
Self::microseconds_from_date(year, month, day, hour, minute, second)
}
pub fn get_microseconds_since_epoch(&self) -> u64 {
loop {
while Self::read_cmos_register(CMOS_STATUS_REGISTER_A) & CMOS_UPDATE_IN_PROGRESS_FLAG
> 0
{
spin_loop();
}
let microseconds_since_epoch_1 = self.read_all_values();
if Self::read_cmos_register(CMOS_STATUS_REGISTER_A) & CMOS_UPDATE_IN_PROGRESS_FLAG > 0 {
continue;
}
let microseconds_since_epoch_2 = self.read_all_values();
if microseconds_since_epoch_1 == microseconds_since_epoch_2 {
return microseconds_since_epoch_1;
}
}
}
}
static BOOT_TIME: OnceCell<u64> = OnceCell::new();
pub fn get_boot_time() -> u64 {
*BOOT_TIME.get().unwrap()
}
pub fn init() {
let boot_time = match boot_info().platform_info {
PlatformInfo::Multiboot { .. } => {
let current_time = without_interrupts(|| Rtc::new().get_microseconds_since_epoch());
let boot_time = current_time - processor::get_timer_ticks();
OffsetDateTime::from_unix_timestamp_nanos(boot_time as i128 * 1000).unwrap()
}
PlatformInfo::LinuxBootParams { .. } => {
let current_time = without_interrupts(|| Rtc::new().get_microseconds_since_epoch());
let boot_time = current_time - processor::get_timer_ticks();
OffsetDateTime::from_unix_timestamp_nanos(boot_time as i128 * 1000).unwrap()
}
PlatformInfo::Uhyve { boot_time, .. } => boot_time,
};
info!("HermitCore-rs booted on {boot_time}");
let micros = u64::try_from(boot_time.unix_timestamp_nanos() / 1000).unwrap();
BOOT_TIME.set(micros).unwrap();
}