From 75ca7e03772458b4e7f3e9cabb254b0e59c83f7a Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Tue, 26 Aug 2025 23:59:24 +0800 Subject: [PATCH] Add the `arch::cpu::extension` module --- ostd/src/arch/riscv/cpu/extension.rs | 6 +- ostd/src/arch/x86/cpu/context/mod.rs | 27 +++--- ostd/src/arch/x86/cpu/extension.rs | 121 ++++++++++++++++++++++++ ostd/src/arch/x86/cpu/mod.rs | 1 + ostd/src/arch/x86/kernel/apic/x2apic.rs | 6 +- ostd/src/arch/x86/kernel/apic/xapic.rs | 11 ++- ostd/src/arch/x86/mod.rs | 104 ++++++++------------ ostd/src/arch/x86/timer/apic.rs | 6 +- ostd/src/arch/x86/trap/syscall.rs | 20 +--- ostd/src/lib.rs | 1 + 10 files changed, 192 insertions(+), 111 deletions(-) create mode 100644 ostd/src/arch/x86/cpu/extension.rs diff --git a/ostd/src/arch/riscv/cpu/extension.rs b/ostd/src/arch/riscv/cpu/extension.rs index 1eb6807a1..206ba1548 100644 --- a/ostd/src/arch/riscv/cpu/extension.rs +++ b/ostd/src/arch/riscv/cpu/extension.rs @@ -8,7 +8,7 @@ use spin::Once; use crate::arch::boot::DEVICE_TREE; /// Detects available RISC-V ISA extensions. -pub fn init() { +pub(in crate::arch) fn init() { let mut global_isa_extensions = IsaExtensions::all(); let device_tree = DEVICE_TREE.get().expect("Device tree not initialized"); @@ -33,6 +33,8 @@ pub fn init() { global_isa_extensions &= cpu_isa_extensions; } + log::info!("Detected ISA extensions: {:?}", global_isa_extensions); + GLOBAL_ISA_EXTENSIONS.call_once(|| global_isa_extensions); } @@ -107,7 +109,7 @@ macro_rules! define_isa_extensions { )* ) => { bitflags! { - /// RISC-V ISA extensions + /// RISC-V ISA extensions. pub struct IsaExtensions: u128 { $( #[doc = $doc] diff --git a/ostd/src/arch/x86/cpu/context/mod.rs b/ostd/src/arch/x86/cpu/context/mod.rs index 6c76d52d7..df9cad416 100644 --- a/ostd/src/arch/x86/cpu/context/mod.rs +++ b/ostd/src/arch/x86/cpu/context/mod.rs @@ -18,10 +18,7 @@ use x86_64::registers::{ }; use crate::{ - arch::{ - trap::{RawUserContext, TrapFrame}, - CPU_FEATURES, - }, + arch::trap::{RawUserContext, TrapFrame}, mm::Vaddr, task::scheduler, trap::call_irq_callback_functions, @@ -505,8 +502,8 @@ impl FpuContext { /// Creates a new FPU context. pub fn new() -> Self { let mut area_size = size_of::(); - if CPU_FEATURES.get().unwrap().has_xsave() { - area_size = area_size.max(*XSAVE_AREA_SIZE.get().unwrap()); + if let Some(xsave_area_size) = XSAVE_AREA_SIZE.get() { + area_size = area_size.max(*xsave_area_size); } Self { @@ -519,7 +516,7 @@ impl FpuContext { pub fn save(&mut self) { let mem_addr = self.as_bytes_mut().as_mut_ptr(); - if CPU_FEATURES.get().unwrap().has_xsave() { + if XSTATE_MAX_FEATURES.is_completed() { unsafe { _xsave64(mem_addr, XFEATURE_MASK_USER_RESTORE) }; } else { unsafe { _fxsave64(mem_addr) }; @@ -532,8 +529,8 @@ impl FpuContext { pub fn load(&mut self) { let mem_addr = self.as_bytes().as_ptr(); - if CPU_FEATURES.get().unwrap().has_xsave() { - let rs_mask = XFEATURE_MASK_USER_RESTORE & XSTATE_MAX_FEATURES.get().unwrap(); + if let Some(xstate_max_features) = XSTATE_MAX_FEATURES.get() { + let rs_mask = XFEATURE_MASK_USER_RESTORE & *xstate_max_features; unsafe { _xrstor64(mem_addr, rs_mask) }; } else { @@ -593,8 +590,8 @@ struct XSaveArea { impl XSaveArea { fn new() -> Self { - let features = if CPU_FEATURES.get().unwrap().has_xsave() { - XCr0::read().bits() & XSTATE_MAX_FEATURES.get().unwrap() + let features = if let Some(xstate_max_features) = XSTATE_MAX_FEATURES.get() { + XCr0::read().bits() & *xstate_max_features } else { 0 }; @@ -647,7 +644,9 @@ static XSAVE_AREA_SIZE: Once = Once::new(); const MAX_XSAVE_AREA_SIZE: usize = 4096; pub(in crate::arch) fn enable_essential_features() { - if CPU_FEATURES.get().unwrap().has_xsave() { + use super::extension::{has_extensions, IsaExtensions}; + + if has_extensions(IsaExtensions::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; @@ -656,7 +655,9 @@ pub(in crate::arch) fn enable_essential_features() { }); } - if CPU_FEATURES.get().unwrap().has_fpu() { + // We now assume that all x86-64 CPUs should have the FPU. Otherwise, we should check + // `has_extensions(IsaExtensions::FPU)` here. + { let mut cr0 = Cr0::read(); cr0.remove(Cr0Flags::TASK_SWITCHED | Cr0Flags::EMULATE_COPROCESSOR); diff --git a/ostd/src/arch/x86/cpu/extension.rs b/ostd/src/arch/x86/cpu/extension.rs new file mode 100644 index 000000000..b544c7612 --- /dev/null +++ b/ostd/src/arch/x86/cpu/extension.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! x86 ISA extensions. + +use core::arch::x86_64::CpuidResult; + +use bitflags::bitflags; +use spin::Once; + +use super::cpuid::cpuid; + +/// Detects available x86 ISA extensions. +pub(in crate::arch) fn init() { + let mut global_isa_extensions = IsaExtensions::empty(); + + for ext_leaf in EXTENSION_TABLE.iter() { + let Some(CpuidResult { ebx, ecx, edx, .. }) = cpuid(ext_leaf.leaf, ext_leaf.subleaf) else { + continue; + }; + + for ext_data in ext_leaf.data.iter() { + let bits = match ext_data.reg { + Reg::Ebx => ebx, + Reg::Ecx => ecx, + Reg::Edx => edx, + }; + if bits & (1 << ext_data.bit) != 0 { + global_isa_extensions |= ext_data.flag; + } + } + } + + log::info!("Detected ISA extensions: {:?}", global_isa_extensions); + + GLOBAL_ISA_EXTENSIONS.call_once(|| global_isa_extensions); +} + +/// Checks if the specified set of ISA extensions are available. +pub fn has_extensions(required: IsaExtensions) -> bool { + GLOBAL_ISA_EXTENSIONS.get().unwrap().contains(required) +} + +static GLOBAL_ISA_EXTENSIONS: Once = Once::new(); + +macro_rules! define_isa_extensions { + { $(leaf $leaf:literal, subleaf $subleaf:literal => { + $($name:ident, $reg:ident ($bit:literal), $doc:literal; )* + })* } => { + define_isa_extension_type! { + $($($name, $doc;)*)* + } + + const EXTENSION_TABLE: &[ExtensionLeaf] = &[ + $(ExtensionLeaf { + leaf: $leaf, + subleaf: $subleaf, + data: &[ + $(ExtensionData { + reg: Reg::$reg, + bit: $bit, + flag: IsaExtensions::$name, + },)* + ] + },)* + ]; + }; +} + +macro_rules! define_isa_extension_type { + { $($name:ident, $doc:literal;)* } => { + bitflags! { + /// x86 ISA extensions. + pub struct IsaExtensions: u128 { + $( + #[doc = $doc] + const $name = 1u128 << ${index()}; + )* + } + } + }; +} + +/// Extensions that describe in a CPUID leaf. +struct ExtensionLeaf { + leaf: u32, + subleaf: u32, + data: &'static [ExtensionData], +} + +/// An extension and its position (i.e., the register and the bit) in the CPUID result. +struct ExtensionData { + reg: Reg, + bit: u32, + flag: IsaExtensions, +} + +enum Reg { + Ebx, + Ecx, + Edx, +} + +define_isa_extensions! { + leaf 1, subleaf 0 => { + X2APIC, Ecx(21), "The processor supports x2APIC feature."; + TSC_DEADLINE, Ecx(24), "The processor's local APIC timer supports \ + one-shot operation using a TSC deadline value."; + XSAVE, Ecx(26), "The processor supports the XSAVE/XRSTOR \ + processor extended states feature, \ + the XSETBV/XGETBV instructions, and XCR0."; + AVX, Ecx(28), "The processor supports the AVX instruction extensions."; + RDRAND, Ecx(30), "The processor supports RDRAND instruction."; + + XAPIC, Edx( 9), "APIC On-Chip."; + } + + leaf 7, subleaf 0 => { + FSGSBASE, Ebx( 0), "Supports RDFSBASE/RDGSBASE/WRFSBASE/WRGSBASE."; + AVX512F, Ebx(16), "Supports the AVX512F instruction extensions."; + } +} diff --git a/ostd/src/arch/x86/cpu/mod.rs b/ostd/src/arch/x86/cpu/mod.rs index b68d85f99..eacd55ca5 100644 --- a/ostd/src/arch/x86/cpu/mod.rs +++ b/ostd/src/arch/x86/cpu/mod.rs @@ -4,4 +4,5 @@ pub mod context; pub mod cpuid; +pub mod extension; pub mod local; diff --git a/ostd/src/arch/x86/kernel/apic/x2apic.rs b/ostd/src/arch/x86/kernel/apic/x2apic.rs index 3727bf19c..57de232a9 100644 --- a/ostd/src/arch/x86/kernel/apic/x2apic.rs +++ b/ostd/src/arch/x86/kernel/apic/x2apic.rs @@ -27,9 +27,9 @@ impl X2Apic { } pub(super) fn has_x2apic() -> bool { - // x2apic::X2APIC::new() - let value = unsafe { core::arch::x86_64::__cpuid(1) }; - value.ecx & 0x20_0000 != 0 + use crate::arch::cpu::extension::{has_extensions, IsaExtensions}; + + has_extensions(IsaExtensions::X2APIC) } pub fn enable(&mut self) { diff --git a/ostd/src/arch/x86/kernel/apic/xapic.rs b/ostd/src/arch/x86/kernel/apic/xapic.rs index d0d7d7663..bad5688da 100644 --- a/ostd/src/arch/x86/kernel/apic/xapic.rs +++ b/ostd/src/arch/x86/kernel/apic/xapic.rs @@ -34,6 +34,12 @@ impl XApic { }) } + pub(super) fn has_xapic() -> bool { + use crate::arch::cpu::extension::{has_extensions, IsaExtensions}; + + has_extensions(IsaExtensions::XAPIC) + } + /// Reads a register from the MMIO region. fn read(&self, offset: u32) -> u32 { assert!(offset as usize % 4 == 0); @@ -58,11 +64,6 @@ impl XApic { let svr: u32 = (1 << 8) | 15; self.write(xapic::XAPIC_SVR, svr); } - - pub(super) fn has_xapic() -> bool { - let value = unsafe { core::arch::x86_64::__cpuid(1) }; - value.edx & 0x100 != 0 - } } impl super::Apic for XApic { diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index e00d00b80..9bf2ec456 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -18,17 +18,9 @@ pub(crate) mod task; pub mod timer; pub mod trap; -use io::construct_io_mem_allocator_builder; -use spin::Once; -use x86::cpuid::{CpuId, FeatureInfo}; - #[cfg(feature = "cvm_guest")] pub(crate) mod tdx_guest; -use core::sync::atomic::Ordering; - -use log::warn; - #[cfg(feature = "cvm_guest")] pub(crate) fn init_cvm_guest() { match ::tdx_guest::init_tdx() { @@ -50,8 +42,6 @@ pub(crate) fn init_cvm_guest() { } } -static CPU_FEATURES: Once = Once::new(); - /// Architecture-specific initialization on the bootstrapping processor. /// /// It should be called when the heap and frame allocators are available. @@ -64,7 +54,7 @@ pub(crate) unsafe fn late_init_on_bsp() { // SAFETY: This function is only called once on BSP. unsafe { trap::init() }; - let io_mem_builder = construct_io_mem_allocator_builder(); + let io_mem_builder = io::construct_io_mem_allocator_builder(); kernel::apic::init(&io_mem_builder).expect("APIC doesn't exist"); kernel::irq::init(&io_mem_builder); @@ -79,7 +69,7 @@ pub(crate) unsafe fn late_init_on_bsp() { } else { match iommu::init(&io_mem_builder) { Ok(_) => {} - Err(err) => warn!("IOMMU initialization error:{:?}", err), + Err(err) => log::warn!("IOMMU initialization error:{:?}", err), } }); @@ -112,6 +102,8 @@ pub(crate) fn interrupts_ack(irq_number: usize) { /// Returns the frequency of TSC. The unit is Hz. pub fn tsc_freq() -> u64 { + use core::sync::atomic::Ordering; + kernel::tsc::TSC_FREQ.load(Ordering::Acquire) } @@ -125,10 +117,16 @@ pub fn read_tsc() -> u64 { /// Reads a hardware generated 64-bit random value. /// -/// Returns None if no random value was generated. +/// Returns `None` if no random value was generated. pub fn read_random() -> Option { use core::arch::x86_64::_rdrand64_step; + use cpu::extension::{has_extensions, IsaExtensions}; + + if !has_extensions(IsaExtensions::RDRAND) { + return None; + } + // Recommendation from "Intel® Digital Random Number Generator (DRNG) Software // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. @@ -144,72 +142,46 @@ pub fn read_random() -> Option { None } -fn has_avx() -> bool { - use core::arch::x86_64::{__cpuid, __cpuid_count}; - - let cpuid_result = unsafe { __cpuid(0) }; - if cpuid_result.eax < 1 { - // CPUID function 1 is not supported - return false; - } - - let cpuid_result = unsafe { __cpuid_count(1, 0) }; - // Check for AVX (bit 28 of ecx) - cpuid_result.ecx & (1 << 28) != 0 -} - -fn has_avx512() -> bool { - use core::arch::x86_64::{__cpuid, __cpuid_count}; - - let cpuid_result = unsafe { __cpuid(0) }; - if cpuid_result.eax < 7 { - // CPUID function 7 is not supported - return false; - } - - let cpuid_result = unsafe { __cpuid_count(7, 0) }; - // Check for AVX-512 Foundation (bit 16 of ebx) - cpuid_result.ebx & (1 << 16) != 0 -} - pub(crate) fn enable_cpu_features() { + use cpu::extension::{has_extensions, IsaExtensions}; use x86_64::registers::{control::Cr4Flags, model_specific::EferFlags, xcontrol::XCr0Flags}; - CPU_FEATURES.call_once(|| { - let cpuid = CpuId::new(); - cpuid.get_feature_info().unwrap() - }); + cpu::extension::init(); let mut cr4 = x86_64::registers::control::Cr4::read(); - cr4 |= Cr4Flags::FSGSBASE - | Cr4Flags::OSXSAVE - | Cr4Flags::OSFXSR - | Cr4Flags::OSXMMEXCPT_ENABLE - | Cr4Flags::PAGE_GLOBAL; - unsafe { - x86_64::registers::control::Cr4::write(cr4); + cr4 |= Cr4Flags::OSFXSR | Cr4Flags::OSXMMEXCPT_ENABLE | Cr4Flags::PAGE_GLOBAL; + if has_extensions(IsaExtensions::XSAVE) { + cr4 |= Cr4Flags::OSXSAVE; } - - let mut xcr0 = x86_64::registers::xcontrol::XCr0::read(); - - xcr0 |= XCr0Flags::SSE; - - if has_avx() { - xcr0 |= XCr0Flags::AVX; + // For now, we unconditionally require the `rdfsbase`, `wrfsbase`, `rdgsbase`, and `wrgsbase` + // instructions because they are used when switching contexts, getting the address of a + // CPU-local variable, e.t.c. Meanwhile, this is at a very early stage of the boot process, so + // we want to avoid failing immediately even if we cannot enable these instructions (though the + // kernel will certainly fail later when they are absent). + // + // Note that this also enables the userspace to control their own FS/GS bases, which requires + // the kernel to properly deal with the arbitrary base values set by the userspace program. + if has_extensions(IsaExtensions::FSGSBASE) { + cr4 |= Cr4Flags::FSGSBASE; } + unsafe { x86_64::registers::control::Cr4::write(cr4) }; - if has_avx512() { - xcr0 |= XCr0Flags::OPMASK | XCr0Flags::ZMM_HI256 | XCr0Flags::HI16_ZMM; - } - - unsafe { - x86_64::registers::xcontrol::XCr0::write(xcr0); + if has_extensions(IsaExtensions::XSAVE) { + let mut xcr0 = x86_64::registers::xcontrol::XCr0::read(); + xcr0 |= XCr0Flags::SSE; + if has_extensions(IsaExtensions::AVX) { + xcr0 |= XCr0Flags::AVX; + } + if has_extensions(IsaExtensions::AVX512F) { + xcr0 |= XCr0Flags::OPMASK | XCr0Flags::ZMM_HI256 | XCr0Flags::HI16_ZMM; + } + unsafe { x86_64::registers::xcontrol::XCr0::write(xcr0) }; } cpu::context::enable_essential_features(); unsafe { - // enable non-executable page protection + // Enable non-executable page protection. x86_64::registers::model_specific::Efer::update(|efer| { *efer |= EferFlags::NO_EXECUTE_ENABLE; }); diff --git a/ostd/src/arch/x86/timer/apic.rs b/ostd/src/arch/x86/timer/apic.rs index d8daee997..aef68347a 100644 --- a/ostd/src/arch/x86/timer/apic.rs +++ b/ostd/src/arch/x86/timer/apic.rs @@ -54,11 +54,9 @@ pub(super) fn timer_callback() { /// Determines if the current system supports tsc_deadline mode APIC timer fn is_tsc_deadline_mode_supported() -> bool { - use x86::cpuid::cpuid; + use crate::arch::cpu::extension::{has_extensions, IsaExtensions}; - const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24; - let cpuid = cpuid!(1); - (cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0 + has_extensions(IsaExtensions::TSC_DEADLINE) } fn init_timer(timer_irq: &IrqLine) { diff --git a/ostd/src/arch/x86/trap/syscall.rs b/ostd/src/arch/x86/trap/syscall.rs index 5e60a2877..52186fee2 100644 --- a/ostd/src/arch/x86/trap/syscall.rs +++ b/ostd/src/arch/x86/trap/syscall.rs @@ -18,10 +18,8 @@ use core::arch::global_asm; -use x86::cpuid::CpuId; use x86_64::{ registers::{ - control::{Cr4, Cr4Flags}, model_specific::{Efer, EferFlags, LStar, SFMask}, rflags::RFlags, }, @@ -43,13 +41,8 @@ global_asm!( /// The caller needs to ensure that `gdt::init` has been called before, so the segment selectors /// used in the `syscall` and `sysret` instructions have been properly initialized. pub(super) unsafe fn init() { - let cpuid = CpuId::new(); - - assert!(cpuid - .get_extended_processor_and_feature_identifiers() - .unwrap() - .has_syscall_sysret()); - assert!(cpuid.get_extended_feature_info().unwrap().has_fsgsbase()); + // We now assume that all x86-64 CPUs should support the `syscall` and `sysret` instructions. + // Otherwise, we should check `has_extensions(IsaExtensions::SYSCALL)` here. // Flags to clear on syscall. // @@ -69,15 +62,6 @@ pub(super) unsafe fn init() { efer.insert(EferFlags::SYSTEM_CALL_EXTENSIONS); }); } - - // SAFETY: Enabling the `rdfsbase`, `wrfsbase`, `rdgsbase`, and `wrgsbase` instructions is safe - // as long as the kernel properly deals with the arbitrary base values set by the userspace - // program. (FIXME: Do we really need to unconditionally enable them?) - unsafe { - Cr4::update(|cr4| { - cr4.insert(Cr4Flags::FSGSBASE); - }) - }; } extern "sysv64" { diff --git a/ostd/src/lib.rs b/ostd/src/lib.rs index a20a2b769..f8e764f39 100644 --- a/ostd/src/lib.rs +++ b/ostd/src/lib.rs @@ -13,6 +13,7 @@ #![feature(iter_from_coroutine)] #![feature(let_chains)] #![feature(linkage)] +#![feature(macro_metavar_expr)] #![feature(min_specialization)] #![feature(negative_impls)] #![feature(ptr_metadata)]