From e896c0e08b9b53f4e65d7f6bea350640306179ca Mon Sep 17 00:00:00 2001 From: vasilito Date: Wed, 8 Jul 2026 00:29:43 +0300 Subject: [PATCH] kernel: add real MSR scheme access --- src/arch/x86_shared/idt.rs | 2 + src/arch/x86_shared/interrupt/ipi.rs | 18 ++++++ src/arch/x86_shared/ipi.rs | 3 +- src/percpu.rs | 65 ++++++++++++++++++- src/scheme/sys/msr.rs | 93 +++++++++++++--------------- 5 files changed, 130 insertions(+), 51 deletions(-) diff --git a/src/arch/x86_shared/idt.rs b/src/arch/x86_shared/idt.rs index 500645855d..a4d9eb6a9d 100644 --- a/src/arch/x86_shared/idt.rs +++ b/src/arch/x86_shared/idt.rs @@ -247,10 +247,12 @@ fn init_generic(cpu_id: LogicalCpuId, idt: &mut Idt, backup_stack_end: usize) { current_idt[IpiKind::Switch as usize].set_func(ipi::switch); current_idt[IpiKind::Tlb as usize].set_func(ipi::tlb); current_idt[IpiKind::Pit as usize].set_func(ipi::pit); + current_idt[IpiKind::Msr as usize].set_func(ipi::msr); idt.set_reserved_mut(IpiKind::Wakeup as u8, true); idt.set_reserved_mut(IpiKind::Switch as u8, true); idt.set_reserved_mut(IpiKind::Tlb as u8, true); idt.set_reserved_mut(IpiKind::Pit as u8, true); + idt.set_reserved_mut(IpiKind::Msr as u8, true); #[cfg(target_arch = "x86")] { diff --git a/src/arch/x86_shared/interrupt/ipi.rs b/src/arch/x86_shared/interrupt/ipi.rs index 20e9d436a1..9e4c129e97 100644 --- a/src/arch/x86_shared/interrupt/ipi.rs +++ b/src/arch/x86_shared/interrupt/ipi.rs @@ -1,6 +1,7 @@ use crate::{ arch::device::local_apic::the_local_apic, context, percpu::PercpuBlock, sync::CleanLockToken, }; +use x86::msr::{rdmsr, wrmsr}; interrupt!(wakeup, || { unsafe { the_local_apic().eoi() }; @@ -26,3 +27,20 @@ interrupt!(pit, || { let mut token = unsafe { CleanLockToken::new() }; context::switch::tick(&mut token); }); + +interrupt!(msr, || { + let percpu = PercpuBlock::current(); + let mailbox = percpu.msr_mailbox(); + let op = mailbox.request(); + + let result = if op.is_write { + unsafe { wrmsr(op.msr as u32, op.value) }; + op.value + } else { + unsafe { rdmsr(op.msr as u32) } + }; + + mailbox.finish_handle(result); + + unsafe { the_local_apic().eoi() }; +}); diff --git a/src/arch/x86_shared/ipi.rs b/src/arch/x86_shared/ipi.rs index f38db763c4..89cd246f14 100644 --- a/src/arch/x86_shared/ipi.rs +++ b/src/arch/x86_shared/ipi.rs @@ -5,9 +5,10 @@ pub enum IpiKind { Tlb = 0x41, Switch = 0x42, Pit = 0x43, + Msr = 0x44, #[cfg(feature = "profiling")] - Profile = 0x44, + Profile = 0x45, } #[derive(Clone, Copy, Debug)] diff --git a/src/percpu.rs b/src/percpu.rs index f4ad5e66e6..011f889693 100644 --- a/src/percpu.rs +++ b/src/percpu.rs @@ -4,7 +4,7 @@ use alloc::{ }; use core::{ cell::{Cell, RefCell}, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, + sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, AtomicU8, Ordering}, }; use rmm::Arch; @@ -46,9 +46,65 @@ pub struct PercpuBlock { pub misc_arch_info: crate::arch::device::ArchPercpuMisc, + pub msr_mailbox: MsrMailbox, + pub stats: CpuStats, } +#[repr(C)] +pub struct MsrMailbox { + cpu_id: AtomicU32, + msr: AtomicU64, + value: AtomicU64, + kind: AtomicU8, + done: AtomicBool, +} + +impl MsrMailbox { + pub const fn new() -> Self { + Self { + cpu_id: AtomicU32::new(u32::MAX), + msr: AtomicU64::new(0), + value: AtomicU64::new(0), + kind: AtomicU8::new(0), + done: AtomicBool::new(true), + } + } + + pub fn begin_request(&self, cpu_id: u32, msr: u32, is_write: bool, value: u64) { + self.done.store(false, Ordering::Relaxed); + self.cpu_id.store(cpu_id, Ordering::Relaxed); + self.msr.store(msr as u64, Ordering::Relaxed); + self.value.store(value, Ordering::Relaxed); + self.kind.store(is_write as u8, Ordering::Release); + } + + pub fn wait_done(&self) -> bool { + self.done.load(Ordering::Acquire) + } + + pub fn request(&self) -> MsrRequest { + MsrRequest { + msr: self.msr.load(Ordering::Acquire), + value: self.value.load(Ordering::Acquire), + is_write: self.kind.load(Ordering::Acquire) != 0, + } + } + + pub fn finish_handle(&self, value: u64) { + self.value.store(value, Ordering::Release); + self.done.store(true, Ordering::Release); + } + + pub fn read_value(&self) -> u64 { self.value.load(Ordering::Acquire) } +} + +pub struct MsrRequest { + pub msr: u64, + pub value: u64, + pub is_write: bool, +} + static ALL_PERCPU_BLOCKS: [AtomicPtr; MAX_CPU_COUNT as usize] = [const { AtomicPtr::new(core::ptr::null_mut()) }; MAX_CPU_COUNT as usize]; @@ -199,7 +255,14 @@ impl PercpuBlock { misc_arch_info: ArchPercpuMisc::default(), + msr_mailbox: MsrMailbox::new(), + stats: CpuStats::default(), } } + pub fn msr_mailbox(&self) -> &MsrMailbox { &self.msr_mailbox } +} + +pub fn get_percpu_block(cpu: LogicalCpuId) -> Option<&'static PercpuBlock> { + unsafe { ALL_PERCPU_BLOCKS[cpu.get() as usize].load(Ordering::Acquire).as_ref() } } diff --git a/src/scheme/sys/msr.rs b/src/scheme/sys/msr.rs index 90ca21976a..94e48316b4 100644 --- a/src/scheme/sys/msr.rs +++ b/src/scheme/sys/msr.rs @@ -11,61 +11,19 @@ //! `scheme:msr` interface for ring-3 access, but this kernel-side //! helper is for the scheme to forward requests to the active CPU). //! -//! Note: in this kernel fork, MSR access is implemented as a per-CPU -//! `Arc>>` storage. The hardware MSRs are -//! accessible only from ring 0 (kernel); this scheme is a thin wrapper -//! that validates CPU + register index and lets userspace store/retrieve -//! the values. This matches the existing -//! `local/recipes/system/redbear-power/source/src/msr.rs` library -//! expectations on a Linux host and gives `cpufreqd` a real R/W path -//! on Redox bare metal. - -use core::sync::atomic::{AtomicU32, Ordering}; -use spin::Mutex; +use x86::msr::{rdmsr, wrmsr}; use crate::cpu_count; +use crate::ipi::{ipi_single, IpiKind}; +use crate::percpu::PercpuBlock; use syscall::{ - error::{Error, Result, EBADF, EINVAL, ENOENT, EPERM}, + error::{Error, Result, EBUSY, EBADF, EINVAL, ENOENT, EPERM, ETIMEDOUT}, }; use crate::scheme::CallerCtx; use crate::sync::CleanLockToken; use crate::syscall::usercopy::{UserSliceRo, UserSliceWo}; -const MSR_BUCKETS: usize = 1024; - -/// One bucket entry: a (cpu, msr) → value mapping. -#[derive(Clone, Copy, Debug)] -struct MsrEntry { - cpu: u32, - msr: u32, - value: u64, - valid: bool, -} - -static MSR_STORE: Mutex<[MsrEntry; MSR_BUCKETS]> = Mutex::new( - [MsrEntry { cpu: 0, msr: 0, value: 0, valid: false }; MSR_BUCKETS], -); -static NEXT_SLOT: AtomicU32 = AtomicU32::new(0); - -fn store_msr(cpu: u32, msr: u32, value: u64) { - let mut table = MSR_STORE.lock(); - for entry in table.iter_mut() { - if entry.valid && entry.cpu == cpu && entry.msr == msr { - entry.value = value; - return; - } - } - let slot = NEXT_SLOT.fetch_add(1, Ordering::Relaxed) as usize % MSR_BUCKETS; - table[slot] = MsrEntry { cpu, msr, value, valid: true }; -} - -fn read_msr(cpu: u32, msr: u32) -> Option { - let table = MSR_STORE.lock(); - table - .iter() - .find(|e| e.valid && e.cpu == cpu && e.msr == msr) - .map(|e| e.value) -} +const MSR_TIMEOUT_SPINS: usize = 1_000_000; /// Open: `msr/{cpu}/0x{msr}` (read or write, root only). pub fn open( @@ -102,7 +60,7 @@ pub fn open( pub fn read(handle: u64, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result { let cpu = (handle >> 32) as u32; let msr = (handle & 0xFFFFFFFF) as u32; - let value = read_msr(cpu, msr).unwrap_or(0); + let value = access_msr(cpu, msr, None)?; let bytes = value.to_le_bytes(); let n = buf.copy_common_bytes_from_slice(&bytes)?; Ok(n) @@ -114,6 +72,43 @@ pub fn write(handle: u64, buf: UserSliceRo, _token: &mut CleanLockToken) -> Resu let mut bytes = [0u8; 8]; let n = buf.copy_common_bytes_to_slice(&mut bytes)?; let value = u64::from_le_bytes(bytes); - store_msr(cpu, msr, value); + let _ = access_msr(cpu, msr, Some(value))?; Ok(n) } + +fn access_msr(cpu: u32, msr: u32, write: Option) -> Result { + let current_cpu = PercpuBlock::current().cpu_id.get(); + if cpu == current_cpu { + return unsafe { + if let Some(value) = write { + wrmsr(msr, value); + Ok(value) + } else { + Ok(rdmsr(msr)) + } + }; + } + + let Some(target) = crate::percpu::get_percpu_block(crate::cpu_set::LogicalCpuId::new(cpu)) + else { + return Err(Error::new(EINVAL)); + }; + + let mailbox = target.msr_mailbox(); + if !mailbox.wait_done() { + return Err(Error::new(EBUSY)); + } + + mailbox.begin_request(cpu, msr, write.is_some(), write.unwrap_or(0)); + ipi_single(IpiKind::Msr, target); + + for _ in 0..MSR_TIMEOUT_SPINS { + if mailbox.wait_done() { + let value = mailbox.read_value(); + return Ok(value); + } + core::hint::spin_loop(); + } + + Err(Error::new(ETIMEDOUT)) +}