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