From d1ece2c811a689a2671bcd54fb3c30a506822152 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 12 Apr 2020 00:50:47 +0200 Subject: [PATCH] Add a basic acpi: scheme, currently only for MCFG. --- src/acpi/mod.rs | 6 +- src/scheme/acpi.rs | 362 +++++++++++++++++++++++++++++++++++++++++++++ src/scheme/mod.rs | 10 ++ 3 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 src/scheme/acpi.rs diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs index 96f50a11..5f507b22 100644 --- a/src/acpi/mod.rs +++ b/src/acpi/mod.rs @@ -33,7 +33,7 @@ mod dmar; mod fadt; pub mod madt; mod rsdt; -mod sdt; +pub mod sdt; mod xsdt; pub mod aml; mod rxsdt; @@ -42,7 +42,7 @@ mod rsdp; const TRAMPOLINE: usize = 0x7E00; const AP_STARTUP: usize = TRAMPOLINE + 512; -fn get_sdt(sdt_address: usize, active_table: &mut ActivePageTable) -> &'static Sdt { +pub fn get_sdt(sdt_address: usize, active_table: &mut ActivePageTable) -> &'static Sdt { { let page = Page::containing_address(VirtualAddress::new(sdt_address)); if active_table.translate_page(page).is_none() { @@ -193,7 +193,7 @@ pub fn set_global_s_state(state: u8) { } } -type SdtSignature = (String, [u8; 6], [u8; 8]); +pub type SdtSignature = (String, [u8; 6], [u8; 8]); pub static SDT_POINTERS: RwLock<Option<BTreeMap<SdtSignature, &'static Sdt>>> = RwLock::new(None); pub static SDT_ORDER: RwLock<Option<Vec<SdtSignature>>> = RwLock::new(None); diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs new file mode 100644 index 00000000..93a9a19e --- /dev/null +++ b/src/scheme/acpi.rs @@ -0,0 +1,362 @@ +use core::convert::{TryFrom, TryInto}; +use core::sync::atomic::{self, AtomicUsize}; +use core::fmt::Write; +use core::str; + +use alloc::vec::Vec; +use alloc::collections::BTreeMap; + +use syscall::scheme::Scheme; +use syscall::data::Stat; +use syscall::flag::{O_DIRECTORY, O_STAT, O_ACCMODE, O_WRONLY, O_RDWR}; +use syscall::error::{EACCES, EBADF, EBADFD, EISDIR, EINVAL, EIO, ENOENT, ENOTDIR}; +use syscall::{MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET}; +use syscall::{Error, Result}; + +use spin::{Mutex, RwLock}; + +use crate::acpi::SdtSignature; +use crate::acpi::sdt::Sdt; +use crate::paging::ActivePageTable; + +#[derive(Clone, Copy)] +struct PhysSlice { + phys_ptr: usize, + len: usize, + /// These appear to be identity mapped, so this is technically not needed. + virt: usize, +} + +/// A scheme used to access ACPI tables needed for some drivers to function (e.g. pcid with the +/// PCIe "MCFG" table). +/// +/// # Layout +/// * `/tables` +/// * _can be listed to retrieve the available tables_ +/// * e.g. MCFG-<OEM ID in hex>-<OEM TABLE ID in hex> +/// * _maybe_ the MADT, in case some userspace driver takes care of the I/O APIC. +/// * _perhaps_ some interface for e.g. power management. +pub struct AcpiScheme { + handles: RwLock<BTreeMap<usize, Mutex<Handle>>>, + tables: Vec<(SdtSignature, PhysSlice)>, + next_fd: AtomicUsize, +} + +const TOPLEVEL_DIR_CONTENTS: &[u8] = b"tables\n"; +const ALLOWED_TABLES: &[[u8; 4]] = &[*b"MCFG"]; + +// XXX: Why can't core also have something like std::io::Take? It's not even real I/O! +/// An internal wrapper struct that limits the number of bytes that can be fmt-written, in order to +/// properly return the length when reading directories etc. The bytes that cannot be written will +/// be discarded. +struct Take<'a> { + buf: &'a mut [u8], + offset: usize, +} + +impl Take<'_> { + pub fn write_to_buf<'a>(buf: &'a mut [u8]) -> Take<'a> { + Take { + offset: 0, + buf, + } + } + pub fn bytes_currently_written(&self) -> usize { + self.offset + } +} + +impl<'a> core::fmt::Write for Take<'a> +{ + fn write_str(&mut self, string: &str) -> core::fmt::Result { + if self.offset > self.buf.len() { return Ok(()) } + + let string_bytes = string.as_bytes(); + let max = core::cmp::min(string_bytes.len() + self.offset, self.buf.len()) - self.offset; + self.buf[self.offset..self.offset + max].copy_from_slice(&string_bytes[..max]); + self.offset += max; + Ok(()) + } +} + +enum Handle { + TopLevel(usize), // seek offset + Tables(usize), // seek offset + + Table { + name: [u8; 4], + oem_id: [u8; 6], + oem_table_id: [u8; 8], + + offset: usize, // seek offset + }, +} + +impl AcpiScheme { + fn get_tables() -> Vec<(SdtSignature, PhysSlice)> { + let mut active_table = unsafe { ActivePageTable::new() }; + + let mut tables = Vec::new(); + + for allowed_tbl_name in ALLOWED_TABLES.iter() { + use crate::acpi::{find_sdt, get_sdt, get_sdt_signature}; + + // it appears that the SDTs are identity mapped, in which case we can just call get_sdt + // whenever we need to and use the slice as if it was physical. + + let table_name_str = str::from_utf8(allowed_tbl_name).expect("ACPI table name wasn't correct UTF-8"); + + for sdt in find_sdt(table_name_str) { + let virt = get_sdt(sdt as *const Sdt as usize, &mut active_table) as *const Sdt as usize; + let signature = get_sdt_signature(sdt); + let sdt_pointer = sdt as *const Sdt as usize; + let len = sdt.length as usize; + assert_eq!(virt, sdt_pointer); + tables.push((signature, PhysSlice { phys_ptr: sdt_pointer, len, virt })); + } + } + tables + } + pub fn new() -> Self { + Self { + handles: RwLock::new(BTreeMap::new()), + tables: Self::get_tables(), + next_fd: AtomicUsize::new(0), + } + } + fn lookup_signature_index(&self, name: [u8; 4], oem_id: [u8; 6], oem_table_id: [u8; 8]) -> Option<usize> { + self.tables.iter().position(|((sig_name, sig_oem_id, sig_oem_table_id), _)| sig_name.as_bytes() == &name && sig_oem_id == &oem_id && sig_oem_table_id == &oem_table_id) + } + fn lookup_signature(&self, name: [u8; 4], oem_id: [u8; 6], oem_table_id: [u8; 8]) -> Option<PhysSlice> { + Some(self.tables[self.lookup_signature_index(name, oem_id, oem_table_id)?].1) + } +} + +fn parse_table_filename(filename: &[u8]) -> Option<([u8; 4], [u8; 6], [u8; 8])> { + // the table identifier takes the form: + // 1. a four byte table name, like 'APIC' (MADT) or 'MCFG'. + // 2. a dash followed by 12 hexadecimal digits (6 bytes when decoded) composing the OEM ID. + // 3. another dash followed by 16 hex digits (8 bytes), composing the OEM Table ID. + // hence, the table is 4 + 1 + 12 + 1 + 16 = 34 bytes long. + if filename.len() != 34 { return None } + let mut table_identifier = [0u8; 34]; + table_identifier.copy_from_slice(filename); + + let table_name = &table_identifier[..4]; + if table_identifier[4] != b'-' { return None } + let oem_id_hex = &table_identifier[5..17]; + if table_identifier[17] != b'-' { return None } + let oem_table_id_hex = &table_identifier[18..34]; + + let oem_id_hex_str = str::from_utf8(oem_id_hex).ok()?; + let oem_table_id_hex_str = str::from_utf8(oem_table_id_hex).ok()?; + + let mut oem_id = [0u8; 6]; + + for index in 0..oem_id.len() { + oem_id[index] = u8::from_str_radix(&oem_id_hex_str[index * 2..(index + 1) * 2], 16).ok()?; + } + + let mut oem_table_id = [0u8; 8]; + + for index in 0..oem_table_id.len() { + oem_table_id[index] = u8::from_str_radix(&oem_table_id_hex_str[index * 2..(index + 1) * 2], 16).ok()?; + } + + Some((table_name.try_into().unwrap(), oem_id, oem_table_id)) +} +fn serialize_table_filename(buffer: &mut [u8], (table_name, oem_id, oem_table_id): ([u8; 4], [u8; 6], [u8; 8])) -> usize { + let mut wrapper = Take::write_to_buf(buffer); + write!(wrapper, "{}-", str::from_utf8(&table_name).expect("Acpi table id wasn't valid UTF-8")).unwrap(); + for b in &oem_id { + write!(wrapper, "{:2x}", b).unwrap(); + } + write!(wrapper, "-").unwrap(); + for b in &oem_table_id { + write!(wrapper, "{:2x}", b).unwrap(); + } + wrapper.bytes_currently_written() +} + +impl Scheme for AcpiScheme { + fn open(&self, path: &[u8], flags: usize, opener_uid: u32, _opener_gid: u32) -> Result<usize> { + if opener_uid != 0 { return Err(Error::new(EACCES)) } + + let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?; + let path_str = path_str.trim_start_matches('/'); + + // TODO: Use some kind of component iterator. + + let new_handle = if path_str.starts_with("tables") { + let subpath = (&path_str[6..]).trim_start_matches('/'); + + if subpath.is_empty() { + // List of ACPI tables + if (flags & O_DIRECTORY == 0 && flags & O_STAT == 0) || (flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR) { + return Err(Error::new(EISDIR)); + } + Handle::Tables(0) + } else { + if (flags & O_DIRECTORY != 0 && flags & O_STAT == 0) { + return Err(Error::new(ENOTDIR)); + } + if flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR { + return Err(Error::new(EINVAL)); + } + let (name, oem_id, oem_table_id) = parse_table_filename(subpath.as_bytes()).ok_or(Error::new(ENOENT))?; + + if self.lookup_signature_index(name, oem_id, oem_table_id).is_none() { + return Err(Error::new(ENOENT)); + } + Handle::Table { + name, + oem_id, + oem_table_id, + offset: 0, + } + } + } else if path.is_empty() { + // Top-level + if (flags & O_DIRECTORY == 0 && flags & O_STAT == 0) || (flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR) { + return Err(Error::new(EISDIR)); + } + Handle::TopLevel(0) + } else { + return Err(Error::new(ENOENT)); + }; + let new_fd = self.next_fd.fetch_add(1, atomic::Ordering::SeqCst); + self.handles.write().insert(new_fd, Mutex::new(new_handle)); + Ok(new_fd) + } + fn fpath(&self, id: usize, buf: &mut [u8]) -> Result<usize> { + let handles_guard = self.handles.read(); + let handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + Ok(match &*handle { + &Handle::TopLevel(_) => { + let path = b"acpi:"; + let max = core::cmp::min(buf.len(), path.len()); + buf[..max].copy_from_slice(&path[..]); + max + } + &Handle::Tables(_) => { + let path = b"acpi:tables"; + let max = core::cmp::min(buf.len(), path.len()); + buf[..max].copy_from_slice(&path[..]); + max + } + &Handle::Table { name, oem_id, oem_table_id, .. } => { + let base_path = b"acpi:tables/"; + let base_max = core::cmp::min(buf.len(), base_path.len()); + buf[..base_max].copy_from_slice(&base_path[..]); + serialize_table_filename(&mut buf[base_max..], (name, oem_id, oem_table_id)) + } + }) + } + fn fstat(&self, id: usize, stat: &mut Stat) -> Result<usize> { + let handles_guard = self.handles.read(); + let handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + match &*handle { + &Handle::TopLevel(_) => { + stat.st_mode = MODE_DIR; + stat.st_size = TOPLEVEL_DIR_CONTENTS.len() as u64; + } + &Handle::Tables(_) => { + stat.st_mode = MODE_DIR; + stat.st_size = (self.tables.len() * 35) as u64; // fixed size of 34 bytes for the file names, plus a newline + } + &Handle::Table { name, oem_id, oem_table_id, .. } => { + let len = self.lookup_signature(name, oem_id, oem_table_id).ok_or(Error::new(EBADFD))?.len; + + stat.st_mode = MODE_FILE; + stat.st_size = len as u64; + } + } + Ok(0) + } + fn seek(&self, id: usize, pos: usize, whence: usize) -> Result<usize> { + let handles_guard = self.handles.read(); + let mut handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + let (cur_offset, length) = match &*handle { + &Handle::TopLevel(offset) => (offset, TOPLEVEL_DIR_CONTENTS.len()), + &Handle::Tables(offset) => (offset, self.tables.len() * 35), + &Handle::Table { name, oem_id, oem_table_id, offset } => (offset, self.lookup_signature(name, oem_id, oem_table_id).ok_or(Error::new(EBADFD))?.len), + }; + let new_offset = match whence { + SEEK_CUR => core::cmp::min(cur_offset + pos, length), + SEEK_END => core::cmp::min(length + pos, length), + SEEK_SET => core::cmp::min(length, pos), + _ => return Err(Error::new(EINVAL)), + }; + match &mut *handle { + &mut Handle::Table { ref mut offset, .. } | &mut Handle::Tables(ref mut offset) | &mut Handle::TopLevel(ref mut offset) => *offset = new_offset, + } + Ok(new_offset) + } + fn read(&self, id: usize, mut buf: &mut [u8]) -> Result<usize> { + let handles_guard = self.handles.read(); + let mut handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + match &mut *handle { + &mut Handle::TopLevel(ref mut offset) => { + let max_bytes_to_read = core::cmp::min(buf.len(), TOPLEVEL_DIR_CONTENTS.len()); + let bytes_to_read = core::cmp::max(max_bytes_to_read, *offset) - *offset; + buf[..bytes_to_read].copy_from_slice(&TOPLEVEL_DIR_CONTENTS[*offset..*offset + bytes_to_read]); + *offset += bytes_to_read; + Ok(bytes_to_read) + } + &mut Handle::Tables(ref mut offset) => { + if *offset >= self.tables.len() * 35 { + return Ok(0); + } + // one really good thing with fixed size filenames, is that no index has to be + // stored anywhere! + let base_table_index = *offset / 35; + let mut bytes_to_skip = *offset % 35; + let mut bytes_read = 0; + + for index in base_table_index..self.tables.len() { + let &(ref name_string, oem_id, oem_table_id) = &self.tables[index].0; + let signature = (name_string.as_bytes().try_into().or(Err(Error::new(EIO)))?, oem_id, oem_table_id); + + let mut src_buf = [0u8; 35]; + serialize_table_filename(&mut src_buf[..34], signature); + src_buf[34] = b'\n'; + + let max_bytes_to_read = core::cmp::min(buf.len(), src_buf.len()); + let bytes_to_read = core::cmp::max(max_bytes_to_read, bytes_to_skip) - bytes_to_skip; + buf[..bytes_to_read].copy_from_slice(&src_buf[..bytes_to_read]); + bytes_read += bytes_to_read; + bytes_to_skip = 0; + buf = &mut buf[..bytes_to_read]; + } + *offset += bytes_read; + Ok(bytes_read) + } + &mut Handle::Table { + name, + oem_id, + oem_table_id, + ref mut offset, + } => { + let index = self.lookup_signature_index(name, oem_id, oem_table_id).ok_or(Error::new(EBADFD))?; + let (_, PhysSlice { phys_ptr, len, virt: old_virt }) = self.tables[index]; + assert_eq!(phys_ptr, old_virt); + let new_virt = crate::acpi::get_sdt(phys_ptr, unsafe { &mut ActivePageTable::new() }) as *const Sdt as usize; + + let table_contents = unsafe { core::slice::from_raw_parts(new_virt as *const u8, len) }; + + let max_bytes_to_read = core::cmp::min(buf.len(), table_contents.len()); + let bytes_to_read = core::cmp::max(max_bytes_to_read, *offset) - *offset; + buf[..bytes_to_read].copy_from_slice(&table_contents[*offset..*offset + bytes_to_read]); + *offset += bytes_to_read; + Ok(bytes_to_read) + } + } + } + fn write(&self, id: usize, buf: &[u8]) -> Result<usize> { + Err(Error::new(EBADF)) + } +} diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index e539de94..5f921071 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -16,6 +16,9 @@ use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::syscall::error::*; use crate::syscall::scheme::Scheme; +#[cfg(feature = "acpi")] +use self::acpi::AcpiScheme; + use self::debug::DebugScheme; use self::event::EventScheme; use self::initfs::InitFsScheme; @@ -28,6 +31,10 @@ use self::root::RootScheme; use self::sys::SysScheme; use self::time::TimeScheme; +/// When compiled with the "acpi" feature - `acpi:` - allows drivers to read a limited set of ACPI tables. +#[cfg(feature = "acpi")] +pub mod acpi; + /// `debug:` - provides access to serial console pub mod debug; @@ -136,6 +143,9 @@ impl SchemeList { let ns = self.new_ns(); // These schemes should only be available on the root + #[cfg(feature = "acpi")] { + self.insert(ns, Box::new(*b"acpi"), |_| Arc::new(AcpiScheme::new())).unwrap(); + } self.insert(ns, Box::new(*b"debug"), |scheme_id| Arc::new(DebugScheme::new(scheme_id))).unwrap(); self.insert(ns, Box::new(*b"initfs"), |_| Arc::new(InitFsScheme::new())).unwrap(); self.insert(ns, Box::new(*b"irq"), |scheme_id| Arc::new(IrqScheme::new(scheme_id))).unwrap(); -- GitLab