diff --git a/kernel/src/arch/x86/cpu.rs b/kernel/src/arch/x86/cpu.rs index 7b7aaa1c5..2a42d8a15 100644 --- a/kernel/src/arch/x86/cpu.rs +++ b/kernel/src/arch/x86/cpu.rs @@ -1,7 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; + use ostd::{ - cpu::{CpuException, CpuExceptionInfo, RawGeneralRegs, UserContext}, + cpu::{cpuid, CpuException, CpuExceptionInfo, RawGeneralRegs, UserContext}, Pod, }; @@ -128,3 +134,389 @@ impl TryFrom<&CpuExceptionInfo> for PageFaultInfo { }) } } + +/// CPU Information structure +/// +/// Reference: +/// - https://www.felixcloutier.com/x86/cpuid +/// +/// FIXME: The crate x86 works not well on AMD CPUs, so some information may be missing. +pub struct CpuInfo { + processor: u32, + vendor_id: String, + cpu_family: u32, + model: u32, + model_name: String, + stepping: u32, + microcode: u32, + cpu_mhz: u32, + cache_size: u32, + tlb_size: u32, + physical_id: u32, + siblings: u32, + core_id: u32, + cpu_cores: u32, + apicid: u32, + initial_apicid: u32, + cpuid_level: u32, + flags: String, + bugs: String, + clflush_size: u8, + cache_alignment: u32, + address_sizes: String, + power_management: String, +} + +impl CpuInfo { + pub fn new(processor_id: u32) -> Self { + Self { + processor: processor_id, + vendor_id: Self::get_vendor_id(), + cpu_family: Self::get_cpu_family(), + model: Self::get_model(), + model_name: Self::get_model_name(), + stepping: Self::get_stepping(), + microcode: Self::get_microcode(), + cpu_mhz: Self::get_clock_speed().unwrap_or(0), + cache_size: Self::get_cache_size().unwrap_or(0), + tlb_size: Self::get_tlb_size().unwrap_or(0), + physical_id: Self::get_physical_id().unwrap_or(0), + siblings: Self::get_siblings_count().unwrap_or(0), + core_id: Self::get_core_id(), + cpu_cores: Self::get_cpu_cores(), + apicid: Self::get_apicid(), + initial_apicid: Self::get_initial_apicid(), + cpuid_level: Self::get_cpuid_level(), + flags: Self::get_cpu_flags(), + bugs: Self::get_cpu_bugs(), + // bogomips: Self::get_bogomips(), + clflush_size: Self::get_clflush_size(), + cache_alignment: Self::get_cache_alignment(), + address_sizes: Self::get_address_sizes(), + power_management: Self::get_power_management(), + } + } + + /// Collect and format CPU information into a `String` + pub fn collect_cpu_info(&self) -> String { + format!( + "processor\t: {}\n\ + vendor_id\t: {}\n\ + cpu family\t: {}\n\ + model\t\t: {}\n\ + model name\t: {}\n\ + stepping\t: {}\n\ + microcode\t: 0x{:x}\n\ + cpu MHz\t\t: {}\n\ + cache size\t: {} KB\n\ + TLB size\t: {} 4K pages\n\ + physical id\t: {}\n\ + siblings\t: {}\n\ + core id\t\t: {}\n\ + cpu cores\t: {}\n\ + apicid\t\t: {}\n\ + initial apicid\t: {}\n\ + cpuid level\t: {}\n\ + flags\t\t: {}\n\ + bugs\t\t: {}\n\ + clflush size\t: {} bytes\n\ + cache_alignment\t: {} bytes\n\ + address sizes\t: {}\n\ + power management: {}\n", + self.processor, + self.vendor_id, + self.cpu_family, + self.model, + self.model_name, + self.stepping, + self.microcode, + self.cpu_mhz, + self.cache_size / 1024, + self.tlb_size, + self.physical_id, + self.siblings, + self.core_id, + self.cpu_cores, + self.apicid, + self.initial_apicid, + self.cpuid_level, + self.flags, + self.bugs, + self.clflush_size, + self.cache_alignment, + self.address_sizes, + self.power_management + ) + } + + fn get_vendor_id() -> String { + let cpuid = cpuid::CpuId::new(); + cpuid.get_vendor_info().unwrap().to_string() + } + + fn get_cpu_family() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.family_id().into() + } + + fn get_model() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.model_id().into() + } + + fn get_stepping() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.stepping_id().into() + } + + fn get_model_name() -> String { + let cpuid = cpuid::CpuId::new(); + let brand_string = cpuid.get_processor_brand_string().unwrap(); + brand_string.as_str().to_string() + } + + fn get_microcode() -> u32 { + let cpuid = cpuid::cpuid!(0x1); + cpuid.ecx + } + + fn get_clock_speed() -> Option { + let cpuid = cpuid::CpuId::new(); + let tsc_info = cpuid.get_tsc_info()?; + Some( + (tsc_info.tsc_frequency().unwrap_or(0) / 1_000_000) + .try_into() + .unwrap(), + ) + } + + /// Get cache size in KB + fn get_cache_size() -> Option { + let cpuid = cpuid::CpuId::new(); + let cache_info = cpuid.get_cache_info()?; + + for cache in cache_info { + let desc = cache.desc(); + if let Some(size) = desc.split_whitespace().find(|word| { + word.ends_with("KBytes") || word.ends_with("MBytes") || word.ends_with("GBytes") + }) { + let size_str = size + .trim_end_matches(&['K', 'M', 'G'][..]) + .trim_end_matches("Bytes"); + let cache_size = size_str.parse::().unwrap_or(0); + + let cache_size = match size.chars().last().unwrap() { + 'K' => cache_size * 1024, + 'M' => cache_size * 1024 * 1024, + 'G' => cache_size * 1024 * 1024 * 1024, + _ => cache_size, + }; + + return Some(cache_size); + } + } + + None + } + + fn get_tlb_size() -> Option { + let cpuid = cpuid::CpuId::new(); + let cache_info = cpuid.get_cache_info()?; + + for cache in cache_info { + let desc = cache.desc(); + if let Some(size) = desc.split_whitespace().find(|word| word.ends_with("pages")) { + let size_str = size.trim_end_matches("pages"); + let tlb_size = size_str.parse::().unwrap_or(0); + return Some(tlb_size); + } + } + + None + } + + fn get_physical_id() -> Option { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info()?; + Some(feature_info.initial_local_apic_id().into()) + } + + fn get_siblings_count() -> Option { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info()?; + Some(feature_info.max_logical_processor_ids().into()) + } + + fn get_core_id() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.initial_local_apic_id().into() + } + + fn get_cpu_cores() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.max_logical_processor_ids().into() + } + + fn get_apicid() -> u32 { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + feature_info.initial_local_apic_id().into() + } + + fn get_initial_apicid() -> u32 { + Self::get_apicid() + } + + fn get_cpuid_level() -> u32 { + let cpuid = cpuid::CpuId::new(); + if let Some(basic_info) = cpuid.get_tsc_info() { + basic_info.denominator() + } else { + 0 + } + } + + fn get_cpu_flags() -> String { + let cpuid = cpuid::CpuId::new(); + let feature_info = cpuid.get_feature_info().unwrap(); + let mut flags = Vec::new(); + if feature_info.has_fpu() { + flags.push("fpu"); + } + if feature_info.has_vme() { + flags.push("vme"); + } + if feature_info.has_de() { + flags.push("de"); + } + if feature_info.has_pse() { + flags.push("pse"); + } + if feature_info.has_tsc() { + flags.push("tsc"); + } + if feature_info.has_msr() { + flags.push("msr"); + } + if feature_info.has_pae() { + flags.push("pae"); + } + if feature_info.has_mce() { + flags.push("mce"); + } + if feature_info.has_cmpxchg8b() { + flags.push("cx8"); + } + if feature_info.has_apic() { + flags.push("apic"); + } + if feature_info.has_de() { + flags.push("sep"); + } + if feature_info.has_mtrr() { + flags.push("mtrr"); + } + if feature_info.has_pge() { + flags.push("pge"); + } + if feature_info.has_mca() { + flags.push("mca"); + } + if feature_info.has_cmov() { + flags.push("cmov"); + } + if feature_info.has_pat() { + flags.push("pat"); + } + if feature_info.has_pse36() { + flags.push("pse-36"); + } + if feature_info.has_psn() { + flags.push("psn"); + } + if feature_info.has_clflush() { + flags.push("clfsh"); + } + if feature_info.has_ds() { + flags.push("ds"); + } + if feature_info.has_acpi() { + flags.push("acpi"); + } + if feature_info.has_mmx() { + flags.push("mmx"); + } + if feature_info.has_ds() { + flags.push("fxsr"); + } + if feature_info.has_sse() { + flags.push("sse"); + } + if feature_info.has_sse2() { + flags.push("sse2"); + } + if feature_info.has_ss() { + flags.push("ss"); + } + if feature_info.has_htt() { + flags.push("ht"); + } + if feature_info.has_tm() { + flags.push("tm"); + } + if feature_info.has_pbe() { + flags.push("pbe"); + } + flags.join(" ") + } + + // FIXME: https://github.com/torvalds/linux/blob/master/tools/arch/x86/include/asm/cpufeatures.h#L505 + fn get_cpu_bugs() -> String { + " ".to_string() + } + + fn get_clflush_size() -> u8 { + let cpuid = cpuid::CpuId::new(); + cpuid.get_feature_info().unwrap().cflush_cache_line_size() + } + + fn get_cache_alignment() -> u32 { + let cpuid = cpuid::CpuId::new(); + if let Some(cache_info) = cpuid.get_cache_info() { + for cache in cache_info { + let desc = cache.desc(); + if let Some(alignment) = desc + .split_whitespace() + .find(|word| word.ends_with("byte line size")) + { + let alignment_str = alignment.trim_end_matches(" byte line size"); + if let Ok(alignment) = alignment_str.parse::() { + return alignment; + } + } + } + } + + 64 + } + + fn get_address_sizes() -> String { + let leaf = cpuid::cpuid!(0x80000008); // Extended Function CPUID Information + let physical_address_bits = (leaf.eax & 0xFF) as u32; + let virtual_address_bits = ((leaf.eax >> 8) & 0xFF) as u32; + format!( + "{} bits physical, {} bits virtual", + physical_address_bits, virtual_address_bits + ) + } + + // FIXME: add power management information + fn get_power_management() -> String { + " ".to_string() + } +} diff --git a/kernel/src/fs/procfs/cpuinfo.rs b/kernel/src/fs/procfs/cpuinfo.rs new file mode 100644 index 000000000..98805f85d --- /dev/null +++ b/kernel/src/fs/procfs/cpuinfo.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This module offers `/proc/cpuinfo` file support, which provides +//! information about the CPU architecture, cores, and other details. +//! +//! Reference: + +use ostd::cpu::num_cpus; + +use crate::{ + arch::cpu::CpuInfo, + fs::{ + procfs::template::{FileOps, ProcFileBuilder}, + utils::Inode, + }, + prelude::*, +}; + +/// Represents the inode at `/proc/cpuinfo`. +pub struct CpuInfoFileOps; + +impl CpuInfoFileOps { + /// Create a new inode for `/proc/cpuinfo`. + pub fn new_inode(parent: Weak) -> Arc { + 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::>() + .join("\n\n") + } +} + +impl FileOps for CpuInfoFileOps { + /// Retrieve the data for `/proc/cpuinfo`. + fn data(&self) -> Result> { + let output = Self::collect_cpu_info(); + Ok(output.into_bytes()) + } +} diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index 2abb1cf42..3931efaad 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -6,6 +6,7 @@ use loadavg::LoadAvgFileOps; use sys::SysDirOps; use self::{ + cpuinfo::CpuInfoFileOps, meminfo::MemInfoFileOps, pid::PidDirOps, self_::SelfSymOps, @@ -21,6 +22,7 @@ use crate::{ process::{process_table, process_table::PidEvent, Pid}, }; +mod cpuinfo; mod filesystems; mod loadavg; mod meminfo; @@ -110,6 +112,8 @@ impl DirOps for RootDirOps { MemInfoFileOps::new_inode(this_ptr.clone()) } else if name == "loadavg" { LoadAvgFileOps::new_inode(this_ptr.clone()) + } else if name == "cpuinfo" { + CpuInfoFileOps::new_inode(this_ptr.clone()) } else if let Ok(pid) = name.parse::() { let process_ref = process_table::get_process(pid).ok_or_else(|| Error::new(Errno::ENOENT))?; @@ -135,7 +139,8 @@ impl DirOps for RootDirOps { .put_entry_if_not_found("meminfo", || MemInfoFileOps::new_inode(this_ptr.clone())); cached_children .put_entry_if_not_found("loadavg", || LoadAvgFileOps::new_inode(this_ptr.clone())); - + cached_children + .put_entry_if_not_found("cpuinfo", || CpuInfoFileOps::new_inode(this_ptr.clone())); for process in process_table::process_table_mut().iter() { let pid = process.pid().to_string(); cached_children.put_entry_if_not_found(&pid, || { diff --git a/ostd/src/arch/x86/cpu/mod.rs b/ostd/src/arch/x86/cpu/mod.rs index cb5cbe7f7..ecf8bd242 100644 --- a/ostd/src/arch/x86/cpu/mod.rs +++ b/ostd/src/arch/x86/cpu/mod.rs @@ -15,6 +15,7 @@ use log::debug; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use x86::bits64::segmentation::wrfsbase; +pub use x86::cpuid; use x86_64::registers::rflags::RFlags; pub use super::trap::GeneralRegs as RawGeneralRegs;