Check the existence of i8042 and RTC CMOS

This commit is contained in:
Ruihan Li 2025-10-29 23:04:34 +08:00 committed by Tate, Hongliang Tian
parent 9a8e6fd372
commit 668876aeee
10 changed files with 113 additions and 40 deletions

View File

@ -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<Self, I8042ControllerError> {
// TODO: Check the flags in the ACPI table to determine if the PS/2 controller exists. See:
// <https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#ia-pc-boot-architecture-flags>.
if ACPI_INFO
.get()
.unwrap()
.boot_flags
.is_some_and(|flags| !flags.motherboard_implements_8042())
{
// The PS/2 controller does not exist. See:
// <https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#ia-pc-boot-architecture-flags>.
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,

View File

@ -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:
//! <https://elixir.bootlin.com/linux/v6.17.5/source/arch/x86/kernel/rtc.c#L69>
//! <https://www.scs.stanford.edu/23wi-cs212/pintos/specs/mc146818a.pdf>
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:
// <https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#ia-pc-boot-architecture-flags>.
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()

View File

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

View File

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

View File

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

View File

@ -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: <https://wiki.osdev.org/CMOS>
//!
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<u8> {
let acpi_tables = get_acpi_tables()?;
match acpi_tables.find_table::<Fadt>() {
Ok(fadt) => Some(fadt.century),
Err(_) => None,
}
}

View File

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

View File

@ -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<T>(
@ -65,3 +70,38 @@ pub(crate) fn get_acpi_tables() -> Option<AcpiTables<AcpiMemoryHandler>> {
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<NonZeroU8>,
/// IA-PC Boot Architecture Flags; the "IAPC_BOOT_ARCH" field in the FADT.
pub boot_flags: Option<IaPcBootArchFlags>,
}
/// The [`AcpiInfo`] singleton.
pub static ACPI_INFO: Once<AcpiInfo> = 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::<Fadt>()
{
// 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);
}

View File

@ -8,3 +8,5 @@
pub(super) mod acpi;
pub(super) mod apic;
pub(super) mod tsc;
pub use acpi::{AcpiInfo, ACPI_INFO};

View File

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