Add the `arch::cpu::cpuid` module

This commit is contained in:
Ruihan Li 2025-08-28 23:10:12 +08:00 committed by Tate, Hongliang Tian
parent 652657fba5
commit 9de70e38de
12 changed files with 887 additions and 513 deletions

View File

@ -13,6 +13,8 @@ TME = "TME"
BA = "BA" BA = "BA"
ND = "ND" ND = "ND"
Fo = "Fo" Fo = "Fo"
pn = "pn"
sme = "sme"
Inh = "Inh" Inh = "Inh"
DELET = "DELET" DELET = "DELET"
wrk = "wrk" wrk = "wrk"

View File

@ -1,9 +1,11 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use alloc::{format, string::String}; use core::fmt;
use ostd::{ use ostd::{
arch::cpu::context::{CpuExceptionInfo, UserContext}, arch::cpu::context::{CpuExceptionInfo, UserContext},
cpu::PinCurrentCpu,
task::DisabledPreemptGuard,
user::UserContextApi, user::UserContextApi,
Pod, Pod,
}; };
@ -161,21 +163,27 @@ impl TryFrom<&CpuExceptionInfo> for PageFaultInfo {
} }
} }
/// CPU Information structure. /// CPU information to be shown in `/proc/cpuinfo`.
///
/// Different CPUs may have different information, such as the core ID. Therefore, [`Self::new`]
/// should be called on every CPU.
//
// TODO: Implement CPU information retrieval on LoongArch platforms. // TODO: Implement CPU information retrieval on LoongArch platforms.
pub struct CpuInfo { pub struct CpuInformation {
processor: u32, processor: u32,
} }
impl CpuInfo { impl CpuInformation {
pub fn new(processor_id: u32) -> Self { /// Constructs the information for the current CPU.
pub fn new(guard: &DisabledPreemptGuard) -> Self {
Self { Self {
processor: processor_id, processor: guard.current_cpu().as_usize() as u32,
} }
} }
}
/// Collect and format CPU information into a `String`.
pub fn collect_cpu_info(&self) -> String { impl fmt::Display for CpuInformation {
format!("processor\t: {}\n", self.processor) fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
} writeln!(f, "processor\t: {}", self.processor)
}
} }

View File

@ -1,9 +1,11 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use alloc::{format, string::String}; use core::fmt;
use ostd::{ use ostd::{
arch::cpu::context::{CpuExceptionInfo, UserContext}, arch::cpu::context::{CpuExceptionInfo, UserContext},
cpu::PinCurrentCpu,
task::DisabledPreemptGuard,
user::UserContextApi, user::UserContextApi,
Pod, Pod,
}; };
@ -157,21 +159,27 @@ impl TryFrom<&CpuExceptionInfo> for PageFaultInfo {
} }
} }
/// CPU Information structure. /// CPU information to be shown in `/proc/cpuinfo`.
///
/// Different CPUs may have different information, such as the core ID. Therefore, [`Self::new`]
/// should be called on every CPU.
//
// TODO: Implement CPU information retrieval on RISC-V platforms. // TODO: Implement CPU information retrieval on RISC-V platforms.
pub struct CpuInfo { pub struct CpuInformation {
processor: u32, processor: u32,
} }
impl CpuInfo { impl CpuInformation {
pub fn new(processor_id: u32) -> Self { /// Constructs the information for the current CPU.
pub fn new(guard: &DisabledPreemptGuard) -> Self {
Self { Self {
processor: processor_id, processor: guard.current_cpu().as_usize() as u32,
} }
} }
}
/// Collect and format CPU information into a `String`.
pub fn collect_cpu_info(&self) -> String { impl fmt::Display for CpuInformation {
format!("processor\t: {}\n", self.processor) fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
} writeln!(f, "processor\t: {}", self.processor)
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,10 @@ pub fn init() {
rootfs::init(); rootfs::init();
} }
pub fn init_on_each_cpu() {
procfs::init_on_each_cpu();
}
pub fn init_in_first_kthread(fs_resolver: &FsResolver) { pub fn init_in_first_kthread(fs_resolver: &FsResolver) {
rootfs::init_in_first_kthread(fs_resolver).unwrap(); rootfs::init_in_first_kthread(fs_resolver).unwrap();
} }

View File

@ -5,10 +5,15 @@
//! //!
//! Reference: <https://man7.org/linux/man-pages/man5/proc_cpuinfo.5.html> //! Reference: <https://man7.org/linux/man-pages/man5/proc_cpuinfo.5.html>
use ostd::cpu::num_cpus; use ostd::{
cpu::{all_cpus, PinCurrentCpu},
cpu_local,
task::disable_preempt,
};
use spin::Once;
use crate::{ use crate::{
arch::cpu::CpuInfo, arch::cpu::CpuInformation,
fs::{ fs::{
procfs::template::{FileOps, ProcFileBuilder}, procfs::template::{FileOps, ProcFileBuilder},
utils::Inode, utils::Inode,
@ -20,30 +25,30 @@ use crate::{
pub struct CpuInfoFileOps; pub struct CpuInfoFileOps;
impl CpuInfoFileOps { impl CpuInfoFileOps {
/// Create a new inode for `/proc/cpuinfo`. /// Creates a new inode for `/proc/cpuinfo`.
pub fn new_inode(parent: Weak<dyn Inode>) -> Arc<dyn Inode> { pub fn new_inode(parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
ProcFileBuilder::new(Self).parent(parent).build().unwrap() ProcFileBuilder::new(Self).parent(parent).build().unwrap()
} }
/// Collect and format CPU information for all cores.
fn collect_cpu_info() -> String {
let num_cpus = num_cpus() as u32;
// Iterate over each core and collect CPU information
(0..num_cpus)
.map(|core_id| {
let cpuinfo = CpuInfo::new(core_id);
cpuinfo.collect_cpu_info()
})
.collect::<Vec<String>>()
.join("\n\n")
}
} }
impl FileOps for CpuInfoFileOps { impl FileOps for CpuInfoFileOps {
/// Retrieve the data for `/proc/cpuinfo`. /// Retrieves the data for `/proc/cpuinfo`.
fn data(&self) -> Result<Vec<u8>> { fn data(&self) -> Result<Vec<u8>> {
let output = Self::collect_cpu_info(); let output = all_cpus()
.map(|cpu| CPU_INFORMATION.get_on_cpu(cpu).wait().to_string())
.collect::<Vec<String>>()
.join("\n");
Ok(output.into_bytes()) Ok(output.into_bytes())
} }
} }
cpu_local! {
static CPU_INFORMATION: Once<CpuInformation> = Once::new();
}
pub(super) fn init_on_each_cpu() {
let guard = disable_preempt();
CPU_INFORMATION
.get_on_cpu(guard.current_cpu())
.call_once(|| CpuInformation::new(&guard));
}

View File

@ -40,6 +40,10 @@ pub(super) fn init() {
super::registry::register(&ProcFsType).unwrap(); super::registry::register(&ProcFsType).unwrap();
} }
pub(super) fn init_on_each_cpu() {
cpuinfo::init_on_each_cpu();
}
/// Magic number. /// Magic number.
const PROC_MAGIC: u64 = 0x9fa0; const PROC_MAGIC: u64 = 0x9fa0;
/// Root Inode ID. /// Root Inode ID.

View File

@ -33,7 +33,7 @@ use kcmdline::KCmdlineArg;
use ostd::{ use ostd::{
arch::qemu::{exit_qemu, QemuExitCode}, arch::qemu::{exit_qemu, QemuExitCode},
boot::boot_info, boot::boot_info,
cpu::{set::CpuSet, CpuId}, cpu::CpuId,
}; };
use process::{spawn_init_process, Process}; use process::{spawn_init_process, Process};
use sched::SchedPolicy; use sched::SchedPolicy;
@ -88,12 +88,11 @@ fn main() {
// Spawn all AP idle threads. // Spawn all AP idle threads.
ostd::boot::smp::register_ap_entry(ap_init); ostd::boot::smp::register_ap_entry(ap_init);
init_on_each_cpu();
// Spawn the first kernel thread on BSP. // Spawn the first kernel thread on BSP.
let mut affinity = CpuSet::new_empty();
affinity.add(CpuId::bsp());
ThreadOptions::new(first_kthread) ThreadOptions::new(first_kthread)
.cpu_affinity(affinity) .cpu_affinity(CpuId::bsp().into())
.sched_policy(SchedPolicy::Idle) .sched_policy(SchedPolicy::Idle)
.spawn(); .spawn();
} }
@ -109,6 +108,10 @@ fn init() {
fs::init(); fs::init();
} }
fn init_on_each_cpu() {
fs::init_on_each_cpu();
}
fn init_in_first_kthread(fs_resolver: &FsResolver) { fn init_in_first_kthread(fs_resolver: &FsResolver) {
// Work queue should be initialized before interrupt is enabled, // Work queue should be initialized before interrupt is enabled,
// in case any irq handler uses work queue as bottom half // in case any irq handler uses work queue as bottom half
@ -127,6 +130,8 @@ fn init_in_first_process(ctx: &Context) {
} }
fn ap_init() { fn ap_init() {
init_on_each_cpu();
fn ap_idle_thread() { fn ap_idle_thread() {
log::info!( log::info!(
"Kernel idle thread for CPU #{} started.", "Kernel idle thread for CPU #{} started.",

View File

@ -36,8 +36,6 @@ cfg_if! {
} }
} }
pub use x86::cpuid;
/// Userspace CPU context, including general-purpose registers and exception information. /// Userspace CPU context, including general-purpose registers and exception information.
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
#[repr(C)] #[repr(C)]
@ -649,29 +647,14 @@ static XSAVE_AREA_SIZE: Once<usize> = Once::new();
const MAX_XSAVE_AREA_SIZE: usize = 4096; const MAX_XSAVE_AREA_SIZE: usize = 4096;
pub(in crate::arch) fn enable_essential_features() { pub(in crate::arch) fn enable_essential_features() {
XSTATE_MAX_FEATURES.call_once(|| { if CPU_FEATURES.get().unwrap().has_xsave() {
const XSTATE_CPUID: u32 = 0x0000000d; XSTATE_MAX_FEATURES.call_once(|| super::cpuid::query_xstate_max_features().unwrap());
// Find user xstates supported by the processor.
let res0 = cpuid::cpuid!(XSTATE_CPUID, 0);
let mut features = res0.eax as u64 + ((res0.edx as u64) << 32);
// Find supervisor xstates supported by the processor.
let res1 = cpuid::cpuid!(XSTATE_CPUID, 1);
features |= res1.ecx as u64 + ((res1.edx as u64) << 32);
features
});
XSAVE_AREA_SIZE.call_once(|| { XSAVE_AREA_SIZE.call_once(|| {
let cpuid = cpuid::CpuId::new(); let xsave_area_size = super::cpuid::query_xsave_area_size().unwrap() as usize;
let size = cpuid assert!(xsave_area_size <= MAX_XSAVE_AREA_SIZE);
.get_extended_state_info() xsave_area_size
.unwrap()
.xsave_area_size_enabled_features() as usize;
debug_assert!(size <= MAX_XSAVE_AREA_SIZE);
size
}); });
}
if CPU_FEATURES.get().unwrap().has_fpu() { if CPU_FEATURES.get().unwrap().has_fpu() {
let mut cr0 = Cr0::read(); let mut cr0 = Cr0::read();

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: MPL-2.0
//! CPU information from the CPUID instruction.
use core::arch::x86_64::CpuidResult;
use spin::Once;
static MAX_LEAF: Once<u32> = Once::new();
static MAX_EXTENDED_LEAF: Once<u32> = Once::new();
#[repr(u32)]
enum Leaf {
Base = 0x00,
Xstate = 0x0d,
Tsc = 0x15,
ExtBase = 0x80000000,
}
/// Executes the CPUID instruction for the given leaf and subleaf.
///
/// This method will return `None` if the leaf is not supported.
pub fn cpuid(leaf: u32, subleaf: u32) -> Option<CpuidResult> {
fn raw_cpuid(leaf: u32, subleaf: u32) -> CpuidResult {
// SAFETY: It is safe to execute the CPUID instruction.
unsafe { core::arch::x86_64::__cpuid_count(leaf, subleaf) }
}
let max_leaf = if leaf < Leaf::ExtBase as u32 {
*MAX_LEAF.call_once(|| raw_cpuid(Leaf::Base as u32, 0).eax)
} else {
*MAX_EXTENDED_LEAF.call_once(|| raw_cpuid(Leaf::ExtBase as u32, 0).eax)
};
if leaf > max_leaf {
None
} else {
Some(raw_cpuid(leaf, subleaf))
}
}
/// Queries the frequency in Hz of the Time Stamp Counter (TSC).
///
/// This is based on the information given by the CPUID instruction in the Time Stamp Counter and
/// Nominal Core Crystal Clock Information Leaf.
///
/// Note that the CPUID leaf is currently only supported by new Intel CPUs. This method will return
/// `None` if it is not supported.
pub(in crate::arch) fn query_tsc_freq() -> Option<u64> {
let CpuidResult {
eax: denominator,
ebx: numerator,
ecx: crystal_freq,
..
} = cpuid(Leaf::Tsc as u32, 0)?;
if denominator == 0 || numerator == 0 {
return None;
}
// If the nominal core crystal clock frequency is not enumerated, we can either obtain that
// information from a hardcoded table or rely on the processor base frequency. The Intel
// documentation recommends the first approach [1], but Linux uses the second approach because
// the first approach is difficult to implement correctly for all corner cases [2]. However,
// the second approach does not provide 100% accurate frequencies, so Linux must adjust them at
// runtime [2]. For now, we avoid these headaches by faithfully reporting that the TSC
// frequency is unavailable.
//
// [1]: Intel(R) 64 and IA-32 Architectures Software Developers Manual,
// Section 20.7.3, Determining the Processor Base Frequency
// [2]: https://github.com/torvalds/linux/commit/604dc9170f2435d27da5039a3efd757dceadc684
if crystal_freq == 0 {
return None;
}
Some((crystal_freq as u64) * (numerator as u64) / (denominator as u64))
}
/// Queries the supported XSTATE features, i.e., the supported bits of `XCR0` and `IA32_XSS`.
pub(in crate::arch) fn query_xstate_max_features() -> Option<u64> {
let res0 = cpuid(Leaf::Xstate as u32, 0)?;
let res1 = cpuid(Leaf::Xstate as u32, 1)?;
// Supported bits in `XCR0`.
let xcr_bits = (res0.eax as u64) | ((res0.edx as u64) << 32);
// Supported bits in `IA32_XSS`.
let xss_bits = (res1.ecx as u64) | ((res1.edx as u64) << 32);
Some(xcr_bits | xss_bits)
}
/// Queries the size in bytes of the XSAVE area containing states enabled by `XCRO` and `IA32_XSS`.
pub(in crate::arch) fn query_xsave_area_size() -> Option<u32> {
cpuid(Leaf::Xstate as u32, 1).map(|res| res.ebx)
}

View File

@ -3,4 +3,5 @@
//! CPU context & state control and CPU local memory. //! CPU context & state control and CPU local memory.
pub mod context; pub mod context;
pub mod cpuid;
pub mod local; pub mod local;

View File

@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
#![expect(unused_variables)]
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use log::info; use log::info;
use x86::cpuid::cpuid;
use crate::{ use crate::{
arch::{ arch::{
@ -18,58 +15,21 @@ use crate::{
trap::irq::IrqLine, trap::irq::IrqLine,
}; };
/// The frequency of TSC(Hz) /// The frequency in Hz of the Time Stamp Counter (TSC).
pub(in crate::arch) static TSC_FREQ: AtomicU64 = AtomicU64::new(0); pub(in crate::arch) static TSC_FREQ: AtomicU64 = AtomicU64::new(0);
pub fn init_tsc_freq() { pub fn init_tsc_freq() {
let tsc_freq = use crate::arch::cpu::cpuid::query_tsc_freq as determine_tsc_freq_via_cpuid;
determine_tsc_freq_via_cpuid().map_or_else(determine_tsc_freq_via_pit, |freq| freq);
let tsc_freq = determine_tsc_freq_via_cpuid().unwrap_or_else(determine_tsc_freq_via_pit);
TSC_FREQ.store(tsc_freq, Ordering::Relaxed); TSC_FREQ.store(tsc_freq, Ordering::Relaxed);
info!("TSC frequency:{:?} Hz", tsc_freq); info!("TSC frequency: {:?} Hz", tsc_freq);
} }
/// Determines TSC frequency via CPUID. If the CPU does not support calculating TSC frequency by /// Determines the TSC frequency with the help of the Programmable Interval Timer (PIT).
/// CPUID, the function will return None. The unit of the return value is KHz.
/// ///
/// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c` /// When the TSC frequency is not enumerated in the results of the CPUID instruction, it can
/// /// leverage the PIT to calculate the TSC frequency.
pub fn determine_tsc_freq_via_cpuid() -> Option<u64> {
// Check the max cpuid supported
let cpuid = cpuid!(0);
let max_cpuid = cpuid.eax;
if max_cpuid <= 0x15 {
return None;
}
// TSC frequecny = ecx * ebx / eax
// CPUID 0x15: Time Stamp Counter and Nominal Core Crystal Clock Information Leaf
let mut cpuid = cpuid!(0x15);
if cpuid.eax == 0 || cpuid.ebx == 0 {
return None;
}
let eax_denominator = cpuid.eax;
let ebx_numerator = cpuid.ebx;
let mut crystal_khz = cpuid.ecx / 1000;
// Some Intel SoCs like Skylake and Kabylake don't report the crystal
// clock, but we can easily calculate it to a high degree of accuracy
// by considering the crystal ratio and the CPU speed.
if crystal_khz == 0 && max_cpuid >= 0x16 {
cpuid = cpuid!(0x16);
let base_mhz = cpuid.eax;
crystal_khz = base_mhz * 1000 * eax_denominator / ebx_numerator;
}
if crystal_khz == 0 {
None
} else {
let crystal_hz = crystal_khz as u64 * 1000;
Some(crystal_hz * ebx_numerator as u64 / eax_denominator as u64)
}
}
/// When kernel cannot get the TSC frequency from CPUID, it can leverage
/// the PIT to calculate this frequency.
pub fn determine_tsc_freq_via_pit() -> u64 { pub fn determine_tsc_freq_via_pit() -> u64 {
// Allocate IRQ // Allocate IRQ
let mut irq = IrqLine::alloc().unwrap(); let mut irq = IrqLine::alloc().unwrap();
@ -100,7 +60,7 @@ pub fn determine_tsc_freq_via_pit() -> u64 {
return FREQUENCY.load(Ordering::Acquire); return FREQUENCY.load(Ordering::Acquire);
fn pit_callback(trap_frame: &TrapFrame) { fn pit_callback(_trap_frame: &TrapFrame) {
static IN_TIME: AtomicU64 = AtomicU64::new(0); static IN_TIME: AtomicU64 = AtomicU64::new(0);
static TSC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0); static TSC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
// Set a certain times of callbacks to calculate the frequency // Set a certain times of callbacks to calculate the frequency