diff --git a/.typos.toml b/.typos.toml index 7f2e63873..2a332390a 100644 --- a/.typos.toml +++ b/.typos.toml @@ -13,6 +13,8 @@ TME = "TME" BA = "BA" ND = "ND" Fo = "Fo" +pn = "pn" +sme = "sme" Inh = "Inh" DELET = "DELET" wrk = "wrk" @@ -32,4 +34,4 @@ extend-exclude = [ "test/build/initramfs/opt/gvisor/blocklists/pty_test", "test/src/syscall/gvisor/blocklists/sync_test", "test/build/initramfs/opt/gvisor/blocklists/sync_test", -] \ No newline at end of file +] diff --git a/kernel/src/arch/loongarch/cpu.rs b/kernel/src/arch/loongarch/cpu.rs index f8d78b668..a08984f55 100644 --- a/kernel/src/arch/loongarch/cpu.rs +++ b/kernel/src/arch/loongarch/cpu.rs @@ -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) } } diff --git a/kernel/src/arch/riscv/cpu.rs b/kernel/src/arch/riscv/cpu.rs index 11ba9c580..707c1a69e 100644 --- a/kernel/src/arch/riscv/cpu.rs +++ b/kernel/src/arch/riscv/cpu.rs @@ -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) } } diff --git a/kernel/src/arch/x86/cpu.rs b/kernel/src/arch/x86/cpu.rs index d9bcb0971..9bae0dd16 100644 --- a/kernel/src/arch/x86/cpu.rs +++ b/kernel/src/arch/x86/cpu.rs @@ -1,24 +1,24 @@ // SPDX-License-Identifier: MPL-2.0 -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{borrow::ToOwned, collections::btree_set::BTreeSet, string::String, vec::Vec}; +use core::{arch::x86_64::CpuidResult, ffi::CStr, fmt, str}; use ostd::{ arch::{ - cpu::context::{cpuid, CpuException, PageFaultErrorCode, RawPageFaultInfo, UserContext}, + cpu::{ + context::{CpuException, PageFaultErrorCode, RawPageFaultInfo, UserContext}, + cpuid::cpuid, + }, tsc_freq, }, + cpu::{num_cpus, PinCurrentCpu}, mm::Vaddr, + sync::SpinLock, + task::DisabledPreemptGuard, Pod, }; -use crate::{ - arch::cpu::cpuid::VendorInfo, cpu::LinuxAbi, thread::exception::PageFaultInfo, - vm::perms::VmPerms, -}; +use crate::{cpu::LinuxAbi, thread::exception::PageFaultInfo, vm::perms::VmPerms}; impl LinuxAbi for UserContext { fn syscall_num(&self) -> usize { @@ -168,15 +168,16 @@ impl TryFrom<&CpuException> for PageFaultInfo { } } +#[derive(Clone, Copy)] enum CpuVendor { Intel, Amd, Unknown, } -impl From<&VendorInfo> for CpuVendor { - fn from(info: &VendorInfo) -> Self { - match info.as_str() { +impl CpuVendor { + pub(self) fn parse(s: &str) -> Self { + match s { "GenuineIntel" => Self::Intel, "AuthenticAMD" => Self::Amd, _ => Self::Unknown, @@ -184,409 +185,706 @@ impl From<&VendorInfo> for CpuVendor { } } -/// 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, +#[repr(u32)] +enum CpuidLeaf { + Base = 0x00, + Feature = 0x01, + Cache = 0x04, + Topology = 0x0b, + + // ExtBase = 0x80000000, + ExtBrand1 = 0x80000002, + ExtBrand2 = 0x80000003, + ExtBrand3 = 0x80000004, + ExtCacheL1 = 0x80000005, + ExtCacheL2L3 = 0x80000006, + ExtAddrSizes = 0x80000008, } -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(), +/// A collection of CPU cores. +/// +/// Note that hyperthreading can result in multiple logical processors within one core. +static CPU_CORES: SpinLock> = SpinLock::new(BTreeSet::new()); + +/// 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. +// +// Implementation notes: For each field in this structure that is conditionally shown in Linux, it +// should be wrapped in an `Option`. Please conduct the Linux implementation when adding a new +// field, see . +pub struct CpuInformation { + processor: u32, + vendor_id: String, + cpu_family: u8, + model: u8, + model_name: String, + stepping: Option, + cpu_khz: Option, + cache_size: Option, + core_id: u32, + apicid: u32, + cpuid_level: u32, + flags: Vec<&'static str>, + tlb_size: Option, + clflush_size: u32, + cache_alignment: u32, + address_sizes: (u32, u32), +} + +impl CpuInformation { + /// Constructs the information for the current CPU. + pub fn new(guard: &DisabledPreemptGuard) -> Self { + let mut result = Self { + processor: guard.current_cpu().as_usize() as u32, + vendor_id: "unknown".to_owned(), + cpu_family: 0, + model: 0, + model_name: "unknown".to_owned(), + stepping: None, + // FIXME: The CPU frequency may not be equal to the TSC frequency. It may not even be a + // constant (i.e., the real CPU frequency can be adjusted due to the workload). + cpu_khz: Some((tsc_freq() / 1000) as u32), + cache_size: None, + core_id: 0, + apicid: 0, + cpuid_level: 0, + flags: Vec::new(), + tlb_size: None, + clflush_size: 64, + cache_alignment: 64, + address_sizes: (36, 48), + }; + + let vendor = result.fill_vendor_info(); + result.fill_version_info(vendor); + result.fill_brand_info(); + result.fill_cache_info(vendor); + result.fill_topology_info(); + result.fill_feature_info(); + result.fill_address_info(); + + result + } + + fn fill_vendor_info(&mut self) -> CpuVendor { + let Some(CpuidResult { eax, ebx, ecx, edx }) = cpuid(CpuidLeaf::Base as u32, 0) else { + return CpuVendor::Unknown; + }; + + self.cpuid_level = eax; + + let vendor = [ebx.to_le_bytes(), edx.to_le_bytes(), ecx.to_le_bytes()]; + let Ok(vendor_str) = str::from_utf8(vendor.as_flattened()) else { + return CpuVendor::Unknown; + }; + self.vendor_id = vendor_str.to_owned(); + + CpuVendor::parse(vendor_str) + } + + fn fill_version_info(&mut self, vendor: CpuVendor) { + let Some(CpuidResult { eax, ebx, .. }) = cpuid(CpuidLeaf::Feature as u32, 0) else { + return; + }; + + let stepping = eax & 0xf; + let base_model = (eax >> 4) & 0xf; + let base_family = (eax >> 8) & 0xf; + let ext_model = (eax >> 16) & 0xf; + let ext_family = (eax >> 20) & 0xff; + + // "The Extended Family ID needs to be examined only when the Family ID is 0FH." + let family = if base_family == 0xf { + base_family + ext_family + } else { + base_family + }; + + let has_ext_model = match vendor { + // The Intel manual says: "The Extended Model ID needs to be examined only when the + // Family ID is 06H or 0FH." + CpuVendor::Intel => base_family == 0x6 || base_family == 0xf, + // The AMD manual says: "If BaseFamily[3:0] is less than Fh, then ExtFamily is reserved + // and Family is equal to BaseFamily[3:0]." + CpuVendor::Amd | CpuVendor::Unknown => base_family == 0xf, + }; + let model = if has_ext_model { + base_model | (ext_model << 4) + } else { + base_model + }; + + self.cpu_family = family as u8; + self.model = model as u8; + self.stepping = Some(stepping as u8); + + // "Bits 15-08: CLFLUSH line size (Value ∗ 8 = cache line size in bytes; used also by + // CLFLUSHOPT)." + self.clflush_size = ((ebx >> 8) & 0xff) * 8; + self.cache_alignment = self.clflush_size; + // Bits 31-24: Initial APIC ID. + self.apicid = ebx >> 24; + self.core_id = self.apicid; + } + + fn fill_brand_info(&mut self) { + let Some(CpuidResult { + eax: eax1, + ebx: ebx1, + ecx: ecx1, + edx: edx1, + }) = cpuid(CpuidLeaf::ExtBrand1 as u32, 0) + else { + return; + }; + let Some(CpuidResult { + eax: eax2, + ebx: ebx2, + ecx: ecx2, + edx: edx2, + }) = cpuid(CpuidLeaf::ExtBrand2 as u32, 0) + else { + return; + }; + let Some(CpuidResult { + eax: eax3, + ebx: ebx3, + ecx: ecx3, + edx: edx3, + }) = cpuid(CpuidLeaf::ExtBrand3 as u32, 0) + else { + return; + }; + + #[rustfmt::skip] + let brand = [ + eax1.to_le_bytes(), ebx1.to_le_bytes(), ecx1.to_le_bytes(), edx1.to_le_bytes(), + eax2.to_le_bytes(), ebx2.to_le_bytes(), ecx2.to_le_bytes(), edx2.to_le_bytes(), + eax3.to_le_bytes(), ebx3.to_le_bytes(), ecx3.to_le_bytes(), edx3.to_le_bytes(), + ]; + let Ok(brand_cstr) = CStr::from_bytes_until_nul(brand.as_flattened()) else { + return; + }; + let Ok(brand_str) = brand_cstr.to_str() else { + return; + }; + + self.model_name = brand_str.to_owned(); + } + + fn fill_cache_info(&mut self, vendor: CpuVendor) { + match vendor { + CpuVendor::Intel => self.fill_cache_info_intel(), + CpuVendor::Amd | CpuVendor::Unknown => self.fill_cache_info_amd(), } } - /// Collect and format CPU information into a `String` - pub fn collect_cpu_info(&self) -> String { - format!( + fn fill_cache_info_intel(&mut self) { + let mut l1size = 0; + let mut l2size = 0; + let mut l3size = 0; + + for subleaf in 0.. { + let Some(CpuidResult { eax, ebx, ecx, .. }) = cpuid(CpuidLeaf::Cache as u32, subleaf) + else { + break; + }; + + // Bits 04-00, Cache Type Field: Null - No more caches. + if eax & 0x1f == 0 { + break; + } + + let system_coherency_line_size = ebx & 0xfff; + let physical_line_partitions = (ebx >> 12) & 0x3ff; + let ways_of_associativity = (ebx >> 22) & 0x3ff; + let number_of_sets = ecx; + + let size = (system_coherency_line_size + 1) + * (physical_line_partitions + 1) + * (ways_of_associativity + 1) + * (number_of_sets + 1); + + // Bits 07-05, Cache Level (starts at 1). + match (eax >> 5) & 0x7 { + 1 => l1size += size, + 2 => l2size += size, + 3 => l3size += size, + _ => (), + } + } + + if l3size != 0 { + self.cache_size = Some(l3size / 1024); + } else if l2size != 0 { + self.cache_size = Some(l2size / 1024); + } else if l1size != 0 { + self.cache_size = Some(l1size / 1024); + } + } + + fn fill_cache_info_amd(&mut self) { + let l1_cache_size = + if let Some(CpuidResult { ecx, edx, .. }) = cpuid(CpuidLeaf::ExtCacheL1 as u32, 0) { + // Bits 31-24, L1DcSize: L1 data cache size in KB. + let l1d_cache_size = ecx >> 24; + // Bits 31-24, L1IcSize: L1 instruction cache size KB. + let l1i_cache_size = edx >> 24; + l1d_cache_size + l1i_cache_size + } else { + 0 + }; + + let (l2_tlb_size, l2_cache_size) = + if let Some(CpuidResult { ebx, ecx, .. }) = cpuid(CpuidLeaf::ExtCacheL2L3 as u32, 0) { + // Bits 11-0, L2ITlb4KSize: L2 instruction TLB number of entries for 4-KB pages. + let l2i_tlb_size = ebx & 0xfff; + // Bits 27-16, L2DTlb4KSize: L2 data TLB number of entries for 4-KB pages. + let l2d_tlb_size = (ebx >> 16) & 0xfff; + // Bits 31-16, L2Size: L2 cache size in KB. + let l2_cache_size = ecx >> 16; + (l2i_tlb_size + l2d_tlb_size, l2_cache_size) + } else { + (0, 0) + }; + + // The Linux implementation here is quite odd, we just follow its logic: + // - If the L3 cache is present, it will be ignored on AMD CPUs [1] but it will be reported as + // the cache size on Intel CPUs [2]. + // - The TLB size is never counted on Intel CPUs. + // - The L1 TLB is never counted on 64-bit AMD CPUs. But the L2 TLB will be counted. + // + // [1]: https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kernel/cpu/common.c#L811 + // [2]: https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kernel/cpu/cacheinfo.c#L359 + if l2_tlb_size != 0 { + self.tlb_size = Some(l2_tlb_size); + } + if l2_cache_size != 0 { + self.cache_size = Some(l2_cache_size); + } else if l1_cache_size != 0 { + self.cache_size = Some(l1_cache_size); + } + } + + fn fill_topology_info(&mut self) { + let Some(CpuidResult { eax, edx, .. }) = cpuid(CpuidLeaf::Topology as u32, 0) else { + return; + }; + + // "Bits 31-00: x2APIC ID of the current logical processor." + self.apicid = edx; + // "Bits 04-00: The number of bits that the x2APIC ID must be shifted to the right to + // address instances of the next higher-scoped domain." + self.core_id = self.apicid >> (eax & 0x1f); + + CPU_CORES.lock().insert(self.core_id); + } + + fn fill_feature_info(&mut self) { + macro_rules! parse_feature_bits { + ($feature_word:expr; $($bit:literal => $name:literal,)*) => {{ + let feature_word = $feature_word; + $(if feature_word & (1 << $bit) != 0 { + self.flags.push($name); + })* + }}; + } + + // The feature detection code below is based on + // . + // The specific commit is provided for the convenience of future synchronization with the + // latest Linux implementation. Please don't forget to update the commit hash after you + // modified the code below. + // + // Note that only Intel- and AMD-defined features are currently supported. We do not + // include features defined by other vendors or defined by the Linux kernel. + + if let Some(CpuidResult { edx, .. }) = cpuid(0x00000001, 0) { + parse_feature_bits!(edx; + 0 => "fpu", + 1 => "vme", + 2 => "de", + 3 => "pse", + 4 => "tsc", + 5 => "msr", + 6 => "pae", + 7 => "mce", + 8 => "cx8", + 9 => "apic", + 11 => "sep", + 12 => "mtrr", + 13 => "pge", + 14 => "mca", + 15 => "cmov", + 16 => "pat", + 17 => "pse36", + 18 => "pn", + 19 => "clflush", + 21 => "dts", + 22 => "acpi", + 23 => "mmx", + 24 => "fxsr", + 25 => "sse", + 26 => "sse2", + 27 => "ss", + 28 => "ht", + 29 => "tm", + 30 => "ia64", + 31 => "pbe", + ); + } + + if let Some(CpuidResult { edx, .. }) = cpuid(0x80000001, 0) { + parse_feature_bits!(edx; + 11 => "syscall", + 19 => "mp", + 20 => "nx", + 22 => "mmxext", + 25 => "fxsr_opt", + 26 => "pdpe1gb", + 27 => "rdtscp", + 29 => "lm", + 30 => "3dnowext", + 31 => "3dnow", + ); + } + + if let Some(CpuidResult { ecx, .. }) = cpuid(0x00000001, 0) { + parse_feature_bits!(ecx; + 0 => "pni", + 1 => "pclmulqdq", + 2 => "dtes64", + 3 => "monitor", + 4 => "ds_cpl", + 5 => "vmx", + 6 => "smx", + 7 => "est", + 8 => "tm2", + 9 => "ssse3", + 10 => "cid", + 11 => "sdbg", + 12 => "fma", + 13 => "cx16", + 14 => "xtpr", + 15 => "pdcm", + 17 => "pcid", + 18 => "dca", + 19 => "sse4_1", + 20 => "sse4_2", + 21 => "x2apic", + 22 => "movbe", + 23 => "popcnt", + 24 => "tsc_deadline_timer", + 25 => "aes", + 26 => "xsave", + 28 => "avx", + 29 => "f16c", + 30 => "rdrand", + 31 => "hypervisor", + ); + } + + if let Some(CpuidResult { ecx, .. }) = cpuid(0x80000001, 0) { + parse_feature_bits!(ecx; + 0 => "lahf_lm", + 1 => "cmp_legacy", + 2 => "svm", + 3 => "extapic", + 4 => "cr8_legacy", + 5 => "abm", + 6 => "sse4a", + 7 => "misalignsse", + 8 => "3dnowprefetch", + 9 => "osvw", + 10 => "ibs", + 11 => "xop", + 12 => "skinit", + 13 => "wdt", + 15 => "lwp", + 16 => "fma4", + 17 => "tce", + 19 => "nodeid_msr", + 21 => "tbm", + 22 => "topoext", + 23 => "perfctr_core", + 24 => "perfctr_nb", + 26 => "bpext", + 27 => "ptsc", + 28 => "perfctr_llc", + 29 => "mwaitx", + ); + } + + if let Some(CpuidResult { ebx, .. }) = cpuid(0x00000007, 0) { + parse_feature_bits!(ebx; + 0 => "fsgsbase", + 1 => "tsc_adjust", + 2 => "sgx", + 3 => "bmi1", + 4 => "hle", + 5 => "avx2", + 7 => "smep", + 8 => "bmi2", + 9 => "erms", + 10 => "invpcid", + 11 => "rtm", + 12 => "cqm", + 14 => "mpx", + 15 => "rdt_a", + 16 => "avx512f", + 17 => "avx512dq", + 18 => "rdseed", + 19 => "adx", + 20 => "smap", + 21 => "avx512ifma", + 23 => "clflushopt", + 24 => "clwb", + 25 => "intel_pt", + 26 => "avx512pf", + 27 => "avx512er", + 28 => "avx512cd", + 29 => "sha_ni", + 30 => "avx512bw", + 31 => "avx512vl", + ); + } + + if let Some(CpuidResult { eax, .. }) = cpuid(0x0000000d, 1) { + parse_feature_bits!(eax; + 0 => "xsaveopt", + 1 => "xsavec", + 2 => "xgetbv1", + 3 => "xsaves", + ); + } + + if let Some(CpuidResult { eax, .. }) = cpuid(0x00000007, 1) { + parse_feature_bits!(eax; + 4 => "avx_vnni", + 5 => "avx512_bf16", + 17 => "fred", + 18 => "kernel", + 26 => "lam", + ); + } + + if let Some(CpuidResult { ebx, .. }) = cpuid(0x80000008, 0) { + parse_feature_bits!(ebx; + 0 => "clzero", + 1 => "irperf", + 2 => "xsaveerptr", + 4 => "rdpru", + 9 => "wbnoinvd", + 23 => "amd_ppin", + 25 => "virt_ssbd", + 27 => "cppc", + 31 => "brs", + ); + } + + if let Some(CpuidResult { eax, .. }) = cpuid(0x00000006, 0) { + parse_feature_bits!(eax; + 0 => "dtherm", + 1 => "ida", + 2 => "arat", + 4 => "pln", + 6 => "pts", + 7 => "hwp", + 8 => "hwp_notify", + 9 => "hwp_act_window", + 10 => "hwp_epp", + 11 => "hwp_pkg_req", + 19 => "hfi", + ); + } + + if let Some(CpuidResult { edx, .. }) = cpuid(0x8000000a, 0) { + parse_feature_bits!(edx; + 0 => "npt", + 1 => "lbrv", + 2 => "svm_lock", + 3 => "nrip_save", + 4 => "tsc_scale", + 5 => "vmcb_clean", + 6 => "flushbyasid", + 7 => "decodeassists", + 10 => "pausefilter", + 12 => "pfthreshold", + 13 => "avic", + 15 => "v_vmsave_vmload", + 16 => "vgif", + 18 => "x2avic", + 20 => "v_spec_ctrl", + 25 => "vnmi", + ); + } + + if let Some(CpuidResult { ecx, .. }) = cpuid(0x00000007, 0) { + parse_feature_bits!(ecx; + 1 => "avx512vbmi", + 2 => "umip", + 3 => "pku", + 4 => "ospke", + 5 => "waitpkg", + 6 => "avx512_vbmi2", + 8 => "gfni", + 9 => "vaes", + 10 => "vpclmulqdq", + 11 => "avx512_vnni", + 12 => "avx512_bitalg", + 13 => "tme", + 14 => "avx512_vpopcntdq", + 16 => "la57", + 22 => "rdpid", + 24 => "bus_lock_detect", + 25 => "cldemote", + 27 => "movdiri", + 28 => "movdir64b", + 29 => "enqcmd", + 30 => "sgx_lc", + ); + } + + if let Some(CpuidResult { ebx, .. }) = cpuid(0x80000007, 0) { + parse_feature_bits!(ebx; + 0 => "overflow_recov", + 1 => "succor", + 3 => "smca", + ); + } + + if let Some(CpuidResult { edx, .. }) = cpuid(0x00000007, 0) { + parse_feature_bits!(edx; + 2 => "avx512_4vnniw", + 3 => "avx512_4fmaps", + 4 => "fsrm", + 8 => "avx512_vp2intersect", + 10 => "md_clear", + 14 => "serialize", + 16 => "tsxldtrk", + 18 => "pconfig", + 19 => "arch_lbr", + 20 => "ibt", + 22 => "amx_bf16", + 23 => "avx512_fp16", + 24 => "amx_tile", + 25 => "amx_int8", + 28 => "flush_l1d", + 29 => "arch_capabilities", + ); + } + + if let Some(CpuidResult { eax, .. }) = cpuid(0x8000001f, 0) { + parse_feature_bits!(eax; + 0 => "sme", + 1 => "sev", + 3 => "sev_es", + 4 => "sev_snp", + 14 => "debug_swap", + 28 => "svsm", + ); + } + } + + fn fill_address_info(&mut self) { + let Some(CpuidResult { eax, .. }) = cpuid(CpuidLeaf::ExtAddrSizes as u32, 0) else { + return; + }; + + // "Bits 07-00: #Physical Address Bits*." + self.address_sizes.0 = eax & 0xff; + // "Bits 15-08: #Linear Address Bits." + self.address_sizes.1 = (eax >> 8) & 0xff; + } +} + +impl fmt::Display for CpuInformation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, "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\ + model name\t: {}\n", + self.processor, self.vendor_id, self.cpu_family, self.model, self.model_name, + )?; + + if let Some(stepping) = self.stepping { + writeln!(f, "stepping\t: {}", stepping)?; + } else { + writeln!(f, "stepping\t: unknown")?; + } + + // TODO: Add the `microcode` field. + + if let Some(cpu_khz) = self.cpu_khz { + writeln!(f, "cpu MHz\t\t: {}.{:03}", cpu_khz / 1000, cpu_khz % 1000)?; + } else { + writeln!(f, "cpu MHz\t\t: Unknown")?; + } + + if let Some(cache_size) = self.cache_size { + writeln!(f, "cache size\t: {} KB", cache_size)?; + } + + // Note that we don't support NUMA now, so we assume that all CPUs are on the same package + // (i.e., their physical IDs are all zeros). + let siblings = num_cpus(); + let cpu_cores = CPU_CORES.lock().len(); + write!( + f, + "physical id\t: 0\n\ siblings\t: {}\n\ core id\t\t: {}\n\ cpu cores\t: {}\n\ apicid\t\t: {}\n\ - initial apicid\t: {}\n\ + initial apicid\t: {}\n", + siblings, self.core_id, cpu_cores, self.apicid, self.apicid + )?; + + write!( + f, + "fpu\t\t: yes\n\ + fpu_exception\t: yes\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, + wp\t\t: yes\n", 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 vendor_info = cpuid.get_vendor_info()?; - match CpuVendor::from(&vendor_info) { - CpuVendor::Intel => { - let tsc_info = cpuid.get_tsc_info()?; - Some( - (tsc_info.tsc_frequency().unwrap_or(0) / 1_000_000) - .try_into() - .unwrap(), - ) - } - CpuVendor::Amd | CpuVendor::Unknown => { - let tsc_freq_hz = tsc_freq(); // always > 0 - Some((tsc_freq_hz / 1_000_000) as u32) - } - } - } - - /// Get cache size in KB - fn get_cache_size() -> Option { - let cpuid = cpuid::CpuId::new(); - let vendor_info = cpuid.get_vendor_info()?; - match CpuVendor::from(&vendor_info) { - CpuVendor::Intel => { - 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 - } - CpuVendor::Amd => { - let cache = cpuid.get_l2_l3_cache_and_tlb_info()?; - Some(cache.l2cache_size() as u32 * 1024) - } - CpuVendor::Unknown => None, - } - } - - fn get_tlb_size() -> Option { - let cpuid = cpuid::CpuId::new(); - let vendor_info = cpuid.get_vendor_info()?; - match CpuVendor::from(&vendor_info) { - CpuVendor::Intel => { - 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 - } - CpuVendor::Amd => { - let cache = cpuid.get_l2_l3_cache_and_tlb_info()?; - Some(cache.dtlb_4k_size() as u32) - } - CpuVendor::Unknown => 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 { - cpuid::cpuid!(0x0).eax - } - - 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() * 8 - } - - 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; - } - } - } + write!(f, "flags\t\t:")?; + for flag in self.flags.iter() { + write!(f, " {}", flag)?; } - 64 - } + // TODO: Add the `vmx flags` field. - 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 - ) - } + // TODO: Fill the `bugs` field. + write!( + f, + "\n\ + bugs\t\t:\n" + )?; - // FIXME: add power management information - fn get_power_management() -> String { - " ".to_string() + // TODO: Add the `bogomips` field. + + if let Some(tlb_size) = self.tlb_size { + writeln!(f, "TLB size\t: {} 4K pages", tlb_size)?; + } + + write!( + f, + "clflush size\t: {}\n\ + cache_alignment\t: {}\n\ + address sizes\t: {} bits physical, {} bits virtual\n", + self.clflush_size, self.cache_alignment, self.address_sizes.0, self.address_sizes.1, + )?; + + // TODO: Fill the `power management` field. + writeln!(f, "power management:")?; + + Ok(()) } } diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index 110160d40..4260bac3a 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -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(); } diff --git a/kernel/src/fs/procfs/cpuinfo.rs b/kernel/src/fs/procfs/cpuinfo.rs index 98805f85d..80ce302c2 100644 --- a/kernel/src/fs/procfs/cpuinfo.rs +++ b/kernel/src/fs/procfs/cpuinfo.rs @@ -5,10 +5,15 @@ //! //! Reference: -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) -> 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`. + /// Retrieves the data for `/proc/cpuinfo`. fn data(&self) -> Result> { - let output = Self::collect_cpu_info(); + let output = all_cpus() + .map(|cpu| CPU_INFORMATION.get_on_cpu(cpu).wait().to_string()) + .collect::>() + .join("\n"); Ok(output.into_bytes()) } } + +cpu_local! { + static CPU_INFORMATION: Once = 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)); +} diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index 31f98967c..ea8e95468 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -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. diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index c3a54b524..63cded977 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -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.", diff --git a/ostd/src/arch/x86/cpu/context/mod.rs b/ostd/src/arch/x86/cpu/context/mod.rs index e7ab27b24..6c76d52d7 100644 --- a/ostd/src/arch/x86/cpu/context/mod.rs +++ b/ostd/src/arch/x86/cpu/context/mod.rs @@ -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 = 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 - }); - - 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 - }); + 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 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(); diff --git a/ostd/src/arch/x86/cpu/cpuid.rs b/ostd/src/arch/x86/cpu/cpuid.rs new file mode 100644 index 000000000..90798d237 --- /dev/null +++ b/ostd/src/arch/x86/cpu/cpuid.rs @@ -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 = Once::new(); +static MAX_EXTENDED_LEAF: Once = 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 { + 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 { + 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 { + 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 { + cpuid(Leaf::Xstate as u32, 1).map(|res| res.ebx) +} diff --git a/ostd/src/arch/x86/cpu/mod.rs b/ostd/src/arch/x86/cpu/mod.rs index bf160e756..b68d85f99 100644 --- a/ostd/src/arch/x86/cpu/mod.rs +++ b/ostd/src/arch/x86/cpu/mod.rs @@ -3,4 +3,5 @@ //! CPU context & state control and CPU local memory. pub mod context; +pub mod cpuid; pub mod local; diff --git a/ostd/src/arch/x86/kernel/tsc.rs b/ostd/src/arch/x86/kernel/tsc.rs index 713a3b8b5..9a1648270 100644 --- a/ostd/src/arch/x86/kernel/tsc.rs +++ b/ostd/src/arch/x86/kernel/tsc.rs @@ -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 { - // 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