diff --git a/src/scheme/sys/mod.rs b/src/scheme/sys/mod.rs index 8f26187a79..943f526215 100644 --- a/src/scheme/sys/mod.rs +++ b/src/scheme/sys/mod.rs @@ -33,10 +33,43 @@ mod fdstat; mod iostat; mod irq; mod log; +mod msr; mod stat; mod syscall; mod uname; +/// Extract the (cpu<<32 | msr) u64 handle stored in an MSR fd's +/// data buffer. Returns None if the fd is not an MSR fd. We clone the +/// data Arc to drop the HANDLES read lock before calling data.read() +/// (which needs &mut token). +fn decode_msr_handle(id: usize, token: &mut CleanLockToken) -> Option { + type MsrData = Arc>>>; + // Wrap the lookup in a closure so the inner `return` doesn't exit + // decode_msr_handle itself; instead it returns a value from the + // closure, which the outer let-block receives as Option. + let mut lookup = || -> Option { + let _handles = HANDLES.read(token.token()); + let h_opt = _handles.get(id).ok(); + let h = h_opt?; + if let Handle::Resource { data, path, .. } = h { + if *path == "msr" { + return Some(Arc::clone(data)); + } + } + None + }; + let data_arc: Option = lookup(); + let data: MsrData = data_arc?; + let b = data.read(token.token()); + b.as_ref().and_then(|b| { + if b.len() >= 8 { + Some(u64::from_le_bytes(b[..8].try_into().ok()?)) + } else { + None + } + }) +} + enum Handle { TopLevel, Resource { @@ -132,6 +165,23 @@ impl KernelScheme for SysScheme { if path.is_empty() { let id = HANDLES.write(token.token()).insert(Handle::TopLevel); + Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) + } else if path == "msr" || path.starts_with("msr/") { + // /scheme/sys/msr/{cpu}/0x{msr} — Phase G.1: MSR R/W scheme + // for cpufreqd and redbear-power. Open is parse-only; reads + // and writes happen via the handle's read/write paths below. + let msr_path = path.strip_prefix("msr/").unwrap_or(""); + let handle = msr::open(msr_path, _flags, _fcntl_flags, &ctx, token)?; + // Store the (cpu<<32 | msr) handle in the data buffer; the + // path string is intentionally omitted (the static array + // version would require 'static lifetime which user_buf + // doesn't have). The dispatch in kreadoff/kwriteoff uses + // a tag in the data buffer instead. + let id = HANDLES.write(token.token()).insert(Handle::Resource { + path: "msr", + kind: Kind::Rd(|_| Ok(Vec::new())), + data: Arc::new(RwLock::new(Some(handle.to_le_bytes().to_vec()))), + }); Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) } else { //Have to iterate to get the path without allocation @@ -212,6 +262,14 @@ impl KernelScheme for SysScheme { return Ok(0); }; + // MSR scheme: /scheme/sys/msr/{cpu}/0x{msr_hex} read. + // The handle's data buffer stores the (cpu<<32 | msr) packed u64 + // written by `msr::open`. Decode, dispatch to msr::read. + let msr_handle: Option = decode_msr_handle(id, token); + if let Some(handle_u64) = msr_handle { + return msr::read(handle_u64, buffer, token); + } + let (kind, data_lock) = { match HANDLES.read(token.token()).get(id)? { Handle::Resource { kind, data, .. } => (*kind, data.clone()), @@ -240,6 +298,13 @@ impl KernelScheme for SysScheme { _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { + // MSR scheme: /scheme/sys/msr/{cpu}/0x{msr_hex} write. + // Dispatch to msr::write if the path is an MSR path. + let msr_handle: Option = decode_msr_handle(id, token); + if let Some(handle_u64) = msr_handle { + return msr::write(handle_u64, buffer, token); + } + let (handler, intermediate, len) = match HANDLES.read(token.token()).get(id)? { Handle::TopLevel | Handle::Resource { diff --git a/src/scheme/sys/msr.rs b/src/scheme/sys/msr.rs new file mode 100644 index 0000000000..90ca21976a --- /dev/null +++ b/src/scheme/sys/msr.rs @@ -0,0 +1,119 @@ +//! /scheme/sys/msr — Model-Specific Register R/W +//! +//! Path layout: `/scheme/sys/msr/{cpu}/0x{msr_hex}`. Open for write, +//! then write 8 bytes (little-endian u64). Read for read returns the +//! current register value. +//! +//! CPU enumeration comes from `crate::cpu::cpu_count()` (8 by default +//! on QEMU Arrow Lake sim, up to 16 on the real LG Gram). The exact +//! set of accessible MSRs depends on CPU features; the MSR read/write +//! uses `core::arch::x86_64::{rdmsr, wrmsr}` (in userspace we use the +//! `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 crate::cpu_count; +use syscall::{ + error::{Error, Result, EBADF, EINVAL, ENOENT, EPERM}, +}; +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) +} + +/// Open: `msr/{cpu}/0x{msr}` (read or write, root only). +pub fn open( + path: &str, + _flags: usize, + _fcntl_flags: u32, + caller: &CallerCtx, + _token: &mut CleanLockToken, +) -> Result { + if caller.uid != 0 { + return Err(Error::new(EPERM)); + } + let trimmed = path.trim_matches('/'); + if trimmed.is_empty() { + return Err(Error::new(EBADF)); + } + let rest = trimmed.strip_prefix("msr").ok_or(Error::new(ENOENT))?; + let rest = rest.trim_matches('/'); + let mut parts = rest.split('/'); + let cpu_str = parts.next().ok_or(Error::new(EINVAL))?; + let msr_str = parts.next().ok_or(Error::new(EINVAL))?; + if parts.next().is_some() { + return Err(Error::new(EINVAL)); + } + let cpu: u32 = cpu_str.parse().map_err(|_| Error::new(EINVAL))?; + let msr_clean = msr_str.strip_prefix("0x").unwrap_or(msr_str); + let msr = u32::from_str_radix(msr_clean, 16).map_err(|_| Error::new(EINVAL))?; + if cpu >= cpu_count() { + return Err(Error::new(EINVAL)); + } + Ok(((cpu as u64) << 32) | (msr as u64)) +} + +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 bytes = value.to_le_bytes(); + let n = buf.copy_common_bytes_from_slice(&bytes)?; + Ok(n) +} + +pub fn write(handle: u64, buf: UserSliceRo, _token: &mut CleanLockToken) -> Result { + let cpu = (handle >> 32) as u32; + let msr = (handle & 0xFFFFFFFF) as u32; + 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); + Ok(n) +}