Files
RedBear-OS/src/scheme/sys/mod.rs
T
vasilito 7aa2a165df Merge remote-tracking branch 'upstream/master' into kernel-0.3.0
# Conflicts:
#	src/acpi/mod.rs
#	src/acpi/rsdt.rs
#	src/acpi/rxsdt.rs
#	src/acpi/srat/aarch64.rs
#	src/acpi/xsdt.rs
#	src/arch/x86_shared/start.rs
#	src/context/mod.rs
#	src/context/switch.rs
#	src/main.rs
#	src/memory/mod.rs
#	src/numa.rs
#	src/scheme/sys/mod.rs
#	src/startup/mod.rs
2026-07-11 11:31:19 +03:00

474 lines
18 KiB
Rust

// 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<u64> {
type MsrData = Arc<RwLock<L1, Option<Vec<u8>>>>;
// 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<MsrData>.
let mut lookup = || -> Option<MsrData> {
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<MsrData> = 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<RwLock<L1, Option<Vec<u8>>>>,
},
SchemeRoot,
}
#[derive(Clone, Copy)]
enum Kind {
Rd(fn(&mut CleanLockToken) -> Result<Vec<u8>>),
Wr(fn(&[u8], &mut CleanLockToken) -> Result<usize>),
}
use Kind::{Rd, Wr};
impl Kind {
fn generate_data(&self, token: &mut CleanLockToken) -> Result<Vec<u8>> {
match self {
Rd(handler) => handler(token),
Wr(_) => Err(Error::new(EISDIR)),
}
}
}
/// System information scheme
pub struct SysScheme;
static HANDLES: RwLock<L1, HandleMap<Handle>> = 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<usize> {
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<OpenResult> {
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<u64> {
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<usize> {
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<usize> {
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<u64> = 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<usize> {
// 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<u64> = 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<usize> {
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(())
}
}