diff --git a/kernel/comps/keyboard/src/i8042_chip/controller.rs b/kernel/comps/keyboard/src/i8042_chip/controller.rs index 2eeee0dc3..9bd31ccc6 100644 --- a/kernel/comps/keyboard/src/i8042_chip/controller.rs +++ b/kernel/comps/keyboard/src/i8042_chip/controller.rs @@ -7,7 +7,7 @@ use bitflags::bitflags; use ostd::{ - arch::device::io_port::ReadWriteAccess, + arch::{device::io_port::ReadWriteAccess, kernel::ACPI_INFO}, io::IoPort, sync::{LocalIrqDisabled, SpinLock}, }; @@ -112,8 +112,17 @@ const MAX_WAITING_COUNT: usize = 64; impl I8042Controller { fn new() -> Result { - // TODO: Check the flags in the ACPI table to determine if the PS/2 controller exists. See: - // . + if ACPI_INFO + .get() + .unwrap() + .boot_flags + .is_some_and(|flags| !flags.motherboard_implements_8042()) + { + // The PS/2 controller does not exist. See: + // . + return Err(I8042ControllerError::NotPresent); + } + let controller = Self { data_port: IoPort::acquire(0x60).unwrap(), status_or_command_port: IoPort::acquire(0x64).unwrap(), @@ -212,6 +221,7 @@ impl I8042Controller { /// Errors that can occur when initializing the i8042 controller. #[derive(Debug, Clone, Copy)] pub(super) enum I8042ControllerError { + NotPresent, ControllerTestFailed, FirstPortTestFailed, SecondPortTestFailed, diff --git a/kernel/comps/time/src/rtc/cmos.rs b/kernel/comps/time/src/rtc/cmos.rs index b46d80266..009733f00 100644 --- a/kernel/comps/time/src/rtc/cmos.rs +++ b/kernel/comps/time/src/rtc/cmos.rs @@ -1,9 +1,22 @@ // SPDX-License-Identifier: MPL-2.0 +//! CMOS RTC. +//! +//! "CMOS" is a tiny bit of very low power static memory that lives on the same chip as the +//! Real-Time Clock (RTC). +//! +//! According to the Linux implementation, in x86, if the CMOS/RTC is present at the legacy +//! addresses (I/O Ports 0x70 and 0x71), then it is an MC146818 CMOS/RTC. Therefore, in this +//! module, the register addresses and data interpretation are based on the MC146818 datasheet. +//! +//! Reference: +//! +//! + use core::num::NonZeroU8; use log::warn; -use ostd::{arch::device::io_port::{ReadWriteAccess, WriteOnlyAccess}, io::IoPort, sync::SpinLock}; +use ostd::{arch::{device::io_port::{ReadWriteAccess, WriteOnlyAccess}, kernel::ACPI_INFO}, io::IoPort, sync::SpinLock}; use crate::SystemTime; use super::Driver; @@ -27,6 +40,15 @@ impl Driver for RtcCmos { const IOPORT_SEL: u16 = 0x70; const IOPORT_VAL: u16 = 0x71; + let acpi_info = ACPI_INFO.get().unwrap(); + + if acpi_info.boot_flags.is_some_and(|flags| flags.use_time_and_alarm_namespace_for_rtc()) { + // "If set, indicates that the CMOS RTC is either not implemented, or does not exist at + // the legacy addresses". See: + // . + return None; + } + let (io_sel, io_val) = match (IoPort::acquire(IOPORT_SEL), IoPort::acquire(IOPORT_VAL)) { (Ok(io_sel), Ok(io_val)) => (io_sel, io_val), _ => { @@ -35,7 +57,7 @@ impl Driver for RtcCmos { } }; - let century_register = ostd::arch::device::cmos::century_register().and_then(NonZeroU8::new); + let century_register = acpi_info.century_register; let mut access = CmosAccess { io_sel, @@ -44,6 +66,14 @@ impl Driver for RtcCmos { }; let status_b = access.read_status_b(); + // Ideally, the absence of the CMOS RTC should be reported in the ACPI tables (We've + // checked the flag above). However, the ACPI tables are sometimes unreliable (e.g., QEMU + // never sets the flag), so we need to perform additional checks. + if !access.check_presence() { + warn!("CMOS RTC reports unexpected status, ignoring this device"); + return None; + } + Some(Self { access: SpinLock::new(access), status_b, @@ -72,6 +102,8 @@ enum Register { StatusA = 0x0A, StatusB = 0x0B, + // `StatusC` is not used. + StatusD = 0x0D, } bitflags::bitflags! { @@ -94,6 +126,15 @@ bitflags::bitflags! { } } +bitflags::bitflags! { + struct StatusD: u8 { + /// The valid RAM and time (VRT) bit. + /// + /// This bit is set when the RAM and time are valid. + const VRT = 1 << 7; + } +} + impl CmosAccess { pub(self) fn read_register(&mut self, reg: Register) -> u8 { self.read_register_impl(reg as u8) @@ -111,6 +152,12 @@ impl CmosAccess { StatusB::from_bits_truncate(self.read_register_impl(Register::StatusB as u8)) } + pub(self) fn check_presence(&mut self) -> bool { + // If a working CMOS RTC is present, `VRT` should be set and all other reserved bits should + // not be set. + self.read_register_impl(Register::StatusD as u8) == StatusD::VRT.bits() + } + fn read_register_impl(&mut self, reg: u8) -> u8 { self.io_sel.write(reg); self.io_val.read() diff --git a/kernel/comps/time/src/rtc/mod.rs b/kernel/comps/time/src/rtc/mod.rs index 6a6fafb90..882245971 100644 --- a/kernel/comps/time/src/rtc/mod.rs +++ b/kernel/comps/time/src/rtc/mod.rs @@ -32,7 +32,7 @@ macro_rules! declare_rtc_drivers { } )* - log::warn!("No RTC device found, falling back to a dummy RTC."); + log::warn!("No RTC device found, falling back to a dummy RTC"); Arc::new(RtcDummy) } diff --git a/ostd/src/arch/loongarch/device/mod.rs b/ostd/src/arch/loongarch/device/mod.rs index 222526819..44ffc29d4 100644 --- a/ostd/src/arch/loongarch/device/mod.rs +++ b/ostd/src/arch/loongarch/device/mod.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 //! Device-related APIs. -//! This module mainly contains the APIs that should exposed to the device driver like PCI, RTC pub mod io_port; diff --git a/ostd/src/arch/riscv/device/mod.rs b/ostd/src/arch/riscv/device/mod.rs index 222526819..44ffc29d4 100644 --- a/ostd/src/arch/riscv/device/mod.rs +++ b/ostd/src/arch/riscv/device/mod.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 //! Device-related APIs. -//! This module mainly contains the APIs that should exposed to the device driver like PCI, RTC pub mod io_port; diff --git a/ostd/src/arch/x86/device/cmos.rs b/ostd/src/arch/x86/device/cmos.rs deleted file mode 100644 index c11b37fdc..000000000 --- a/ostd/src/arch/x86/device/cmos.rs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! 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). -//! -//! Reference: -//! - -use acpi::fadt::Fadt; - -use crate::arch::kernel::acpi::get_acpi_tables; - -/// 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(fadt) => Some(fadt.century), - Err(_) => None, - } -} diff --git a/ostd/src/arch/x86/device/mod.rs b/ostd/src/arch/x86/device/mod.rs index d588d790a..cdefb2d30 100644 --- a/ostd/src/arch/x86/device/mod.rs +++ b/ostd/src/arch/x86/device/mod.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 //! Device-related APIs. -//! This module mainly contains the APIs that should exposed to the device driver like PCI, RTC -pub mod cmos; pub mod io_port; pub mod serial; diff --git a/ostd/src/arch/x86/kernel/acpi/mod.rs b/ostd/src/arch/x86/kernel/acpi/mod.rs index 7c37d0e28..7ee51efd6 100644 --- a/ostd/src/arch/x86/kernel/acpi/mod.rs +++ b/ostd/src/arch/x86/kernel/acpi/mod.rs @@ -1,12 +1,17 @@ // SPDX-License-Identifier: MPL-2.0 -pub mod dmar; -pub mod remapping; +pub(in crate::arch) mod dmar; +pub(in crate::arch) mod remapping; -use core::ptr::NonNull; +use core::{num::NonZeroU8, ptr::NonNull}; -use acpi::{rsdp::Rsdp, AcpiHandler, AcpiTables}; +use acpi::{ + fadt::{Fadt, IaPcBootArchFlags}, + rsdp::Rsdp, + AcpiHandler, AcpiTables, +}; use log::warn; +use spin::Once; use crate::{ boot::{self, BootloaderAcpiArg}, @@ -14,7 +19,7 @@ use crate::{ }; #[derive(Debug, Clone)] -pub struct AcpiMemoryHandler {} +pub(crate) struct AcpiMemoryHandler {} impl AcpiHandler for AcpiMemoryHandler { unsafe fn map_physical_region( @@ -65,3 +70,38 @@ pub(crate) fn get_acpi_tables() -> Option> { Some(acpi_tables) } + +/// The platform information provided by the ACPI tables. +/// +/// Currently, this structure contains only a limited set of fields, far fewer than those in all +/// ACPI tables. However, the goal is to expand it properly to keep the simplicity of the OSTD code +/// while enabling OSTD users to safely retrieve information from the ACPI tables. +#[derive(Debug)] +pub struct AcpiInfo { + /// The RTC CMOS RAM index to the century of data value; the "CENTURY" field in the FADT. + pub century_register: Option, + /// IA-PC Boot Architecture Flags; the "IAPC_BOOT_ARCH" field in the FADT. + pub boot_flags: Option, +} + +/// The [`AcpiInfo`] singleton. +pub static ACPI_INFO: Once = Once::new(); + +pub(in crate::arch) fn init() { + let mut acpi_info = AcpiInfo { + century_register: None, + boot_flags: None, + }; + + if let Some(acpi_tables) = get_acpi_tables() + && let Ok(fadt) = acpi_tables.find_table::() + { + // A zero means that the century register does not exist. + acpi_info.century_register = NonZeroU8::new(fadt.century); + acpi_info.boot_flags = Some(fadt.iapc_boot_arch); + }; + + log::info!("[ACPI]: Collected information {:?}", acpi_info); + + ACPI_INFO.call_once(|| acpi_info); +} diff --git a/ostd/src/arch/x86/kernel/mod.rs b/ostd/src/arch/x86/kernel/mod.rs index 168c1baa2..b48ef230a 100644 --- a/ostd/src/arch/x86/kernel/mod.rs +++ b/ostd/src/arch/x86/kernel/mod.rs @@ -8,3 +8,5 @@ pub(super) mod acpi; pub(super) mod apic; pub(super) mod tsc; + +pub use acpi::{AcpiInfo, ACPI_INFO}; diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 19dcdb387..300b661ac 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -8,7 +8,7 @@ pub mod device; pub(crate) mod io; pub(crate) mod iommu; pub mod irq; -mod kernel; +pub mod kernel; pub(crate) mod mm; pub mod qemu; pub(crate) mod serial; @@ -65,6 +65,8 @@ pub(crate) unsafe fn late_init_on_bsp() { kernel::tsc::init_tsc_freq(); timer::init_on_bsp(); + kernel::acpi::init(); + // SAFETY: We're on the BSP and we're ready to boot all APs. unsafe { crate::boot::smp::boot_all_aps() };