Files
RedBear-OS/src/arch/x86_64/alternative.rs
T
2024-01-17 13:52:01 -07:00

272 lines
8.7 KiB
Rust

#![allow(unused_imports)]
use core::mem::size_of;
use spin::Once;
use x86::controlregs::{Cr4, Xcr0};
use crate::{
context::memory::PageSpan,
cpuid::{cpuid_always, feature_info, has_ext_feat},
paging::{KernelMapper, Page, PageFlags, VirtualAddress, PAGE_SIZE},
};
#[cfg(all(cpu_feature_never = "xsave", not(cpu_feature_never = "xsaveopt")))]
compile_error!("cannot force-disable xsave without force-disabling xsaveopt");
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct AltReloc {
pub name_start: *const u8,
pub name_len: usize,
pub code_start: *mut u8,
pub origcode_len: usize,
pub padded_len: usize,
pub _rsvd: usize,
pub altcode_start: *const u8,
pub altcode_len: usize,
}
#[cold]
pub unsafe fn early_init(bsp: bool) {
let relocs_offset = crate::kernel_executable_offsets::__altrelocs_start();
let relocs_size = crate::kernel_executable_offsets::__altrelocs_end() - relocs_offset;
assert_eq!(relocs_size % size_of::<AltReloc>(), 0);
let relocs = core::slice::from_raw_parts(
relocs_offset as *const AltReloc,
relocs_size / size_of::<AltReloc>(),
);
let mut enable = KcpuFeatures::empty();
if cfg!(not(cpu_feature_never = "smap")) && has_ext_feat(|feat| feat.has_smap()) {
// SMAP (Supervisor-Mode Access Prevention) forbids the kernel from accessing any
// userspace-accessible pages, with the necessary exception of when RFLAGS.AC = 1. This
// limits user-memory accesses to the UserSlice wrapper, so that no data outside of
// usercopy functions can be accidentally accessed by the kernel.
x86::controlregs::cr4_write(x86::controlregs::cr4() | Cr4::CR4_ENABLE_SMAP);
// Clear CLAC in (the probably unlikely) case the bootloader set it earlier.
x86::bits64::rflags::clac();
enable |= KcpuFeatures::SMAP;
} else {
assert!(cfg!(not(cpu_feature_always = "smap")));
}
if cfg!(not(cpu_feature_never = "fsgsbase"))
&& let Some(f) = cpuid_always().get_extended_feature_info()
&& f.has_fsgsbase()
{
x86::controlregs::cr4_write(x86::controlregs::cr4() | x86::controlregs::Cr4::CR4_ENABLE_FSGSBASE);
enable |= KcpuFeatures::FSGSBASE;
} else {
assert!(cfg!(not(cpu_feature_always = "fsgsbase")));
}
#[cfg(not(cpu_feature_never = "xsave"))]
if feature_info().has_xsave() {
use raw_cpuid::{ExtendedRegisterStateLocation, ExtendedRegisterType};
x86::controlregs::cr4_write(
x86::controlregs::cr4() | x86::controlregs::Cr4::CR4_ENABLE_OS_XSAVE,
);
let mut xcr0 = Xcr0::XCR0_FPU_MMX_STATE | Xcr0::XCR0_SSE_STATE;
let ext_state_info = cpuid_always()
.get_extended_state_info()
.expect("must be present if XSAVE is supported");
enable |= KcpuFeatures::XSAVE;
enable.set(KcpuFeatures::XSAVEOPT, ext_state_info.has_xsaveopt());
let info = xsave::XsaveInfo {
ymm_upper_offset: feature_info().has_avx().then(|| {
xcr0 |= Xcr0::XCR0_AVX_STATE;
x86::controlregs::xcr0_write(xcr0);
let state = ext_state_info
.iter()
.find(|state| {
state.register() == ExtendedRegisterType::Avx
&& state.location() == ExtendedRegisterStateLocation::Xcr0
})
.expect("CPUID said AVX was supported but there's no state info");
if state.size() as usize != 16 * core::mem::size_of::<u128>() {
log::warn!("Unusual AVX state size {}", state.size());
}
state.offset()
}),
xsave_size: ext_state_info.xsave_area_size_enabled_features(),
};
log::debug!("XSAVE: {:?}", info);
xsave::XSAVE_INFO.call_once(|| info);
} else {
assert!(cfg!(not(cpu_feature_always = "xsave")));
}
if !bsp {
return;
}
#[cfg(feature = "self_modifying")]
overwrite(&relocs, enable);
#[cfg(not(feature = "self_modifying"))]
let _ = relocs;
if cfg!(not(feature = "self_modifying")) {
assert!(
cfg!(not(cpu_feature_auto = "smap"))
&& cfg!(not(cpu_feature_auto = "fsgsbase"))
&& cfg!(not(cpu_feature_auto = "xsave"))
&& cfg!(not(cpu_feature_auto = "xsaveopt"))
);
}
FEATURES.call_once(|| enable);
}
#[cfg(feature = "self_modifying")]
unsafe fn overwrite(relocs: &[AltReloc], enable: KcpuFeatures) {
let mut mapper = KernelMapper::lock();
for reloc in relocs.iter().copied() {
let name = core::str::from_utf8(core::slice::from_raw_parts(
reloc.name_start,
reloc.name_len,
))
.expect("invalid feature name");
let altcode = core::slice::from_raw_parts(reloc.altcode_start, reloc.altcode_len);
let dst_pages = PageSpan::between(
Page::containing_address(VirtualAddress::new(reloc.code_start as usize)),
Page::containing_address(VirtualAddress::new(
(reloc.code_start as usize + reloc.origcode_len).next_multiple_of(PAGE_SIZE),
)),
);
for page in dst_pages.pages() {
mapper
.remap(
page.start_address(),
PageFlags::new().write(true).execute(true).global(true),
)
.unwrap()
.flush();
}
let code = core::slice::from_raw_parts_mut(reloc.code_start, reloc.padded_len);
log::trace!(
"feature {} current {:x?} altcode {:x?}",
name,
code,
altcode
);
let feature_is_enabled = match name {
"smap" => enable.contains(KcpuFeatures::SMAP),
"fsgsbase" => enable.contains(KcpuFeatures::FSGSBASE),
"xsave" => enable.contains(KcpuFeatures::XSAVE),
"xsaveopt" => enable.contains(KcpuFeatures::XSAVEOPT),
//_ => panic!("unknown altcode relocation: {}", name),
_ => true,
};
if !feature_is_enabled {
continue;
}
let (dst, dst_nops) = code.split_at_mut(altcode.len());
dst.copy_from_slice(altcode);
// XXX: The `.nops` directive only works for constant lengths, and the variable `.skip -X`
// only outputs the (slower) single-byte 0x90 NOP.
// This table is from the "Software Optimization Guide for AMD Family 19h Processors" (November
// 2020).
const NOPS_TABLE: [&[u8]; 11] = [
&[0x90],
&[0x66, 0x90],
&[0x0f, 0x1f, 0x00],
&[0x0f, 0x1f, 0x40, 0x00],
&[0x0f, 0x1f, 0x44, 0x00, 0x00],
&[0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00],
&[0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00],
&[0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[
0x66, 0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
];
for chunk in dst_nops.chunks_mut(NOPS_TABLE.len()) {
chunk.copy_from_slice(NOPS_TABLE[chunk.len() - 1]);
}
log::trace!("feature {} new {:x?} altcode {:x?}", name, code, altcode);
for page in dst_pages.pages() {
mapper
.remap(
page.start_address(),
PageFlags::new().write(false).execute(true).global(true),
)
.unwrap()
.flush();
}
}
}
bitflags! {
pub struct KcpuFeatures: usize {
const SMAP = 1;
const FSGSBASE = 2;
const XSAVE = 4;
const XSAVEOPT = 8;
}
}
static FEATURES: Once<KcpuFeatures> = Once::new();
pub fn features() -> KcpuFeatures {
*FEATURES.get().expect("early_cpu_init was not called")
}
#[cfg(not(cpu_feature_never = "xsave"))]
mod xsave {
use super::*;
#[derive(Debug)]
pub struct XsaveInfo {
pub ymm_upper_offset: Option<u32>,
pub xsave_size: u32,
}
pub(super) static XSAVE_INFO: Once<XsaveInfo> = Once::new();
pub fn info() -> Option<&'static XsaveInfo> {
XSAVE_INFO.get()
}
}
pub fn kfx_size() -> usize {
#[cfg(not(cpu_feature_never = "xsave"))]
{
match xsave::info() {
Some(info) => FXSAVE_SIZE + XSAVE_HEADER_SIZE + info.xsave_size as usize,
None => FXSAVE_SIZE,
}
}
#[cfg(cpu_feature_never = "xsave")]
{
// FXSAVE size
FXSAVE_SIZE
}
}
pub const FXSAVE_SIZE: usize = 512;
pub const XSAVE_HEADER_SIZE: usize = 64;