Reimplement `RtcCmos`

This commit is contained in:
Ruihan Li 2025-10-29 21:19:20 +08:00 committed by Tate, Hongliang Tian
parent 21365dd0bd
commit bbe0e3f3bb
4 changed files with 178 additions and 102 deletions

1
Cargo.lock generated
View File

@ -315,6 +315,7 @@ name = "aster-time"
version = "0.1.0"
dependencies = [
"aster-util",
"bitflags 2.9.1",
"chrono",
"component",
"log",

View File

@ -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 }

View File

@ -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()
}
}

View File

@ -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,
}
}