kernel: add real MSR scheme access

This commit is contained in:
2026-07-08 00:29:43 +03:00
parent a240e73e44
commit e896c0e08b
5 changed files with 130 additions and 51 deletions
+2
View File
@@ -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")]
{
+18
View File
@@ -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() };
});
+2 -1
View File
@@ -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)]
+64 -1
View File
@@ -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<PercpuBlock>; 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() }
}
+44 -49
View File
@@ -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<Mutex<HashMap<u32, u64>>>` 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<u64> {
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<usize> {
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<u64>) -> Result<u64> {
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))
}