262 lines
8.0 KiB
Rust
262 lines
8.0 KiB
Rust
use core::{
|
|
convert::TryInto,
|
|
str,
|
|
sync::atomic::{self, AtomicUsize},
|
|
};
|
|
|
|
use alloc::{boxed::Box, collections::BTreeMap};
|
|
|
|
use spin::{Mutex, Once, RwLock};
|
|
|
|
use crate::{
|
|
acpi::{RxsdtEnum, RXSDT_ENUM}, context::file::InternalFlags, event, sync::WaitCondition
|
|
};
|
|
|
|
use crate::syscall::{
|
|
data::Stat,
|
|
error::{Error, Result, EACCES, EBADF, EBADFD, EINTR, EINVAL, EISDIR, ENOENT, ENOTDIR, EROFS},
|
|
flag::{
|
|
EventFlags, EVENT_READ, MODE_CHR, MODE_DIR, MODE_FILE, O_ACCMODE, O_CREAT, O_DIRECTORY,
|
|
O_EXCL, O_RDONLY, O_STAT, O_SYMLINK,
|
|
},
|
|
usercopy::UserSliceWo,
|
|
};
|
|
|
|
use super::{CallerCtx, GlobalSchemes, KernelScheme, OpenResult};
|
|
|
|
/// A scheme used to access the RSDT or XSDT, which is needed for e.g. `acpid` to function.
|
|
pub struct AcpiScheme;
|
|
|
|
struct Handle {
|
|
kind: HandleKind,
|
|
stat: bool,
|
|
}
|
|
#[derive(Eq, PartialEq)]
|
|
enum HandleKind {
|
|
TopLevel,
|
|
Rxsdt,
|
|
ShutdownPipe,
|
|
}
|
|
|
|
// Using BTreeMap as hashbrown doesn't have a const constructor.
|
|
static HANDLES: RwLock<BTreeMap<usize, Handle>> = RwLock::new(BTreeMap::new());
|
|
static NEXT_FD: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
static DATA: Once<Box<[u8]>> = Once::new();
|
|
|
|
const TOPLEVEL_CONTENTS: &[u8] = b"rxsdt\nkstop\n";
|
|
|
|
static KSTOP_WAITCOND: WaitCondition = WaitCondition::new();
|
|
static KSTOP_FLAG: Mutex<bool> = Mutex::new(false);
|
|
|
|
pub fn register_kstop() -> bool {
|
|
*KSTOP_FLAG.lock() = true;
|
|
let mut waiters_awoken = KSTOP_WAITCOND.notify();
|
|
|
|
let handles = HANDLES.read();
|
|
|
|
for (&fd, _) in handles
|
|
.iter()
|
|
.filter(|(_, handle)| handle.kind == HandleKind::ShutdownPipe)
|
|
{
|
|
event::trigger(GlobalSchemes::Acpi.scheme_id(), fd, EVENT_READ);
|
|
waiters_awoken += 1;
|
|
}
|
|
|
|
if waiters_awoken == 0 {
|
|
log::error!("No userspace ACPI handler was notified when trying to shutdown. This is bad.");
|
|
// Let the kernel shutdown without ACPI.
|
|
return false;
|
|
}
|
|
|
|
// TODO: Context switch directly to the waiting context, to avoid annoying timeouts.
|
|
true
|
|
}
|
|
|
|
impl AcpiScheme {
|
|
pub fn init() {
|
|
// NOTE: This __must__ be called from the main kernel context, while initializing all
|
|
// schemes. If it is called by any other context, then all ACPI data will probably not even
|
|
// be mapped.
|
|
|
|
let mut data_init = false;
|
|
|
|
DATA.call_once(|| {
|
|
data_init = true;
|
|
|
|
let table = match RXSDT_ENUM.get() {
|
|
Some(RxsdtEnum::Rsdt(rsdt)) => rsdt.as_slice(),
|
|
Some(RxsdtEnum::Xsdt(xsdt)) => xsdt.as_slice(),
|
|
None => {
|
|
log::warn!("expected RXSDT_ENUM to be initialized before AcpiScheme, is ACPI available?");
|
|
&[]
|
|
}
|
|
};
|
|
|
|
Box::from(table)
|
|
});
|
|
|
|
if !data_init {
|
|
log::error!("AcpiScheme::init called multiple times");
|
|
}
|
|
}
|
|
}
|
|
|
|
impl KernelScheme for AcpiScheme {
|
|
fn kopen(&self, path: &str, flags: usize, ctx: CallerCtx) -> Result<OpenResult> {
|
|
let path = path.trim_start_matches('/');
|
|
|
|
if ctx.uid != 0 {
|
|
return Err(Error::new(EACCES));
|
|
}
|
|
if flags & O_CREAT == O_CREAT {
|
|
return Err(Error::new(EROFS));
|
|
}
|
|
if flags & O_EXCL == O_EXCL || flags & O_SYMLINK == O_SYMLINK {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
if flags & O_ACCMODE != O_RDONLY && flags & O_STAT != O_STAT {
|
|
return Err(Error::new(EROFS));
|
|
}
|
|
let (handle_kind, int_flags) = match path {
|
|
"" => {
|
|
if flags & O_DIRECTORY != O_DIRECTORY && flags & O_STAT != O_STAT {
|
|
return Err(Error::new(EISDIR));
|
|
}
|
|
|
|
(HandleKind::TopLevel, InternalFlags::POSITIONED)
|
|
}
|
|
"rxsdt" => {
|
|
if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT {
|
|
return Err(Error::new(ENOTDIR));
|
|
}
|
|
(HandleKind::Rxsdt, InternalFlags::POSITIONED)
|
|
}
|
|
"kstop" => {
|
|
if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT {
|
|
return Err(Error::new(ENOTDIR));
|
|
}
|
|
(HandleKind::ShutdownPipe, InternalFlags::empty())
|
|
}
|
|
_ => return Err(Error::new(ENOENT)),
|
|
};
|
|
|
|
let fd = NEXT_FD.fetch_add(1, atomic::Ordering::Relaxed);
|
|
let mut handles_guard = HANDLES.write();
|
|
|
|
let _ = handles_guard.insert(
|
|
fd,
|
|
Handle {
|
|
kind: handle_kind,
|
|
// TODO: Redundant
|
|
stat: flags & O_STAT == O_STAT,
|
|
},
|
|
);
|
|
|
|
Ok(OpenResult::SchemeLocal(fd, int_flags))
|
|
}
|
|
fn fsize(&self, id: usize) -> Result<u64> {
|
|
let mut handles = HANDLES.write();
|
|
let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
|
|
|
if handle.stat {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
|
|
Ok(match handle.kind {
|
|
HandleKind::Rxsdt => DATA.get().ok_or(Error::new(EBADFD))?.len() as u64,
|
|
HandleKind::ShutdownPipe => 1,
|
|
HandleKind::TopLevel => TOPLEVEL_CONTENTS.len() as u64,
|
|
})
|
|
}
|
|
// TODO
|
|
fn fevent(&self, id: usize, _flags: EventFlags) -> Result<EventFlags> {
|
|
let handles = HANDLES.read();
|
|
let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
|
|
if handle.stat {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
|
|
Ok(EventFlags::empty())
|
|
}
|
|
fn close(&self, id: usize) -> Result<()> {
|
|
if HANDLES.write().remove(&id).is_none() {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
Ok(())
|
|
}
|
|
fn kreadoff(&self, id: usize, dst_buf: UserSliceWo, offset: u64, _flags: u32, _stored_flags: u32) -> Result<usize> {
|
|
let Ok(offset) = usize::try_from(offset) else {
|
|
return Ok(0);
|
|
};
|
|
|
|
let mut handles = HANDLES.write();
|
|
let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
|
|
|
if handle.stat {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
|
|
let data = match handle.kind {
|
|
HandleKind::ShutdownPipe => {
|
|
if dst_buf.is_empty() {
|
|
return Ok(0);
|
|
}
|
|
|
|
loop {
|
|
let flag_guard = KSTOP_FLAG.lock();
|
|
|
|
if *flag_guard {
|
|
break;
|
|
} else if !KSTOP_WAITCOND.wait(flag_guard, "waiting for kstop") {
|
|
return Err(Error::new(EINTR));
|
|
}
|
|
}
|
|
|
|
return dst_buf.copy_exactly(&[0x42]).map(|()| 1);
|
|
}
|
|
HandleKind::Rxsdt => DATA.get().ok_or(Error::new(EBADFD))?,
|
|
HandleKind::TopLevel => TOPLEVEL_CONTENTS,
|
|
};
|
|
|
|
let src_offset = core::cmp::min(offset, data.len());
|
|
let src_buf = data
|
|
.get(src_offset..)
|
|
.expect("expected data to be at least data.len() bytes long");
|
|
|
|
dst_buf.copy_common_bytes_from_slice(src_buf)
|
|
}
|
|
fn kfstat(&self, id: usize, buf: UserSliceWo) -> Result<()> {
|
|
let handles = HANDLES.read();
|
|
let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
|
|
buf.copy_exactly(&match handle.kind {
|
|
HandleKind::Rxsdt => {
|
|
let data = DATA.get().ok_or(Error::new(EBADFD))?;
|
|
|
|
Stat {
|
|
st_mode: MODE_FILE,
|
|
st_size: data.len().try_into().unwrap_or(u64::max_value()),
|
|
..Default::default()
|
|
}
|
|
}
|
|
HandleKind::TopLevel => Stat {
|
|
st_mode: MODE_DIR,
|
|
st_size: TOPLEVEL_CONTENTS
|
|
.len()
|
|
.try_into()
|
|
.unwrap_or(u64::max_value()),
|
|
..Default::default()
|
|
},
|
|
HandleKind::ShutdownPipe => Stat {
|
|
st_mode: MODE_CHR,
|
|
st_size: 1,
|
|
..Default::default()
|
|
},
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|