From 7ac5bdbae0d7b279323c89e707bc08b5abdbf237 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Wed, 10 Mar 2021 14:06:43 +0100 Subject: [PATCH] WIP: Implement userspace-driven shutdown. --- src/acpi/dmar/mod.rs | 3 +- src/acpi/hpet.rs | 3 +- src/acpi/madt.rs | 3 +- src/acpi/mod.rs | 41 ------ src/arch/x86_64/device/ioapic.rs | 3 - src/arch/x86_64/stop.rs | 25 +++- src/scheme/acpi.rs | 214 +++++++++++++++++++++++++------ src/scheme/mod.rs | 2 +- src/sync/wait_condition.rs | 2 +- 9 files changed, 203 insertions(+), 93 deletions(-) diff --git a/src/acpi/dmar/mod.rs b/src/acpi/dmar/mod.rs index 8b107e2..caf8726 100644 --- a/src/acpi/dmar/mod.rs +++ b/src/acpi/dmar/mod.rs @@ -5,7 +5,7 @@ use self::drhd::Drhd; use crate::memory::Frame; use crate::paging::{ActivePageTable, PageFlags, PhysicalAddress}; -use super::{find_sdt, load_table, get_sdt_signature}; +use super::find_sdt; pub mod drhd; @@ -22,7 +22,6 @@ impl Dmar { pub fn init(active_table: &mut ActivePageTable) { let dmar_sdt = find_sdt("DMAR"); let dmar = if dmar_sdt.len() == 1 { - load_table(get_sdt_signature(dmar_sdt[0])); Dmar::new(dmar_sdt[0]) } else { println!("Unable to find DMAR"); diff --git a/src/acpi/hpet.rs b/src/acpi/hpet.rs index 269fa60..498a668 100644 --- a/src/acpi/hpet.rs +++ b/src/acpi/hpet.rs @@ -6,7 +6,7 @@ use crate::memory::Frame; use crate::paging::{ActivePageTable, PhysicalAddress, Page, PageFlags, VirtualAddress}; use super::sdt::Sdt; -use super::{ACPI_TABLE, find_sdt, load_table, get_sdt_signature}; +use super::{ACPI_TABLE, find_sdt}; #[repr(packed)] #[derive(Clone, Copy, Debug, Default)] @@ -38,7 +38,6 @@ impl Hpet { pub fn init(active_table: &mut ActivePageTable) { let hpet_sdt = find_sdt("HPET"); let hpet = if hpet_sdt.len() == 1 { - load_table(get_sdt_signature(hpet_sdt[0])); Hpet::new(hpet_sdt[0], active_table) } else { println!("Unable to find HPET"); diff --git a/src/acpi/madt.rs b/src/acpi/madt.rs index 031598c..f6dabb6 100644 --- a/src/acpi/madt.rs +++ b/src/acpi/madt.rs @@ -4,7 +4,7 @@ use crate::memory::{allocate_frames, Frame}; use crate::paging::{ActivePageTable, Page, PageFlags, PhysicalAddress, VirtualAddress}; use super::sdt::Sdt; -use super::{find_sdt, load_table, get_sdt_signature}; +use super::find_sdt; use core::intrinsics::{atomic_load, atomic_store}; use core::sync::atomic::Ordering; @@ -31,7 +31,6 @@ impl Madt { pub fn init(active_table: &mut ActivePageTable) { let madt_sdt = find_sdt("APIC"); let madt = if madt_sdt.len() == 1 { - load_table(get_sdt_signature(madt_sdt[0])); Madt::new(madt_sdt[0]) } else { println!("Unable to find MADT"); diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs index 3d6adeb..348c35f 100644 --- a/src/acpi/mod.rs +++ b/src/acpi/mod.rs @@ -81,11 +81,6 @@ pub unsafe fn init(active_table: &mut ActivePageTable, already_supplied_rsdps: O *sdt_ptrs = Some(BTreeMap::new()); } - { - let mut order = SDT_ORDER.write(); - *order = Some(vec!()); - } - // Search for RSDP if let Some(rsdp) = RSDP::get_rsdp(active_table, already_supplied_rsdps) { info!("RSDP: {:?}", rsdp); @@ -149,7 +144,6 @@ pub unsafe fn init(active_table: &mut ActivePageTable, already_supplied_rsdps: O pub type SdtSignature = (String, [u8; 6], [u8; 8]); pub static SDT_POINTERS: RwLock>> = RwLock::new(None); -pub static SDT_ORDER: RwLock>> = RwLock::new(None); pub fn find_sdt(name: &str) -> Vec<&'static Sdt> { let mut sdts: Vec<&'static Sdt> = vec!(); @@ -170,41 +164,6 @@ pub fn get_sdt_signature(sdt: &'static Sdt) -> SdtSignature { (signature, sdt.oem_id, sdt.oem_table_id) } -pub fn load_table(signature: SdtSignature) { - let mut order = SDT_ORDER.write(); - - if let Some(ref mut o) = *order { - o.push(signature); - } -} - -pub fn get_signature_from_index(index: usize) -> Option { - if let Some(ref order) = *(SDT_ORDER.read()) { - if index < order.len() { - Some(order[index].clone()) - } else { - None - } - } else { - None - } -} - -pub fn get_index_from_signature(signature: SdtSignature) -> Option { - if let Some(ref order) = *(SDT_ORDER.read()) { - let mut i = order.len(); - while i > 0 { - i -= 1; - - if order[i] == signature { - return Some(i); - } - } - } - - None -} - pub struct Acpi { pub hpet: RwLock>, pub next_ctx: RwLock, diff --git a/src/arch/x86_64/device/ioapic.rs b/src/arch/x86_64/device/ioapic.rs index dfd405c..e92d452 100644 --- a/src/arch/x86_64/device/ioapic.rs +++ b/src/arch/x86_64/device/ioapic.rs @@ -24,9 +24,6 @@ impl IoApicRegs { // offset 0x10 unsafe { self.pointer.offset(4) } } - fn read_ioregsel(&self) -> u32 { - unsafe { ptr::read_volatile::(self.ioregsel()) } - } fn write_ioregsel(&mut self, value: u32) { unsafe { ptr::write_volatile::(self.ioregsel() as *mut u32, value) } } diff --git a/src/arch/x86_64/stop.rs b/src/arch/x86_64/stop.rs index 8c928c3..f915d0a 100644 --- a/src/arch/x86_64/stop.rs +++ b/src/arch/x86_64/stop.rs @@ -1,5 +1,10 @@ #[cfg(feature = "acpi")] -use crate::acpi; +use crate::{ + context, + scheme::acpi, + time, +}; + use crate::syscall::io::{Io, Pio}; #[no_mangle] @@ -29,8 +34,22 @@ pub unsafe extern fn kstop() -> ! { // FIXME: RPC into userspace, maybe allowing the kernel ACPI scheme to support e.g. registering // an event queue, so that a special file can only be read/written when about to shut down. - // #[cfg(feature = "acpi")] - // acpi::set_global_s_state(5); + #[cfg(feature = "acpi")] + { + // Tell whatever driver that handles ACPI, that it should enter the S5 state (i.e. + // shutdown). + acpi::register_kstop(); + + // Since this driver is a userspace process, and we do not use any magic like directly + // context switching, we have to wait for the userspace driver to complete, with a timeout. + // + // We switch context, and wait for one second. + while time::monotonic().0 < 1 { + if ! context::switch() { + break; + } + } + } // Magic shutdown code for bochs and qemu (older versions). for c in "Shutdown".bytes() { diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs index 815b91c..d09fe69 100644 --- a/src/scheme/acpi.rs +++ b/src/scheme/acpi.rs @@ -1,36 +1,41 @@ use core::convert::TryInto; -use core::fmt::Write; use core::str; use core::sync::atomic::{self, AtomicUsize}; use alloc::boxed::Box; use alloc::collections::BTreeMap; -use alloc::vec::Vec; -use syscall::data::Stat; -use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDWR, O_STAT, O_WRONLY, SEEK_SET, SEEK_CUR, SEEK_END}; -use syscall::scheme::{calc_seek_offset_usize, Scheme}; -use syscall::{Error, Result}; -use syscall::{MODE_DIR, MODE_FILE}; - -use spin::{Once, RwLock}; +use spin::{Mutex, Once, RwLock}; use crate::acpi::{RXSDT_ENUM, RxsdtEnum}; +use crate::event; +use crate::scheme::SchemeId; +use crate::sync::WaitCondition; -#[derive(Clone, Copy)] -struct PhysSlice { - phys_ptr: usize, - len: usize, - /// These appear to be identity mapped, so this is technically not needed. - virt: usize, -} +use crate::syscall::data::Stat; +use crate::syscall::error::{EACCES, EBADF, EBADFD, EINTR, EINVAL, EISDIR, ENOENT, ENOTDIR, EROFS}; +use crate::syscall::flag::{ + EventFlags, EVENT_READ, + MODE_CHR, MODE_DIR, MODE_FILE, + O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_STAT, O_SYMLINK, + SEEK_SET, SEEK_CUR, SEEK_END, +}; +use crate::syscall::scheme::Scheme; +use crate::syscall::error::{Error, Result}; /// A scheme used to access the RSDT or XSDT, which is needed for e.g. `acpid` to function. pub struct AcpiScheme; struct Handle { offset: usize, + kind: HandleKind, + stat: bool, +} +#[derive(Eq, PartialEq)] +enum HandleKind { + TopLevel, + Rxsdt, + ShutdownPipe, } static HANDLES: RwLock> = RwLock::new(BTreeMap::new()); @@ -38,15 +43,50 @@ static NEXT_FD: AtomicUsize = AtomicUsize::new(0); static DATA: Once> = Once::new(); +const TOPLEVEL_CONTENTS: &[u8] = b"rxsdt\nkstop\n"; + +static KSTOP_WAITCOND: WaitCondition = WaitCondition::new(); +static KSTOP_FLAG: Mutex = Mutex::new(false); + +static SCHEME_ID: Once = Once::new(); + +pub fn register_kstop() -> bool { + *KSTOP_FLAG.lock() = true; + let mut waiters_awoken = KSTOP_WAITCOND.notify(); + + if let Some(&acpi_scheme) = SCHEME_ID.r#try() { + let handles = HANDLES.read(); + + for (&fd, _) in handles.iter().filter(|(_, handle)| handle.kind == HandleKind::ShutdownPipe) { + event::trigger(acpi_scheme, fd, EVENT_READ); + waiters_awoken += 1; + } + } else { + log::error!("Calling register_kstop before kernel ACPI scheme was initialized"); + } + + 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 new() -> Self { + pub fn new(id: SchemeId) -> Self { // 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 initialized = false; + let mut data_init = false; + let mut id_init = false; DATA.call_once(|| { + data_init = true; + let rsdt_or_xsdt = RXSDT_ENUM .r#try() .expect("expected RXSDT_ENUM to be initialized before AcpiScheme"); @@ -58,8 +98,13 @@ impl AcpiScheme { Box::from(table) }); + SCHEME_ID.call_once(|| { + id_init = true; - if !initialized { + id + }); + + if !data_init || !id_init { log::error!("AcpiScheme::init called multiple times"); } @@ -68,40 +113,91 @@ impl AcpiScheme { } impl Scheme for AcpiScheme { - fn open(&self, _path: &str, flags: usize, opener_uid: u32, _opener_gid: u32) -> Result { + fn open(&self, path: &str, flags: usize, opener_uid: u32, _opener_gid: u32) -> Result { + let path = path.trim_start_matches('/'); + if opener_uid != 0 { return Err(Error::new(EACCES)); } - if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT { - return Err(Error::new(ENOTDIR)); + 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 = match path { + "" => { + if flags & O_DIRECTORY != O_DIRECTORY && flags & O_STAT != O_STAT { + return Err(Error::new(EISDIR)); + } + + HandleKind::TopLevel + } + "rxsdt" => { + if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT { + return Err(Error::new(ENOTDIR)); + } + HandleKind::Rxsdt + } + "kstop" => { + if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT { + return Err(Error::new(ENOTDIR)); + } + HandleKind::ShutdownPipe + } + _ => return Err(Error::new(ENOENT)), + }; let fd = NEXT_FD.fetch_add(1, atomic::Ordering::Relaxed); - let mut handles_guard = HANDLES.write(); - let handle = Handle { offset: 0 }; - let _ = handles_guard.insert(fd, handle); + let _ = handles_guard.insert(fd, Handle { + offset: 0, + kind: handle_kind, + stat: flags & O_STAT == O_STAT, + }); Ok(fd) } fn fstat(&self, id: usize, stat: &mut Stat) -> Result { - if ! HANDLES.read().contains_key(&id) { - return Err(Error::new(EBADF)); + let handles = HANDLES.read(); + let handle = handles.get(&id).ok_or(Error::new(EBADF))?; + + match handle.kind { + HandleKind::Rxsdt => { + let data = DATA.r#try().ok_or(Error::new(EBADFD))?; + + stat.st_mode = MODE_FILE; + stat.st_size = data.len().try_into().unwrap_or(u64::max_value()); + } + HandleKind::TopLevel => { + stat.st_mode = MODE_DIR; + stat.st_size = TOPLEVEL_CONTENTS.len().try_into().unwrap_or(u64::max_value()); + } + HandleKind::ShutdownPipe => { + stat.st_mode = MODE_CHR; + stat.st_size = 1; + } } - let data = DATA.r#try().ok_or(Error::new(EBADFD))?; - - stat.st_mode = MODE_FILE; - stat.st_size = data.len().try_into().unwrap_or(u64::max_value()); - Ok(0) } fn seek(&self, id: usize, pos: isize, whence: usize) -> Result { let mut handles = HANDLES.write(); let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; - let data = DATA.r#try().ok_or(Error::new(EBADFD))?; + if handle.stat { + return Err(Error::new(EBADF)); + } + + let file_len = match handle.kind { + HandleKind::Rxsdt => DATA.r#try().ok_or(Error::new(EBADFD))?.len(), + HandleKind::ShutdownPipe => 1, + HandleKind::TopLevel => TOPLEVEL_CONTENTS.len(), + }; let new_offset = match whence { SEEK_SET => pos as usize, @@ -111,9 +207,9 @@ impl Scheme for AcpiScheme { handle.offset.saturating_add(pos as usize) } SEEK_END => if pos < 0 { - data.len().checked_sub((-pos) as usize).ok_or(Error::new(EINVAL))? + file_len.checked_sub((-pos) as usize).ok_or(Error::new(EINVAL))? } else { - data.len() + file_len } _ => return Err(Error::new(EINVAL)), }; @@ -126,7 +222,38 @@ impl Scheme for AcpiScheme { let mut handles = HANDLES.write(); let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; - let data = DATA.r#try().ok_or(Error::new(EBADFD))?; + if handle.stat { + return Err(Error::new(EBADF)); + } + + let data = match handle.kind { + HandleKind::ShutdownPipe => { + let dst_byte = match dst_buf.first_mut() { + None => return Ok(0), + Some(dst) => if handle.offset >= 1 { + return Ok(0) + } else { + dst + }, + }; + + 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)); + } + } + + *dst_byte = 0x42; + handle.offset = 1; + return Ok(1); + } + HandleKind::Rxsdt => DATA.r#try().ok_or(Error::new(EBADFD))?, + HandleKind::TopLevel => TOPLEVEL_CONTENTS, + }; let src_offset = core::cmp::min(handle.offset, data.len()); let src_buf = data @@ -136,14 +263,25 @@ impl Scheme for AcpiScheme { let bytes_to_copy = core::cmp::min(dst_buf.len(), src_buf.len()); dst_buf[..bytes_to_copy].copy_from_slice(&src_buf[..bytes_to_copy]); + handle.offset += bytes_to_copy; Ok(bytes_to_copy) } + fn fevent(&self, id: usize, flags: EventFlags) -> Result { + 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 write(&self, _id: usize, _buf: &[u8]) -> Result { Err(Error::new(EBADF)) } fn close(&self, id: usize) -> Result { - if ! HANDLES.read().contains_key(&id) { + if HANDLES.write().remove(&id).is_none() { return Err(Error::new(EBADF)); } Ok(0) diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index cccf62e..74405b0 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -162,7 +162,7 @@ impl SchemeList { // These schemes should only be available on the root #[cfg(all(feature = "acpi", target_arch = "x86_64"))] { - self.insert(ns, "kernel/acpi", |_| Arc::new(AcpiScheme::new())).unwrap(); + self.insert(ns, "kernel/acpi", |scheme_id| Arc::new(AcpiScheme::new(scheme_id))).unwrap(); } self.insert(ns, "debug", |scheme_id| Arc::new(DebugScheme::new(scheme_id))).unwrap(); self.insert(ns, "initfs", |_| Arc::new(InitFsScheme::new())).unwrap(); diff --git a/src/sync/wait_condition.rs b/src/sync/wait_condition.rs index 2865bd3..8f6566e 100644 --- a/src/sync/wait_condition.rs +++ b/src/sync/wait_condition.rs @@ -10,7 +10,7 @@ pub struct WaitCondition { } impl WaitCondition { - pub fn new() -> WaitCondition { + pub const fn new() -> WaitCondition { WaitCondition { contexts: Mutex::new(Vec::new()) }