// TODO: This scheme can be simplified significantly, and through it, several other APIs where it's // dubious whether they require dedicated schemes (like irq, dtb, acpi). In particular, the kernel // could abandon the filesystem-like APIs here in favor of SYS_CALL, and instead let userspace wrap // those to say shell-accessible fs-like APIs. use ::syscall::{ dirent::{DirEntry, DirentBuf, DirentKind}, EACCES, EINVAL, EIO, EISDIR, ENOTDIR, EPERM, }; use alloc::{sync::Arc, vec::Vec}; use core::str; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use crate::arch::interrupt; use crate::{ context::file::InternalFlags, sync::{CleanLockToken, RwLock, L1}, syscall::{ data::Stat, error::{Error, Result, EBADF, ENOENT}, flag::{MODE_DIR, MODE_FILE}, usercopy::{UserSliceRo, UserSliceWo}, }, }; use super::{CallerCtx, HandleMap, KernelScheme, OpenResult, StrOrBytes}; mod block; mod context; mod cpu; mod cpu_stat; mod exe; mod fdstat; mod iostat; mod irq; mod log; mod mem; 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 { path: &'static str, kind: Kind, data: Arc>>>, }, SchemeRoot, } #[derive(Clone, Copy)] enum Kind { Rd(fn(&mut CleanLockToken) -> Result>), Wr(fn(&[u8], &mut CleanLockToken) -> Result), } use Kind::{Rd, Wr}; impl Kind { fn generate_data(&self, token: &mut CleanLockToken) -> Result> { match self { Rd(handler) => handler(token), Wr(_) => Err(Error::new(EISDIR)), } } } /// System information scheme pub struct SysScheme; static HANDLES: RwLock> = RwLock::new(HandleMap::new()); const FILES: &[(&str, Kind)] = &[ ("block", Rd(block::resource)), ("context", Rd(context::resource)), ("cpu", Rd(cpu::resource)), #[cfg(feature = "sys_fdstat")] ("fdstat", Rd(fdstat::resource)), ("exe", Rd(exe::resource)), ("iostat", Rd(iostat::resource)), ("irq", Rd(irq::resource)), ("log", Rd(log::resource)), ("mem", Rd(mem::resource)), ("numa", Rd(crate::numa::get_numa_info)), ("numa_dist", Rd(crate::numa::get_numa_distance_info)), ("syscall", Rd(syscall::resource)), ("uname", Rd(uname::resource)), ("env", Rd(|_| Ok(Vec::from(crate::startup::init_env())))), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ("spurious_irq", Rd(interrupt::irq::spurious_irq_resource)), ("stat", Rd(stat::resource)), // Disabled because the debugger is inherently unsafe and probably will break the system. /* ("trigger_debugger", Rd(|token| unsafe { crate::debugger::debugger(None, token); Ok(Vec::new()) })), */ ( "update_time_offset", Wr(crate::time::sys_update_time_offset), ), ( "kstop", Wr(|arg, token| unsafe { match arg.trim_ascii() { b"shutdown" => crate::stop::kstop(token), b"reset" => crate::stop::kreset(), b"emergency_reset" => crate::stop::emergency_reset(), // Phase I.5: hardware-agnostic s2idle / Modern // Standby. acpid writes "s2idle" to // /scheme/sys/kstop. The kernel sets // S2IDLE_REQUESTED and signals the kstop // handle's EVENT_READ so acpid's blocked // read unblocks. acpid then runs the AML // entry sequence (\_TTS(0), \_PTS(0), // \_SST(3)) and yields to the kernel. The // kernel's idle path sees S2IDLE_REQUESTED // and calls mwait_loop(). When an interrupt // breaks MWAIT, the mwait_loop post-handler // clears S2IDLE_REQUESTED and signals the // kstop event again with reason=2 (s2idle // wake). acpid's blocked read unblocks and // runs the AML resume sequence (\_SST(2), // \_WAK(0), \_SST(1)). b"s2idle" => { crate::stop::enter_s2idle(); crate::scheme::acpi::kstop_set_reason(2); Ok(0) } b"s3" => { // Phase II: the s3 arg may include a // SLP_TYP byte (the SLP_TYP value from // acpid's \_S3 AML package). Format: arg is // "s3X" where X is the SLP_TYP byte (0-7). // If absent (arg.len() == 3), the kernel // uses 5 which is the most common S3 SLP_TYP // for modern x86 systems. if arg.len() == 4 { let slp_typ = arg[3]; if slp_typ >= 1 && slp_typ <= 7 { crate::scheme::acpi::kstop_set_s3_slp_typ(slp_typ); } } // Default: if no SLP_TYP was provided, the // kernel uses 5 which is the standard S3 // SLP_TYP for x86 systems. The acpid // should set the exact value via // kstop_set_s3_slp_typ() if the default is // wrong. if crate::scheme::acpi::S3_SLP_TYP .load(core::sync::atomic::Ordering::Acquire) == 0 { crate::scheme::acpi::kstop_set_s3_slp_typ(5); } crate::stop::enter_s3(token) } _ => Err(Error::new(EINVAL)), } }), ), ]; impl KernelScheme for SysScheme { fn scheme_root(&self, token: &mut CleanLockToken) -> Result { let id = HANDLES.write(token.token()).insert(Handle::SchemeRoot); Ok(id) } fn kopenat( &self, id: usize, user_buf: StrOrBytes, _flags: usize, _fcntl_flags: u32, ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result { if !matches!(HANDLES.read(token.token()).get(id)?, Handle::SchemeRoot) { return Err(Error::new(EACCES)); } let path = user_buf .as_str() .or(Err(Error::new(EINVAL)))? .trim_matches('/'); if path.is_empty() { let id = HANDLES.write(token.token()).insert(Handle::TopLevel); Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) } else if let Some(rest) = path.strip_prefix("cpu/") { // /scheme/sys/cpu/{cpu}/stat — per-CPU load counters compatible // with Linux /proc/stat. Only the `stat` file is exposed for // now; cpufreqd and redbear-power open it directly. let mut parts = rest.split('/').filter(|p| !p.is_empty()); let cpu_str = parts.next().ok_or(Error::new(EINVAL))?; let file = parts.next().ok_or(Error::new(EINVAL))?; if parts.next().is_some() || file != "stat" { return Err(Error::new(ENOENT)); } let cpu: u32 = cpu_str.parse().map_err(|_| Error::new(EINVAL))?; let data = cpu_stat::resource_for(cpu, token)?; let id = HANDLES.write(token.token()).insert(Handle::Resource { path: "cpu_stat", kind: Kind::Rd(|_| Ok(Vec::new())), data: Arc::new(RwLock::new(Some(data))), }); 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. // Pass the full "msr/{cpu}/0x{msr}" path to msr::open so it // can do its own strip_prefix("msr") and parse the remainder. // Stripping here (the previous behavior) left "0/0x199" // which msr::open's strip_prefix("msr") rejected with ENOENT, // causing every MSR open to fail and cpufreqd to oscillate. let handle = msr::open(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 let entry = FILES .iter() .find(|(entry_path, _)| *entry_path == path) .ok_or(Error::new(ENOENT))?; if matches!(entry.1, Wr(_)) && ctx.uid != 0 { return Err(Error::new(EPERM)); } // TODO: Initialize resources during openat to use them as a snapshot. let id = HANDLES.write(token.token()).insert(Handle::Resource { path: entry.0, kind: entry.1, data: Arc::new(RwLock::new(None)), }); Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) } } fn fsize(&self, id: usize, token: &mut CleanLockToken) -> Result { let (kind, data_lock) = { match HANDLES.read(token.token()).get(id)? { Handle::TopLevel => return Ok(0), Handle::Resource { kind, data, .. } => (*kind, data.clone()), Handle::SchemeRoot => return Err(Error::new(EBADF)), } }; if matches!(kind, Kind::Wr(_)) { return Ok(0); } let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; Ok(data.len() as u64) } fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> { HANDLES.write(token.token()).remove(id)?; Ok(()) } fn kfpath(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result { let path = match HANDLES.read(token.token()).get(id)? { Handle::TopLevel => "", Handle::Resource { path, .. } => path, Handle::SchemeRoot => return Err(Error::new(EBADF)), }; const FIRST: &[u8] = b"sys:"; let mut bytes_read = buf.copy_common_bytes_from_slice(FIRST)?; if let Some(remaining) = buf.advance(FIRST.len()) { bytes_read += remaining.copy_common_bytes_from_slice(path.as_bytes())?; } Ok(bytes_read) } fn kreadoff( &self, id: usize, buffer: UserSliceWo, pos: u64, _flags: u32, _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { let Ok(pos) = usize::try_from(pos) else { 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()), _ => return Err(Error::new(EBADF)), } }; let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; let avail_buf = data.get(pos..).unwrap_or(&[]); buffer.copy_common_bytes_from_slice(avail_buf) } fn kwriteoff( &self, id: usize, buffer: UserSliceRo, _pos: u64, _flags: u32, _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 { kind: Kind::Rd(_), .. } => return Err(Error::new(EISDIR)), Handle::Resource { kind: Kind::Wr(handler), .. } => { let mut intermediate = [0_u8; 256]; let len = buffer.copy_common_bytes_to_slice(&mut intermediate)?; (*handler, intermediate, len) } Handle::SchemeRoot => return Err(Error::new(EBADF)), }; handler(&intermediate[..len], token) } fn getdents( &self, id: usize, buf: UserSliceWo, header_size: u16, first_index: u64, token: &mut CleanLockToken, ) -> Result { let Ok(first_index) = usize::try_from(first_index) else { return Ok(0); }; match HANDLES.read(token.token()).get(id)? { Handle::Resource { .. } => Err(Error::new(ENOTDIR)), Handle::TopLevel => { let mut buf = DirentBuf::new(buf, header_size).ok_or(Error::new(EIO))?; for (this_idx, (name, _)) in FILES.iter().enumerate().skip(first_index) { buf.entry(DirEntry { inode: this_idx as u64, next_opaque_id: this_idx as u64 + 1, kind: DirentKind::Regular, name, })?; } Ok(buf.finalize()) } Handle::SchemeRoot => Err(Error::new(EBADF)), } } fn kfstat(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { let stat_base = { let handles = HANDLES.read(token.token()); match handles.get(id)? { Handle::Resource { kind, data, .. } => Some((*kind, data.clone())), Handle::TopLevel => None, Handle::SchemeRoot => return Err(Error::new(EBADF)), } }; let stat = if let Some((kind, data_lock)) = stat_base { let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; let size = match kind { Kind::Rd(_) => data.len() as u64, Kind::Wr(_) => 0, }; Stat { st_mode: 0o666 | MODE_FILE, st_uid: 0, st_gid: 0, st_size: size, ..Default::default() } } else { Stat { st_mode: 0o444 | MODE_DIR, st_uid: 0, st_gid: 0, st_size: 0, ..Default::default() } }; buf.copy_exactly(&stat)?; Ok(()) } }