Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers.git", rev =
zerocopy = { version = "0.8.2", features = ["derive"] }
zeroize = { version = "1.8.2", default-features = false }

#Github repos
cpufeature = { git = "https://github.com/coconut-svsm/cpufeature.git", rev = "0182d61954372552fe2a40477340e949471a49ee" }

# Verus repos
verus_builtin = { version = "=0.0.0-2026-04-12-0118", default-features = false }
verus_builtin_macros = { version = "=0.0.0-2026-04-12-0118", features = ["vpanic"], default-features = false }
Expand Down
1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ doctest = true
bootdefs.workspace = true
bootimg.workspace = true
cpuarch.workspace = true
cpufeature.workspace = true
libaproxy = { workspace = true, optional = true }
elf.workspace = true
syscall.workspace = true
Expand Down
15 changes: 9 additions & 6 deletions kernel/src/cpu/control_regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
// Author: Joerg Roedel <jroedel@suse.de>

use crate::address::{Address, PhysAddr};
use crate::cpu::features::{Feature, cpu_has_feat};
use crate::cpu::features::cpu_has_feat;
use core::arch::asm;
use cpuarch::x86::CR0Flags;
use cpuarch::x86::CR4Flags;
use cpufeature::leaves::{
CET_SS, X86_FEATURE_PGE, X86_FEATURE_SMAP, X86_FEATURE_SMEP, X86_FEATURE_UMIP,
};

#[inline]
pub fn cr0_init() {
Expand All @@ -33,25 +36,25 @@ pub fn cr4_init() {
// All processors that are capable of virtualization will support global
// page table entries, so there is no reason to support any processor that
// does not enumerate PGE capability.
assert!(cpu_has_feat(Feature::Pge), "CPU does not support PGE");
assert!(cpu_has_feat(&X86_FEATURE_PGE), "CPU does not support PGE");

cr4.insert(CR4Flags::PGE); // Enable Global Pages

if !cfg!(feature = "nosmep") {
assert!(cpu_has_feat(Feature::Smep), "CPU does not support SMEP");
assert!(cpu_has_feat(&X86_FEATURE_SMEP), "CPU does not support SMEP");
cr4.insert(CR4Flags::SMEP);
}

if !cfg!(feature = "nosmap") {
assert!(cpu_has_feat(Feature::Smap), "CPU does not support SMAP");
assert!(cpu_has_feat(&X86_FEATURE_SMAP), "CPU does not support SMAP");
cr4.insert(CR4Flags::SMAP);
}

if cpu_has_feat(Feature::Umip) {
if cpu_has_feat(&X86_FEATURE_UMIP) {
cr4.insert(CR4Flags::UMIP);
}

if cpu_has_feat(Feature::CetSS) {
if cpu_has_feat(&CET_SS) {
cr4.insert(CR4Flags::CET);
}

Expand Down
201 changes: 41 additions & 160 deletions kernel/src/cpu/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,170 +5,51 @@
// Authors: Joerg Roedel <jroedel@suse.de>
// Carlos López <clopez@suse.de>

use core::arch::x86_64::CpuidResult;

use crate::{platform::cpuid, utils::immut_after_init::ImmutAfterInitCell};

/// The CPUID output register for a particular feature
#[derive(Clone, Copy, Debug)]
enum CpuidReg {
Eax,
Ebx,
Ecx,
Edx,
}

/// A discoverable CPU feature.
///
/// At the moment this type is used for CPUID detection, but it can
/// be expanded to use other sources of information.
#[derive(Debug)]
struct CpuFeat {
/// Raw value
val: ImmutAfterInitCell<u32>,
/// CPUID leaf
leaf: u32,
/// CPUID subleaf
subleaf: u32,
/// CPUID output register
reg: CpuidReg,
/// Bitshift to apply to raw value
shift: u8,
/// Bitmask to apply to raw value
bitsize: u8,
/// Expected value after shift + mask
expected: u32,
}

impl CpuFeat {
/// Create a new feature, indicated by a single bit in the specified
/// register in the given CPUID leaf.
const fn new_bit(leaf: u32, reg: CpuidReg, bit: u8) -> Self {
Self::new(leaf, reg, bit, 1, 1)
}

/// Create a new feature, indicated by the full value of a register
/// in the given CPUID leaf.
const fn new_u32(leaf: u32, reg: CpuidReg, expected: u32) -> Self {
Self::new(leaf, reg, 0, u32::BITS as u8, expected)
}

/// Create a new CPU feature, detected by querying the given CPUID leaf, and applying
/// a bitshift and bitmask on the specified output register to compare it to the given
/// expected value.
const fn new(leaf: u32, reg: CpuidReg, shift: u8, bitsize: u8, expected: u32) -> Self {
// This method is only called from const context, so these assertions have no
// runtime effect.
assert!((shift as u32) < u32::BITS);
assert!((bitsize as u32) <= u32::BITS);
Self {
val: ImmutAfterInitCell::uninit(),
leaf,
subleaf: 0,
reg,
shift,
bitsize,
expected,
}
}

/// Create a copy of the given CPU feature, but with the specified
/// subleaf.
const fn with_subfn(mut self, subleaf: u32) -> Self {
self.subleaf = subleaf;
self
}

/// Get the CPUID register that corresponds to this feature
const fn get_reg(&self, cpuid: &CpuidResult) -> u32 {
match self.reg {
CpuidReg::Eax => cpuid.eax,
CpuidReg::Ebx => cpuid.ebx,
CpuidReg::Ecx => cpuid.ecx,
CpuidReg::Edx => cpuid.edx,
}
}

const fn mask(&self) -> u32 {
((1u64 << self.bitsize) - 1) as u32
}

fn get_or_init(&self) -> u32 {
if let Ok(val) = self.val.try_get_inner() {
return *val;
}
let val = cpuid(self.leaf, self.subleaf).map_or(0, |c| self.get_reg(&c));
// If init() fails it means the cell got initialized by someone else
// concurrently, which is always benign.
let _ = self.val.init(val);
val
}

/// Gets the raw value of this feature, lazily querying CPUID if
/// the value is not cached from a previous query
fn get(&self) -> u32 {
(self.get_or_init() >> self.shift) & self.mask()
}

/// Checks whether this feature is available by comparing it to
/// its expected value, lazily querying CPUID if the value is not
/// cached from a previous query
fn enabled(&self) -> bool {
self.get() == self.expected
}
}

/// Macro to generate a CPU feature lookup table
macro_rules! define_cpu_feats {
(
$(
$variant:ident => $feat_expr:expr
),* $(,)?
) => {

// Feature enum to use as index
#[repr(usize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Feature {
$( $variant, )*
}

/// The number of defined features
const NUM_FEATS: usize = [ $( stringify!($variant) ),* ].len();

static CPU_FEATS: [CpuFeat; NUM_FEATS] = [
$( $feat_expr, )*
];
};
use crate::platform::cpuid;
use cpufeature::CpuidFeature;
use cpufeature::CpuidRegister;

/// Raw EAX from CPUID leaf 0x8000_0008 — processor capacity parameters.
pub const PHYS_ADDR_SIZES: CpuidFeature = CpuidFeature {
leaf: 0x8000_0008,
subleaf: 0,
register: CpuidRegister::Eax,
shift: 0,
width: 32,
};

/// CPUID.4000_0001:EAX — Hyper-V interface signature ("Hv#1").
pub const HYPERV_INTERFACE: CpuidFeature = CpuidFeature {
leaf: 0x4000_0001,
subleaf: 0,
register: CpuidRegister::Eax,
shift: 0,
width: 32,
};

const HYPERV_INTERFACE_SIGNATURE: u32 = 0x3123_7648;

fn cpuid_field(feature: &CpuidFeature) -> Option<u32> {
cpuid(feature).map(|regs| {
let raw = feature
.register
.select(regs.eax, regs.ebx, regs.ecx, regs.edx);
let mask = ((1u64 << feature.width) as u32).wrapping_sub(1);
(raw >> feature.shift) & mask
})
}

/// Check if the given feature is enabled by comparing it to its expected
/// value.
pub fn cpu_has_feat(feat: Feature) -> bool {
// Because of #[repr(usize)], this cast matches the array index perfectly.
CPU_FEATS[feat as usize].enabled()
}

/// Gets the raw value of the given feature
pub fn cpu_get_feat(feat: Feature) -> u32 {
CPU_FEATS[feat as usize].get()
pub fn cpu_has_feat(feature: &CpuidFeature) -> bool {
if core::ptr::eq(feature, &HYPERV_INTERFACE) {
cpuid_field(feature) == Some(HYPERV_INTERFACE_SIGNATURE)
} else {
cpuid_field(feature) == Some(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we can consider the feature present if it is Some and not zero.

cpuid_field(feature).is_some_and(|f| f != 0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this works as well

}
}

define_cpu_feats! {
X2Apic => CpuFeat::new_bit(0x0000_0001, CpuidReg::Ecx, 21),
Xsave => CpuFeat::new_bit(0x0000_0001, CpuidReg::Ecx, 26),
Pge => CpuFeat::new_bit(0x0000_0001, CpuidReg::Edx, 13),
Sse1 => CpuFeat::new_bit(0x0000_0001, CpuidReg::Edx, 25),
Smep => CpuFeat::new_bit(0x0000_0007, CpuidReg::Ebx, 7),
Smap => CpuFeat::new_bit(0x0000_0007, CpuidReg::Ebx, 20),
Umip => CpuFeat::new_bit(0x0000_0007, CpuidReg::Ecx, 2),
CetSS => CpuFeat::new_bit(0x0000_0007, CpuidReg::Ecx, 7),
Xcr0X87 => CpuFeat::new_bit(0x0000_000d, CpuidReg::Eax, 0),
Xcr0Sse => CpuFeat::new_bit(0x0000_000d, CpuidReg::Eax, 1),
Xcr0Avx => CpuFeat::new_bit(0x0000_000d, CpuidReg::Eax, 2),
XsaveOpt => CpuFeat::new_bit(0x0000_000d, CpuidReg::Eax, 0).with_subfn(1),
HyperV => CpuFeat::new_u32(0x40000001, CpuidReg::Eax, 0x31237648),
PhysAddrSizes => CpuFeat::new_u32(0x80000008, CpuidReg::Eax, 0),
InvlpgbMax => CpuFeat::new(0x80000008, CpuidReg::Edx, 0, u16::BITS as u8, 0),
Cbit => CpuFeat::new(0x8000001f, CpuidReg::Ebx, 0, 6, 0),
/// Gets the raw value of the given feature.
pub fn cpu_get_feat(feature: &CpuidFeature) -> u32 {
cpuid_field(feature).unwrap_or(0)
}
11 changes: 6 additions & 5 deletions kernel/src/cpu/percpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

extern crate alloc;

use super::features::{Feature, cpu_has_feat};
use super::gdt::GDT;
use super::ipi::IpiState;
use super::isst::Isst;
Expand All @@ -16,6 +15,7 @@ use super::tss::{IST_DF, X86Tss};
use crate::address::{Address, PhysAddr, VirtAddr};
use crate::cpu::control_regs::{read_cr0, read_cr4};
use crate::cpu::efer::read_efer;
use crate::cpu::features::{HYPERV_INTERFACE, cpu_has_feat};
use crate::cpu::idt::common::INT_INJ_VECTOR;
use crate::cpu::tss::TSS_LIMIT;
use crate::cpu::vmsa::{init_guest_vmsa, init_svsm_vmsa};
Expand Down Expand Up @@ -75,6 +75,7 @@ use core::sync::atomic::AtomicU64;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
use cpuarch::vmsa::VMSA;
use cpufeature::leaves::CET_SS;

// PERCPU areas virtual addresses into shared memory
pub static PERCPU_AREAS: PerCpuAreas = PerCpuAreas::new();
Expand Down Expand Up @@ -837,14 +838,14 @@ impl PerCpu {
// Reserve ranges and initialize allocator for temporary mappings
self.initialize_vm_ranges()?;

if cpu_has_feat(Feature::CetSS) {
if cpu_has_feat(&CET_SS) {
self.allocate_init_shadow_stack()?;
}

// Allocate per-cpu context switch stack
self.allocate_context_switch_stack()?;

if cpu_has_feat(Feature::CetSS) {
if cpu_has_feat(&CET_SS) {
self.allocate_context_switch_shadow_stack()?;
}

Expand All @@ -854,7 +855,7 @@ impl PerCpu {
// Setup TSS
self.setup_tss();

if cpu_has_feat(Feature::CetSS) {
if cpu_has_feat(&CET_SS) {
// Allocate ISST shadow stacks
self.allocate_isst_shadow_stacks()?;

Expand All @@ -869,7 +870,7 @@ impl PerCpu {

// Allocate hypercall pages if running on Hyper-V, unless this is the
// BSP (where they will be allocated later).
if self.shared.cpu_index() != 0 && cpu_has_feat(Feature::HyperV) {
if self.shared.cpu_index() != 0 && cpu_has_feat(&HYPERV_INTERFACE) {
self.allocate_hypercall_pages()?;
}

Expand Down
Loading