From bbe0e3f3bbb6ca9ce1bb66e7cce854ca17da7ec8 Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Wed, 29 Oct 2025 21:19:20 +0800 Subject: [PATCH] Reimplement `RtcCmos` --- Cargo.lock | 1 + kernel/comps/time/Cargo.toml | 1 + kernel/comps/time/src/rtc/cmos.rs | 250 ++++++++++++++++++++---------- ostd/src/arch/x86/device/cmos.rs | 28 ++-- 4 files changed, 178 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abf34a421..84e521f66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,7 @@ name = "aster-time" version = "0.1.0" dependencies = [ "aster-util", + "bitflags 2.9.1", "chrono", "component", "log", diff --git a/kernel/comps/time/Cargo.toml b/kernel/comps/time/Cargo.toml index 43e446d19..fc5bfcb6c 100644 --- a/kernel/comps/time/Cargo.toml +++ b/kernel/comps/time/Cargo.toml @@ -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 } diff --git a/kernel/comps/time/src/rtc/cmos.rs b/kernel/comps/time/src/rtc/cmos.rs index 0a0f74763..b46d80266 100644 --- a/kernel/comps/time/src/rtc/cmos.rs +++ b/kernel/comps/time/src/rtc/cmos.rs @@ -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, + status_b: StatusB, } -fn is_updating() -> bool { - CMOS_ADDRESS.write(0x0A); - CMOS_DATA.read() & 0x80 != 0 +impl Driver for RtcCmos { + fn try_new() -> Option { + // 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: + // + // + 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, + io_val: IoPort, + century_register: Option, +} + +#[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 { + 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, 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 for SystemTime { } } } - -pub struct RtcCmos { - century_register: u8, -} - -impl Driver for RtcCmos { - fn try_new() -> Option { - Some(RtcCmos { - century_register: century_register().unwrap_or(0), - }) - } - - fn read_rtc(&self) -> SystemTime { - CmosData::read_rtc(self.century_register).into() - } -} diff --git a/ostd/src/arch/x86/device/cmos.rs b/ostd/src/arch/x86/device/cmos.rs index 2415f28f5..c11b37fdc 100644 --- a/ostd/src/arch/x86/device/cmos.rs +++ b/ostd/src/arch/x86/device/cmos.rs @@ -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: //! -#![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 = IoPort::new(0x70); - /// CMOS data I/O port - pub static CMOS_DATA: IoPort = 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 { let acpi_tables = get_acpi_tables()?; match acpi_tables.find_table::() { - Ok(a) => Some(a.century), - Err(er) => None, + Ok(fadt) => Some(fadt.century), + Err(_) => None, } }