Add the `arch::cpu::extension` module

This commit is contained in:
Ruihan Li 2025-08-26 23:59:24 +08:00 committed by Tate, Hongliang Tian
parent 9de70e38de
commit 75ca7e0377
10 changed files with 192 additions and 111 deletions

View File

@ -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]

View File

@ -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::<FxSaveArea>();
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<usize> = 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);

View File

@ -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<IsaExtensions> = 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.";
}
}

View File

@ -4,4 +4,5 @@
pub mod context;
pub mod cpuid;
pub mod extension;
pub mod local;

View File

@ -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) {

View File

@ -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 {

View File

@ -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<FeatureInfo> = 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<u64> {
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 Developers Manual" - Volume 1 - Section 7.3.17.1.
@ -144,72 +142,46 @@ pub fn read_random() -> Option<u64> {
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;
});

View File

@ -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) {

View File

@ -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" {

View File

@ -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)]