Add the `arch::cpu::cpuid` module
This commit is contained in:
parent
652657fba5
commit
9de70e38de
|
|
@ -13,6 +13,8 @@ TME = "TME"
|
|||
BA = "BA"
|
||||
ND = "ND"
|
||||
Fo = "Fo"
|
||||
pn = "pn"
|
||||
sme = "sme"
|
||||
Inh = "Inh"
|
||||
DELET = "DELET"
|
||||
wrk = "wrk"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{format, string::String};
|
||||
use core::fmt;
|
||||
|
||||
use ostd::{
|
||||
arch::cpu::context::{CpuExceptionInfo, UserContext},
|
||||
cpu::PinCurrentCpu,
|
||||
task::DisabledPreemptGuard,
|
||||
user::UserContextApi,
|
||||
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.
|
||||
pub struct CpuInfo {
|
||||
pub struct CpuInformation {
|
||||
processor: u32,
|
||||
}
|
||||
|
||||
impl CpuInfo {
|
||||
pub fn new(processor_id: u32) -> Self {
|
||||
impl CpuInformation {
|
||||
/// Constructs the information for the current CPU.
|
||||
pub fn new(guard: &DisabledPreemptGuard) -> 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 {
|
||||
format!("processor\t: {}\n", self.processor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CpuInformation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "processor\t: {}", self.processor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{format, string::String};
|
||||
use core::fmt;
|
||||
|
||||
use ostd::{
|
||||
arch::cpu::context::{CpuExceptionInfo, UserContext},
|
||||
cpu::PinCurrentCpu,
|
||||
task::DisabledPreemptGuard,
|
||||
user::UserContextApi,
|
||||
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.
|
||||
pub struct CpuInfo {
|
||||
pub struct CpuInformation {
|
||||
processor: u32,
|
||||
}
|
||||
|
||||
impl CpuInfo {
|
||||
pub fn new(processor_id: u32) -> Self {
|
||||
impl CpuInformation {
|
||||
/// Constructs the information for the current CPU.
|
||||
pub fn new(guard: &DisabledPreemptGuard) -> 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 {
|
||||
format!("processor\t: {}\n", self.processor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CpuInformation {
|
||||
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
|
|
@ -71,6 +71,10 @@ pub fn init() {
|
|||
rootfs::init();
|
||||
}
|
||||
|
||||
pub fn init_on_each_cpu() {
|
||||
procfs::init_on_each_cpu();
|
||||
}
|
||||
|
||||
pub fn init_in_first_kthread(fs_resolver: &FsResolver) {
|
||||
rootfs::init_in_first_kthread(fs_resolver).unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
//!
|
||||
//! 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::{
|
||||
arch::cpu::CpuInfo,
|
||||
arch::cpu::CpuInformation,
|
||||
fs::{
|
||||
procfs::template::{FileOps, ProcFileBuilder},
|
||||
utils::Inode,
|
||||
|
|
@ -20,30 +25,30 @@ use crate::{
|
|||
pub struct 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> {
|
||||
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 {
|
||||
/// Retrieve the data for `/proc/cpuinfo`.
|
||||
/// Retrieves the data for `/proc/cpuinfo`.
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ pub(super) fn init() {
|
|||
super::registry::register(&ProcFsType).unwrap();
|
||||
}
|
||||
|
||||
pub(super) fn init_on_each_cpu() {
|
||||
cpuinfo::init_on_each_cpu();
|
||||
}
|
||||
|
||||
/// Magic number.
|
||||
const PROC_MAGIC: u64 = 0x9fa0;
|
||||
/// Root Inode ID.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use kcmdline::KCmdlineArg;
|
|||
use ostd::{
|
||||
arch::qemu::{exit_qemu, QemuExitCode},
|
||||
boot::boot_info,
|
||||
cpu::{set::CpuSet, CpuId},
|
||||
cpu::CpuId,
|
||||
};
|
||||
use process::{spawn_init_process, Process};
|
||||
use sched::SchedPolicy;
|
||||
|
|
@ -88,12 +88,11 @@ fn main() {
|
|||
|
||||
// Spawn all AP idle threads.
|
||||
ostd::boot::smp::register_ap_entry(ap_init);
|
||||
init_on_each_cpu();
|
||||
|
||||
// Spawn the first kernel thread on BSP.
|
||||
let mut affinity = CpuSet::new_empty();
|
||||
affinity.add(CpuId::bsp());
|
||||
ThreadOptions::new(first_kthread)
|
||||
.cpu_affinity(affinity)
|
||||
.cpu_affinity(CpuId::bsp().into())
|
||||
.sched_policy(SchedPolicy::Idle)
|
||||
.spawn();
|
||||
}
|
||||
|
|
@ -109,6 +108,10 @@ fn init() {
|
|||
fs::init();
|
||||
}
|
||||
|
||||
fn init_on_each_cpu() {
|
||||
fs::init_on_each_cpu();
|
||||
}
|
||||
|
||||
fn init_in_first_kthread(fs_resolver: &FsResolver) {
|
||||
// Work queue should be initialized before interrupt is enabled,
|
||||
// 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() {
|
||||
init_on_each_cpu();
|
||||
|
||||
fn ap_idle_thread() {
|
||||
log::info!(
|
||||
"Kernel idle thread for CPU #{} started.",
|
||||
|
|
|
|||
|
|
@ -36,8 +36,6 @@ cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
pub use x86::cpuid;
|
||||
|
||||
/// Userspace CPU context, including general-purpose registers and exception information.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
#[repr(C)]
|
||||
|
|
@ -649,29 +647,14 @@ static XSAVE_AREA_SIZE: Once<usize> = Once::new();
|
|||
const MAX_XSAVE_AREA_SIZE: usize = 4096;
|
||||
|
||||
pub(in crate::arch) fn enable_essential_features() {
|
||||
XSTATE_MAX_FEATURES.call_once(|| {
|
||||
const XSTATE_CPUID: u32 = 0x0000000d;
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
XSTATE_MAX_FEATURES.call_once(|| super::cpuid::query_xstate_max_features().unwrap());
|
||||
XSAVE_AREA_SIZE.call_once(|| {
|
||||
let cpuid = cpuid::CpuId::new();
|
||||
let size = cpuid
|
||||
.get_extended_state_info()
|
||||
.unwrap()
|
||||
.xsave_area_size_enabled_features() as usize;
|
||||
debug_assert!(size <= MAX_XSAVE_AREA_SIZE);
|
||||
size
|
||||
let xsave_area_size = super::cpuid::query_xsave_area_size().unwrap() as usize;
|
||||
assert!(xsave_area_size <= MAX_XSAVE_AREA_SIZE);
|
||||
xsave_area_size
|
||||
});
|
||||
}
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_fpu() {
|
||||
let mut cr0 = Cr0::read();
|
||||
|
|
|
|||
|
|
@ -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 Developer’s 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)
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@
|
|||
//! CPU context & state control and CPU local memory.
|
||||
|
||||
pub mod context;
|
||||
pub mod cpuid;
|
||||
pub mod local;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#![expect(unused_variables)]
|
||||
|
||||
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
|
||||
use log::info;
|
||||
use x86::cpuid::cpuid;
|
||||
|
||||
use crate::{
|
||||
arch::{
|
||||
|
|
@ -18,58 +15,21 @@ use crate::{
|
|||
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 fn init_tsc_freq() {
|
||||
let tsc_freq =
|
||||
determine_tsc_freq_via_cpuid().map_or_else(determine_tsc_freq_via_pit, |freq| freq);
|
||||
use crate::arch::cpu::cpuid::query_tsc_freq as determine_tsc_freq_via_cpuid;
|
||||
|
||||
let tsc_freq = determine_tsc_freq_via_cpuid().unwrap_or_else(determine_tsc_freq_via_pit);
|
||||
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
|
||||
/// CPUID, the function will return None. The unit of the return value is KHz.
|
||||
/// Determines the TSC frequency with the help of the Programmable Interval Timer (PIT).
|
||||
///
|
||||
/// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c`
|
||||
///
|
||||
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.
|
||||
/// 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_pit() -> u64 {
|
||||
// Allocate IRQ
|
||||
let mut irq = IrqLine::alloc().unwrap();
|
||||
|
|
@ -100,7 +60,7 @@ pub fn determine_tsc_freq_via_pit() -> u64 {
|
|||
|
||||
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 TSC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
// Set a certain times of callbacks to calculate the frequency
|
||||
|
|
|
|||
Loading…
Reference in New Issue