Reimplement `RtcCmos`
This commit is contained in:
parent
21365dd0bd
commit
bbe0e3f3bb
|
|
@ -315,6 +315,7 @@ name = "aster-time"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aster-util",
|
||||
"bitflags 2.9.1",
|
||||
"chrono",
|
||||
"component",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ aster-util = { path = "../../libs/aster-util" }
|
|||
component = { path = "../../libs/comp-sys/component" }
|
||||
log = "0.4"
|
||||
spin = "0.9.4"
|
||||
bitflags = "2.5"
|
||||
|
||||
[target.riscv64imac-unknown-none-elf.dependencies]
|
||||
chrono = { version = "0.4.38", default-features = false }
|
||||
|
|
|
|||
|
|
@ -1,27 +1,125 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::sync::atomic::{AtomicU8, Ordering::Relaxed};
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
use ostd::arch::device::cmos::{century_register, CMOS_ADDRESS, CMOS_DATA};
|
||||
use log::warn;
|
||||
use ostd::{arch::device::io_port::{ReadWriteAccess, WriteOnlyAccess}, io::IoPort, sync::SpinLock};
|
||||
|
||||
use crate::SystemTime;
|
||||
use super::Driver;
|
||||
|
||||
static CENTURY_REGISTER: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
fn get_cmos(reg: u8) -> u8 {
|
||||
CMOS_ADDRESS.write(reg);
|
||||
CMOS_DATA.read()
|
||||
pub struct RtcCmos {
|
||||
access: SpinLock<CmosAccess>,
|
||||
status_b: StatusB,
|
||||
}
|
||||
|
||||
fn is_updating() -> bool {
|
||||
CMOS_ADDRESS.write(0x0A);
|
||||
CMOS_DATA.read() & 0x80 != 0
|
||||
impl Driver for RtcCmos {
|
||||
fn try_new() -> Option<Self> {
|
||||
// TODO: Due to historical reasons, the "NMI Enable" bit (named `NMI_EN` in Intel's
|
||||
// datasheet) and the "Real Time Clock Index" bits are assigned to the same I/O port
|
||||
// (`IOPORT_SEL`). Currently, we do not support NMIs. However, once we add support, we
|
||||
// should reconsider the safety impact to allow OSTD users to safely manipulate the NMI
|
||||
// enablement.
|
||||
//
|
||||
// Reference:
|
||||
// <https://edc.intel.com/content/www/id/id/design/publications/core-ultra-p200s-series-processors-soc-i-o-registers/nmi-enable-and-real-time-clock-index-nmi-en-offset-70/>
|
||||
// <https://wiki.osdev.org/CMOS#Non-Maskable_Interrupts>
|
||||
const IOPORT_SEL: u16 = 0x70;
|
||||
const IOPORT_VAL: u16 = 0x71;
|
||||
|
||||
let (io_sel, io_val) = match (IoPort::acquire(IOPORT_SEL), IoPort::acquire(IOPORT_VAL)) {
|
||||
(Ok(io_sel), Ok(io_val)) => (io_sel, io_val),
|
||||
_ => {
|
||||
warn!("Failed to acquire CMOS RTC PIO region");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let century_register = ostd::arch::device::cmos::century_register().and_then(NonZeroU8::new);
|
||||
|
||||
let mut access = CmosAccess {
|
||||
io_sel,
|
||||
io_val,
|
||||
century_register,
|
||||
};
|
||||
let status_b = access.read_status_b();
|
||||
|
||||
Some(Self {
|
||||
access: SpinLock::new(access),
|
||||
status_b,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_rtc(&self) -> SystemTime {
|
||||
CmosData::read_rtc(self).into()
|
||||
}
|
||||
}
|
||||
|
||||
struct CmosAccess {
|
||||
io_sel: IoPort<u8, WriteOnlyAccess>,
|
||||
io_val: IoPort<u8, ReadWriteAccess>,
|
||||
century_register: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
enum Register {
|
||||
Second = 0x00,
|
||||
Minute = 0x02,
|
||||
Hour = 0x04,
|
||||
Day = 0x07,
|
||||
Month = 0x08,
|
||||
Year = 0x09,
|
||||
|
||||
StatusA = 0x0A,
|
||||
StatusB = 0x0B,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
struct StatusA: u8 {
|
||||
/// The update in progress (UIP) bit.
|
||||
const UIP = 1 << 7;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
struct StatusB: u8 {
|
||||
/// The data mode (DM) bit.
|
||||
///
|
||||
/// This bit is set when the binary format is used; otherwise, the BCD format is used.
|
||||
const DM_BINARY = 1 << 2;
|
||||
/// The clock mode (CM) bit.
|
||||
///
|
||||
/// This bit is set when the 24-hour format is used; otherwise, the 12-hour format is used.
|
||||
const CM_24HOUR = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl CmosAccess {
|
||||
pub(self) fn read_register(&mut self, reg: Register) -> u8 {
|
||||
self.read_register_impl(reg as u8)
|
||||
}
|
||||
|
||||
pub(self) fn read_century(&mut self) -> Option<u8> {
|
||||
self.century_register.map(|r| self.read_register_impl(r.get()))
|
||||
}
|
||||
|
||||
pub(self) fn read_status_a(&mut self) -> StatusA {
|
||||
StatusA::from_bits_truncate(self.read_register_impl(Register::StatusA as u8))
|
||||
}
|
||||
|
||||
pub(self) fn read_status_b(&mut self) -> StatusB {
|
||||
StatusB::from_bits_truncate(self.read_register_impl(Register::StatusB as u8))
|
||||
}
|
||||
|
||||
fn read_register_impl(&mut self, reg: u8) -> u8 {
|
||||
self.io_sel.write(reg);
|
||||
self.io_val.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct CmosData {
|
||||
century: u8,
|
||||
century: Option<NonZeroU8>,
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
|
|
@ -31,21 +129,42 @@ struct CmosData {
|
|||
}
|
||||
|
||||
impl CmosData {
|
||||
fn from_rtc_raw(century_register: u8) -> Self {
|
||||
while is_updating() {}
|
||||
pub(self) fn read_rtc(rtc: &RtcCmos) -> Self {
|
||||
let mut access = rtc.access.lock();
|
||||
|
||||
let second = get_cmos(0x00);
|
||||
let minute = get_cmos(0x02);
|
||||
let hour = get_cmos(0x04);
|
||||
let day = get_cmos(0x07);
|
||||
let month = get_cmos(0x08);
|
||||
let year = get_cmos(0x09) as u16;
|
||||
let mut now = Self::from_rtc_raw(&mut access);
|
||||
// Retry if the new value differs from the old one. An RTC update may occur in the
|
||||
// meantime, which would result in an invalid value.
|
||||
while let new = Self::from_rtc_raw(&mut access) && now != new {
|
||||
now = new;
|
||||
}
|
||||
|
||||
let century = if century_register != 0 {
|
||||
get_cmos(century_register)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
drop(access);
|
||||
|
||||
if !rtc.status_b.contains(StatusB::DM_BINARY) {
|
||||
now.convert_bcd_to_binary();
|
||||
}
|
||||
if !rtc.status_b.contains(StatusB::CM_24HOUR) {
|
||||
now.convert_12_hour_to_24_hour();
|
||||
}
|
||||
now.modify_year();
|
||||
|
||||
now
|
||||
}
|
||||
|
||||
fn from_rtc_raw(access: &mut CmosAccess) -> Self {
|
||||
// Wait if the RTC updates are in progress.
|
||||
while access.read_status_a().contains(StatusA::UIP) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
let second = access.read_register(Register::Second);
|
||||
let minute = access.read_register(Register::Minute);
|
||||
let hour = access.read_register(Register::Hour);
|
||||
let day = access.read_register(Register::Day);
|
||||
let month = access.read_register(Register::Month);
|
||||
let year = access.read_register(Register::Year) as u16;
|
||||
let century = access.read_century().and_then(NonZeroU8::new);
|
||||
|
||||
CmosData {
|
||||
century,
|
||||
|
|
@ -58,54 +177,35 @@ impl CmosData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts BCD to binary values.
|
||||
/// ref: https://wiki.osdev.org/CMOS#Reading_All_RTC_Time_and_Date_Registers
|
||||
fn convert_bcd_to_binary(&mut self, register_b: u8) {
|
||||
if register_b & 0x04 == 0 {
|
||||
self.second = (self.second & 0x0F) + ((self.second / 16) * 10);
|
||||
self.minute = (self.minute & 0x0F) + ((self.minute / 16) * 10);
|
||||
self.hour =
|
||||
((self.hour & 0x0F) + (((self.hour & 0x70) / 16) * 10)) | (self.hour & 0x80);
|
||||
self.day = (self.day & 0x0F) + ((self.day / 16) * 10);
|
||||
self.month = (self.month & 0x0F) + ((self.month / 16) * 10);
|
||||
self.year = (self.year & 0x0F) + ((self.year / 16) * 10);
|
||||
if CENTURY_REGISTER.load(Relaxed) != 0 {
|
||||
self.century = (self.century & 0x0F) + ((self.century / 16) * 10);
|
||||
} else {
|
||||
// 2000 ~ 2099
|
||||
const DEFAULT_21_CENTURY: u8 = 20;
|
||||
self.century = DEFAULT_21_CENTURY;
|
||||
}
|
||||
/// Converts BCD values to binary values.
|
||||
fn convert_bcd_to_binary(&mut self) {
|
||||
fn bcd_to_binary(val: u8) -> u8 {
|
||||
(val & 0xF) + (val >> 4) * 10
|
||||
}
|
||||
|
||||
self.second = bcd_to_binary(self.second);
|
||||
self.minute = bcd_to_binary(self.minute);
|
||||
self.hour = bcd_to_binary(self.hour & !Self::HOUR_IS_AFTERNOON) | (self.hour & Self::HOUR_IS_AFTERNOON);
|
||||
self.day = bcd_to_binary(self.day);
|
||||
self.month = bcd_to_binary(self.month);
|
||||
self.year = bcd_to_binary(self.year as u8) as u16;
|
||||
self.century = self.century.and_then(|c| NonZeroU8::new(bcd_to_binary(c.get())));
|
||||
}
|
||||
|
||||
const HOUR_IS_AFTERNOON: u8 = 0x80;
|
||||
|
||||
/// Converts the 12-hour clock to the 24-hour clock.
|
||||
fn convert_12_hour_to_24_hour(&mut self) {
|
||||
if self.hour & Self::HOUR_IS_AFTERNOON != 0 {
|
||||
self.hour = (self.hour & !Self::HOUR_IS_AFTERNOON) + 12;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts 12 hour clock to 24 hour clock.
|
||||
fn convert_12_hour_to_24_hour(&mut self, register_b: u8) {
|
||||
// bit1 in register_b is not set if 12 hour format is enable
|
||||
// if highest bit in hour is set, then it is pm
|
||||
if ((register_b & 0x02) == 0) && ((self.hour & 0x80) != 0) {
|
||||
self.hour = ((self.hour & 0x7F) + 12) % 24;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts raw year (10, 20 etc.) to real year (2010, 2020 etc.).
|
||||
/// Converts the year without the century (e.g., 10) to the year with the century (e.g., 2010).
|
||||
fn modify_year(&mut self) {
|
||||
self.year += self.century as u16 * 100;
|
||||
}
|
||||
const DEFAULT_21_CENTURY: u8 = 20;
|
||||
|
||||
pub fn read_rtc(century_register: u8) -> Self {
|
||||
let mut now = Self::from_rtc_raw(century_register);
|
||||
while let new = Self::from_rtc_raw(century_register) && now != new {
|
||||
now = new;
|
||||
}
|
||||
|
||||
let register_b: u8 = get_cmos(0x0B);
|
||||
|
||||
now.convert_bcd_to_binary(register_b);
|
||||
now.convert_12_hour_to_24_hour(register_b);
|
||||
now.modify_year();
|
||||
|
||||
now
|
||||
self.year += (self.century.map(NonZeroU8::get).unwrap_or(DEFAULT_21_CENTURY) as u16) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,19 +222,3 @@ impl From<CmosData> for SystemTime {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RtcCmos {
|
||||
century_register: u8,
|
||||
}
|
||||
|
||||
impl Driver for RtcCmos {
|
||||
fn try_new() -> Option<RtcCmos> {
|
||||
Some(RtcCmos {
|
||||
century_register: century_register().unwrap_or(0),
|
||||
})
|
||||
}
|
||||
|
||||
fn read_rtc(&self) -> SystemTime {
|
||||
CmosData::read_rtc(self.century_register).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,24 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Provides CMOS I/O port access.
|
||||
//! Provides CMOS information.
|
||||
//!
|
||||
//! "CMOS" is a tiny bit of very low power static memory that lives on the same chip as the Real-Time Clock (RTC).
|
||||
//! "CMOS" is a tiny bit of very low power static memory that lives on the same chip as the
|
||||
//! Real-Time Clock (RTC).
|
||||
//!
|
||||
//! Reference: <https://wiki.osdev.org/CMOS>
|
||||
//!
|
||||
|
||||
#![expect(unused_variables)]
|
||||
|
||||
use acpi::fadt::Fadt;
|
||||
use x86_64::instructions::port::{ReadOnlyAccess, WriteOnlyAccess};
|
||||
|
||||
use crate::{
|
||||
arch::kernel::acpi::get_acpi_tables,
|
||||
io::{sensitive_io_port, IoPort},
|
||||
};
|
||||
use crate::arch::kernel::acpi::get_acpi_tables;
|
||||
|
||||
sensitive_io_port!(unsafe {
|
||||
/// CMOS address I/O port
|
||||
pub static CMOS_ADDRESS: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x70);
|
||||
/// CMOS data I/O port
|
||||
pub static CMOS_DATA: IoPort<u8, ReadOnlyAccess> = IoPort::new(0x71);
|
||||
});
|
||||
|
||||
/// Gets the century register location. This function is used in RTC(Real Time Clock) module initialization.
|
||||
/// Gets the century register location.
|
||||
///
|
||||
/// This function is used to get the century value from the Real-Time Clock (RTC).
|
||||
pub fn century_register() -> Option<u8> {
|
||||
let acpi_tables = get_acpi_tables()?;
|
||||
match acpi_tables.find_table::<Fadt>() {
|
||||
Ok(a) => Some(a.century),
|
||||
Err(er) => None,
|
||||
Ok(fadt) => Some(fadt.century),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue