From be10f3bc5bf1febfd2808c3f3dbaf0f7c13b28b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 21:27:12 +0000 Subject: [PATCH 1/9] The AML interpreter, from the ACPI 6.5 specification, pure and host-tested MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit userland/acpiserver/aml (package toyos-aml) loads a machine's DSDT and SSDTs into one namespace and evaluates its objects: the interpreter stage of issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md. It is written from the ACPI Specification 6.5 alone (chapter 20's grammar, §5.3-5.5's namespace, loading and method execution, §19.3.5's conversions and §19.6's operators), cited by section at each rule. It sits inside the ACPI server, its first user by the track, as the compositor's desktop and the soundserver's mixer sit inside theirs; the server itself is stage 1's, on its own branch. A definition block is interpreted as it is read, at load as a method is at its invocation (§5.4.2), so a name in an argument position is resolved when reached and a method invocation takes the arguments its method declares. Hardware is reached only through the caller's Host: SystemMemory, SystemIO, PCI_Config (by _ADR, _BBN and _SEG) and EmbeddedControl. Every evaluation is bounded in steps, nesting, object size and time asked to sleep, and malformed bytes are refused by name. _OSI answers as the owner ruled, like Windows: yes to every Windows version string Microsoft publishes, no to anything else. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C9qc7GuMaHZ9xhKsZ57RVz --- userland/Cargo.lock | 4 + userland/Cargo.toml | 1 + userland/acpiserver/aml/Cargo.toml | 12 + userland/acpiserver/aml/src/exec.rs | 1708 +++++++++++++++++++ userland/acpiserver/aml/src/field.rs | 357 ++++ userland/acpiserver/aml/src/lib.rs | 312 ++++ userland/acpiserver/aml/src/name.rs | 87 + userland/acpiserver/aml/src/namespace.rs | 195 +++ userland/acpiserver/aml/src/object.rs | 273 +++ userland/acpiserver/aml/src/stream.rs | 154 ++ userland/acpiserver/aml/tests/common/mod.rs | 420 +++++ userland/acpiserver/aml/tests/evaluate.rs | 491 ++++++ userland/acpiserver/aml/tests/hostile.rs | 238 +++ userland/acpiserver/aml/tests/namespace.rs | 274 +++ userland/acpiserver/aml/tests/regions.rs | 325 ++++ 15 files changed, 4851 insertions(+) create mode 100644 userland/acpiserver/aml/Cargo.toml create mode 100644 userland/acpiserver/aml/src/exec.rs create mode 100644 userland/acpiserver/aml/src/field.rs create mode 100644 userland/acpiserver/aml/src/lib.rs create mode 100644 userland/acpiserver/aml/src/name.rs create mode 100644 userland/acpiserver/aml/src/namespace.rs create mode 100644 userland/acpiserver/aml/src/object.rs create mode 100644 userland/acpiserver/aml/src/stream.rs create mode 100644 userland/acpiserver/aml/tests/common/mod.rs create mode 100644 userland/acpiserver/aml/tests/evaluate.rs create mode 100644 userland/acpiserver/aml/tests/hostile.rs create mode 100644 userland/acpiserver/aml/tests/namespace.rs create mode 100644 userland/acpiserver/aml/tests/regions.rs diff --git a/userland/Cargo.lock b/userland/Cargo.lock index 18acfe2e4a2..34f3286ee11 100644 --- a/userland/Cargo.lock +++ b/userland/Cargo.lock @@ -4172,6 +4172,10 @@ dependencies = [ name = "toyos-abi" version = "0.16.0" +[[package]] +name = "toyos-aml" +version = "0.1.0" + [[package]] name = "toyos-blockhold" version = "0.1.0" diff --git a/userland/Cargo.toml b/userland/Cargo.toml index 55a41520e05..2762836c562 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "acpiserver/aml", "calc", "compositor", "compositor/desktop", diff --git a/userland/acpiserver/aml/Cargo.toml b/userland/acpiserver/aml/Cargo.toml new file mode 100644 index 00000000000..3664c404c3a --- /dev/null +++ b/userland/acpiserver/aml/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "toyos-aml" +description = "The ACPI Machine Language interpreter, pure: a machine's DSDT and SSDTs into one namespace, its objects evaluated, every access to hardware through the caller, and a refusal by name for anything malformed." +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" + +[lib] +doctest = false + +[lints.rust] +warnings = "deny" diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs new file mode 100644 index 00000000000..17fda394642 --- /dev/null +++ b/userland/acpiserver/aml/src/exec.rs @@ -0,0 +1,1708 @@ +//! The interpreter proper: a definition block's term list run at load, and a +//! control method's run when invoked, by one walk over the bytes (§5.4.2: +//! "the interpretation of the definition block during the definition block +//! loading is similar to the interpretation of the control method"). +//! +//! Terms are interpreted as they are read, never parsed ahead: a name in an +//! argument position is resolved when it is reached, so a method invocation +//! takes as many arguments as the method it names declares, and the body of +//! an If not taken is skipped by its PkgLength unread. +//! +//! Every evaluation is bounded: in steps, in nesting (terms, invocations and +//! field accesses together, each a frame of this walk), in the size of any +//! object, and in time asked to sleep. A bound reached is a refusal. + +use alloc::rc::Rc; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; +use core::cell::{Cell, RefCell}; +use core::cmp::Ordering; + +use crate::field::{flags, BufField, Field, Kind, Region}; +use crate::name::{text, Path, Seg}; +use crate::namespace::{Namespace, NodeId}; +use crate::object::{ + copy, decimal, fit, hex2, joined, to_buf, to_int, to_str, Body, Method, Mutex, Object, Ref, Slot, Width, +}; +use crate::stream::{starts_name, Cursor}; +use crate::{Error, Host, MAX_BYTES, MAX_DEPTH, MAX_STEPS, MAX_WAIT_US, REVISION, WINDOWS}; + +pub(crate) struct Machine<'a> { + pub(crate) ns: &'a mut Namespace, + pub(crate) host: &'a mut dyn Host, + pub(crate) w: Width, + steps: u64, + depth: u32, + waited_us: u64, + /// Every Mutex acquire not yet released, in order. + held: Vec>, + /// The SyncLevel of each held Mutex and running Serialized method, in + /// order; the last is the current level (§19.6.88). + levels: Vec, + global: u32, +} + +pub(crate) struct Frame { + locals: [Slot; 8], + args: Vec, + scope: NodeId, + table: Rc<[u8]>, + /// Objects this frame created (§5.5.2.3), destroyed when a method exits. + pub(crate) created: Vec, + held: usize, +} + +pub(crate) enum Flow { + Next, + Break, + Continue, + Return(Object), +} + +enum Target { + None, + Debug, + Local(usize), + Arg(usize), + Node(NodeId), + Ref(Ref), +} + +fn slot(o: Object) -> Slot { + Rc::new(RefCell::new(o)) +} + +impl Frame { + pub(crate) fn new(scope: NodeId, args: Vec, table: Rc<[u8]>, held: usize) -> Frame { + Frame { + locals: core::array::from_fn(|_| slot(Object::Uninit)), + args: args.into_iter().map(slot).collect(), + scope, + table, + created: Vec::new(), + held, + } + } +} + +fn type_name(code: u64) -> &'static [u8] { + match code { + 4 => b"[Package]", + 5 => b"[Field]", + 6 => b"[Device]", + 7 => b"[Event]", + 8 => b"[Control Method]", + 9 => b"[Mutex]", + 10 => b"[Operation Region]", + 11 => b"[Power Resource]", + 13 => b"[Thermal Zone]", + 14 => b"[Buffer Field]", + 16 => b"[Debug Object]", + _ => b"[Uninitialized Object]", + } +} + +fn bounded(len: usize) -> Result<(), Error> { + if len > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } +} + +/// A NameString written as ASL text, for DerefOf of a String (§19.6.30). +fn path_of_text(s: &[u8]) -> Result { + let bad = Error::Rule("DerefOf of a String that is not a name (§19.6.30)"); + let mut rest = s; + let mut root = false; + let mut up = 0; + if let [b'\\', r @ ..] = rest { + root = true; + rest = r; + } + while let [b'^', r @ ..] = rest { + up += 1; + rest = r; + } + let mut segs = Vec::new(); + if !rest.is_empty() { + for part in rest.split(|&c| c == b'.') { + if part.is_empty() || part.len() > 4 { + return Err(bad); + } + let mut seg = [b'_'; 4]; + seg[..part.len()].copy_from_slice(part); + segs.push(Seg::new(seg).ok_or(Error::Rule("DerefOf of a String that is not a name (§19.6.30)"))?); + } + } + Ok(Path { root, up, segs }) +} + +impl<'a> Machine<'a> { + pub(crate) fn new(ns: &'a mut Namespace, host: &'a mut dyn Host, w: Width) -> Self { + Machine { ns, host, w, steps: 0, depth: 0, waited_us: 0, held: Vec::new(), levels: Vec::new(), global: 0 } + } + + pub(crate) fn step(&mut self) -> Result<(), Error> { + self.steps += 1; + if self.steps > MAX_STEPS { Err(Error::Bound("more steps than one evaluation may take")) } else { Ok(()) } + } + + pub(crate) fn enter(&mut self) -> Result<(), Error> { + if self.depth >= MAX_DEPTH { + return Err(Error::Bound("terms, invocations and field accesses nest deeper than this interpreter goes")); + } + self.depth += 1; + Ok(()) + } + + pub(crate) fn leave(&mut self) { + self.depth -= 1; + } + + /// Ends an evaluation: whatever it still holds is let go, and holding + /// anything at its end is itself a refusal (§19.6.88: "the top-level + /// control method cannot exit while still holding ownership of a Mutex"). + pub(crate) fn finish(mut self, r: Result) -> Result { + let held = !self.held.is_empty(); + for m in self.held.drain(..) { + m.held.set(0); + } + self.levels.clear(); + let mut released = Ok(()); + if self.global > 0 { + self.global = 0; + released = self.host.global_lock(false).map_err(|d| Error::Host(d.0)); + } + let r = r?; + released?; + if held { + return Err(Error::Rule("an evaluation ends holding a Mutex (§19.6.88)")); + } + Ok(r) + } + + pub(crate) fn take_global(&mut self) -> Result<(), Error> { + if self.global == 0 { + self.host.global_lock(true).map_err(|d| Error::Host(d.0))?; + } + self.global += 1; + Ok(()) + } + + pub(crate) fn drop_global(&mut self) -> Result<(), Error> { + self.global = self.global.saturating_sub(1); + if self.global == 0 { + self.host.global_lock(false).map_err(|d| Error::Host(d.0))?; + } + Ok(()) + } + + fn wait(&mut self, us: u64) -> Result<(), Error> { + self.waited_us = self.waited_us.saturating_add(us); + if self.waited_us > MAX_WAIT_US { + return Err(Error::Bound("more time asleep than one evaluation may spend")); + } + Ok(()) + } + + fn define(&mut self, f: &mut Frame, p: &Path, o: Object) -> Result { + let id = self.ns.create(f.scope, p, o)?; + f.created.push(id); + Ok(id) + } + + fn resolve(&self, f: &Frame, p: &Path) -> Result { + self.ns.resolve(f.scope, p).ok_or_else(|| Error::NotFound(text(p))) + } + + fn node_object(&self, id: NodeId) -> Result { + self.ns.object(id).cloned().ok_or_else(|| Error::NotFound(String::from("an object its method's exit destroyed"))) + } + + /// The integer a named child of `scope` evaluates to, if it exists. + pub(crate) fn named_int(&mut self, scope: NodeId, seg: Seg) -> Result, Error> { + let Some(id) = self.ns.child(scope, seg) else { return Ok(None) }; + let v = match self.node_object(id)? { + Object::Method(m) if m.args == 0 => self.invoke(id, m, Vec::new())?, + _ => self.node_value(id)?, + }; + to_int(&v, self.w).map(Some) + } + + pub(crate) fn evaluate(&mut self, id: NodeId, args: Vec) -> Result { + match self.node_object(id)? { + Object::Method(m) => { + if args.len() != usize::from(m.args) { + return Err(Error::Rule("an evaluation passes a method another number of arguments than it declares")); + } + self.invoke(id, m, args) + } + _ if !args.is_empty() => Err(Error::Rule("an evaluation passes arguments to an object that is not a method")), + _ => self.node_value(id), + } + } + + /// What a named object evaluates to in an argument position: a field is + /// read, data is itself, anything else is a reference to it (§19.6.101: + /// "Named References to non-Data Objects ... are instead returned ... as + /// references"). + fn node_value(&mut self, id: NodeId) -> Result { + Ok(match self.node_object(id)? { + Object::Field(f) => self.read_field(&f)?, + Object::BufField(b) => self.read_buf_field(&b)?, + o @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_) | Object::Ref(_)) => o, + _ => Object::Ref(Ref::Node(id)), + }) + } + + /// The object a reference refers to (§19.6.30). + fn deref(&mut self, r: &Ref) -> Result { + match r { + Ref::Node(id) => self.node_value(*id), + Ref::Slot(s) => Ok(s.borrow().clone()), + Ref::Elem(p, i) => { + let e = p.borrow().get(*i).cloned().ok_or(Error::Rule("an Index reference past its package's end"))?; + match e { + Object::Uninit => Err(Error::Rule("DerefOf an uninitialized package element (§19.6.30)")), + Object::Lazy(l) => self.lazy(&l), + e => Ok(e), + } + } + Ref::BufField(b) => self.read_buf_field(b), + } + } + + /// A package element a name gave (§19.6.101): data is resolved to its + /// value, anything else is a reference; a name not yet defined is + /// resolved when read. + fn element(&mut self, scope: NodeId, p: &Path) -> Result { + match self.ns.resolve(scope, p) { + Some(id) => Ok(match self.node_value(id)? { + d @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_)) => copy(&d)?, + o => o, + }), + None => Ok(Object::Lazy(Rc::new((p.clone(), scope)))), + } + } + + fn lazy(&mut self, l: &(Path, NodeId)) -> Result { + match self.element(l.1, &l.0)? { + Object::Lazy(_) => Err(Error::NotFound(text(&l.0))), + o => Ok(o), + } + } + + pub(crate) fn resolve_lazy(&mut self, o: Object) -> Result { + match o { + Object::Lazy(l) => self.lazy(&l), + o => Ok(o), + } + } + + // ---- invocation ------------------------------------------------------- + + pub(crate) fn invoke(&mut self, node: NodeId, m: Rc, args: Vec) -> Result { + self.enter()?; + let r = self.invoke_in(node, &m, args); + self.leave(); + r + } + + fn invoke_in(&mut self, node: NodeId, m: &Method, args: Vec) -> Result { + let (table, start, end) = match &m.body { + Body::Osi => return self.osi(args), + Body::Aml { table, start, end } => (table.clone(), *start, *end), + }; + if m.serialized { + if self.levels.last().is_some_and(|&l| m.sync < l) { + return Err(Error::Rule("a Serialized method's SyncLevel is below the current level (§19.6.88)")); + } + self.levels.push(m.sync); + } + let mut f = Frame::new(node, args, table.clone(), self.held.len()); + let mut c = Cursor::new(&table, start, end); + let flow = self.term_list(&mut f, &mut c); + for &id in f.created.iter().rev() { + self.ns.remove(id); + } + if m.serialized { + self.levels.pop(); + } + let flow = flow?; + if self.held.len() > f.held { + return Err(Error::Rule("a method exits holding a Mutex it acquired (§19.6.88)")); + } + match flow { + Flow::Next => Ok(Object::Uninit), + Flow::Return(v) => Ok(v), + Flow::Break | Flow::Continue => Err(Error::Rule("a Break or Continue outside any While (§19.6.8, §19.6.16)")), + } + } + + /// `\_OSI` (§5.7.2), answered as the owner ruled: yes to every Windows + /// version string Microsoft publishes, no to anything else. + fn osi(&mut self, args: Vec) -> Result { + let s = match args.first() { + Some(Object::Str(s)) => s.borrow().clone(), + _ => return Err(Error::Type("_OSI's argument is not a String (§5.7.2)")), + }; + Ok(Object::Int(self.w.bool(WINDOWS.iter().any(|w| w.as_bytes() == s.as_slice())))) + } + + // ---- term lists ------------------------------------------------------- + + pub(crate) fn term_list(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + while !c.done() { + match self.term(f, c)? { + Flow::Next => {} + other => return Ok(other), + } + } + Ok(Flow::Next) + } + + fn term(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + self.step()?; + self.enter()?; + let r = self.term_in(f, c); + self.leave(); + r + } + + fn term_in(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let op = c.peek()?; + if starts_name(op) { + // MethodInvocation (§20.2.5): a name alone in a term list. + let p = c.name()?; + let id = self.resolve(f, &p)?; + let Object::Method(m) = self.node_object(id)? else { + return Err(c.malformed("a name in a term list names no method (§20.2.5)")); + }; + let args = self.call_args(f, c, &m)?; + self.invoke(id, m, args)?; + return Ok(Flow::Next); + } + match op { + 0x06 => self.def_alias(f, c), + 0x08 => self.def_name(f, c), + 0x10 => self.def_scope(f, c), + 0x14 => self.def_method(f, c), + 0x15 => self.def_external(c), + 0x8A | 0x8B | 0x8C | 0x8D | 0x8F => self.def_create_field(f, c), + 0x86 => self.notify(f, c), + 0xA0 => self.if_else(f, c), + 0xA1 => Err(c.malformed("an Else that follows no If (§20.2.5.3)")), + 0xA2 => self.while_loop(f, c), + 0xA3 | 0xCC => { + // Noop, and BreakPoint, which outside a debugger "is equivalent to Noop" (§19.6.9). + c.byte()?; + Ok(Flow::Next) + } + 0xA4 => { + c.byte()?; + let v = self.arg(f, c)?; + Ok(Flow::Return(copy(&v)?)) + } + 0xA5 => { + c.byte()?; + Ok(Flow::Break) + } + 0x9F => { + c.byte()?; + Ok(Flow::Continue) + } + 0x5B => match c.peek2()? { + 0x01 => self.def_mutex(f, c), + 0x02 => self.def_event(f, c), + 0x13 => self.def_create_field(f, c), + 0x80 => self.def_region(f, c), + 0x81 | 0x86 | 0x87 => self.def_field(f, c), + 0x82 | 0x84 | 0x85 => self.def_scoped(f, c), + 0x83 => Err(c.malformed("ProcessorOp, permanently reserved since ACPI 6.4 (§20.3)")), + 0x88 => Err(Error::Unsupported("DataTableRegion")), + 0x20 => Err(Error::Unsupported("Load")), + 0x21 | 0x22 => self.delay(f, c), + 0x24 | 0x26 | 0x27 => self.sync_statement(f, c), + 0x32 => self.fatal(f, c), + 0x12 | 0x1F | 0x23 | 0x25 | 0x28 | 0x29 | 0x33 => self.expr(f, c).map(|_| Flow::Next), + _ => Err(c.malformed("not a term (§20.2.5)")), + }, + 0x11..=0x13 | 0x70..=0x85 | 0x87..=0x89 | 0x8E | 0x90..=0x99 | 0x9C..=0x9E => { + self.expr(f, c).map(|_| Flow::Next) + } + _ => Err(c.malformed("not a term (§20.2.5)")), + } + } + + fn call_args(&mut self, f: &mut Frame, c: &mut Cursor<'_>, m: &Method) -> Result, Error> { + (0..m.args).map(|_| self.arg(f, c)).collect() + } + + fn sub<'c>(c: &Cursor<'c>, end: usize) -> Cursor<'c> { + Cursor::new(c.bytes, c.at, end) + } + + fn if_else(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let end = c.pkg_end()?; + let mut body = Self::sub(c, end); + let taken = self.int_arg(f, &mut body)? != 0; + let flow = if taken { self.term_list(f, &mut body)? } else { Flow::Next }; + c.at = end; + if !c.done() && c.peek()? == 0xA1 { + c.byte()?; + let end = c.pkg_end()?; + if !taken { + let mut alt = Self::sub(c, end); + let flow = self.term_list(f, &mut alt)?; + c.at = end; + return Ok(flow); + } + c.at = end; + } + Ok(flow) + } + + fn while_loop(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let end = c.pkg_end()?; + let start = c.at; + loop { + self.step()?; + let mut body = Cursor::new(c.bytes, start, end); + if self.int_arg(f, &mut body)? == 0 { + break; + } + match self.term_list(f, &mut body)? { + Flow::Next | Flow::Continue => {} + Flow::Break => break, + Flow::Return(v) => return Ok(Flow::Return(v)), + } + } + c.at = end; + Ok(Flow::Next) + } + + // ---- named objects (§20.2.5.1, §20.2.5.2) ------------------------------ + + fn def_alias(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let source = c.name()?; + let alias = c.name()?; + let target = self.resolve(f, &source)?; + self.ns.alias(f.scope, &alias, target)?; + Ok(Flow::Next) + } + + fn def_name(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let p = c.name()?; + let v = self.data_object(f, c)?; + self.define(f, &p, v)?; + Ok(Flow::Next) + } + + fn def_scope(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let end = c.pkg_end()?; + let mut body = Self::sub(c, end); + let p = body.name()?; + let id = self.resolve(f, &p)?; + // §19.6.120: a Scope's location is a predefined scope, a Device, a + // Processor, a Thermal Zone or a Power Resource. + if !matches!(self.node_object(id)?, Object::Scope | Object::Device | Object::ThermalZone | Object::PowerResource) { + return Err(Error::Type("a Scope names an object that opens no scope (§19.6.120)")); + } + let flow = self.within(f, id, &mut body)?; + c.at = end; + Ok(flow) + } + + fn within(&mut self, f: &mut Frame, scope: NodeId, body: &mut Cursor<'_>) -> Result { + let outer = core::mem::replace(&mut f.scope, scope); + let flow = self.term_list(f, body); + f.scope = outer; + flow + } + + /// Device, PowerResource and ThermalZone (§20.2.5.2): a named object + /// whose term list runs in its own scope. + fn def_scoped(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let op = c.byte()?; + let end = c.pkg_end()?; + let mut body = Self::sub(c, end); + let p = body.name()?; + let o = match op { + 0x82 => Object::Device, + 0x85 => Object::ThermalZone, + _ => { + // SystemLevel and ResourceOrder, which only OSPM's power + // resource management reads. + body.byte()?; + body.word()?; + Object::PowerResource + } + }; + let id = self.define(f, &p, o)?; + let flow = self.within(f, id, &mut body)?; + c.at = end; + Ok(flow) + } + + fn def_method(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let end = c.pkg_end()?; + let mut head = Self::sub(c, end); + let p = head.name()?; + let flags = head.byte()?; + let m = Method { + body: Body::Aml { table: f.table.clone(), start: head.at, end }, + args: flags & 0x07, + serialized: flags & 0x08 != 0, + sync: flags >> 4, + }; + self.define(f, &p, Object::Method(Rc::new(m)))?; + c.at = end; + Ok(Flow::Next) + } + + /// External (§20.2.5.2) tells a disassembler what another table defines, + /// and defines nothing. + fn def_external(&mut self, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.name()?; + c.byte()?; + if c.byte()? > 7 { + return Err(c.malformed("an External's ArgumentCount is above 7 (§20.2.5.2)")); + } + Ok(Flow::Next) + } + + fn def_mutex(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + let p = c.name()?; + let flags = c.byte()?; + if flags & 0xF0 != 0 { + return Err(c.malformed("a Mutex's SyncFlags sets reserved bits 4-7 (§20.2.5.2)")); + } + self.define(f, &p, Object::Mutex(Rc::new(Mutex { sync: flags, held: Cell::new(0), global: false })))?; + Ok(Flow::Next) + } + + fn def_event(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + let p = c.name()?; + self.define(f, &p, Object::Event(Rc::new(Cell::new(0))))?; + Ok(Flow::Next) + } + + fn def_region(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + let p = c.name()?; + let space = c.byte()?; + // Table 5.182: 0x0C-0x7E are reserved. + if (0x0C..=0x7E).contains(&space) { + return Err(c.malformed("an OperationRegion names a reserved address space (Table 5.182)")); + } + let base = self.int_arg(f, c)?; + let len = self.int_arg(f, c)?; + let r = Region { space, base, len, scope: f.scope, pci: Cell::new(None) }; + self.define(f, &p, Object::Region(Rc::new(r)))?; + Ok(Flow::Next) + } + + fn field_of(&self, f: &Frame, p: &Path) -> Result, Error> { + match self.node_object(self.resolve(f, p)?)? { + Object::Field(x) => Ok(x), + _ => Err(Error::Type("an IndexField's or BankField's register is not a field unit (§19.6.63, §19.6.7)")), + } + } + + fn region_of(&self, f: &Frame, p: &Path) -> Result, Error> { + match self.node_object(self.resolve(f, p)?)? { + Object::Region(r) => Ok(r), + _ => Err(Error::Type("a field's RegionName is not an operation region (§19.6.47)")), + } + } + + /// Field, IndexField and BankField (§20.2.5.2): a FieldList of named + /// units laid out bit after bit. + fn def_field(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let op = c.byte()?; + let end = c.pkg_end()?; + let mut l = Self::sub(c, end); + let kind = match op { + 0x81 => Kind::Region(self.region_of(f, &l.name()?)?), + 0x86 => { + let index = self.field_of(f, &l.name()?)?; + let data = self.field_of(f, &l.name()?)?; + Kind::Index { index, data } + } + _ => { + let region = self.region_of(f, &l.name()?)?; + let bank = self.field_of(f, &l.name()?)?; + let value = self.int_arg(f, &mut l)?; + Kind::Bank { region, bank, value } + } + }; + let (mut access, lock, update) = flags(l.byte()?).map_err(|why| l.malformed(why))?; + let mut bit = 0u64; + while !l.done() { + self.step()?; + match l.peek()? { + 0x00 => { + l.byte()?; + bit = bit.checked_add(l.pkg_value()?.1 as u64).ok_or(l.malformed("a FieldList overflows"))?; + } + 0x01 | 0x03 => { + let ext = l.byte()? == 0x03; + access = flags(l.byte()? & 0x0F).map_err(|why| l.malformed(why))?.0; + l.byte()?; + if ext { + l.byte()?; + } + } + 0x02 => { + // ConnectField: the connection of a GeneralPurposeIO or + // GenericSerialBus field, which this interpreter does not + // access. + l.byte()?; + if l.peek()? == 0x11 { + self.data_object(f, &mut l)?; + } else { + l.name()?; + } + } + _ => { + let seg = l.seg()?; + let len = l.pkg_value()?.1 as u64; + if len == 0 { + return Err(l.malformed("a field unit of zero bits")); + } + let to = bit.checked_add(len).filter(|&e| e <= 1 << 62).ok_or(l.malformed("a FieldList overflows"))?; + let unit = Field { kind: kind.clone(), bit, len, access, lock, update }; + self.define(f, &Path { root: false, up: 0, segs: vec![seg] }, Object::Field(Rc::new(unit)))?; + bit = to; + } + } + } + c.at = end; + Ok(Flow::Next) + } + + /// CreateBitField, CreateByteField, CreateWordField, CreateDWordField, + /// CreateQWordField and CreateField (§19.6.18-23): a field over a buffer, + /// which must hold it whole. + fn def_create_field(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let op = c.byte()?; + let op = if op == 0x5B { c.byte()? } else { op }; + // SourceBuff is evaluated as a buffer (§19.6.18-23): a Buffer is the + // one the field reaches into, anything else converts to a new one. + let data = match self.arg(f, c)? { + Object::Buf(b) => b, + o => crate::object::bytes(to_buf(&o, self.w)?), + }; + let index = self.int_arg(f, c)?; + let (bit, len) = match op { + 0x8D => (Some(index), 1), + 0x8C => (index.checked_mul(8), 8), + 0x8B => (index.checked_mul(8), 16), + 0x8A => (index.checked_mul(8), 32), + 0x8F => (index.checked_mul(8), 64), + _ => { + let n = self.int_arg(f, c)?; + if n == 0 { + return Err(Error::Rule("CreateField of zero bits (§19.6.21)")); + } + (Some(index), n) + } + }; + let p = c.name()?; + let size = (data.borrow().len() as u64).saturating_mul(8); + let bit = bit.filter(|b| b.checked_add(len).is_some_and(|e| e <= size)); + let bit = bit.ok_or(Error::Rule("a buffer field reaches past its buffer (§19.6.18-23)"))?; + self.define(f, &p, Object::BufField(Rc::new(BufField { data, bit, len })))?; + Ok(Flow::Next) + } + + // ---- statements (§20.2.5.3) ------------------------------------------- + + fn notify(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let t = self.super_name(f, c)?; + let v = self.int_arg(f, c)?; + let id = self.node_of(f, &t)?; + // §19.6.94: a device, processor, or thermal zone. + if !matches!(self.node_object(id)?, Object::Device | Object::ThermalZone) { + return Err(Error::Type("Notify of an object that is not a device or thermal zone (§19.6.94)")); + } + let path = self.ns.path_of(id, None); + self.host.notify(&path, v); + Ok(Flow::Next) + } + + fn delay(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let sleep = c.byte()? == 0x22; + let n = self.int_arg(f, c)?; + if sleep { + self.wait(n.saturating_mul(1000))?; + self.host.sleep(n); + } else { + // UsecTime := TermArg => ByteData (§20.2.5.3). + if n > 0xFF { + return Err(Error::Rule("a Stall longer than the 255 µs its ByteData holds (§20.2.5.3)")); + } + self.wait(n)?; + self.host.stall(n); + } + Ok(Flow::Next) + } + + fn fatal(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + let kind = c.byte()?; + let code = c.dword()?; + let arg = self.int_arg(f, c)?; + Err(Error::Fatal { kind, code, arg }) + } + + /// Signal, Reset and Release. + fn sync_statement(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + let op = c.byte()?; + let t = self.super_name(f, c)?; + let id = self.node_of(f, &t)?; + match (op, self.node_object(id)?) { + (0x24, Object::Event(e)) => e.set(e.get().saturating_add(1)), + (0x26, Object::Event(e)) => e.set(0), + (0x27, Object::Mutex(m)) => self.release(&m)?, + _ => return Err(Error::Type("Signal, Reset or Release of an object of the wrong type (§19.6)")), + } + Ok(Flow::Next) + } + + fn acquire(&mut self, m: &Rc) -> Result<(), Error> { + // §19.6.88: an Acquire's SyncLevel is equal to or above the current one. + if m.held.get() == 0 && self.levels.last().is_some_and(|&l| m.sync < l) { + return Err(Error::Rule("Acquire of a Mutex below the current SyncLevel (§19.6.88)")); + } + if m.global && m.held.get() == 0 { + self.take_global()?; + } + m.held.set(m.held.get() + 1); + self.held.push(m.clone()); + self.levels.push(m.sync); + Ok(()) + } + + fn release(&mut self, m: &Rc) -> Result<(), Error> { + if m.held.get() == 0 { + return Err(Error::Rule("Release of a Mutex not held (§19.6.115)")); + } + // §19.6.88: a Release's SyncLevel is the current one. + if self.levels.last() != Some(&m.sync) { + return Err(Error::Rule("Release of a Mutex whose SyncLevel is not the current one (§19.6.88)")); + } + let at = self.held.iter().rposition(|h| Rc::ptr_eq(h, m)).ok_or(Error::Rule("Release of a Mutex not held"))?; + self.held.remove(at); + self.levels.pop(); + m.held.set(m.held.get() - 1); + if m.global && m.held.get() == 0 { + self.drop_global()?; + } + Ok(()) + } + + // ---- arguments -------------------------------------------------------- + + pub(crate) fn int_arg(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let o = self.arg(f, c)?; + to_int(&o, self.w) + } + + /// A TermArg (§20.2.5). + pub(crate) fn arg(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + self.step()?; + self.enter()?; + let r = self.arg_in(f, c); + self.leave(); + r + } + + fn arg_in(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let op = c.peek()?; + if starts_name(op) { + return self.named(f, c); + } + match op { + 0x60..=0x67 => { + c.byte()?; + Ok(f.locals[usize::from(op - 0x60)].borrow().clone()) + } + 0x68..=0x6E => self.read_arg(f, c, usize::from(op - 0x68)), + 0x00 | 0x01 | 0xFF | 0x0A..=0x0E => self.data_object(f, c), + 0x5B if c.peek2()? == 0x30 => self.data_object(f, c), + 0x5B if c.peek2()? == 0x31 => Err(Error::Type("the Debug object is write-only (§19.6.26)")), + _ => self.expr(f, c), + } + } + + /// A name in an argument position: a method it names is invoked. + fn named(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let p = c.name()?; + let id = self.resolve(f, &p)?; + match self.node_object(id)? { + Object::Method(m) => { + let args = self.call_args(f, c, &m)?; + self.invoke(id, m, args) + } + _ => self.node_value(id), + } + } + + fn read_arg(&mut self, f: &mut Frame, c: &mut Cursor<'_>, i: usize) -> Result { + c.byte()?; + let v = f.args.get(i).ok_or(c.malformed("an ArgX past the method's argument count"))?.borrow().clone(); + // Table 19.9: reading an ArgX that holds a reference reads its target. + match v { + Object::Ref(r) => self.deref(&r), + v => Ok(v), + } + } + + /// A DataObject (§20.2.3), as Name defines and a package holds. + fn data_object(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let ones = self.w.ones(); + Ok(match c.byte()? { + 0x00 => Object::Int(0), + 0x01 => Object::Int(1), + 0xFF => Object::Int(ones), + 0x0A => Object::Int(u64::from(c.byte()?)), + 0x0B => Object::Int(u64::from(c.word()?)), + 0x0C => Object::Int(u64::from(c.dword()?) & ones), + 0x0E => Object::Int(c.qword()? & ones), + 0x0D => { + let mut s = Vec::new(); + loop { + match c.byte()? { + 0 => break, + b @ 0x01..=0x7F => s.push(b), + _ => return Err(c.malformed("a String holds a byte AsciiChar does not allow (§20.2.3)")), + } + bounded(s.len())?; + } + Object::str(s) + } + 0x11 => { + let end = c.pkg_end()?; + let mut b = Self::sub(c, end); + let size = self.int_arg(f, &mut b)?; + let size = usize::try_from(size).map_err(|_| Error::Bound("a buffer larger than this interpreter holds"))?; + bounded(size)?; + let mut v = c.bytes.get(b.at..end).ok_or(c.malformed("a Buffer runs past the table"))?.to_vec(); + // §19.6.10: the larger of BufferSize and the initializer's length. + if v.len() < size { + v.resize(size, 0); + } + c.at = end; + Object::buf(v) + } + op @ (0x12 | 0x13) => { + let end = c.pkg_end()?; + let mut p = Self::sub(c, end); + let count = if op == 0x12 { u64::from(p.byte()?) } else { self.int_arg(f, &mut p)? }; + let count = usize::try_from(count).map_err(|_| Error::Bound("a package larger than this interpreter holds"))?; + bounded(count)?; + let mut elems = Vec::new(); + while !p.done() { + self.step()?; + if elems.len() == count { + return Err(p.malformed("a package holds more elements than its NumElements (§19.6.101)")); + } + let e = if starts_name(p.peek()?) { + let path = p.name()?; + self.element(f.scope, &path)? + } else { + self.enter()?; + let e = self.data_object(f, &mut p); + self.leave(); + e? + }; + elems.push(e); + } + elems.resize(count, Object::Uninit); + c.at = end; + Object::Pkg(Rc::new(RefCell::new(elems))) + } + 0x5B if c.byte()? == 0x30 => Object::Int(REVISION), + _ => return Err(Error::Malformed { at: c.at.saturating_sub(1), why: "not a DataObject (§20.2.3)" }), + }) + } + + // ---- targets (§20.2.2) ------------------------------------------------ + + fn super_name(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + match self.super_name_or(f, c)? { + Ok(t) => Ok(t), + Err(p) => Err(Error::NotFound(text(&p))), + } + } + + /// A SuperName, or the name it is when that does not resolve, which + /// CondRefOf answers rather than refuses (§19.6.14). + fn super_name_or(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result, Error> { + let op = c.peek()?; + if starts_name(op) { + let p = c.name()?; + return Ok(self.ns.resolve(f.scope, &p).map(Target::Node).ok_or(p)); + } + Ok(Ok(match op { + 0x60..=0x67 => { + c.byte()?; + Target::Local(usize::from(op - 0x60)) + } + 0x68..=0x6E => { + c.byte()?; + let i = usize::from(op - 0x68); + if i >= f.args.len() { + return Err(c.malformed("an ArgX past the method's argument count")); + } + Target::Arg(i) + } + 0x5B if c.peek2()? == 0x31 => { + c.byte()?; + c.byte()?; + Target::Debug + } + 0x83 => { + // DerefOf as a SuperName names what its operand refers to. + c.byte()?; + match self.arg(f, c)? { + Object::Ref(r) => Target::Ref(r), + Object::Str(s) => { + let p = path_of_text(&s.borrow())?; + Target::Node(self.resolve(f, &p)?) + } + _ => return Err(Error::Type("DerefOf of an object that is not a reference or a name (§19.6.30)")), + } + } + 0x71 | 0x88 => match self.expr(f, c)? { + Object::Ref(r) => Target::Ref(r), + _ => return Err(Error::Type("a SuperName that is not a reference")), + }, + _ => return Err(c.malformed("not a SuperName (§20.2.2)")), + })) + } + + fn target(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + if c.peek()? == 0x00 { + c.byte()?; + return Ok(Target::None); + } + self.super_name(f, c) + } + + /// The node a target names, directly or by a reference it holds. + fn node_of(&self, f: &Frame, t: &Target) -> Result { + let held = match t { + Target::Node(id) => return Ok(*id), + Target::Ref(Ref::Node(id)) => return Ok(*id), + Target::Local(i) => f.locals[*i].borrow().clone(), + Target::Arg(i) => f.args[*i].borrow().clone(), + _ => Object::Uninit, + }; + match held { + Object::Ref(Ref::Node(id)) => Ok(id), + _ => Err(Error::Type("an operand that names no object in the namespace")), + } + } + + /// The object a target is, unread and unconverted, a reference followed + /// once (§19.6.96: "the object type of the base object"). + fn base(&mut self, f: &Frame, t: &Target) -> Result { + let o = match t { + Target::None | Target::Debug => return Err(Error::Type("the Debug object is write-only (§19.6.26)")), + Target::Local(i) => f.locals[*i].borrow().clone(), + Target::Arg(i) => f.args[*i].borrow().clone(), + Target::Node(id) => self.node_object(*id)?, + Target::Ref(r) => Object::Ref(r.clone()), + }; + Ok(match o { + Object::Ref(Ref::Node(id)) => self.node_object(id)?, + Object::Ref(Ref::Slot(s)) => s.borrow().clone(), + Object::Ref(Ref::Elem(p, i)) => { + let e = p.borrow().get(i).cloned().ok_or(Error::Rule("an Index reference past its package's end"))?; + self.resolve_lazy(e)? + } + Object::Ref(Ref::BufField(b)) => Object::BufField(b), + o => o, + }) + } + + /// The value a target holds, read (for Increment and Decrement). + fn read_target(&mut self, f: &Frame, t: &Target) -> Result { + match t { + Target::Local(i) => Ok(f.locals[*i].borrow().clone()), + Target::Arg(i) => { + let v = f.args[*i].borrow().clone(); + match v { + Object::Ref(r) => self.deref(&r), + v => Ok(v), + } + } + Target::Node(id) => self.node_value(*id), + Target::Ref(r) => self.deref(r), + Target::None | Target::Debug => Err(Error::Type("the Debug object is write-only (§19.6.26)")), + } + } + + // ---- store and copy (§19.3.5.8) --------------------------------------- + + /// Store, and every operator's Target: no conversion into a LocalX or an + /// ArgX, which an ArgX holding a reference stores through; conversion to + /// the type a named object already has. + fn store(&mut self, f: &mut Frame, t: Target, v: Object) -> Result<(), Error> { + match t { + Target::None | Target::Debug => Ok(()), + Target::Local(i) => { + let v = copy(&v)?; + *f.locals[i].borrow_mut() = v; + Ok(()) + } + Target::Arg(i) => { + let held = f.args[i].borrow().clone(); + match held { + Object::Ref(r) => self.store_ref(&r, v), + _ => { + let v = copy(&v)?; + *f.args[i].borrow_mut() = v; + Ok(()) + } + } + } + Target::Node(id) => self.store_node(id, v), + Target::Ref(r) => self.store_ref(&r, v), + } + } + + fn store_ref(&mut self, r: &Ref, v: Object) -> Result<(), Error> { + match r { + Ref::Node(id) => self.store_node(*id, v), + Ref::Slot(s) => { + let v = copy(&v)?; + *s.borrow_mut() = v; + Ok(()) + } + Ref::Elem(p, i) => { + let v = copy(&v)?; + let mut p = p.borrow_mut(); + *p.get_mut(*i).ok_or(Error::Rule("an Index reference past its package's end"))? = v; + Ok(()) + } + Ref::BufField(b) => self.write_buf_field(b, v), + } + } + + fn store_node(&mut self, id: NodeId, v: Object) -> Result<(), Error> { + let w = self.w; + match self.node_object(id)? { + Object::Int(_) => self.ns.set(id, Object::Int(to_int(&v, w)?)), + Object::Str(s) => { + let n = to_str(&v, w)?; + *s.borrow_mut() = n; + Ok(()) + } + Object::Buf(b) => { + // Table 19.7: a buffer that exists keeps its size. + let n = to_buf(&v, w)?; + let len = b.borrow().len(); + *b.borrow_mut() = fit(n, len); + Ok(()) + } + Object::Pkg(p) => match copy(&v)? { + Object::Pkg(src) => { + let elems = core::mem::take(&mut *src.borrow_mut()); + *p.borrow_mut() = elems; + Ok(()) + } + _ => Err(Error::Type("a store to a package of an object that is not one (Table 19.6)")), + }, + Object::Field(x) => self.write_field(&x, v), + Object::BufField(b) => self.write_buf_field(&b, v), + Object::Ref(_) => { + let v = copy(&v)?; + self.ns.set(id, v) + } + _ => Err(Error::Type("a store to an object that is not data (Table 19.6)")), + } + } + + /// CopyObject, and the explicit conversions' Target (§19.3.5.5): no + /// conversion; a named object takes the copy's type, a field keeps its + /// own and takes only an Integer or a Buffer (Table 19.8). + fn copy_to(&mut self, f: &mut Frame, t: Target, v: Object) -> Result<(), Error> { + match t { + Target::Node(id) => self.copy_node(id, v), + Target::Ref(Ref::Node(id)) => self.copy_node(id, v), + Target::Ref(Ref::BufField(b)) => match v { + Object::Int(_) | Object::Buf(_) => self.write_buf_field(&b, v), + _ => Err(Error::Type("CopyObject to a field of an object that is not an Integer or Buffer (Table 19.8)")), + }, + Target::Arg(i) => { + let held = f.args[i].borrow().clone(); + match held { + Object::Ref(Ref::Node(id)) => self.copy_node(id, v), + Object::Ref(r) => self.store_ref(&r, v), + _ => { + let v = copy(&v)?; + *f.args[i].borrow_mut() = v; + Ok(()) + } + } + } + t => self.store(f, t, v), + } + } + + fn copy_node(&mut self, id: NodeId, v: Object) -> Result<(), Error> { + match (self.node_object(id)?, &v) { + (Object::Field(x), Object::Int(_) | Object::Buf(_)) => self.write_field(&x, v), + (Object::BufField(b), Object::Int(_) | Object::Buf(_)) => self.write_buf_field(&b, v), + (Object::Field(_) | Object::BufField(_), _) => { + Err(Error::Type("CopyObject to a field of an object that is not an Integer or Buffer (Table 19.8)")) + } + (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_) | Object::Ref(_), _) => { + let v = copy(&v)?; + self.ns.set(id, v) + } + _ => Err(Error::Type("CopyObject's destination is not a data object (§19.6.17)")), + } + } + + // ---- expressions (§20.2.5.4) ------------------------------------------ + + /// An ExpressionOpcode, dispatched to one function a family so that the + /// frames a nested expression stacks stay small. + fn expr(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let at = c.at; + match c.byte()? { + 0x11..=0x13 => { + c.at = at; + self.data_object(f, c) + } + 0x70 => self.store_op(f, c), + 0x9D => self.copy_op(f, c), + op @ (0x72 | 0x74 | 0x77 | 0x79 | 0x7A | 0x7B | 0x7C | 0x7D | 0x7E | 0x7F | 0x85) => self.binary(f, c, op), + 0x78 => self.divide(f, c), + op @ 0x80..=0x82 => self.unary(f, c, op), + op @ (0x75 | 0x76) => self.step_op(f, c, op), + op @ 0x90..=0x92 => self.logic(f, c, op), + op @ 0x93..=0x95 => self.compare_op(f, c, op), + 0x73 => self.concat(f, c), + 0x84 => self.concat_res(f, c), + op @ (0x96..=0x99 | 0x9C | 0x9E) => self.convert(f, c, op), + 0x71 => { + let t = self.super_name(f, c)?; + Ok(Object::Ref(self.ref_of(f, t)?)) + } + 0x83 => self.deref_op(f, c), + 0x88 => self.index_op(f, c), + 0x87 => self.size_of(f, c), + 0x8E => self.object_type(f, c), + 0x89 => self.match_op(f, c), + 0x5B => self.ext(f, c, at), + _ => Err(Error::Malformed { at, why: "not an expression (§20.2.5.4)" }), + } + } + + fn store_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let v = self.arg(f, c)?; + let t = self.super_name(f, c)?; + self.store(f, t, v.clone())?; + Ok(v) + } + + fn copy_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let v = self.arg(f, c)?; + let t = match c.peek()? { + 0x60..=0x6E => self.super_name(f, c)?, + b if starts_name(b) => self.super_name(f, c)?, + _ => return Err(c.malformed("CopyObject's destination is not a SimpleName (§20.2.5.4)")), + }; + self.copy_to(f, t, v.clone())?; + Ok(v) + } + + fn binary(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let w = self.w; + let a = self.int_arg(f, c)?; + let b = self.int_arg(f, c)?; + let t = self.target(f, c)?; + let wide = |n: u64| n >= u64::from(w.bits); + let r = match op { + 0x72 => a.wrapping_add(b), + 0x74 => a.wrapping_sub(b), + 0x77 => a.wrapping_mul(b), + 0x79 if wide(b) => 0, + 0x79 => a << b, + 0x7A if wide(b) => 0, + 0x7A => a >> b, + 0x7B => a & b, + 0x7C => !(a & b), + 0x7D => a | b, + 0x7E => !(a | b), + 0x7F => a ^ b, + _ => a.checked_rem(b).ok_or(Error::Rule("Mod by zero (§19.6.86)"))?, + } & w.ones(); + self.store(f, t, Object::Int(r))?; + Ok(Object::Int(r)) + } + + fn divide(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let a = self.int_arg(f, c)?; + let b = self.int_arg(f, c)?; + let rem = self.target(f, c)?; + let quo = self.target(f, c)?; + let q = a.checked_div(b).ok_or(Error::Rule("Divide by zero (§19.6.32)"))?; + self.store(f, rem, Object::Int(a % b))?; + self.store(f, quo, Object::Int(q))?; + Ok(Object::Int(q)) + } + + fn unary(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let a = self.int_arg(f, c)?; + let t = self.target(f, c)?; + let r = match op { + 0x80 => !a & self.w.ones(), + // One-based bit positions, 0 for none set (§19.6.48, §19.6.49). + 0x81 => 64 - u64::from(a.leading_zeros()), + _ if a == 0 => 0, + _ => u64::from(a.trailing_zeros()) + 1, + }; + self.store(f, t, Object::Int(r))?; + Ok(Object::Int(r)) + } + + /// Increment and Decrement (§19.6.61, §19.6.27). + fn step_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let t = self.super_name(f, c)?; + let v = self.read_target(f, &t)?; + let v = to_int(&v, self.w)?; + let r = if op == 0x75 { v.wrapping_add(1) } else { v.wrapping_sub(1) } & self.w.ones(); + self.store(f, t, Object::Int(r))?; + Ok(Object::Int(r)) + } + + fn logic(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let a = self.int_arg(f, c)? != 0; + let r = match op { + 0x92 => !a, + 0x90 => self.int_arg(f, c)? != 0 && a, + _ => self.int_arg(f, c)? != 0 || a, + }; + Ok(Object::Int(self.w.bool(r))) + } + + fn compare_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let a = self.arg(f, c)?; + let b = self.arg(f, c)?; + let o = self.compare(&a, &b)?; + let want = match op { + 0x93 => Ordering::Equal, + 0x94 => Ordering::Greater, + _ => Ordering::Less, + }; + Ok(Object::Int(self.w.bool(o == want))) + } + + fn deref_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + match self.arg(f, c)? { + Object::Ref(r) => self.deref(&r), + Object::Str(s) => { + let p = path_of_text(&s.borrow())?; + let id = self.resolve(f, &p)?; + self.node_value(id) + } + _ => Err(Error::Type("DerefOf of an object that is not a reference or a name (§19.6.30)")), + } + } + + fn index_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let src = self.arg(f, c)?; + let i = self.int_arg(f, c)?; + let t = self.target(f, c)?; + let r = self.index(src, i)?; + self.store(f, t, Object::Ref(r.clone()))?; + Ok(Object::Ref(r)) + } + + fn size_of(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let t = self.super_name(f, c)?; + let n = match self.base(f, &t)? { + Object::Buf(b) | Object::Str(b) => b.borrow().len(), + Object::Pkg(p) => p.borrow().len(), + _ => return Err(Error::Type("SizeOf of an object that is not a buffer, string or package (§19.6.124)")), + }; + Ok(Object::Int(n as u64)) + } + + fn object_type(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let t = self.super_name(f, c)?; + let code = match t { + Target::Debug => 16, + t => self.base(f, &t)?.type_code(), + }; + Ok(Object::Int(code)) + } + + /// The `ExtOpPrefix` expressions. + fn ext(&mut self, f: &mut Frame, c: &mut Cursor<'_>, at: usize) -> Result { + match c.byte()? { + 0x12 => self.cond_ref_of(f, c), + 0x23 => self.acquire_op(f, c), + 0x25 => self.wait_op(f, c), + 0x28 => self.bcd_in(f, c), + 0x29 => self.bcd_out(f, c), + 0x33 => Ok(Object::Int(self.host.timer() & self.w.ones())), + 0x1F => Err(Error::Unsupported("LoadTable")), + 0x20 => Err(Error::Unsupported("Load")), + _ => Err(Error::Malformed { at, why: "not an expression (§20.2.5.4)" }), + } + } + + fn cond_ref_of(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let found = self.super_name_or(f, c)?; + let t = self.target(f, c)?; + match found { + Ok(s) => { + let r = self.ref_of(f, s)?; + self.store(f, t, Object::Ref(r))?; + Ok(Object::Int(self.w.ones())) + } + Err(_) => Ok(Object::Int(0)), + } + } + + fn acquire_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let t = self.super_name(f, c)?; + c.word()?; + let id = self.node_of(f, &t)?; + let Object::Mutex(m) = self.node_object(id)? else { + return Err(Error::Type("Acquire of an object that is not a Mutex (§19.6.2)")); + }; + // One invocation runs at a time, so no Mutex is owned by another: + // every Acquire is granted, and none times out. + self.acquire(&m)?; + Ok(Object::Int(0)) + } + + fn wait_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let t = self.super_name(f, c)?; + let timeout = self.int_arg(f, c)?; + let id = self.node_of(f, &t)?; + let Object::Event(e) = self.node_object(id)? else { + return Err(Error::Type("Wait on an object that is not an Event (§19.6.147)")); + }; + if e.get() > 0 { + e.set(e.get() - 1); + return Ok(Object::Int(0)); + } + // Nothing else runs while this invocation waits, so no signal can + // arrive: the wait times out. + if timeout >= 0xFFFF { + return Err(Error::Rule("a Wait without timeout on an Event no other invocation can signal")); + } + self.wait(timeout.saturating_mul(1000))?; + self.host.sleep(timeout); + Ok(Object::Int(self.w.ones())) + } + + fn bcd_in(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let v = self.int_arg(f, c)?; + let t = self.target(f, c)?; + let mut r = 0u64; + for i in (0..16).rev() { + let d = v >> (4 * i) & 0xF; + if d > 9 { + return Err(Error::Rule("FromBCD of a digit above 9 (§19.6.54)")); + } + r = r * 10 + d; + } + let r = r & self.w.ones(); + self.store(f, t, Object::Int(r))?; + Ok(Object::Int(r)) + } + + fn bcd_out(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let mut v = self.int_arg(f, c)?; + let t = self.target(f, c)?; + let mut r = 0u64; + for i in 0..self.w.bytes() * 2 { + r |= (v % 10) << (4 * i); + v /= 10; + } + if v != 0 { + return Err(Error::Rule("ToBCD of a value with more digits than an integer holds (§19.6.135)")); + } + self.store(f, t, Object::Int(r))?; + Ok(Object::Int(r)) + } + + fn ref_of(&self, f: &Frame, t: Target) -> Result { + match t { + Target::Node(id) => Ok(Ref::Node(id)), + Target::Local(i) => Ok(Ref::Slot(f.locals[i].clone())), + Target::Arg(i) => Ok(Ref::Slot(f.args[i].clone())), + Target::Ref(r) => Ok(r), + Target::None | Target::Debug => Err(Error::Type("a reference to the Debug object")), + } + } + + /// Index (§19.6.62): a package's nth element, or a buffer's or string's + /// nth byte as an 8-bit buffer field. + fn index(&self, src: Object, i: u64) -> Result { + let past = Error::Rule("Index past the end of its source (§19.6.62)"); + let i = usize::try_from(i).map_err(|_| Error::Rule("Index past the end of its source (§19.6.62)"))?; + match src { + Object::Buf(b) | Object::Str(b) => { + if i >= b.borrow().len() { + return Err(past); + } + Ok(Ref::BufField(Rc::new(BufField { data: b, bit: i as u64 * 8, len: 8 }))) + } + Object::Pkg(p) => { + if i >= p.borrow().len() { + return Err(past); + } + Ok(Ref::Elem(p, i)) + } + _ => Err(Error::Type("Index of an object that is not a buffer, string or package (§19.6.62)")), + } + } + + /// The logical comparisons' order (§19.6.69-72): the first operand's type + /// is the one the second converts to; strings and buffers compare byte by + /// byte, a shorter equal prefix the lesser. + fn compare(&self, a: &Object, b: &Object) -> Result { + match a { + Object::Int(x) => Ok((*x & self.w.ones()).cmp(&to_int(b, self.w)?)), + Object::Str(x) => Ok(x.borrow().as_slice().cmp(to_str(b, self.w)?.as_slice())), + Object::Buf(x) => Ok(x.borrow().as_slice().cmp(to_buf(b, self.w)?.as_slice())), + _ => Err(Error::Type("a comparison of an object that is not an integer, string or buffer (§19.6.69)")), + } + } + + /// The ObjectType code of an object, a reference's being its target's. + fn type_of(&mut self, o: &Object) -> Result { + Ok(match o { + Object::Ref(Ref::Node(id)) => self.node_object(*id)?.type_code(), + Object::Ref(Ref::Slot(s)) => s.borrow().type_code(), + Object::Ref(Ref::Elem(p, i)) => p.borrow().get(*i).map_or(0, Object::type_code), + Object::Ref(Ref::BufField(_)) => 14, + o => o.type_code(), + }) + } + + /// Concatenate (§19.6.12, Table 19.30). + fn concat(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let a = self.arg(f, c)?; + let b = self.arg(f, c)?; + let t = self.target(f, c)?; + let w = self.w; + let data = |o: &Object| matches!(o, Object::Int(_) | Object::Str(_) | Object::Buf(_)); + let named = |m: &mut Self, o: &Object| -> Result, Error> { Ok(type_name(m.type_of(o)?).to_vec()) }; + let tail_str = |m: &mut Self, o: &Object| if data(o) { to_str(o, w) } else { named(m, o) }; + let r = match &a { + Object::Int(x) => { + let mut v = w.le(*x); + v.extend(w.le(to_int(&b, w)?)); + Object::buf(v) + } + Object::Str(x) => { + let mut v = x.borrow().clone(); + v.extend(tail_str(self, &b)?); + Object::str(v) + } + Object::Buf(x) => { + let mut v = x.borrow().clone(); + if data(&b) { + v.extend(to_buf(&b, w)?); + } else { + v.extend(named(self, &b)?); + v.push(0); + } + Object::buf(v) + } + other => { + let mut v = named(self, other)?; + v.extend(tail_str(self, &b)?); + Object::str(v) + } + }; + match &r { + Object::Str(v) | Object::Buf(v) => bounded(v.borrow().len())?, + _ => {} + } + self.store(f, t, r.clone())?; + Ok(r) + } + + /// ConcatenateResTemplate (§19.6.13): both templates' descriptors, then a + /// new End Tag whose checksum makes the bytes sum to zero (§6.4.2.9). + fn concat_res(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let a = self.arg(f, c)?; + let b = self.arg(f, c)?; + let t = self.target(f, c)?; + let mut out = Vec::new(); + for o in [&a, &b] { + let v = to_buf(o, self.w)?; + match v.len() { + 0 => {} + 1 => return Err(Error::Rule("ConcatenateResTemplate of a one-byte template (§19.6.13)")), + n if v[n - 2] == 0x79 => out.extend_from_slice(&v[..n - 2]), + _ => return Err(Error::Rule("ConcatenateResTemplate of a template without an End Tag (§6.4.2.9)")), + } + } + out.push(0x79); + let sum = out.iter().fold(0u8, |s, &x| s.wrapping_add(x)); + out.push(0u8.wrapping_sub(sum)); + bounded(out.len())?; + let r = Object::buf(out); + self.store(f, t, r.clone())?; + Ok(r) + } + + /// The explicit conversions (§19.3.5.2) and Mid (§19.6.85). + fn convert(&mut self, f: &mut Frame, c: &mut Cursor<'_>, op: u8) -> Result { + let w = self.w; + let src = self.arg(f, c)?; + let r = match op { + 0x96 => Object::buf(to_buf(&src, w)?), + 0x97 => match &src { + Object::Int(v) => Object::str(decimal(*v)), + Object::Str(s) => Object::str(s.borrow().clone()), + Object::Buf(b) => Object::str(joined(&b.borrow(), b',', |x| decimal(u64::from(x)))), + _ => return Err(Error::Type("ToDecimalString of an object that is not an integer, string or buffer")), + }, + // §19.6.138 names no form for a buffer's values; each is written + // in the two-digit form the Buffer to String rule uses (Table 19.7). + 0x98 => match &src { + Object::Buf(b) => Object::str(joined(&b.borrow(), b',', hex2)), + o => Object::str(to_str(o, w)?), + }, + 0x99 => Object::Int(match &src { + Object::Str(s) => int_of_text(&s.borrow(), w)?, + o => to_int(o, w)?, + }), + 0x9C => { + let b = to_buf(&src, w)?; + let n = self.int_arg(f, c)?; + let n = if n == w.ones() { usize::MAX } else { usize::try_from(n).unwrap_or(usize::MAX) }; + Object::str(b.iter().take(n).take_while(|&&x| x != 0).copied().collect()) + } + _ => { + let i = self.int_arg(f, c)?; + let n = self.int_arg(f, c)?; + let (data, is_str) = match &src { + Object::Str(s) => (s.borrow().clone(), true), + o => (to_buf(o, w)?, false), + }; + let start = usize::try_from(i).unwrap_or(usize::MAX).min(data.len()); + let end = start.saturating_add(usize::try_from(n).unwrap_or(usize::MAX)).min(data.len()); + let part = data[start..end].to_vec(); + let r = if is_str { Object::str(part) } else { Object::buf(part) }; + let t = self.target(f, c)?; + self.store(f, t, r.clone())?; + return Ok(r); + } + }; + let t = self.target(f, c)?; + self.copy_to(f, t, r.clone())?; + Ok(r) + } + + /// Match (§19.6.80). + fn match_op(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + let Object::Pkg(p) = self.arg(f, c)? else { + return Err(Error::Type("Match of an object that is not a package (§19.6.80)")); + }; + let op1 = c.byte()?; + let m1 = self.arg(f, c)?; + let op2 = c.byte()?; + let m2 = self.arg(f, c)?; + let start = self.int_arg(f, c)?; + if op1 > 5 || op2 > 5 { + return Err(c.malformed("a MatchOpcode above 5 (§20.2.5.4)")); + } + for m in [&m1, &m2] { + if !matches!(m, Object::Int(_) | Object::Str(_) | Object::Buf(_)) { + return Err(Error::Type("a MatchObject that is not an integer, string or buffer (§19.6.80)")); + } + } + let len = p.borrow().len(); + let start = usize::try_from(start).unwrap_or(usize::MAX); + for i in start..len { + self.step()?; + let e = p.borrow().get(i).cloned().unwrap_or(Object::Uninit); + let e = match e { + Object::Lazy(l) => self.lazy(&l)?, + e => e, + }; + if !matches!(e, Object::Int(_) | Object::Str(_) | Object::Buf(_)) { + continue; + } + if self.matches(&e, op1, &m1) && self.matches(&e, op2, &m2) { + return Ok(Object::Int(i as u64)); + } + } + Ok(Object::Int(self.w.ones())) + } + + /// One Match comparison: the element converted to the MatchObject's + /// type, an element that does not convert matching nothing. + fn matches(&self, e: &Object, op: u8, m: &Object) -> bool { + if op == 0 { + return true; + } + let e = match m { + Object::Int(_) => to_int(e, self.w).map(Object::Int), + Object::Str(_) => to_str(e, self.w).map(Object::str), + _ => to_buf(e, self.w).map(Object::buf), + }; + let Ok(o) = e.and_then(|e| self.compare(&e, m)) else { return false }; + match op { + 1 => o == Ordering::Equal, + 2 => o != Ordering::Greater, + 3 => o == Ordering::Less, + 4 => o != Ordering::Less, + _ => o == Ordering::Greater, + } + } +} + +/// ToInteger of a String (§19.6.139): decimal, or hexadecimal after `0x`; +/// a value an integer cannot hold is refused, as is anything else. +fn int_of_text(s: &[u8], w: Width) -> Result { + let (digits, radix) = match s { + [b'0', b'x' | b'X', rest @ ..] => (rest, 16), + _ => (s, 10), + }; + if digits.is_empty() { + return Err(Error::Type("ToInteger of a string with no digits (§19.6.139)")); + } + let mut v = 0u64; + for &ch in digits { + let d = char::from(ch).to_digit(radix).ok_or(Error::Type("ToInteger of a string that is not a number (§19.6.139)"))?; + v = v + .checked_mul(u64::from(radix)) + .and_then(|v| v.checked_add(u64::from(d))) + .filter(|&v| v <= w.ones()) + .ok_or(Error::Rule("ToInteger of a number larger than an integer holds (§19.6.139)"))?; + } + Ok(v) +} diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs new file mode 100644 index 00000000000..3479806629f --- /dev/null +++ b/userland/acpiserver/aml/src/field.rs @@ -0,0 +1,357 @@ +//! Field units over operation regions (§19.6.47, §19.6.63, §19.6.7) and +//! buffer fields over buffers (§19.6.18-23, §19.6.62). +//! +//! A field is read and written in access units: aligned, of the width its +//! access type names, each one [`Host`](crate::Host) access. A unit the field +//! covers in part is completed by the field's update rule. A Lock field holds +//! the Global Lock across the whole access. An IndexField reaches a unit by +//! writing its byte offset to the index field and then accessing the data +//! field; a BankField writes its bank value to the bank field first. + +use alloc::rc::Rc; +use alloc::vec; +use alloc::vec::Vec; +use core::cell::Cell; + +use crate::exec::Machine; +use crate::name::Seg; +use crate::namespace::NodeId; +use crate::object::{fit, to_buf, to_int, Bytes, Object}; +use crate::{Address, Error, Width, MAX_BYTES}; + +pub(crate) struct Region { + pub(crate) space: u8, + pub(crate) base: u64, + pub(crate) len: u64, + /// The scope the region was declared in: for PCI_Config, the device it + /// addresses. + pub(crate) scope: NodeId, + pub(crate) pci: Cell>, +} + +#[derive(Clone, Copy)] +pub(crate) struct Pci { + segment: u16, + bus: u8, + device: u8, + function: u8, +} + +#[derive(Clone)] +pub(crate) enum Kind { + Region(Rc), + Bank { region: Rc, bank: Rc, value: u64 }, + Index { index: Rc, data: Rc }, +} + +pub(crate) struct Field { + pub(crate) kind: Kind, + pub(crate) bit: u64, + pub(crate) len: u64, + /// AccessType (§20.2.5.2): 0 AnyAcc, 1 Byte, 2 Word, 3 DWord, 4 QWord, 5 Buffer. + pub(crate) access: u8, + pub(crate) lock: bool, + /// UpdateRule (§20.2.5.2): 0 Preserve, 1 WriteAsOnes, 2 WriteAsZeros. + pub(crate) update: u8, +} + +pub(crate) struct BufField { + pub(crate) data: Bytes, + pub(crate) bit: u64, + pub(crate) len: u64, +} + +/// Bytes enough for `bits`, bounded by what this interpreter holds. +fn bytes_for(bits: u64) -> Result { + let n = bits.div_ceil(8); + usize::try_from(n).ok().filter(|&n| n <= MAX_BYTES).ok_or(Error::Bound("a field larger than this interpreter holds")) +} + +fn bit(b: &[u8], i: u64) -> bool { + b.get((i / 8) as usize).is_some_and(|x| x >> (i % 8) & 1 == 1) +} + +fn set_bit(b: &mut [u8], i: u64, on: bool) { + if let Some(x) = b.get_mut((i / 8) as usize) { + let m = 1u8 << (i % 8); + if on { *x |= m } else { *x &= !m } + } +} + +/// A field's value (§19.6.47): an Integer when it fits one, else a Buffer. +fn value(b: Vec, bits: u64, w: Width) -> Result { + if bits <= u64::from(w.bits) { Ok(Object::Int(w.int_of_bytes(&b)?)) } else { Ok(Object::buf(b)) } +} + +fn width(bytes: u64) -> crate::Access { + match bytes { + 1 => crate::Access::Byte, + 2 => crate::Access::Word, + 4 => crate::Access::DWord, + _ => crate::Access::QWord, + } +} + +fn unit_ones(w: u64) -> u64 { + if w >= 8 { u64::MAX } else { (1u64 << (8 * w)) - 1 } +} + +/// The FieldFlags byte (§20.2.5.2), refused where it sets what is reserved. +pub(crate) fn flags(b: u8) -> Result<(u8, bool, u8), &'static str> { + let access = b & 0x0F; + let update = (b >> 5) & 0x3; + if access > 5 { + return Err("a field's AccessType is reserved (§20.2.5.2)"); + } + if update == 3 { + return Err("a field's UpdateRule is reserved (§20.2.5.2)"); + } + if b & 0x80 != 0 { + return Err("a field's FieldFlags sets reserved bit 7 (§20.2.5.2)"); + } + Ok((access, b & 0x10 != 0, update)) +} + +impl Machine<'_> { + /// The bytes of one access unit, by the access type and, for an access + /// type of AnyAcc, the narrowest naturally aligned unit holding the whole + /// field (§19.6.47: "accesses within the parent object are performed + /// naturally aligned"), else bytes. + fn unit(&self, f: &Field) -> Result { + let space = match &f.kind { + Kind::Region(r) | Kind::Bank { region: r, .. } => Some(r.space), + Kind::Index { .. } => None, + }; + // Table 19.34: EmbeddedControl, SystemCMOS, GeneralPurposeIO and PCC + // permit byte access only. + let bytes_only = matches!(space, Some(0x03 | 0x05 | 0x08 | 0x0A)); + let w = match f.access { + 0 if bytes_only => 1, + 0 => [1u64, 2, 4, 8] + .into_iter() + .find(|&w| f.bit / (8 * w) == (f.bit + f.len - 1) / (8 * w)) + .unwrap_or(1), + 1 => 1, + 2 => 2, + 3 => 4, + 4 => 8, + _ => return Err(Error::Unsupported("BufferAcc, which only the SMBus, IPMI and GenericSerialBus spaces use")), + }; + if bytes_only && w != 1 { + return Err(Error::Type("a field wider than a byte in a space Table 19.34 permits ByteAcc alone")); + } + Ok(w) + } + + fn address(&mut self, r: &Region, offset: u64, w: u64) -> Result { + let past = || Error::Rule("a field access runs past its operation region (§19.6.47)"); + if offset.checked_add(w).ok_or_else(past)? > r.len { + return Err(past()); + } + let at = r.base.checked_add(offset).ok_or_else(past)?; + match r.space { + 0x00 => Ok(Address::Memory(at)), + 0x01 => Ok(Address::Io(at)), + 0x02 => { + // PCI configuration space is 4096 bytes a function. + let offset = u16::try_from(at).ok().filter(|&o| u64::from(o) + w <= 0x1000); + let offset = offset.ok_or(Error::Rule("a PCI_Config access past a function's 4096 bytes"))?; + let p = self.pci(r)?; + Ok(Address::PciConfig { segment: p.segment, bus: p.bus, device: p.device, function: p.function, offset }) + } + 0x03 => { + // §12: the embedded controller's space is 256 bytes. + let at = u8::try_from(at).ok().filter(|&a| u64::from(a) + w <= 0x100); + Ok(Address::EmbeddedControl(at.ok_or(Error::Rule("an EmbeddedControl access past its 256 bytes (§12)"))?)) + } + 0x04 => Err(Error::Unsupported("the SMBus address space")), + 0x05 => Err(Error::Unsupported("the SystemCMOS address space")), + 0x06 => Err(Error::Unsupported("the PciBarTarget address space")), + 0x07 => Err(Error::Unsupported("the IPMI address space")), + 0x08 => Err(Error::Unsupported("the GeneralPurposeIO address space")), + 0x09 => Err(Error::Unsupported("the GenericSerialBus address space")), + 0x0A => Err(Error::Unsupported("the PCC address space")), + 0x0B => Err(Error::Unsupported("the PlatformRtMechanism address space")), + 0x7F => Err(Error::Unsupported("the FFixedHW address space")), + _ => Err(Error::Unsupported("an OEM-defined address space")), + } + } + + /// The function a PCI_Config region addresses: its device's `_ADR` + /// (§6.1.1: device in the high word, function in the low), on the bus a + /// host bridge's `_BBN` names (§6.5.5), in the segment group its `_SEG` + /// names or 0 without one (§6.5.6). The region is declared in the host + /// bridge itself or in a device directly below it. + fn pci(&mut self, r: &Region) -> Result { + if let Some(p) = r.pci.get() { + return Ok(p); + } + let device = r.scope; + let bbn = Seg(*b"_BBN"); + let bridge = if self.ns.child(device, bbn).is_some() { + device + } else { + match self.ns.parent(device) { + Some(p) if self.ns.child(p, bbn).is_some() => p, + _ => return Err(Error::Unsupported("a PCI_Config region not on a host bridge's bus, which names a _BBN")), + } + }; + let adr = self.named_int(device, Seg(*b"_ADR"))?.ok_or(Error::NotFound(self.ns.path_of(device, Some(Seg(*b"_ADR")))))?; + let bus = self.named_int(bridge, bbn)?.unwrap_or(0); + let segment = self.named_int(bridge, Seg(*b"_SEG"))?.unwrap_or(0); + let (dev, fun) = (adr >> 16 & 0xFFFF, adr & 0xFFFF); + if dev > 31 || fun > 7 { + return Err(Error::Rule("an _ADR that names no single PCI function (§6.1.1)")); + } + let p = Pci { segment: segment as u16, bus: bus as u8, device: dev as u8, function: fun as u8 }; + r.pci.set(Some(p)); + Ok(p) + } + + fn unit_read(&mut self, f: &Field, offset: u64, w: u64) -> Result { + match &f.kind { + Kind::Region(r) => { + let at = self.address(r, offset, w)?; + self.host.read(at, width(w)).map_err(|d| Error::Host(d.0)) + } + Kind::Bank { region, bank, value } => { + self.write_field(bank, Object::Int(*value))?; + let at = self.address(region, offset, w)?; + self.host.read(at, width(w)).map_err(|d| Error::Host(d.0)) + } + Kind::Index { index, data } => { + self.write_field(index, Object::Int(offset))?; + let v = self.read_field(data)?; + to_int(&v, self.w) + } + } + } + + fn unit_write(&mut self, f: &Field, offset: u64, w: u64, v: u64) -> Result<(), Error> { + match &f.kind { + Kind::Region(r) => { + let at = self.address(r, offset, w)?; + self.host.write(at, width(w), v).map_err(|d| Error::Host(d.0)) + } + Kind::Bank { region, bank, value } => { + self.write_field(bank, Object::Int(*value))?; + let at = self.address(region, offset, w)?; + self.host.write(at, width(w), v).map_err(|d| Error::Host(d.0)) + } + Kind::Index { index, data } => { + self.write_field(index, Object::Int(offset))?; + self.write_field(data, Object::Int(v)) + } + } + } + + pub(crate) fn read_field(&mut self, f: &Field) -> Result { + self.enter()?; + let r = self.locked(f.lock, |m| m.read_units(f)); + self.leave(); + value(r?, f.len, self.w) + } + + fn read_units(&mut self, f: &Field) -> Result, Error> { + let mut out = vec![0u8; bytes_for(f.len)?]; + let w = self.unit(f)?; + let span = 8 * w; + for u in f.bit / span..=(f.bit + f.len - 1) / span { + self.step()?; + let v = self.unit_read(f, u * w, w)?; + let (lo, hi) = (u * span, (u + 1) * span); + for b in f.bit.max(lo)..(f.bit + f.len).min(hi) { + set_bit(&mut out, b - f.bit, v >> (b - lo) & 1 == 1); + } + } + Ok(out) + } + + /// A store to a field unit (Table 19.7): an Integer overwrites the whole + /// field; a Buffer is written in pieces of the field's size, lower first, + /// each zero-extended; a String is written a character at a time. + pub(crate) fn write_field(&mut self, f: &Field, v: Object) -> Result<(), Error> { + let n = bytes_for(f.len)?; + let pieces: Vec> = match &v { + Object::Int(x) => vec![fit(x.to_le_bytes().to_vec(), n)], + Object::Buf(b) if b.borrow().is_empty() => vec![vec![0; n]], + Object::Buf(b) => b.borrow().chunks(n).map(|c| fit(c.to_vec(), n)).collect(), + Object::Str(s) => s.borrow().iter().map(|&c| fit(vec![c], n)).collect(), + _ => return Err(Error::Type("a store to a field unit of an object that is not an integer, buffer or string")), + }; + self.enter()?; + let r = self.locked(f.lock, |m| pieces.iter().try_for_each(|p| m.write_units(f, p))); + self.leave(); + r + } + + fn write_units(&mut self, f: &Field, data: &[u8]) -> Result<(), Error> { + let w = self.unit(f)?; + let span = 8 * w; + for u in f.bit / span..=(f.bit + f.len - 1) / span { + self.step()?; + let (lo, hi) = (u * span, (u + 1) * span); + let (s, e) = (f.bit.max(lo), (f.bit + f.len).min(hi)); + let mut v = if s == lo && e == hi { + 0 + } else { + match f.update { + 0 => self.unit_read(f, u * w, w)?, + 1 => unit_ones(w), + _ => 0, + } + }; + for b in s..e { + let m = 1u64 << (b - lo); + if bit(data, b - f.bit) { v |= m } else { v &= !m } + } + self.unit_write(f, u * w, w, v)?; + } + Ok(()) + } + + /// Runs `body` holding the Global Lock where `lock` says (§19.6.47). + fn locked(&mut self, lock: bool, body: impl FnOnce(&mut Self) -> Result) -> Result { + if lock { + self.take_global()?; + } + let r = body(self); + if lock { + let released = self.drop_global(); + return r.and_then(|v| released.map(|()| v)); + } + r + } + + pub(crate) fn read_buf_field(&mut self, f: &BufField) -> Result { + let d = f.data.borrow(); + if f.bit.saturating_add(f.len) > (d.len() as u64).saturating_mul(8) { + return Err(Error::Rule("a buffer field reaches past its buffer, which shrank since")); + } + let mut out = vec![0u8; bytes_for(f.len)?]; + for i in 0..f.len { + set_bit(&mut out, i, bit(&d, f.bit + i)); + } + drop(d); + value(out, f.len, self.w) + } + + /// A store to a buffer field (Table 19.7): the source as bytes, truncated + /// or zero-extended to the field. + pub(crate) fn write_buf_field(&mut self, f: &BufField, v: Object) -> Result<(), Error> { + let src = match &v { + Object::Int(x) => x.to_le_bytes().to_vec(), + Object::Buf(_) | Object::Str(_) => to_buf(&v, self.w)?, + _ => return Err(Error::Type("a store to a buffer field of an object that is not an integer, buffer or string")), + }; + let src = fit(src, bytes_for(f.len)?); + let mut d = f.data.borrow_mut(); + if f.bit.saturating_add(f.len) > (d.len() as u64).saturating_mul(8) { + return Err(Error::Rule("a buffer field reaches past its buffer, which shrank since")); + } + for i in 0..f.len { + set_bit(&mut d, f.bit + i, bit(&src, i)); + } + Ok(()) + } +} diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs new file mode 100644 index 00000000000..255ba2c7e8f --- /dev/null +++ b/userland/acpiserver/aml/src/lib.rs @@ -0,0 +1,312 @@ +//! The ACPI Machine Language interpreter: a machine's DSDT and SSDTs loaded +//! into one namespace, and its objects evaluated, written from the ACPI +//! Specification 6.5, whose sections the code cites at each rule it encodes. +//! +//! A definition block is firmware's, and so untrusted: whatever its bytes, +//! [`Interpreter::load`] and [`Interpreter::evaluate`] return a value or a +//! named [`Error`], never panic, and never run unbounded — every evaluation +//! is bounded in steps, nesting, object size and time asked to sleep. A load +//! refused leaves the namespace without anything that table created. +//! +//! The library touches no hardware. An operation region's field is read and +//! written through the [`Host`] the caller passes, in SystemMemory, +//! SystemIO, PCI_Config and EmbeddedControl space; an access in any other +//! space is refused as [`Error::Unsupported`], as are `Load`, `LoadTable` +//! and `DataTableRegion`. Only one invocation runs at a time, so a Mutex is +//! never contended and an Event is never signalled by anyone else. +//! +//! The predefined objects are the operating system's (§5.7): `\_OSI` answers +//! as the owner ruled ("Like Windows, not Linux"): yes to every Windows +//! version string Microsoft publishes for `_OSI`, no to anything else; +//! `\_OS` is this system's name and `\_REV` is 2, ACPI 2 or greater with +//! 64-bit integers (§5.7.4). + +#![no_std] +#![forbid(unsafe_code)] + +extern crate alloc; + +mod exec; +mod field; +mod name; +mod namespace; +mod object; +mod stream; + +use alloc::rc::Rc; +use alloc::string::String; +use alloc::vec::Vec; +use core::cell::{Cell, RefCell}; + +use exec::{Frame, Machine}; +use name::{Path, Seg}; +use namespace::Namespace; +use object::{Body, Method, Mutex, Object, Ref}; + +pub(crate) use object::Width; + +/// The steps one load or evaluation may take: terms, arguments, loop +/// iterations, field access units. +pub(crate) const MAX_STEPS: u64 = 1 << 20; +/// How deep terms, method invocations and field accesses may nest, together. +pub(crate) const MAX_DEPTH: u32 = 256; +/// How deep a package may nest within packages, where it is copied or +/// handed to the caller. +pub(crate) const MAX_NESTING: usize = 64; +/// The largest string, buffer, field or package, in bytes or elements. +pub(crate) const MAX_BYTES: usize = 1 << 20; +/// The time one evaluation may ask to Sleep, Stall and Wait, together, in µs. +pub(crate) const MAX_WAIT_US: u64 = 10_000_000; +/// What the Revision opcode answers (§19.6.119): this interpreter's revision. +pub(crate) const REVISION: u64 = 1; + +/// The `_OSI` strings answered yes: every one Microsoft publishes for +/// Windows ("How to Identify the Windows Version in ACPI by Using _OSI"). +pub(crate) const WINDOWS: &[&str] = &[ + "Windows 2000", + "Windows 2001", + "Windows 2001 SP1", + "Windows 2001.1", + "Windows 2001 SP2", + "Windows 2001.1 SP1", + "Windows 2006", + "Windows 2006 SP1", + "Windows 2006.1", + "Windows 2009", + "Windows 2012", + "Windows 2013", + "Windows 2015", + "Windows 2016", + "Windows 2017", + "Windows 2017.2", + "Windows 2018", + "Windows 2018.2", + "Windows 2019", + "Windows 2020", + "Windows 2021", + "Windows 2022", +]; + +const HEADER: usize = 36; + +/// Why a table or an evaluation was refused. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Error { + /// The bytes break the AML grammar (§20.2) at this offset into the table. + Malformed { at: usize, why: &'static str }, + /// A name that must resolve does not (§5.3). + NotFound(String), + /// A definition names an object that exists (§5.3: "a name collision ... + /// is considered fatal"). + Exists(String), + /// An operand of a type its operator or §19.3.5 refuses. + Type(&'static str), + /// A rule of an operator's definition is broken (§19.6). + Rule(&'static str), + /// The table's header is refused (§5.2.6). + Table(&'static str), + /// The firmware executed Fatal (§19.6.46): the operating system is to + /// log it and shut down. + Fatal { kind: u8, code: u32, arg: u64 }, + /// What this interpreter bounds was exceeded. + Bound(&'static str), + /// Something the specification defines that this interpreter does not + /// carry. + Unsupported(&'static str), + /// The [`Host`] refused an access. + Host(String), +} + +/// A refusal by the [`Host`], saying why. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Denied(pub String); + +/// The width of one access to an operation region. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Access { + Byte, + Word, + DWord, + QWord, +} + +/// Where an access to an operation region lands (Table 5.182). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Address { + Memory(u64), + Io(u64), + PciConfig { segment: u16, bus: u8, device: u8, function: u8, offset: u16 }, + EmbeddedControl(u8), +} + +/// What the interpreter asks of the operating system. +pub trait Host { + fn read(&mut self, at: Address, width: Access) -> Result; + fn write(&mut self, at: Address, width: Access, value: u64) -> Result<(), Denied>; + /// Sleep (§19.6.125): at least `ms` milliseconds, giving up the processor. + fn sleep(&mut self, ms: u64); + /// Stall (§19.6.127): at least `us` microseconds, keeping the processor. + fn stall(&mut self, us: u64); + /// Timer (§19.6.134): monotonic, in 100 ns units. + fn timer(&mut self) -> u64; + /// Notify (§19.6.94) of the object at this absolute path. + fn notify(&mut self, object: &str, value: u64); + /// Takes (`true`) or gives back (`false`) the firmware's Global Lock + /// (§5.2.10.1), around a Lock field's access and `\_GL`'s ownership. + fn global_lock(&mut self, take: bool) -> Result<(), Denied>; +} + +/// An object as the caller receives or passes it. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + Uninitialized, + Integer(u64), + String(Vec), + Buffer(Vec), + Package(Vec), + /// A reference to the named object at this absolute path. + Reference(String), +} + +/// One machine's namespace, and what loaded it. +pub struct Interpreter { + ns: Namespace, + /// Set by the DSDT's revision (§19.6.29), for every table after it. + width: Option, +} + +impl Default for Interpreter { + fn default() -> Self { + Self::new() + } +} + +impl Interpreter { + /// An empty namespace holding the predefined scopes (§5.3.1) and objects + /// (§5.7). + pub fn new() -> Self { + let mut ns = Namespace::new(); + let root = ns.root(); + let mut put = |name: &[u8; 4], o: Object| { + let p = Path { root: true, up: 0, segs: alloc::vec![Seg(*name)] }; + // The root is empty and every name differs. + let _ = ns.create(root, &p, o); + }; + for scope in [b"_GPE", b"_PR_", b"_SB_", b"_SI_", b"_TZ_"] { + put(scope, Object::Scope); + } + put(b"_GL_", Object::Mutex(Rc::new(Mutex { sync: 0, held: Cell::new(0), global: true }))); + put(b"_OSI", Object::Method(Rc::new(Method { body: Body::Osi, args: 1, serialized: false, sync: 0 }))); + put(b"_OS_", Object::str(b"ToyOS".to_vec())); + put(b"_REV", Object::Int(2)); + Interpreter { ns, width: None } + } + + /// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. + pub fn load(&mut self, host: &mut dyn Host, table: &[u8]) -> Result<(), Error> { + let (signature, revision) = header(table)?; + let w = match (&signature, self.width) { + (b"DSDT", None) => Width { bits: if revision < 2 { 32 } else { 64 } }, + (b"DSDT", Some(_)) => return Err(Error::Table("a second DSDT")), + (_, Some(w)) => w, + (_, None) => return Err(Error::Table("an SSDT before the DSDT, whose revision sets every integer's width")), + }; + let table: Rc<[u8]> = Rc::from(table); + let root = self.ns.root(); + let mut f = Frame::new(root, Vec::new(), table.clone(), 0); + let mut m = Machine::new(&mut self.ns, host, w); + let mut c = stream::Cursor::new(&table, HEADER, table.len()); + let r = m.term_list(&mut f, &mut c).and_then(|flow| match flow { + exec::Flow::Next => Ok(()), + _ => Err(Error::Rule("a Return, Break or Continue at definition block level")), + }); + let r = m.finish(r); + match r { + Ok(()) => { + self.width = Some(w); + Ok(()) + } + Err(e) => { + for &id in f.created.iter().rev() { + self.ns.remove(id); + } + Err(e) + } + } + } + + /// Evaluates the object at an absolute path, written `\_SB.PCI0._STA`: a + /// method is invoked with `args`, anything else is its value. + pub fn evaluate(&mut self, host: &mut dyn Host, path: &str, args: &[Value]) -> Result { + let w = self.width.ok_or(Error::Table("nothing is loaded"))?; + let p = Path::absolute(path)?; + let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(String::from(path)))?; + let args = args.iter().map(|a| self.object_of(a, w, 0)).collect::, _>>()?; + let mut m = Machine::new(&mut self.ns, host, w); + let r = m.evaluate(id, args).and_then(|o| value_of(&mut m, o, 0)); + m.finish(r) + } + + fn object_of(&self, v: &Value, w: Width, depth: usize) -> Result { + if depth > MAX_NESTING { + return Err(Error::Bound("a package nests deeper than this interpreter copies")); + } + Ok(match v { + Value::Uninitialized => Object::Uninit, + Value::Integer(x) => Object::Int(x & w.ones()), + Value::String(s) if s.contains(&0) => return Err(Error::Type("a String argument holds a NUL")), + Value::String(s) => Object::str(s.clone()), + Value::Buffer(b) => Object::buf(b.clone()), + Value::Package(p) => Object::Pkg(Rc::new(RefCell::new( + p.iter().map(|e| self.object_of(e, w, depth + 1)).collect::>()?, + ))), + Value::Reference(path) => { + let p = Path::absolute(path)?; + let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(path.clone()))?; + Object::Ref(Ref::Node(id)) + } + }) + } +} + +fn value_of(m: &mut Machine<'_>, o: Object, depth: usize) -> Result { + if depth > MAX_NESTING { + return Err(Error::Bound("a package nests deeper than this interpreter copies")); + } + Ok(match m.resolve_lazy(o)? { + Object::Uninit => Value::Uninitialized, + Object::Int(x) => Value::Integer(x), + Object::Str(s) => Value::String(s.borrow().clone()), + Object::Buf(b) => Value::Buffer(b.borrow().clone()), + Object::Pkg(p) => { + let elems: Vec = p.borrow().clone(); + let mut out = Vec::with_capacity(elems.len()); + for e in elems { + out.push(value_of(m, e, depth + 1)?); + } + Value::Package(out) + } + Object::Ref(Ref::Node(id)) => Value::Reference(m.ns.path_of(id, None)), + _ => return Err(Error::Unsupported("a reference to an unnamed object, handed to the caller")), + }) +} + +/// The header of a definition block (§5.2.6): its signature and revision, +/// once its length and checksum agree with its bytes. +fn header(t: &[u8]) -> Result<([u8; 4], u8), Error> { + if t.len() < HEADER { + return Err(Error::Table("shorter than the 36-byte header (§5.2.6)")); + } + let len = u32::from_le_bytes([t[4], t[5], t[6], t[7]]); + if usize::try_from(len).ok() != Some(t.len()) { + return Err(Error::Table("its Length is not its size (§5.2.6)")); + } + if t.iter().fold(0u8, |s, &b| s.wrapping_add(b)) != 0 { + return Err(Error::Table("its bytes do not sum to zero (§5.2.6)")); + } + let signature = [t[0], t[1], t[2], t[3]]; + if &signature != b"DSDT" && &signature != b"SSDT" { + return Err(Error::Table("not a DSDT or SSDT (§5.2.11)")); + } + Ok((signature, t[8])) +} diff --git a/userland/acpiserver/aml/src/name.rs b/userland/acpiserver/aml/src/name.rs new file mode 100644 index 00000000000..38bf5c8f5ec --- /dev/null +++ b/userland/acpiserver/aml/src/name.rs @@ -0,0 +1,87 @@ +//! Names (§5.3, §20.2.2): a 32-bit segment, and a path of them that is +//! absolute or relative to a scope by some number of parent prefixes. + +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt; + +use crate::Error; + +/// One NameSeg: `'A'-'Z'` or `'_'`, then three of `'A'-'Z'`, `'0'-'9'`, `'_'`. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Seg(pub(crate) [u8; 4]); + +impl Seg { + pub(crate) fn new(b: [u8; 4]) -> Option { + let lead = matches!(b[0], b'A'..=b'Z' | b'_'); + let rest = b[1..].iter().all(|c| matches!(c, b'A'..=b'Z' | b'0'..=b'9' | b'_')); + (lead && rest).then_some(Seg(b)) + } +} + +impl fmt::Display for Seg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for &c in &self.0 { + write!(f, "{}", char::from(c))?; + } + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct Path { + pub(crate) root: bool, + /// Parent prefixes (`^`), each one scope up from the current one. + pub(crate) up: usize, + pub(crate) segs: Vec, +} + +impl Path { + /// §5.3: the search toward the root applies to a lone NameSeg alone. + pub(crate) fn searches(&self) -> bool { + !self.root && self.up == 0 && self.segs.len() == 1 + } + + /// An absolute path written as text, `\_SB.PCI0._STA`; a segment shorter + /// than four characters is padded with `_`, as §5.3 says compilers pad. + pub(crate) fn absolute(text: &str) -> Result { + let rest = text.strip_prefix('\\').ok_or(Error::Rule("a path the caller names is not absolute"))?; + let mut segs = Vec::new(); + if !rest.is_empty() { + for part in rest.split('.') { + let b = part.as_bytes(); + if b.is_empty() || b.len() > 4 { + return Err(Error::Rule("a segment of the caller's path is not one to four characters")); + } + let mut seg = [b'_'; 4]; + seg[..b.len()].copy_from_slice(b); + segs.push(Seg::new(seg).ok_or(Error::Rule("a segment of the caller's path is not a NameSeg"))?); + } + } + Ok(Path { root: true, up: 0, segs }) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.root { + f.write_str("\\")?; + } + for _ in 0..self.up { + f.write_str("^")?; + } + let mut first = true; + for s in &self.segs { + if !first { + f.write_str(".")?; + } + first = false; + write!(f, "{s}")?; + } + Ok(()) + } +} + +pub(crate) fn text(p: &Path) -> String { + alloc::format!("{p}") +} diff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs new file mode 100644 index 00000000000..0f6c72e118c --- /dev/null +++ b/userland/acpiserver/aml/src/namespace.rs @@ -0,0 +1,195 @@ +//! The one namespace every definition block loads into (§5.3). +//! +//! Nodes live in an arena and are named by index and generation, so a +//! reference to an object a method created and its exit destroyed +//! (§5.5.2.3) resolves to nothing rather than to whatever reused its slot. +//! Nothing here recurses over the tree's depth, which a table chooses. + +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; + +use crate::name::{Path, Seg}; +use crate::object::Object; +use crate::Error; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) struct NodeId { + index: u32, + generation: u32, +} + +struct Node { + seg: Seg, + parent: Option, + children: BTreeMap, + /// An Alias (§19.6.4) acts exactly as its source, which already exists. + alias: Option, + object: Object, + generation: u32, + live: bool, +} + +pub(crate) struct Namespace { + nodes: Vec, + free: Vec, +} + +impl Namespace { + pub(crate) fn new() -> Self { + let root = Node { + seg: Seg(*b"\\___"), + parent: None, + children: BTreeMap::new(), + alias: None, + object: Object::Scope, + generation: 0, + live: true, + }; + Namespace { nodes: alloc::vec![root], free: Vec::new() } + } + + pub(crate) fn root(&self) -> NodeId { + NodeId { index: 0, generation: 0 } + } + + fn node(&self, id: NodeId) -> Option<&Node> { + self.nodes.get(id.index as usize).filter(|n| n.live && n.generation == id.generation) + } + + pub(crate) fn object(&self, id: NodeId) -> Option<&Object> { + self.node(id).map(|n| &n.object) + } + + pub(crate) fn set(&mut self, id: NodeId, object: Object) -> Result<(), Error> { + let n = self.nodes.get_mut(id.index as usize).filter(|n| n.live && n.generation == id.generation); + n.map(|n| n.object = object).ok_or(Error::NotFound(String::from("an object its method's exit destroyed"))) + } + + pub(crate) fn parent(&self, id: NodeId) -> Option { + self.node(id).and_then(|n| n.parent) + } + + /// The object named `seg` directly below `id`, an alias followed. + pub(crate) fn child(&self, id: NodeId, seg: Seg) -> Option { + let c = *self.node(id)?.children.get(&seg)?; + let n = self.node(c)?; + Some(n.alias.unwrap_or(c)).filter(|&t| self.node(t).is_some()) + } + + /// The scope a path's segments are walked from: the root, or `scope` + /// raised by its parent prefixes. A prefix above the root finds nothing + /// (§5.3). + fn start(&self, scope: NodeId, path: &Path) -> Option { + if path.root { + return Some(self.root()); + } + let mut at = scope; + for _ in 0..path.up { + at = self.parent(at)?; + } + Some(at) + } + + /// The object a path names from `scope`, by §5.3's rules: a lone NameSeg + /// is searched for in the scope and then each parent up to the root; + /// anything else is looked up exactly. + pub(crate) fn resolve(&self, scope: NodeId, path: &Path) -> Option { + let mut at = self.start(scope, path)?; + if path.searches() { + loop { + if let Some(found) = self.child(at, path.segs[0]) { + return Some(found); + } + at = self.parent(at)?; + } + } + for &seg in &path.segs { + at = self.child(at, seg)?; + } + Some(at) + } + + /// Creates the object a path names: every segment but the last must + /// exist, and the last must not (§5.3: "a name collision ... is + /// considered fatal"). + pub(crate) fn create(&mut self, scope: NodeId, path: &Path, object: Object) -> Result { + let (last, parents) = path.segs.split_last().ok_or(Error::Rule("a definition names no object"))?; + let missing = || Error::NotFound(crate::name::text(path)); + let mut at = self.start(scope, path).ok_or_else(missing)?; + for &seg in parents { + at = self.child(at, seg).ok_or_else(missing)?; + } + if self.node(at).is_some_and(|n| n.children.contains_key(last)) { + return Err(Error::Exists(self.path_of(at, Some(*last)))); + } + let node = Node { + seg: *last, + parent: Some(at), + children: BTreeMap::new(), + alias: None, + object, + generation: 0, + live: true, + }; + let id = match self.free.pop() { + Some(index) => { + let slot = &mut self.nodes[index as usize]; + let generation = slot.generation.wrapping_add(1); + *slot = Node { generation, ..node }; + NodeId { index, generation } + } + None => { + let index = u32::try_from(self.nodes.len()).map_err(|_| Error::Bound("the namespace's node count"))?; + self.nodes.push(node); + NodeId { index, generation: 0 } + } + }; + let parent = self.nodes.get_mut(at.index as usize).ok_or(Error::Rule("a parent vanished"))?; + parent.children.insert(*last, id); + Ok(id) + } + + pub(crate) fn alias(&mut self, scope: NodeId, path: &Path, target: NodeId) -> Result<(), Error> { + let id = self.create(scope, path, Object::Uninit)?; + if let Some(n) = self.nodes.get_mut(id.index as usize) { + n.alias = Some(target); + } + Ok(()) + } + + /// Destroys an object and everything below it (§5.5.2.3). + pub(crate) fn remove(&mut self, id: NodeId) { + let Some(n) = self.node(id) else { return }; + if let Some(p) = n.parent { + let seg = n.seg; + if let Some(parent) = self.nodes.get_mut(p.index as usize) { + parent.children.remove(&seg); + } + } + let mut doomed = alloc::vec![id]; + while let Some(d) = doomed.pop() { + let Some(n) = self.nodes.get_mut(d.index as usize).filter(|n| n.live && n.generation == d.generation) + else { + continue; + }; + n.live = false; + n.object = Object::Uninit; + doomed.extend(core::mem::take(&mut n.children).into_values()); + self.free.push(d.index); + } + } + + /// The absolute path of a node, and of `child` below it when given. + pub(crate) fn path_of(&self, id: NodeId, child: Option) -> String { + let mut segs: Vec = child.into_iter().collect(); + let mut at = id; + while let Some(n) = self.node(at) { + let Some(p) = n.parent else { break }; + segs.push(n.seg); + at = p; + } + segs.reverse(); + crate::name::text(&Path { root: true, up: 0, segs }) + } +} diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs new file mode 100644 index 00000000000..99723cbe353 --- /dev/null +++ b/userland/acpiserver/aml/src/object.rs @@ -0,0 +1,273 @@ +//! What the namespace and a method's locals hold, and the data conversions +//! of §19.3.5.7, which every implicit and explicit conversion applies. +//! +//! Strings, buffers and packages are shared, not copied, while a term +//! evaluates: a field created over a buffer (§19.6.21) and a reference made +//! by Index (§19.6.62) see the object they were made from. A store copies +//! (§19.3.5.8), and so does a return (§19.6.118). + +use alloc::rc::Rc; +use alloc::vec::Vec; +use core::cell::{Cell, RefCell}; + +use crate::field::{BufField, Field, Region}; +use crate::name::Path; +use crate::namespace::NodeId; +use crate::{Error, MAX_BYTES, MAX_NESTING}; + +pub(crate) type Bytes = Rc>>; +pub(crate) type Elems = Rc>>; +pub(crate) type Slot = Rc>; + +#[derive(Clone)] +pub(crate) enum Object { + Uninit, + Int(u64), + /// ASCII characters, no terminator. + Str(Bytes), + Buf(Bytes), + Pkg(Elems), + Field(Rc), + BufField(Rc), + Ref(Ref), + /// A predefined scope such as `\_SB` (§5.3.1), typeless (§19.6.96). + Scope, + Device, + ThermalZone, + PowerResource, + Method(Rc), + Mutex(Rc), + /// An Event's pending signal count (§19.6.147). + Event(Rc>), + Region(Rc), + /// A package element named by a path that did not resolve when the + /// package was evaluated; it is resolved when read (§19.6.101). + Lazy(Rc<(Path, NodeId)>), +} + +/// An object reference (§19.6.113, §19.6.62). +#[derive(Clone)] +pub(crate) enum Ref { + Node(NodeId), + /// A method's LocalX or ArgX (§19.3.5.8.1: "RefOf (ArgX) returns a + /// reference to ArgX"). + Slot(Slot), + Elem(Elems, usize), + BufField(Rc), +} + +pub(crate) enum Body { + Aml { table: Rc<[u8]>, start: usize, end: usize }, + /// `\_OSI`, which the operating system implements (§5.7.2). + Osi, +} + +pub(crate) struct Method { + pub(crate) body: Body, + pub(crate) args: u8, + pub(crate) serialized: bool, + pub(crate) sync: u8, +} + +pub(crate) struct Mutex { + pub(crate) sync: u8, + pub(crate) held: Cell, + /// `\_GL`, which also takes the firmware's Global Lock (§5.7.1). + pub(crate) global: bool, +} + +pub(crate) fn bytes(v: Vec) -> Bytes { + Rc::new(RefCell::new(v)) +} + +impl Object { + pub(crate) fn str(v: Vec) -> Object { + Object::Str(bytes(v)) + } + + pub(crate) fn buf(v: Vec) -> Object { + Object::Buf(bytes(v)) + } + + /// The value ObjectType returns (§19.6.96, Table 19.36), a reference's + /// being its target's and found by the caller. + pub(crate) fn type_code(&self) -> u64 { + match self { + Object::Uninit | Object::Scope | Object::Lazy(_) | Object::Ref(_) => 0, + Object::Int(_) => 1, + Object::Str(_) => 2, + Object::Buf(_) => 3, + Object::Pkg(_) => 4, + Object::Field(_) => 5, + Object::Device => 6, + Object::Event(_) => 7, + Object::Method(_) => 8, + Object::Mutex(_) => 9, + Object::Region(_) => 10, + Object::PowerResource => 11, + Object::ThermalZone => 13, + Object::BufField(_) => 14, + } + } +} + +/// A copy of an object for a store (§19.3.5.8): data is duplicated, anything +/// else is the same object again. Bounded in size and in nesting, both of +/// which a table can grow without limit by storing a package into itself. +pub(crate) fn copy(o: &Object) -> Result { + let mut weight = 0usize; + copy_in(o, &mut weight, 0) +} + +fn copy_in(o: &Object, weight: &mut usize, depth: usize) -> Result { + if depth > MAX_NESTING { + return Err(Error::Bound("a package nests deeper than this interpreter copies")); + } + let mut weigh = |n: usize| { + *weight = weight.saturating_add(n); + if *weight > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } + }; + Ok(match o { + Object::Str(s) => { + weigh(s.borrow().len())?; + Object::str(s.borrow().clone()) + } + Object::Buf(b) => { + weigh(b.borrow().len())?; + Object::buf(b.borrow().clone()) + } + Object::Pkg(p) => { + let p = p.borrow(); + weigh(p.len())?; + let mut out = Vec::with_capacity(p.len()); + for e in p.iter() { + out.push(copy_in(e, weight, depth + 1)?); + } + Object::Pkg(Rc::new(RefCell::new(out))) + } + other => other.clone(), + }) +} + +/// The integer width a definition block's DSDT revision gives every integer +/// (§19.6.29: "If the ComplianceRevision is less than 2, all integers are +/// restricted to 32 bits"). +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) struct Width { + pub(crate) bits: u32, +} + +impl Width { + pub(crate) fn ones(self) -> u64 { + if self.bits == 32 { u64::from(u32::MAX) } else { u64::MAX } + } + + pub(crate) fn bytes(self) -> usize { + (self.bits / 8) as usize + } + + pub(crate) fn bool(self, b: bool) -> u64 { + if b { self.ones() } else { 0 } + } + + pub(crate) fn le(self, v: u64) -> Vec { + v.to_le_bytes()[..self.bytes()].to_vec() + } + + /// Integer from Buffer (Table 19.7): least significant byte first, up to + /// the integer's width; a zero-length buffer is not allowed. + pub(crate) fn int_of_bytes(self, b: &[u8]) -> Result { + if b.is_empty() { + return Err(Error::Type("a zero-length buffer converts to no integer (Table 19.7)")); + } + Ok(b.iter().take(self.bytes()).enumerate().fold(0, |v, (i, &x)| v | u64::from(x) << (8 * i))) + } + + /// Integer from String (Table 19.7): hexadecimal digits from the first, + /// most significant, up to the first that is not one or the integer's + /// width in digits; a zero-length string is not allowed, nor a `0x`. + pub(crate) fn int_of_str(self, s: &[u8]) -> Result { + if s.is_empty() { + return Err(Error::Type("a zero-length string converts to no integer (Table 19.7)")); + } + let digits = s.iter().take(self.bytes() * 2).map_while(|&c| char::from(c).to_digit(16)); + Ok(digits.fold(0, |v, d| v << 4 | u64::from(d))) + } + + /// String from Integer (Table 19.7): the whole integer in hexadecimal, + /// 8 or 16 characters. + pub(crate) fn hex(self, v: u64) -> Vec { + let n = self.bytes() * 2; + (0..n).rev().map(|i| HEX[((v >> (4 * i)) & 0xF) as usize]).collect() + } +} + +const HEX: &[u8; 16] = b"0123456789ABCDEF"; + +/// The Integer a data object converts to (Table 19.7). +pub(crate) fn to_int(o: &Object, w: Width) -> Result { + match o { + Object::Int(v) => Ok(*v & w.ones()), + Object::Str(s) => w.int_of_str(&s.borrow()), + Object::Buf(b) => w.int_of_bytes(&b.borrow()), + Object::Uninit => Err(Error::Type("an uninitialized object is used as a source (Table 19.6)")), + _ => Err(Error::Type("an operand that converts to no integer (Table 19.6)")), + } +} + +/// The Buffer a data object converts to (Table 19.7): an integer's 4 or 8 +/// bytes, a string's characters with its terminator. +pub(crate) fn to_buf(o: &Object, w: Width) -> Result, Error> { + match o { + Object::Int(v) => Ok(w.le(*v)), + Object::Str(s) => { + let s = s.borrow(); + let mut b = s.clone(); + if !s.is_empty() { + b.push(0); + } + Ok(b) + } + Object::Buf(b) => Ok(b.borrow().clone()), + Object::Uninit => Err(Error::Type("an uninitialized object is used as a source (Table 19.6)")), + _ => Err(Error::Type("an operand that converts to no buffer (Table 19.6)")), + } +} + +/// The String a data object converts to (Table 19.7): an integer in +/// hexadecimal, a buffer as two-digit hexadecimal numbers separated by +/// spaces. +pub(crate) fn to_str(o: &Object, w: Width) -> Result, Error> { + match o { + Object::Int(v) => Ok(w.hex(*v)), + Object::Str(s) => Ok(s.borrow().clone()), + Object::Buf(b) => Ok(joined(&b.borrow(), b' ', hex2)), + Object::Uninit => Err(Error::Type("an uninitialized object is used as a source (Table 19.6)")), + _ => Err(Error::Type("an operand that converts to no string (Table 19.6)")), + } +} + +pub(crate) fn hex2(x: u8) -> Vec { + alloc::vec![HEX[usize::from(x >> 4)], HEX[usize::from(x & 0xF)]] +} + +pub(crate) fn joined(b: &[u8], sep: u8, each: fn(u8) -> Vec) -> Vec { + let mut out = Vec::new(); + for (i, &x) in b.iter().enumerate() { + if i > 0 { + out.push(sep); + } + out.extend(each(x)); + } + out +} + +pub(crate) fn decimal(v: u64) -> Vec { + alloc::format!("{v}").into_bytes() +} + +/// Bytes fitted to `len` bytes: truncated, or zero-extended. +pub(crate) fn fit(mut b: Vec, len: usize) -> Vec { + b.resize(len, 0); + b +} diff --git a/userland/acpiserver/aml/src/stream.rs b/userland/acpiserver/aml/src/stream.rs new file mode 100644 index 00000000000..96b6b21afbd --- /dev/null +++ b/userland/acpiserver/aml/src/stream.rs @@ -0,0 +1,154 @@ +//! A cursor over a definition block's bytes: the fixed-size data, PkgLength +//! (§20.2.4) and NameString (§20.2.2) encodings. Every read is bounded by the +//! end of the term list it is in, and a read past it is a refusal. + +use alloc::vec::Vec; + +use crate::name::{Path, Seg}; +use crate::Error; + +#[derive(Clone)] +pub(crate) struct Cursor<'a> { + pub(crate) bytes: &'a [u8], + pub(crate) at: usize, + pub(crate) end: usize, +} + +impl<'a> Cursor<'a> { + pub(crate) fn new(bytes: &'a [u8], at: usize, end: usize) -> Self { + Cursor { bytes, at, end } + } + + pub(crate) fn malformed(&self, why: &'static str) -> Error { + Error::Malformed { at: self.at, why } + } + + pub(crate) fn done(&self) -> bool { + self.at >= self.end + } + + pub(crate) fn peek(&self) -> Result { + if self.at < self.end { + self.bytes.get(self.at).copied().ok_or(self.malformed("a term runs past the table")) + } else { + Err(self.malformed("a term runs past its enclosing package")) + } + } + + /// The byte after the next, for the two-byte `ExtOpPrefix` opcodes. + pub(crate) fn peek2(&self) -> Result { + let at = self.at + 1; + if at < self.end { + self.bytes.get(at).copied().ok_or(self.malformed("a term runs past the table")) + } else { + Err(self.malformed("a term runs past its enclosing package")) + } + } + + pub(crate) fn byte(&mut self) -> Result { + let b = self.peek()?; + self.at += 1; + Ok(b) + } + + fn le(&mut self, n: usize) -> Result { + let mut v = 0u64; + for i in 0..n { + v |= u64::from(self.byte()?) << (8 * i); + } + Ok(v) + } + + pub(crate) fn word(&mut self) -> Result { + Ok(self.le(2)? as u16) + } + + pub(crate) fn dword(&mut self) -> Result { + Ok(self.le(4)? as u32) + } + + pub(crate) fn qword(&mut self) -> Result { + self.le(8) + } + + /// The value of a PkgLength (§20.2.4), and the offset of its lead byte. + pub(crate) fn pkg_value(&mut self) -> Result<(usize, usize), Error> { + let start = self.at; + let lead = self.byte()?; + let follow = usize::from(lead >> 6); + if follow == 0 { + return Ok((start, usize::from(lead & 0x3F))); + } + // §20.2.4: bits 5-4 of a multi-byte lead are reserved and must be zero. + if lead & 0x30 != 0 { + return Err(Error::Malformed { at: start, why: "a multi-byte PkgLength sets bits 5-4 of its lead byte" }); + } + let mut len = usize::from(lead & 0x0F); + for i in 0..follow { + len |= usize::from(self.byte()?) << (4 + 8 * i); + } + Ok((start, len)) + } + + /// A PkgLength that bounds the rest of its term: the offset it ends at, + /// which lies within the enclosing term list (§5.4.1: "it is fatal for a + /// package length to not fall on a logical boundary"). + pub(crate) fn pkg_end(&mut self) -> Result { + let (start, len) = self.pkg_value()?; + let end = start.checked_add(len).ok_or(Error::Malformed { at: start, why: "a PkgLength overflows" })?; + if end < self.at || end > self.end { + return Err(Error::Malformed { at: start, why: "a PkgLength does not end within its enclosing term" }); + } + Ok(end) + } + + pub(crate) fn seg(&mut self) -> Result { + let at = self.at; + let b = [self.byte()?, self.byte()?, self.byte()?, self.byte()?]; + Seg::new(b).ok_or(Error::Malformed { at, why: "a NameSeg holds a character §5.3 does not allow" }) + } + + /// NameString := | (§20.2.2). + pub(crate) fn name(&mut self) -> Result { + let mut root = false; + let mut up = 0usize; + if self.peek()? == b'\\' { + self.at += 1; + root = true; + } else { + while self.peek()? == b'^' { + self.at += 1; + up += 1; + } + } + let count = match self.peek()? { + 0x00 => { + self.at += 1; + 0 + } + 0x2E => { + self.at += 1; + 2 + } + 0x2F => { + self.at += 1; + // §20.2.2: SegCount can be from 1 to 255. + match self.byte()? { + 0 => return Err(self.malformed("a MultiNamePath counts zero segments")), + n => usize::from(n), + } + } + _ => 1, + }; + let mut segs = Vec::with_capacity(count); + for _ in 0..count { + segs.push(self.seg()?); + } + Ok(Path { root, up, segs }) + } +} + +/// The first byte of a NameString (§20.2.2), as opposed to an opcode. +pub(crate) fn starts_name(b: u8) -> bool { + matches!(b, b'A'..=b'Z' | b'_' | b'\\' | b'^' | 0x2E | 0x2F) +} diff --git a/userland/acpiserver/aml/tests/common/mod.rs b/userland/acpiserver/aml/tests/common/mod.rs new file mode 100644 index 00000000000..f44d04ace9b --- /dev/null +++ b/userland/acpiserver/aml/tests/common/mod.rs @@ -0,0 +1,420 @@ +//! AML assembled by hand from the encodings of §20.2, and a host that +//! records what the interpreter asks of it. + +#![allow(dead_code)] + +use std::collections::BTreeMap; + +use toyos_aml::{Access, Address, Denied, Host, Interpreter, Value}; + +/// PkgLength (§20.2.4) of `n` bytes that follow it: the length counts its +/// own encoding. +pub fn pkg(body: &[u8]) -> Vec { + let n = body.len(); + let mut out = if n < 0x3F { + vec![(n + 1) as u8] + } else { + let follow = if n + 2 <= 0xFFF { 1 } else if n + 3 <= 0xF_FFFF { 2 } else { 3 }; + let v = n + 1 + follow; + let mut b = vec![((follow as u8) << 6) | (v & 0xF) as u8]; + for i in 0..follow { + b.push((v >> (4 + 8 * i)) as u8); + } + b + }; + out.extend_from_slice(body); + out +} + +/// A NameString (§20.2.2) from text: `\`, `^`, and `.`-separated segments +/// padded with `_`. +pub fn name(text: &str) -> Vec { + let mut out = Vec::new(); + let mut rest = text; + if let Some(r) = rest.strip_prefix('\\') { + out.push(b'\\'); + rest = r; + } + while let Some(r) = rest.strip_prefix('^') { + out.push(b'^'); + rest = r; + } + let segs: Vec<[u8; 4]> = if rest.is_empty() { + Vec::new() + } else { + rest.split('.') + .map(|s| { + let mut seg = [b'_'; 4]; + seg[..s.len()].copy_from_slice(s.as_bytes()); + seg + }) + .collect() + }; + match segs.len() { + 0 => out.push(0x00), + 1 => {} + 2 => out.push(0x2E), + n => out.extend([0x2F, n as u8]), + } + for s in segs { + out.extend(s); + } + out +} + +pub fn cat(parts: &[&[u8]]) -> Vec { + parts.concat() +} + +pub fn int(v: u64) -> Vec { + match v { + 0 => vec![0x00], + 1 => vec![0x01], + 2..=0xFF => vec![0x0A, v as u8], + 0x100..=0xFFFF => cat(&[&[0x0B], &(v as u16).to_le_bytes()]), + 0x1_0000..=0xFFFF_FFFF => cat(&[&[0x0C], &(v as u32).to_le_bytes()]), + _ => cat(&[&[0x0E], &v.to_le_bytes()]), + } +} + +pub fn ones() -> Vec { + vec![0xFF] +} + +pub fn string(s: &str) -> Vec { + cat(&[&[0x0D], s.as_bytes(), &[0]]) +} + +pub fn buffer(size: &[u8], init: &[u8]) -> Vec { + cat(&[&[0x11], &pkg(&cat(&[size, init]))]) +} + +pub fn package(elems: &[Vec]) -> Vec { + let mut body = vec![elems.len() as u8]; + for e in elems { + body.extend(e); + } + cat(&[&[0x12], &pkg(&body)]) +} + +pub fn var_package(count: &[u8], elems: &[Vec]) -> Vec { + let mut body = count.to_vec(); + for e in elems { + body.extend(e); + } + cat(&[&[0x13], &pkg(&body)]) +} + +pub fn local(i: u8) -> Vec { + vec![0x60 + i] +} + +pub fn arg(i: u8) -> Vec { + vec![0x68 + i] +} + +pub fn debug() -> Vec { + vec![0x5B, 0x31] +} + +pub fn def_name(n: &str, v: &[u8]) -> Vec { + cat(&[&[0x08], &name(n), v]) +} + +pub fn scope(n: &str, body: &[u8]) -> Vec { + cat(&[&[0x10], &pkg(&cat(&[&name(n), body]))]) +} + +pub fn device(n: &str, body: &[u8]) -> Vec { + cat(&[&[0x5B, 0x82], &pkg(&cat(&[&name(n), body]))]) +} + +pub fn thermal_zone(n: &str, body: &[u8]) -> Vec { + cat(&[&[0x5B, 0x85], &pkg(&cat(&[&name(n), body]))]) +} + +pub fn power_resource(n: &str, body: &[u8]) -> Vec { + cat(&[&[0x5B, 0x84], &pkg(&cat(&[&name(n), &[0x00, 0x00, 0x00], body]))]) +} + +/// MethodFlags (§20.2.5.2): ArgCount, SerializeFlag, SyncLevel. +pub fn method_flags(n: &str, flags: u8, body: &[u8]) -> Vec { + cat(&[&[0x14], &pkg(&cat(&[&name(n), &[flags], body]))]) +} + +pub fn method(n: &str, args: u8, body: &[u8]) -> Vec { + method_flags(n, args, body) +} + +pub fn ret(v: &[u8]) -> Vec { + cat(&[&[0xA4], v]) +} + +pub fn store(v: &[u8], t: &[u8]) -> Vec { + cat(&[&[0x70], v, t]) +} + +pub fn copy_object(v: &[u8], t: &[u8]) -> Vec { + cat(&[&[0x9D], v, t]) +} + +/// One of the `Operand Operand Target` opcodes (§20.2.5.4). +pub fn op2(op: u8, a: &[u8], b: &[u8], t: &[u8]) -> Vec { + cat(&[&[op], a, b, t]) +} + +pub fn op1(op: u8, a: &[u8], t: &[u8]) -> Vec { + cat(&[&[op], a, t]) +} + +pub fn add(a: &[u8], b: &[u8], t: &[u8]) -> Vec { + op2(0x72, a, b, t) +} + +pub fn lequal(a: &[u8], b: &[u8]) -> Vec { + cat(&[&[0x93], a, b]) +} + +pub fn lless(a: &[u8], b: &[u8]) -> Vec { + cat(&[&[0x95], a, b]) +} + +pub fn lgreater(a: &[u8], b: &[u8]) -> Vec { + cat(&[&[0x94], a, b]) +} + +pub fn lnot(a: &[u8]) -> Vec { + cat(&[&[0x92], a]) +} + +pub fn if_(pred: &[u8], body: &[u8]) -> Vec { + cat(&[&[0xA0], &pkg(&cat(&[pred, body]))]) +} + +pub fn else_(body: &[u8]) -> Vec { + cat(&[&[0xA1], &pkg(body)]) +} + +pub fn while_(pred: &[u8], body: &[u8]) -> Vec { + cat(&[&[0xA2], &pkg(&cat(&[pred, body]))]) +} + +pub fn increment(t: &[u8]) -> Vec { + cat(&[&[0x75], t]) +} + +pub fn index(src: &[u8], i: &[u8], t: &[u8]) -> Vec { + cat(&[&[0x88], src, i, t]) +} + +pub fn deref(r: &[u8]) -> Vec { + cat(&[&[0x83], r]) +} + +pub fn ref_of(n: &[u8]) -> Vec { + cat(&[&[0x71], n]) +} + +pub fn size_of(n: &[u8]) -> Vec { + cat(&[&[0x87], n]) +} + +pub fn object_type(n: &[u8]) -> Vec { + cat(&[&[0x8E], n]) +} + +pub fn op_region(n: &str, space: u8, offset: &[u8], len: &[u8]) -> Vec { + cat(&[&[0x5B, 0x80], &name(n), &[space], offset, len]) +} + +/// A field list entry: a named unit of `bits`. +pub fn unit(n: &str, bits: usize) -> Vec { + let mut seg = [b'_'; 4]; + seg[..n.len()].copy_from_slice(n.as_bytes()); + cat(&[&seg, &pkg_value(bits)]) +} + +/// A ReservedField (§20.2.5.2) skipping `bits`. +pub fn skip(bits: usize) -> Vec { + cat(&[&[0x00], &pkg_value(bits)]) +} + +/// The PkgLength encoding of a bare value, as a field list uses it. +pub fn pkg_value(v: usize) -> Vec { + if v <= 0x3F { + vec![v as u8] + } else { + let follow = if v <= 0xFFF { 1 } else if v <= 0xF_FFFF { 2 } else { 3 }; + let mut b = vec![((follow as u8) << 6) | (v & 0xF) as u8]; + for i in 0..follow { + b.push((v >> (4 + 8 * i)) as u8); + } + b + } +} + +pub fn field(region: &str, flags: u8, units: &[Vec]) -> Vec { + cat(&[&[0x5B, 0x81], &pkg(&cat(&[&name(region), &[flags], &units.concat()]))]) +} + +pub fn index_field(index: &str, data: &str, flags: u8, units: &[Vec]) -> Vec { + cat(&[&[0x5B, 0x86], &pkg(&cat(&[&name(index), &name(data), &[flags], &units.concat()]))]) +} + +pub fn bank_field(region: &str, bank: &str, value: &[u8], flags: u8, units: &[Vec]) -> Vec { + cat(&[&[0x5B, 0x87], &pkg(&cat(&[&name(region), &name(bank), value, &[flags], &units.concat()]))]) +} + +/// A definition block (§20.2.1, §5.2.6) with its checksum. +pub fn table(signature: &[u8; 4], revision: u8, body: &[u8]) -> Vec { + let len = (36 + body.len()) as u32; + let mut t = Vec::new(); + t.extend(signature); + t.extend(len.to_le_bytes()); + t.push(revision); + t.push(0); + t.extend(b"TOYOS "); + t.extend(b"TESTTABL"); + t.extend(1u32.to_le_bytes()); + t.extend(b"TOYO"); + t.extend(1u32.to_le_bytes()); + t.extend(body); + let sum = t.iter().fold(0u8, |s, &b| s.wrapping_add(b)); + t[9] = 0u8.wrapping_sub(sum); + t +} + +pub fn dsdt(body: &[u8]) -> Vec { + table(b"DSDT", 2, body) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + Read(Address, Access), + Write(Address, Access, u64), + Sleep(u64), + Stall(u64), + Notify(String, u64), + GlobalLock(bool), +} + +/// A machine of bytes: every address space a sparse map, every request +/// recorded. +#[derive(Default)] +pub struct Machine { + pub bytes: BTreeMap<(u8, u64), u8>, + pub log: Vec, + pub refuse: bool, +} + +fn key(a: Address) -> (u8, u64) { + match a { + Address::Memory(x) => (0, x), + Address::Io(x) => (1, x), + Address::PciConfig { segment, bus, device, function, offset } => ( + 2, + (u64::from(segment) << 32) + | (u64::from(bus) << 24) + | (u64::from(device) << 19) + | (u64::from(function) << 16) + | u64::from(offset), + ), + Address::EmbeddedControl(x) => (3, u64::from(x)), + } +} + +fn bytes(w: Access) -> u64 { + match w { + Access::Byte => 1, + Access::Word => 2, + Access::DWord => 4, + Access::QWord => 8, + } +} + +impl Machine { + pub fn poke(&mut self, a: Address, v: &[u8]) { + let (s, x) = key(a); + for (i, b) in v.iter().enumerate() { + self.bytes.insert((s, x + i as u64), *b); + } + } + + pub fn peek(&self, a: Address, n: usize) -> Vec { + let (s, x) = key(a); + (0..n).map(|i| *self.bytes.get(&(s, x + i as u64)).unwrap_or(&0)).collect() + } + + pub fn accesses(&self) -> Vec { + self.log.iter().filter(|e| matches!(e, Event::Read(..) | Event::Write(..))).cloned().collect() + } +} + +impl Host for Machine { + fn read(&mut self, at: Address, width: Access) -> Result { + self.log.push(Event::Read(at, width)); + if self.refuse { + return Err(Denied("refused".into())); + } + let (s, x) = key(at); + Ok((0..bytes(width)).fold(0, |v, i| v | u64::from(*self.bytes.get(&(s, x + i)).unwrap_or(&0)) << (8 * i))) + } + + fn write(&mut self, at: Address, width: Access, value: u64) -> Result<(), Denied> { + self.log.push(Event::Write(at, width, value)); + if self.refuse { + return Err(Denied("refused".into())); + } + let (s, x) = key(at); + for i in 0..bytes(width) { + self.bytes.insert((s, x + i), (value >> (8 * i)) as u8); + } + Ok(()) + } + + fn sleep(&mut self, ms: u64) { + self.log.push(Event::Sleep(ms)); + } + + fn stall(&mut self, us: u64) { + self.log.push(Event::Stall(us)); + } + + fn timer(&mut self) -> u64 { + 0 + } + + fn notify(&mut self, object: &str, value: u64) { + self.log.push(Event::Notify(object.into(), value)); + } + + fn global_lock(&mut self, take: bool) -> Result<(), Denied> { + self.log.push(Event::GlobalLock(take)); + Ok(()) + } +} + +/// An interpreter with one DSDT of `body` loaded. +pub fn loaded(body: &[u8]) -> (Interpreter, Machine) { + let mut m = Machine::default(); + let mut i = Interpreter::new(); + i.load(&mut m, &dsdt(body)).expect("the DSDT loads"); + (i, m) +} + +/// The value of `\RES` after running method `\MAIN`, whose body is given. +pub fn run(body: &[u8]) -> Value { + let (mut i, mut m) = loaded(&cat(&[&def_name("RES", &int(0)), &method("MAIN", 0, body)])); + i.evaluate(&mut m, "\\MAIN", &[]).expect("MAIN runs"); + i.evaluate(&mut m, "\\RES", &[]).expect("RES reads") +} + +/// What method `\MAIN`, whose body is given, returns. +pub fn returns(body: &[u8]) -> Result { + let (mut i, mut m) = loaded(&method("MAIN", 0, body)); + i.evaluate(&mut m, "\\MAIN", &[]) +} + +pub fn s(text: &str) -> Value { + Value::String(text.as_bytes().to_vec()) +} diff --git a/userland/acpiserver/aml/tests/evaluate.rs b/userland/acpiserver/aml/tests/evaluate.rs new file mode 100644 index 00000000000..fe2cfef0ef9 --- /dev/null +++ b/userland/acpiserver/aml/tests/evaluate.rs @@ -0,0 +1,491 @@ +//! Control methods evaluated (§5.5.2, §19.3.5, §19.6, §20.2.5.3-4). + +mod common; + +use common::*; +use toyos_aml::{Error, Value}; + +const ZERO: &[u8] = &[0x00]; + +fn i(v: u64) -> Result { + Ok(Value::Integer(v)) +} + +fn b(v: &[u8]) -> Result { + Ok(Value::Buffer(v.to_vec())) +} + +fn st(v: &str) -> Result { + Ok(s(v)) +} + +const ONES: u64 = u64::MAX; + +#[test] +fn integer_arithmetic_wraps_at_the_integer_width() { + assert_eq!(returns(&ret(&add(&int(u64::MAX), &int(2), ZERO))), i(1)); + assert_eq!(returns(&ret(&op2(0x74, &int(1), &int(2), ZERO))), i(ONES)); + assert_eq!(returns(&ret(&op2(0x77, &int(6), &int(7), ZERO))), i(42)); + assert_eq!(returns(&ret(&op2(0x79, &int(1), &int(63), ZERO))), i(1 << 63)); + assert_eq!(returns(&ret(&op2(0x79, &int(1), &int(64), ZERO))), i(0)); + assert_eq!(returns(&ret(&op2(0x7A, &int(0x80), &int(4), ZERO))), i(8)); + assert_eq!(returns(&ret(&op2(0x7A, &int(0x80), &int(200), ZERO))), i(0)); + assert_eq!(returns(&ret(&op2(0x7B, &int(0xF0), &int(0x3C), ZERO))), i(0x30)); + assert_eq!(returns(&ret(&op2(0x7C, &int(0xF0), &int(0x3C), ZERO))), i(!0x30)); + assert_eq!(returns(&ret(&op2(0x7D, &int(0xF0), &int(0x0F), ZERO))), i(0xFF)); + assert_eq!(returns(&ret(&op2(0x7E, &int(0xF0), &int(0x0F), ZERO))), i(!0xFF)); + assert_eq!(returns(&ret(&op2(0x7F, &int(0xFF), &int(0x0F), ZERO))), i(0xF0)); + assert_eq!(returns(&ret(&op2(0x85, &int(17), &int(5), ZERO))), i(2)); + assert_eq!(returns(&ret(&op1(0x80, &int(0), ZERO))), i(ONES)); + // FindSetLeftBit and FindSetRightBit: one-based, zero for none (§19.6.48-49). + assert_eq!(returns(&ret(&op1(0x81, &int(0x50), ZERO))), i(7)); + assert_eq!(returns(&ret(&op1(0x82, &int(0x50), ZERO))), i(5)); + assert_eq!(returns(&ret(&op1(0x81, &int(0), ZERO))), i(0)); + assert_eq!(returns(&ret(&op1(0x82, &int(0), ZERO))), i(0)); +} + +#[test] +fn divide_stores_remainder_and_quotient_and_refuses_zero() { + let body = cat(&[&[0x78], &int(17), &int(5), &local(0), &local(1), &ret(&package_of_locals())]); + fn package_of_locals() -> Vec { + add(&op2(0x77, &local(1), &int(0x100), ZERO), &local(0), ZERO) + } + assert_eq!(returns(&body), i(0x302)); + assert_eq!(returns(&ret(&cat(&[&[0x78], &int(1), &int(0), ZERO, ZERO]))), Err(Error::Rule("Divide by zero (§19.6.32)"))); + assert_eq!(returns(&ret(&op2(0x85, &int(1), &int(0), ZERO))), Err(Error::Rule("Mod by zero (§19.6.86)"))); +} + +#[test] +fn bcd_converts_both_ways_and_refuses_what_is_not_bcd() { + assert_eq!(returns(&ret(&cat(&[&[0x5B, 0x29], &int(1234), ZERO]))), i(0x1234)); + assert_eq!(returns(&ret(&cat(&[&[0x5B, 0x28], &int(0x1234), ZERO]))), i(1234)); + assert!(matches!(returns(&ret(&cat(&[&[0x5B, 0x28], &int(0x1A), ZERO]))), Err(Error::Rule(_)))); +} + +#[test] +fn logical_operators_answer_ones_or_zero() { + assert_eq!(returns(&ret(&cat(&[&[0x90], &int(1), &int(2)]))), i(ONES)); + assert_eq!(returns(&ret(&cat(&[&[0x90], &int(1), &int(0)]))), i(0)); + assert_eq!(returns(&ret(&cat(&[&[0x91], &int(0), &int(2)]))), i(ONES)); + assert_eq!(returns(&ret(&lnot(&int(0)))), i(ONES)); + // LNotEqual, LLessEqual and LGreaterEqual are LNot of the others (§20.2.5.4). + assert_eq!(returns(&ret(&lnot(&lequal(&int(1), &int(2))))), i(ONES)); + assert_eq!(returns(&ret(&lnot(&lgreater(&int(2), &int(2))))), i(ONES)); + assert_eq!(returns(&ret(&lnot(&lless(&int(3), &int(2))))), i(ONES)); +} + +/// §19.6.69-72: the first operand's type decides; strings and buffers +/// compare byte by byte, an equal shorter one the lesser. +#[test] +fn comparisons_follow_the_first_operands_type() { + assert_eq!(returns(&ret(&lequal(&string("ABC"), &string("ABC")))), i(ONES)); + assert_eq!(returns(&ret(&lless(&string("AB"), &string("ABC")))), i(ONES)); + assert_eq!(returns(&ret(&lgreater(&string("B"), &string("ABC")))), i(ONES)); + assert_eq!(returns(&ret(&lequal(&int(0x1234), &string("1234")))), i(ONES)); + assert_eq!(returns(&ret(&lequal(&string("00000012"), &int(0x12)))), i(0)); + assert_eq!(returns(&ret(&lequal(&string("0000000000000012"), &int(0x12)))), i(ONES)); + assert_eq!(returns(&ret(&lequal(&buffer(&int(2), &[1, 2]), &int(0x0201)))), i(0)); + assert_eq!(returns(&ret(&lequal(&buffer(&int(8), &[1, 2]), &int(0x0201)))), i(ONES)); + assert!(matches!(returns(&ret(&lequal(&package(&[]), &int(0)))), Err(Error::Type(_)))); +} + +#[test] +fn if_else_and_while_with_break_and_continue() { + let count = cat(&[ + &store(&int(0), &local(0)), + &store(&int(0), &local(1)), + &while_( + &lless(&local(0), &int(10)), + &cat(&[ + &increment(&local(0)), + &if_(&lequal(&local(0), &int(3)), &[0x9F]), + &if_(&lequal(&local(0), &int(8)), &[0xA5]), + &add(&local(1), &local(0), &local(1)), + ]), + ), + &ret(&local(1)), + ]); + // 1+2+4+5+6+7: 3 continued past, 8 broke out. + assert_eq!(returns(&count), i(25)); + let branch = |v: u64| { + cat(&[ + &if_(&lequal(&int(v), &int(1)), &ret(&string("one"))), + &else_(&cat(&[&if_(&lequal(&int(v), &int(2)), &ret(&string("two"))), &else_(&ret(&string("other")))])), + ]) + }; + assert_eq!(returns(&branch(1)), st("one")); + assert_eq!(returns(&branch(2)), st("two")); + assert_eq!(returns(&branch(3)), st("other")); +} + +#[test] +fn break_and_continue_outside_a_while_are_refused() { + assert!(matches!(returns(&[0xA5]), Err(Error::Rule(_)))); + assert!(matches!(returns(&if_(&int(1), &[0x9F])), Err(Error::Rule(_)))); +} + +#[test] +fn methods_take_their_declared_arguments_and_return_a_copy() { + let (mut ip, mut m) = loaded(&cat(&[ + &method("SUM3", 3, &ret(&add(&add(&arg(0), &arg(1), ZERO), &arg(2), ZERO))), + &method("MAIN", 0, &ret(&cat(&[&name("SUM3"), &int(1), &int(2), &int(3)]))), + &def_name("BUF", &buffer(&int(2), &[7, 8])), + &method("GETB", 0, &ret(&name("BUF"))), + &method("POKE", 0, &cat(&[&store(&name("GETB"), &local(0)), &store(&int(9), &index(&local(0), &int(0), ZERO))])), + ])); + assert_eq!(ip.evaluate(&mut m, "\\MAIN", &[]), i(6)); + ip.evaluate(&mut m, "\\POKE", &[]).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\BUF", &[]), b(&[7, 8])); +} + +#[test] +fn a_method_without_return_returns_nothing_usable() { + assert_eq!(returns(&[0xA3]), Ok(Value::Uninitialized)); + let (mut ip, mut m) = loaded(&cat(&[&method("NONE", 0, &[]), &method("MAIN", 0, &ret(&add(&name("NONE"), &int(1), ZERO)))])); + assert!(matches!(ip.evaluate(&mut m, "\\MAIN", &[]), Err(Error::Type(_)))); +} + +#[test] +fn recursion_runs_until_its_bound() { + let fact = method( + "FACT", + 1, + &cat(&[ + &if_(&lless(&arg(0), &int(2)), &ret(&int(1))), + &ret(&op2(0x77, &arg(0), &cat(&[&name("FACT"), &op2(0x74, &arg(0), &int(1), ZERO)]), ZERO)), + ]), + ); + let (mut ip, mut m) = loaded(&fact); + assert_eq!(ip.evaluate(&mut m, "\\FACT", &[Value::Integer(10)]), i(3_628_800)); + assert!(matches!(ip.evaluate(&mut m, "\\FACT", &[Value::Integer(100_000)]), Err(Error::Bound(_)))); +} + +/// Table 19.7, each conversion an operator applies to a source operand. +#[test] +fn source_operands_convert_by_the_conversion_rules() { + // String to Integer: hexadecimal up to the first non-digit. + assert_eq!(returns(&ret(&add(&string("1F"), &int(1), ZERO))), i(0x20)); + assert_eq!(returns(&ret(&add(&string("12G4"), &int(0), ZERO))), i(0x12)); + assert!(matches!(returns(&ret(&add(&string(""), &int(0), ZERO))), Err(Error::Type(_)))); + // Buffer to Integer: least significant byte first, up to eight. + assert_eq!(returns(&ret(&add(&buffer(&int(2), &[0x34, 0x12]), &int(0), ZERO))), i(0x1234)); + assert_eq!(returns(&ret(&add(&buffer(&int(9), &[1, 0, 0, 0, 0, 0, 0, 0, 9]), &int(0), ZERO))), i(1)); + assert!(matches!(returns(&ret(&add(&buffer(&int(0), &[]), &int(0), ZERO))), Err(Error::Type(_)))); + // A package converts to no integer, nor does an uninitialized local. + assert!(matches!(returns(&ret(&add(&package(&[]), &int(0), ZERO))), Err(Error::Type(_)))); + assert!(matches!(returns(&ret(&add(&local(3), &int(0), ZERO))), Err(Error::Type(_)))); +} + +#[test] +fn explicit_conversions_follow_their_definitions() { + let conv = |op: u8, v: &[u8]| returns(&ret(&cat(&[&[op], v, ZERO]))); + assert_eq!(conv(0x96, &int(0x0102)), b(&[2, 1, 0, 0, 0, 0, 0, 0])); + assert_eq!(conv(0x96, &string("AB")), b(&[0x41, 0x42, 0])); + assert_eq!(conv(0x96, &string("")), b(&[])); + assert_eq!(conv(0x97, &int(1234)), st("1234")); + assert_eq!(conv(0x97, &buffer(&int(3), &[1, 20, 255])), st("1,20,255")); + assert_eq!(conv(0x98, &int(0xAB)), st("00000000000000AB")); + assert_eq!(conv(0x98, &buffer(&int(2), &[0xAB, 0x01])), st("AB,01")); + assert_eq!(conv(0x99, &string("0x1F")), i(0x1F)); + assert_eq!(conv(0x99, &string("123")), i(123)); + assert!(matches!(conv(0x99, &string("12z")), Err(Error::Type(_)))); + assert!(matches!(conv(0x99, &string("99999999999999999999")), Err(Error::Rule(_)))); + assert_eq!(conv(0x99, &buffer(&int(2), &[0x34, 0x12])), i(0x1234)); + // ToString: up to Length bytes or a NUL (§19.6.141). + assert_eq!(returns(&ret(&cat(&[&[0x9C], &buffer(&int(5), b"ab\0cd"), &ones(), ZERO]))), st("ab")); + assert_eq!(returns(&ret(&cat(&[&[0x9C], &buffer(&int(4), b"abcd"), &int(3), ZERO]))), st("abc")); + // Buffer to String through Concatenate's second operand (Table 19.7). + assert_eq!(returns(&ret(&op2(0x73, &string(">"), &buffer(&int(2), &[0x0A, 0xFF]), ZERO))), st(">0A FF")); +} + +/// Table 19.30, and the example of §19.3.5.4. +#[test] +fn concatenate_takes_the_first_operands_type() { + assert_eq!(returns(&ret(&op2(0x73, &int(1), &string("2"), ZERO))), b(&[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0])); + assert_eq!(returns(&ret(&op2(0x73, &string("AB"), &int(0x1F), ZERO))), st("AB000000000000001F")); + assert_eq!(returns(&ret(&op2(0x73, &buffer(&int(1), &[1]), &string("A"), ZERO))), b(&[1, 0x41, 0])); + let (mut ip, mut m) = loaded(&cat(&[ + &device("DEVX", &[]), + &method("MAIN", 0, &ret(&op2(0x73, &string("My Object: "), &name("DEVX"), ZERO))), + &def_name("ABCD", &buffer(&int(10), &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0])), + &cat(&[&[0x8A], &name("ABCD"), &int(2), &name("XYZ")]), + &def_name("MNOP", &string("1234")), + &method("EXAM", 0, &ret(&op2(0x73, &name("XYZ"), &name("MNOP"), ZERO))), + ])); + assert_eq!(ip.evaluate(&mut m, "\\MAIN", &[]), st("My Object: [Device]")); + // §19.3.5.4: both convert to Integers and join as a Buffer. Its text + // gives XYZ as 0x05040302, the DWord at byte index 1; §19.6.20 defines + // the field at byte index 2, which holds 3, 4, 5, 6. + assert_eq!(ip.evaluate(&mut m, "\\EXAM", &[]), b(&[3, 4, 5, 6, 0, 0, 0, 0, 0x34, 0x12, 0, 0, 0, 0, 0, 0])); +} + +#[test] +fn mid_takes_what_lies_within() { + let mid = |src: &[u8], at: u64, n: u64| returns(&ret(&cat(&[&[0x9E], src, &int(at), &int(n), ZERO]))); + assert_eq!(mid(&string("ABCDEF"), 1, 3), st("BCD")); + assert_eq!(mid(&string("ABCDEF"), 4, 100), st("EF")); + assert_eq!(mid(&string("ABCDEF"), 10, 1), st("")); + assert_eq!(mid(&buffer(&int(3), &[1, 2, 3]), 1, 1), b(&[2])); +} + +/// ConcatenateResTemplate (§19.6.13), with the End Tag of §6.4.2.9. +#[test] +fn resource_templates_join_under_one_end_tag() { + let irq = buffer(&int(5), &[0x22, 0x10, 0x00, 0x79, 0x00]); + let io = buffer(&int(2), &[0x79, 0x00]); + let r = returns(&ret(&op2(0x84, &irq, &io, ZERO))).unwrap(); + let Value::Buffer(v) = r else { panic!("{r:?}") }; + assert_eq!(&v[..4], &[0x22, 0x10, 0x00, 0x79]); + assert_eq!(v.iter().fold(0u8, |s, &b| s.wrapping_add(b)), 0); + assert!(matches!(returns(&ret(&op2(0x84, &buffer(&int(1), &[0x79]), &io, ZERO))), Err(Error::Rule(_)))); +} + +/// Table 19.8 and §19.3.5.8: a store converts to a named object's type, and +/// a named buffer keeps its size; CopyObject takes the source's type. +#[test] +fn store_converts_to_the_named_type_and_copy_object_does_not() { + let (mut ip, mut m) = loaded(&cat(&[ + &def_name("NUM", &int(0)), + &def_name("STR", &string("x")), + &def_name("BUF", &buffer(&int(4), &[])), + &def_name("PKG", &package(&[int(1)])), + &method( + "MAIN", + 0, + &cat(&[ + &store(&string("1F"), &name("NUM")), + &store(&int(0xAB), &name("STR")), + &store(&string("ABCDEF"), &name("BUF")), + &store(&package(&[int(2), int(3)]), &name("PKG")), + ]), + ), + &method("CPY", 0, ©_object(&string("now a string"), &name("NUM"))), + &method("BAD", 0, &store(&int(1), &name("PKG"))), + ])); + ip.evaluate(&mut m, "\\MAIN", &[]).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\NUM", &[]), i(0x1F)); + assert_eq!(ip.evaluate(&mut m, "\\STR", &[]), st("00000000000000AB")); + assert_eq!(ip.evaluate(&mut m, "\\BUF", &[]), b(b"ABCD")); + assert_eq!(ip.evaluate(&mut m, "\\PKG", &[]), Ok(Value::Package(vec![Value::Integer(2), Value::Integer(3)]))); + ip.evaluate(&mut m, "\\CPY", &[]).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\NUM", &[]), st("now a string")); + assert!(matches!(ip.evaluate(&mut m, "\\BAD", &[]), Err(Error::Type(_)))); +} + +/// §5.5.2.2 and Table 19.10: a store to an ArgX replaces it, unless it holds +/// a reference, which it stores through. +#[test] +fn an_arg_holding_a_reference_stores_through_it() { + let (mut ip, mut m) = loaded(&cat(&[ + &def_name("OBJ", &int(1)), + &method("SET", 1, &store(&int(5), &arg(0))), + &method("BREF", 0, &cat(&[&name("SET"), &ref_of(&name("OBJ"))])), + &method("BVAL", 0, &cat(&[&name("SET"), &name("OBJ")])), + ])); + ip.evaluate(&mut m, "\\BVAL", &[]).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\OBJ", &[]), i(1)); + ip.evaluate(&mut m, "\\BREF", &[]).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\OBJ", &[]), i(5)); +} + +/// §19.6.62: Index of a buffer is a byte field, of a package its element. +#[test] +fn index_reaches_into_buffers_strings_and_packages() { + let (mut ip, mut m) = loaded(&cat(&[ + &def_name("BUFF", &buffer(&int(4), &[1, 2, 3, 4])), + &def_name("SRCB", &buffer(&int(4), &[0x10, 0x20, 0x30, 0x40])), + &def_name("STR", &string("ABCDEFGHIJKL")), + &def_name( + "IO0D", + &package(&[package(&[int(1), int(0x3F8), int(0x3F8), int(1), int(8), int(1)]), package(&[int(2)])]), + ), + &method( + "MAIN", + 0, + &cat(&[ + &store(&int(0x1234_5678), &index(&name("BUFF"), &int(2), ZERO)), + &store(&name("SRCB"), &index(&name("BUFF"), &int(1), ZERO)), + &store(&string("ABCDEFGH"), &index(&name("BUFF"), &int(3), ZERO)), + &store(&string("H"), &index(&name("STR"), &int(2), ZERO)), + &ret(&deref(&index(&deref(&index(&name("IO0D"), &int(0), ZERO)), &int(5), ZERO))), + ]), + ), + &method("PAST", 0, &ret(&index(&name("BUFF"), &int(4), ZERO))), + &method("UNIN", 0, &cat(&[&store(&package(&[]), &local(0)), &ret(&deref(&index(&var_package(&int(2), &[]), &int(1), ZERO)))])), + ])); + assert_eq!(ip.evaluate(&mut m, "\\MAIN", &[]), i(1)); + assert_eq!(ip.evaluate(&mut m, "\\BUFF", &[]), b(&[1, 0x10, 0x78, 0x41])); + assert_eq!(ip.evaluate(&mut m, "\\STR", &[]), st("ABHDEFGHIJKL")); + assert!(matches!(ip.evaluate(&mut m, "\\PAST", &[]), Err(Error::Rule(_)))); + assert!(matches!(ip.evaluate(&mut m, "\\UNIN", &[]), Err(Error::Rule(_)))); +} + +#[test] +fn references_are_made_followed_and_asked_about() { + let cond = |n: &str| cat(&[&[0x5B, 0x12], &name(n), &local(0)]); + let (mut ip, mut m) = loaded(&cat(&[ + &def_name("OBJ", &string("abc")), + &device("DEV", &[]), + &method( + "MAIN", + 0, + &cat(&[ + &store(&ref_of(&name("OBJ")), &local(1)), + &store(&int(9), &deref(&local(1))), + &ret(&package(&[name("OBJ")])), + ]), + ), + &method("HAS", 0, &ret(&cond("OBJ"))), + &method("HASN", 0, &ret(&cond("NOPE"))), + &method("SIZE", 0, &ret(&size_of(&name("OBJ")))), + &method("TYPE", 0, &cat(&[&store(&ref_of(&name("DEV")), &local(2)), &ret(&object_type(&local(2)))])), + &method("OSI", 0, &ret(&cond("\\_OSI"))), + &method("NAMD", 0, &ret(&deref(&string("\\OBJ")))), + ])); + ip.evaluate(&mut m, "\\MAIN", &[]).unwrap(); + // OBJ is a String: the store through the reference converts (§19.3.5.8.3). + assert_eq!(ip.evaluate(&mut m, "\\OBJ", &[]), st("0000000000000009")); + assert_eq!(ip.evaluate(&mut m, "\\HAS", &[]), i(ONES)); + assert_eq!(ip.evaluate(&mut m, "\\HASN", &[]), i(0)); + assert_eq!(ip.evaluate(&mut m, "\\SIZE", &[]), i(16)); + assert_eq!(ip.evaluate(&mut m, "\\TYPE", &[]), i(6)); + assert_eq!(ip.evaluate(&mut m, "\\OSI", &[]), i(ONES)); + assert_eq!(ip.evaluate(&mut m, "\\NAMD", &[]), st("0000000000000009")); +} + +/// The examples of §19.6.80. +#[test] +fn match_finds_the_first_element_both_tests_accept() { + let years = package(&[1981, 1983, 1985, 1987, 1989, 1990, 1991, 1993, 1995, 1997, 1999, 2001].map(int)); + let m = |op1: u8, v1: u64, op2: u8, v2: u64, start: u64| { + returns(&ret(&cat(&[&[0x89], &years, &[op1], &int(v1), &[op2], &int(v2), &int(start)]))) + }; + assert_eq!(m(1, 1993, 0, 0, 0), i(7)); + assert_eq!(m(1, 1984, 0, 0, 0), i(ONES)); + assert_eq!(m(5, 1984, 2, 2000, 0), i(2)); + assert_eq!(m(5, 1984, 2, 2000, 3), i(3)); + assert!(matches!(m(6, 0, 0, 0, 0), Err(Error::Malformed { .. }))); + // An element that does not convert to the MatchObject's type matches nothing. + let mixed = package(&[string("zz"), int(5)]); + assert_eq!(returns(&ret(&cat(&[&[0x89], &mixed, &[1], &int(5), &[0], &int(0), &int(0)]))), i(1)); +} + +#[test] +fn buffers_and_packages_are_sized_as_declared() { + // §19.6.10: the larger of BufferSize and the initializer. + assert_eq!(returns(&ret(&buffer(&int(4), &[1, 2]))), b(&[1, 2, 0, 0])); + assert_eq!(returns(&ret(&buffer(&int(1), &[5, 4, 3]))), b(&[5, 4, 3])); + // §19.6.101: elements past the initializer are uninitialized. + assert_eq!(returns(&ret(&var_package(&int(2), &[int(1)]))), Ok(Value::Package(vec![Value::Integer(1), Value::Uninitialized]))); + let over = cat(&[&[0x12], &pkg(&cat(&[&[1], &int(1), &int(2)]))]); + assert!(matches!(returns(&ret(&over)), Err(Error::Malformed { .. }))); +} + +#[test] +fn mutexes_follow_sync_levels_and_are_released_by_the_end() { + let acquire = |n: &str| cat(&[&[0x5B, 0x23], &name(n), &[0xFF, 0xFF]]); + let release = |n: &str| cat(&[&[0x5B, 0x27], &name(n)]); + let mutex = |n: &str, level: u8| cat(&[&[0x5B, 0x01], &name(n), &[level]]); + let (mut ip, mut m) = loaded(&cat(&[ + &mutex("LOW", 1), + &mutex("HIGH", 5), + &method("GOOD", 0, &cat(&[&acquire("LOW"), &acquire("HIGH"), &release("HIGH"), &release("LOW"), &ret(&int(1))])), + &method("DOWN", 0, &cat(&[&acquire("HIGH"), &acquire("LOW")])), + &method("ORDR", 0, &cat(&[&acquire("LOW"), &acquire("HIGH"), &release("LOW")])), + &method("KEEP", 0, &acquire("LOW")), + &method("NONE", 0, &release("LOW")), + &method("GL", 0, &cat(&[&acquire("\\_GL"), &release("\\_GL")])), + ])); + assert_eq!(ip.evaluate(&mut m, "\\GOOD", &[]), i(1)); + assert!(matches!(ip.evaluate(&mut m, "\\DOWN", &[]), Err(Error::Rule(_)))); + assert!(matches!(ip.evaluate(&mut m, "\\ORDR", &[]), Err(Error::Rule(_)))); + assert!(matches!(ip.evaluate(&mut m, "\\KEEP", &[]), Err(Error::Rule(_)))); + assert!(matches!(ip.evaluate(&mut m, "\\NONE", &[]), Err(Error::Rule(_)))); + // What an evaluation held is let go: the next one starts clean. + assert_eq!(ip.evaluate(&mut m, "\\GOOD", &[]), i(1)); + m.log.clear(); + ip.evaluate(&mut m, "\\GL", &[]).unwrap(); + assert_eq!(m.log, vec![Event::GlobalLock(true), Event::GlobalLock(false)]); +} + +#[test] +fn events_count_signals_and_a_wait_without_one_times_out() { + let event = cat(&[&[0x5B, 0x02], &name("EVT")]); + let signal = cat(&[&[0x5B, 0x24], &name("EVT")]); + let wait = |ms: u64| cat(&[&[0x5B, 0x25], &name("EVT"), &int(ms)]); + let (mut ip, mut m) = loaded(&cat(&[ + &event, + &method("MAIN", 0, &cat(&[&signal, &store(&wait(5), &local(0)), &ret(&add(&local(0), &wait(5), ZERO))])), + &method("EVER", 0, &ret(&wait(0xFFFF))), + ])); + assert_eq!(ip.evaluate(&mut m, "\\MAIN", &[]), i(ONES)); + assert_eq!(m.log, vec![Event::Sleep(5)]); + assert!(matches!(ip.evaluate(&mut m, "\\EVER", &[]), Err(Error::Rule(_)))); +} + +#[test] +fn notify_sleep_stall_and_fatal_reach_the_host() { + let (mut ip, mut m) = loaded(&cat(&[ + &scope("\\_SB", &device("LID0", &[])), + &def_name("NUM", &int(0)), + &method( + "MAIN", + 0, + &cat(&[ + &[0x86], + &name("\\_SB.LID0"), + &int(0x80), + &[0x5B, 0x22], + &int(10), + &[0x5B, 0x21], + &int(50), + ]), + ), + &method("NNUM", 0, &cat(&[&[0x86], &name("NUM"), &int(1)])), + &method("LONG", 0, &cat(&[&[0x5B, 0x21], &int(256)])), + &method("DIE", 0, &cat(&[&[0x5B, 0x32, 0x01], &0xDEAD_u32.to_le_bytes(), &int(7)])), + ])); + ip.evaluate(&mut m, "\\MAIN", &[]).unwrap(); + assert_eq!(m.log, vec![Event::Notify("\\_SB_.LID0".into(), 0x80), Event::Sleep(10), Event::Stall(50)]); + assert!(matches!(ip.evaluate(&mut m, "\\NNUM", &[]), Err(Error::Type(_)))); + assert!(matches!(ip.evaluate(&mut m, "\\LONG", &[]), Err(Error::Rule(_)))); + assert_eq!(ip.evaluate(&mut m, "\\DIE", &[]), Err(Error::Fatal { kind: 1, code: 0xDEAD, arg: 7 })); +} + +/// The owner's ruling, "Like Windows, not Linux": yes to every published +/// Windows version string, no to "Linux" and "FreeBSD". +#[test] +fn osi_answers_like_windows() { + let osi = |q: &str| returns(&ret(&cat(&[&name("\\_OSI"), &string(q)]))); + assert_eq!(osi("Windows 2022"), i(ONES)); + assert_eq!(osi("Windows 2001 SP1"), i(ONES)); + assert_eq!(osi("Linux"), i(0)); + assert_eq!(osi("FreeBSD"), i(0)); + assert_eq!(osi("Module Device"), i(0)); + assert!(matches!(returns(&ret(&cat(&[&name("\\_OSI"), &int(1)]))), Err(Error::Type(_)))); + // A DSDT of revision 1 answers Ones in 32 bits (§5.7.2). + let mut m = Machine::default(); + let mut ip = toyos_aml::Interpreter::new(); + ip.load(&mut m, &table(b"DSDT", 1, &method("Q", 0, &ret(&cat(&[&name("\\_OSI"), &string("Windows 2022")]))))).unwrap(); + assert_eq!(ip.evaluate(&mut m, "\\Q", &[]), i(0xFFFF_FFFF)); +} + +/// `\_S5`, as the ACPI server's power-off evaluates it: SLP_TYPa and +/// SLP_TYPb first. +#[test] +fn s5_evaluates_to_its_sleep_types() { + let (mut ip, mut m) = loaded(&cat(&[ + &def_name("SS5", &int(7)), + &def_name("_S5", &package(&[name("SS5"), int(7), int(0), int(0)])), + ])); + assert_eq!( + ip.evaluate(&mut m, "\\_S5", &[]), + Ok(Value::Package(vec![Value::Integer(7), Value::Integer(7), Value::Integer(0), Value::Integer(0)])) + ); +} + +#[test] +fn the_debug_object_takes_writes_and_refuses_reads() { + assert_eq!(returns(&cat(&[&store(&int(1), &debug()), &ret(&int(2))])), i(2)); + assert!(matches!(returns(&ret(&debug())), Err(Error::Type(_)))); +} diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs new file mode 100644 index 00000000000..33f63df0ed4 --- /dev/null +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -0,0 +1,238 @@ +//! A table is firmware's, and untrusted: malformed or hostile bytes are +//! refused by name, and every evaluation ends. + +mod common; + +use common::*; +use toyos_aml::{Error, Interpreter, Value}; + +const ZERO: &[u8] = &[0x00]; + +fn load(body: &[u8]) -> Result<(), Error> { + Interpreter::new().load(&mut Machine::default(), &dsdt(body)) +} + +#[test] +fn a_package_length_that_leaves_its_package_is_refused() { + // A Scope whose PkgLength claims ten bytes more than the table holds. + let mut s = scope("\\", &def_name("A", &int(1))); + s[1] += 10; + assert!(matches!(load(&s), Err(Error::Malformed { .. }))); + // §20.2.4: bits 5-4 of a multi-byte lead byte must be zero. + let bad = cat(&[&[0x10, 0x70, 0x00], &name("\\")]); + assert!(matches!(load(&bad), Err(Error::Malformed { .. }))); +} + +#[test] +fn names_and_strings_hold_only_what_their_encoding_allows() { + assert!(matches!(load(&cat(&[&[0x08], b"a___", &int(1)])), Err(Error::Malformed { .. }))); + assert!(matches!(load(&cat(&[&[0x08], b"1___", &int(1)])), Err(Error::Malformed { .. }))); + assert!(matches!(load(&cat(&[&[0x08, 0x2F, 0x00], &int(1)])), Err(Error::Malformed { .. }))); + assert!(matches!(load(&def_name("S", &[0x0D, b'a', 0x80, 0x00])), Err(Error::Malformed { .. }))); + assert!(matches!(load(&def_name("S", &[0x0D, b'a'])), Err(Error::Malformed { .. }))); + // A DataObject is all Name takes (§20.2.5.1). + assert!(matches!(load(&def_name("L", &local(0))), Err(Error::Malformed { .. }))); + // A term list holds terms; data alone is none (§20.2.5). + assert!(matches!(load(&int(5)), Err(Error::Malformed { .. }))); + assert!(matches!(load(&[0xA1, 0x01]), Err(Error::Malformed { .. }))); + // A name alone in a term list must name a method. + assert!(matches!(load(&cat(&[&def_name("A", &int(1)), &name("A")])), Err(Error::Malformed { .. }))); +} + +#[test] +fn a_loop_without_end_is_bounded_in_steps() { + assert!(matches!(returns(&while_(&int(1), &[0xA3])), Err(Error::Bound(_)))); + assert!(matches!(load(&while_(&int(1), &[])), Err(Error::Bound(_)))); +} + +#[test] +fn a_loop_that_sleeps_is_bounded_in_time_asked() { + let (mut i, mut m) = loaded(&method("NAP", 0, &while_(&int(1), &cat(&[&[0x5B, 0x22], &int(1000)])))); + assert!(matches!(i.evaluate(&mut m, "\\NAP", &[]), Err(Error::Bound(_)))); + let slept: u64 = m.log.iter().map(|e| if let Event::Sleep(ms) = e { *ms } else { 0 }).sum(); + assert!(slept <= 10_000, "{slept}"); +} + +#[test] +fn nesting_is_bounded_before_the_stack() { + let mut e = int(1); + for _ in 0..20_000 { + e = add(&e, &int(1), ZERO); + } + assert!(matches!(returns(&ret(&e)), Err(Error::Bound(_)))); + let mut p = int(1); + for _ in 0..20_000 { + p = package(&[p]); + } + assert!(matches!(load(&def_name("P", &p)), Err(Error::Bound(_)))); + let forever = method("R", 0, &ret(&name("R"))); + let (mut i, mut m) = loaded(&forever); + assert!(matches!(i.evaluate(&mut m, "\\R", &[]), Err(Error::Bound(_)))); +} + +#[test] +fn objects_are_bounded_in_size() { + assert!(matches!(returns(&ret(&buffer(&ones(), &[]))), Err(Error::Bound(_)))); + assert!(matches!(returns(&ret(&var_package(&ones(), &[]))), Err(Error::Bound(_)))); + // Doubling a buffer until it is larger than this interpreter holds. + let grow = cat(&[ + &store(&buffer(&int(1), &[1]), &local(0)), + &while_(&int(1), &op2(0x73, &local(0), &local(0), &local(0))), + ]); + assert!(matches!(returns(&grow), Err(Error::Bound(_)))); + // A package stored into itself, nesting one deeper each time. + let nest = cat(&[ + &store(&package(&[int(0)]), &local(0)), + &while_(&int(1), &store(&local(0), &index(&local(0), &int(0), ZERO))), + ]); + assert!(matches!(returns(&nest), Err(Error::Bound(_)))); +} + +#[test] +fn a_field_beyond_what_is_held_is_refused() { + let huge = cat(&[&op_region("MEM", 0x00, &int(0), &ones()), &field("MEM", 1, &[unit("HUGE", 0x0FFF_FFFF)])]); + let (mut i, mut m) = loaded(&huge); + assert!(matches!(i.evaluate(&mut m, "\\HUGE", &[]), Err(Error::Bound(_)))); +} + +#[test] +fn a_caller_value_is_refused_where_it_is_malformed() { + let (mut i, mut m) = loaded(&method("ONE", 1, &ret(&arg(0)))); + assert!(matches!(i.evaluate(&mut m, "\\ONE", &[Value::String(b"a\0b".to_vec())]), Err(Error::Type(_)))); + assert!(matches!(i.evaluate(&mut m, "\\ONE", &[Value::Reference("\\NONE".into())]), Err(Error::NotFound(_)))); + assert!(matches!(i.evaluate(&mut m, "\\ONE.TOOLONG", &[]), Err(Error::Rule(_)))); +} + +/// A table holding a little of everything, for the mutations below. +fn seed() -> Vec { + cat(&[ + &scope( + "\\_SB", + &cat(&[ + &device( + "PCI0", + &cat(&[ + &def_name("_BBN", &int(0)), + &device( + "LPCB", + &cat(&[ + &def_name("_ADR", &int(0x001F_0000)), + &op_region("LPCR", 0x02, &int(0x40), &int(0x10)), + &field("LPCR", 1, &[unit("R40", 8), unit("R41", 8)]), + ]), + ), + ]), + ), + &op_region("MEM0", 0x00, &int(0x1000), &int(0x20)), + &field("MEM0", 0x03, &[unit("A", 4), unit("B", 12), skip(16), unit("C", 32), unit("D", 64)]), + &field("MEM0", 0x01, &[skip(128), unit("IDX", 8), unit("DAT", 8)]), + &index_field("IDX", "DAT", 0x01, &[unit("F0", 1), skip(15), unit("F1", 8)]), + &def_name("BUF", &buffer(&int(8), &[1, 2, 3, 4])), + &cat(&[&[0x8A], &name("BUF"), &int(2), &name("BF")]), + &def_name("PKG", &package(&[int(1), string("two"), buffer(&int(1), &[3]), name("BUF"), package(&[int(4)])])), + &cat(&[&[0x5B, 0x01], &name("MUT"), &[0x01]]), + ]), + ), + &def_name("_S5", &package(&[int(7), int(7), int(0), int(0)])), + &method( + "MAIN", + 2, + &cat(&[ + &store(&int(0), &local(0)), + &while_( + &lless(&local(0), &int(4)), + &cat(&[ + &increment(&local(0)), + &if_(&lequal(&local(0), &int(2)), &store(&name("\\_SB.A"), &local(1))), + &else_(&store(&op2(0x73, &string("x"), &local(0), ZERO), &local(2))), + ]), + ), + &store(&add(&arg(0), &arg(1), ZERO), &name("\\_SB.C")), + &store(&int(1), &name("\\_SB.F1")), + &store(&deref(&index(&name("\\_SB.PKG"), &int(4), ZERO)), &local(3)), + &cat(&[&[0x5B, 0x23], &name("\\_SB.MUT"), &[0xFF, 0xFF]]), + &cat(&[&[0x5B, 0x27], &name("\\_SB.MUT")]), + &ret(&cat(&[&[0x89], &name("\\_SB.PKG"), &[1], &int(1), &[0], &int(0), &int(0)])), + ]), + ), + &method("OSI", 0, &ret(&cat(&[&name("\\_OSI"), &string("Windows 2022")]))), + ]) +} + +/// xorshift64: deterministic, so a failure names its iteration. +struct Rng(u64); + +impl Rng { + fn next(&mut self) -> u64 { + self.0 ^= self.0 << 13; + self.0 ^= self.0 >> 7; + self.0 ^= self.0 << 17; + self.0 + } + + fn below(&mut self, n: usize) -> usize { + (self.next() % n as u64) as usize + } +} + +const INTERESTING: [u8; 12] = [0x00, 0x01, 0x0A, 0x0E, 0x11, 0x12, 0x14, 0x40, 0x5B, 0x7F, 0xA2, 0xFF]; + +fn mutate(body: &mut Vec, r: &mut Rng) { + for _ in 0..1 + r.below(4) { + let at = r.below(body.len()); + match r.below(5) { + 0 => body[at] ^= 1 << r.below(8), + 1 => body[at] = INTERESTING[r.below(INTERESTING.len())], + 2 => body[at] = r.next() as u8, + 3 => body.insert(at, INTERESTING[r.below(INTERESTING.len())]), + _ => { + body.remove(at); + } + } + } +} + +/// Mutated tables, each loaded and every object of the seed evaluated: a +/// value or a refusal, never a panic, and always an end. +#[test] +fn mutated_tables_yield_a_value_or_a_refusal() { + let seed = seed(); + { + let (mut i, mut m) = loaded(&seed); + assert_eq!(i.evaluate(&mut m, "\\MAIN", &[Value::Integer(1), Value::Integer(2)]), Ok(Value::Integer(0))); + } + let paths = ["\\MAIN", "\\OSI", "\\_S5", "\\_SB.PKG", "\\_SB.BF", "\\_SB.A", "\\_SB.F1", "\\_SB.PCI0.LPCB.R41"]; + let mut r = Rng(0x2545_F491_4F6C_DD1D); + let (mut loads, mut values) = (0, 0); + for _ in 0..20_000 { + let mut body = seed.clone(); + mutate(&mut body, &mut r); + let mut m = Machine::default(); + let mut i = Interpreter::new(); + if i.load(&mut m, &dsdt(&body)).is_err() { + continue; + } + loads += 1; + for p in paths { + let args = if p == "\\MAIN" { vec![Value::Integer(1), Value::Integer(2)] } else { vec![] }; + if i.evaluate(&mut m, p, &args).is_ok() { + values += 1; + } + } + } + // The mutations reach past the header into the interpreter. + assert!(loads > 0 && values > 0, "{loads} loads, {values} values"); +} + +/// Every prefix of the seed, as a table of its own. +#[test] +fn every_truncation_yields_a_value_or_a_refusal() { + let seed = seed(); + for n in 0..seed.len() { + let mut m = Machine::default(); + let mut i = Interpreter::new(); + if i.load(&mut m, &dsdt(&seed[..n])).is_ok() { + let _ = i.evaluate(&mut m, "\\MAIN", &[Value::Integer(1), Value::Integer(2)]); + } + } +} diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs new file mode 100644 index 00000000000..680122dde5d --- /dev/null +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -0,0 +1,274 @@ +//! Loading definition blocks into the namespace (§5.2.6, §5.3, §5.4.2, +//! §5.5.2.3, §20.2.5.1-2). + +mod common; + +use common::*; +use toyos_aml::{Error, Interpreter, Value}; + +fn int_of(i: &mut Interpreter, m: &mut Machine, path: &str) -> Result { + i.evaluate(m, path, &[]) +} + +#[test] +fn a_header_that_disagrees_with_its_bytes_is_refused() { + let mut m = Machine::default(); + let good = dsdt(&def_name("A", &int(1))); + let mut short = good.clone(); + short.truncate(35); + assert!(matches!(Interpreter::new().load(&mut m, &short), Err(Error::Table(_)))); + + let mut longer = good.clone(); + longer.push(0); + assert!(matches!(Interpreter::new().load(&mut m, &longer), Err(Error::Table(_)))); + + let mut sum = good.clone(); + sum[9] = sum[9].wrapping_add(1); + assert!(matches!(Interpreter::new().load(&mut m, &sum), Err(Error::Table(_)))); + + let facp = table(b"FACP", 2, &def_name("A", &int(1))); + assert!(matches!(Interpreter::new().load(&mut m, &facp), Err(Error::Table(_)))); + + let ssdt = table(b"SSDT", 2, &def_name("A", &int(1))); + assert!(matches!(Interpreter::new().load(&mut m, &ssdt), Err(Error::Table(_)))); + + let mut i = Interpreter::new(); + i.load(&mut m, &good).unwrap(); + assert!(matches!(i.load(&mut m, &good), Err(Error::Table(_)))); + i.load(&mut m, &table(b"SSDT", 2, &def_name("B", &int(2)))).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\B", &[]), Ok(Value::Integer(2))); +} + +#[test] +fn the_predefined_objects_are_there_before_any_table() { + let (mut i, mut m) = loaded(&cat(&[ + &method("TSB", 0, &ret(&object_type(&name("\\_SB")))), + &method("TGL", 0, &ret(&object_type(&name("\\_GL")))), + &method("TOSI", 0, &ret(&object_type(&name("\\_OSI")))), + ])); + assert_eq!(i.evaluate(&mut m, "\\_OS", &[]), Ok(s("ToyOS"))); + assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); + // Table 19.36: a predefined scope is typeless, \_GL a Mutex, \_OSI a Method. + assert_eq!(i.evaluate(&mut m, "\\TSB", &[]), Ok(Value::Integer(0))); + assert_eq!(i.evaluate(&mut m, "\\TGL", &[]), Ok(Value::Integer(9))); + assert_eq!(i.evaluate(&mut m, "\\TOSI", &[]), Ok(Value::Integer(8))); +} + +#[test] +fn a_dsdt_below_revision_2_makes_every_integer_32_bits() { + let body = cat(&[&def_name("ONES", &ones()), &method("MAIN", 0, &ret(&add(&int(0xFFFF_FFFF), &int(2), &[0])))]); + let mut m = Machine::default(); + let mut i = Interpreter::new(); + i.load(&mut m, &table(b"DSDT", 1, &body)).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\ONES", &[]), Ok(Value::Integer(0xFFFF_FFFF))); + assert_eq!(i.evaluate(&mut m, "\\MAIN", &[]), Ok(Value::Integer(1))); + // §19.6.29: the DSDT's revision decides for every SSDT too. + i.load(&mut m, &table(b"SSDT", 2, &def_name("SONE", &ones()))).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\SONE", &[]), Ok(Value::Integer(0xFFFF_FFFF))); +} + +/// §5.3: "Access using a single segment name (_CRS) will actually access the +/// \_SB_.PCI0._CRS object", and the absolute name errors. +#[test] +fn a_lone_name_is_searched_for_toward_the_root_and_a_path_is_not() { + let (mut i, mut m) = loaded(&scope( + "\\_SB", + &device( + "PCI0", + &cat(&[ + &def_name("_CRS", &int(7)), + &device( + "IDE0", + &cat(&[ + &method("LONE", 0, &ret(&name("_CRS"))), + &method("ABS", 0, &ret(&name("\\_SB.PCI0.IDE0._CRS"))), + &method("UP", 0, &ret(&name("^_CRS"))), + &method("DUAL", 0, &ret(&name("PCI0._CRS"))), + ]), + ), + ]), + ), + )); + assert_eq!(int_of(&mut i, &mut m, "\\_SB.PCI0.IDE0.LONE"), Ok(Value::Integer(7))); + assert_eq!(int_of(&mut i, &mut m, "\\_SB.PCI0.IDE0.ABS"), Err(Error::NotFound("\\_SB_.PCI0.IDE0._CRS".into()))); + // A method's names resolve from the method itself (§19.6.84), so one + // prefix reaches IDE0, which holds no _CRS. + assert!(matches!(int_of(&mut i, &mut m, "\\_SB.PCI0.IDE0.UP"), Err(Error::NotFound(_)))); + // "XYZ.ABCD //search rules do not apply" (§5.3). + assert!(matches!(int_of(&mut i, &mut m, "\\_SB.PCI0.IDE0.DUAL"), Err(Error::NotFound(_)))); +} + +#[test] +fn parent_prefixes_climb_one_scope_each() { + let (mut i, mut m) = loaded(&scope( + "\\_SB", + &cat(&[ + &def_name("TOP", &int(1)), + &device( + "A", + &cat(&[ + &def_name("MID", &int(2)), + &device("B", &method("GET", 0, &ret(&add(&name("^^MID"), &name("^^^TOP"), &[0])))), + ]), + ), + ]), + )); + assert_eq!(int_of(&mut i, &mut m, "\\_SB.A.B.GET"), Ok(Value::Integer(3))); +} + +#[test] +fn a_prefix_above_the_root_finds_nothing() { + let (mut i, mut m) = loaded(&method("MAIN", 0, &ret(&name("^^^^X")))); + assert!(matches!(int_of(&mut i, &mut m, "\\MAIN"), Err(Error::NotFound(_)))); +} + +/// §5.3: "Object XYZ must already exist for the ABCD object to be created". +#[test] +fn a_definition_needs_every_segment_but_its_last() { + let mut m = Machine::default(); + let r = Interpreter::new().load(&mut m, &dsdt(&def_name("\\XYZ.ABCD", &int(1)))); + assert!(matches!(r, Err(Error::NotFound(_)))); + let (mut i, mut m) = loaded(&cat(&[&device("\\XYZ", &[]), &def_name("\\XYZ.ABCD", &int(1))])); + assert_eq!(int_of(&mut i, &mut m, "\\XYZ.ABCD"), Ok(Value::Integer(1))); +} + +#[test] +fn a_collision_refuses_the_table_and_leaves_none_of_it() { + let (mut i, mut m) = loaded(&def_name("A", &int(1))); + let ssdt = table(b"SSDT", 2, &cat(&[&device("\\NEW", &def_name("X", &int(2))), &def_name("\\A", &int(3))])); + assert_eq!(i.load(&mut m, &ssdt), Err(Error::Exists("\\A___".into()))); + assert!(matches!(int_of(&mut i, &mut m, "\\NEW.X"), Err(Error::NotFound(_)))); + assert!(matches!(int_of(&mut i, &mut m, "\\NEW"), Err(Error::NotFound(_)))); + assert_eq!(int_of(&mut i, &mut m, "\\A"), Ok(Value::Integer(1))); +} + +#[test] +fn a_scope_opens_only_what_has_one() { + let mut m = Machine::default(); + let r = Interpreter::new().load(&mut m, &dsdt(&cat(&[&def_name("NUM", &int(1)), &scope("NUM", &[])]))); + assert!(matches!(r, Err(Error::Type(_)))); + let (mut i, mut m) = loaded(&cat(&[ + &thermal_zone("\\_TZ.TZ0", &[]), + &power_resource("\\PWR", &[]), + &scope("\\_TZ.TZ0", &def_name("T", &int(1))), + &scope("\\PWR", &def_name("P", &int(2))), + &scope("\\", &def_name("R", &int(3))), + ])); + assert_eq!(int_of(&mut i, &mut m, "\\_TZ.TZ0.T"), Ok(Value::Integer(1))); + assert_eq!(int_of(&mut i, &mut m, "\\PWR.P"), Ok(Value::Integer(2))); + assert_eq!(int_of(&mut i, &mut m, "\\R"), Ok(Value::Integer(3))); +} + +#[test] +fn an_alias_acts_exactly_as_its_source() { + let alias = cat(&[&[0x06], &name("\\SRC"), &name("ALI")]); + let (mut i, mut m) = loaded(&cat(&[ + &def_name("SRC", &int(4)), + &alias, + &method("MAIN", 0, &cat(&[&store(&int(9), &name("ALI")), &ret(&name("SRC"))])), + ])); + assert_eq!(int_of(&mut i, &mut m, "\\ALI"), Ok(Value::Integer(4))); + assert_eq!(int_of(&mut i, &mut m, "\\MAIN"), Ok(Value::Integer(9))); + let mut m = Machine::default(); + let missing = cat(&[&[0x06], &name("\\NONE"), &name("ALI")]); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&missing)), Err(Error::NotFound(_)))); +} + +#[test] +fn an_external_defines_nothing() { + let external = cat(&[&[0x15], &name("\\_SB.PCI0.XYZ"), &[0x08, 0x02]]); + let (mut i, mut m) = loaded(&external); + assert!(matches!(int_of(&mut i, &mut m, "\\_SB.PCI0.XYZ"), Err(Error::NotFound(_)))); + let mut m = Machine::default(); + let bad = cat(&[&[0x15], &name("XYZ"), &[0x08, 0x08]]); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&bad)), Err(Error::Malformed { .. }))); +} + +#[test] +fn the_processor_opcode_is_refused_as_reserved() { + let processor = cat(&[&[0x5B, 0x83], &pkg(&cat(&[&name("CPU0"), &[0x00, 0x10, 0x10, 0x00, 0x00, 0x06]]))]); + let mut m = Machine::default(); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&processor)), Err(Error::Malformed { .. }))); +} + +#[test] +fn definition_block_level_code_runs_at_load() { + let (mut i, mut m) = loaded(&cat(&[ + &if_(&lequal(&name("\\_REV"), &int(2)), &def_name("\\YES", &int(1))), + &else_(&def_name("\\NO", &int(1))), + &method("CFG", 0, &ret(&int(1))), + &if_(&lequal(&name("CFG"), &int(1)), &device("\\DEV", &[])), + ])); + assert_eq!(int_of(&mut i, &mut m, "\\YES"), Ok(Value::Integer(1))); + assert!(matches!(int_of(&mut i, &mut m, "\\NO"), Err(Error::NotFound(_)))); + assert_eq!(int_of(&mut i, &mut m, "\\DEV"), Ok(Value::Reference("\\DEV_".into()))); +} + +/// The example of §5.5.2.3, with CREG and DREG as names of their own. +#[test] +fn names_a_method_creates_go_when_it_exits() { + let (mut i, mut m) = loaded(&cat(&[ + &def_name("CREG", &int(0)), + &def_name("DREG", &int(0)), + &scope( + "\\", + &device( + "XYZ", + &cat(&[ + &def_name("BAR", &int(5)), + &method( + "FOO", + 1, + &cat(&[ + &store(&name("BAR"), &name("CREG")), + &def_name("BAR", &int(7)), + &store(&name("BAR"), &name("DREG")), + &def_name("\\XYZ.FOOB", &int(3)), + &ret(&name("\\XYZ.FOOB")), + ]), + ), + ]), + ), + ), + ])); + for _ in 0..2 { + assert_eq!(i.evaluate(&mut m, "\\XYZ.FOO", &[Value::Integer(0)]), Ok(Value::Integer(3))); + assert_eq!(int_of(&mut i, &mut m, "\\CREG"), Ok(Value::Integer(5))); + assert_eq!(int_of(&mut i, &mut m, "\\DREG"), Ok(Value::Integer(7))); + assert!(matches!(int_of(&mut i, &mut m, "\\XYZ.FOOB"), Err(Error::NotFound(_)))); + assert!(matches!(int_of(&mut i, &mut m, "\\XYZ.FOO.BAR"), Err(Error::NotFound(_)))); + } +} + +/// §19.6.101: a named reference to data is resolved to its value at run +/// time; a name defined after the package is resolved when read. +#[test] +fn a_package_names_its_elements() { + let (mut i, mut m) = loaded(&cat(&[ + &def_name("INT1", &int(0x1234)), + &device("DEV0", &[]), + &def_name("PKG1", &package(&[int(0x3400), name("\\INT1"), name("\\DEV0"), name("\\LATE")])), + &def_name("LATE", &int(5)), + ])); + assert_eq!( + int_of(&mut i, &mut m, "\\PKG1"), + Ok(Value::Package(vec![ + Value::Integer(0x3400), + Value::Integer(0x1234), + Value::Reference("\\DEV0".into()), + Value::Integer(5), + ])) + ); + let (mut i, mut m) = loaded(&def_name("PKG2", &package(&[name("\\NONE")]))); + assert!(matches!(int_of(&mut i, &mut m, "\\PKG2"), Err(Error::NotFound(_)))); +} + +#[test] +fn a_caller_names_only_absolute_paths_and_passes_the_declared_arguments() { + let (mut i, mut m) = loaded(&method("TWO", 2, &ret(&add(&arg(0), &arg(1), &[0])))); + assert!(matches!(i.evaluate(&mut m, "TWO", &[]), Err(Error::Rule(_)))); + assert!(matches!(i.evaluate(&mut m, "\\TWO", &[Value::Integer(1)]), Err(Error::Rule(_)))); + assert_eq!(i.evaluate(&mut m, "\\TWO", &[Value::Integer(1), Value::Integer(2)]), Ok(Value::Integer(3))); + assert!(matches!(i.evaluate(&mut m, "\\_OS", &[Value::Integer(1)]), Err(Error::Rule(_)))); + assert!(matches!(Interpreter::new().evaluate(&mut m, "\\_OS", &[]), Err(Error::Table(_)))); +} diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs new file mode 100644 index 00000000000..b61bc3be292 --- /dev/null +++ b/userland/acpiserver/aml/tests/regions.rs @@ -0,0 +1,325 @@ +//! Field units over operation regions and buffer fields over buffers +//! (§5.5.2.4, §19.6.47, §19.6.63, §19.6.7, §19.6.18-23, Table 19.7). + +mod common; + +use common::*; +use toyos_aml::{Access, Address, Error, Interpreter, Value}; + +/// FieldFlags (§20.2.5.2). +const BYTE: u8 = 1; +const WORD: u8 = 2; +const DWORD: u8 = 3; +const ANY: u8 = 0; +const LOCK: u8 = 0x10; +const ONES_RULE: u8 = 0x20; +const ZEROS_RULE: u8 = 0x40; + +fn mem(a: u64) -> Address { + Address::Memory(a) +} + +fn store_into(body: &[u8], field: &str, v: &[u8]) -> (Machine, Result) { + let (mut i, mut m) = loaded(&cat(&[body, &method("SET", 0, &store(v, &name(field)))])); + m.log.clear(); + let r = i.evaluate(&mut m, "\\SET", &[]); + (m, r) +} + +fn read(body: &[u8], prime: &[(Address, &[u8])], field: &str) -> (Machine, Result) { + let (mut i, mut m) = loaded(body); + for (a, v) in prime { + m.poke(*a, v); + } + m.log.clear(); + let r = i.evaluate(&mut m, field, &[]); + (m, r) +} + +fn memory_region(flags: u8, units: &[Vec]) -> Vec { + cat(&[&op_region("MEM0", 0x00, &int(0x1000), &int(0x10)), &field("MEM0", flags, units)]) +} + +#[test] +fn a_field_is_read_shifted_and_masked_from_its_units() { + let body = memory_region(BYTE, &[unit("LO", 4), unit("HI", 4), unit("NEXT", 8), unit("WIDE", 16)]); + let prime: &[(Address, &[u8])] = &[(mem(0x1000), &[0xA5, 0x3C, 0x34, 0x12])]; + assert_eq!(read(&body, prime, "\\LO").1, Ok(Value::Integer(0x5))); + assert_eq!(read(&body, prime, "\\HI").1, Ok(Value::Integer(0xA))); + assert_eq!(read(&body, prime, "\\NEXT").1, Ok(Value::Integer(0x3C))); + let (m, v) = read(&body, prime, "\\WIDE"); + assert_eq!(v, Ok(Value::Integer(0x1234))); + // ByteAcc: one byte access per unit. + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1002), Access::Byte), Event::Read(mem(0x1003), Access::Byte)]); +} + +/// §19.6.47: "when those 4 bits are modified the UpdateRule specifies how +/// the other 12 bits are treated". +#[test] +fn the_update_rule_completes_a_unit_the_field_covers_in_part() { + let units = [skip(4), unit("MID", 4)]; + let (m, r) = { + let (mut i, mut m) = loaded(&cat(&[&memory_region(WORD, &units), &method("SET", 0, &store(&int(0xF), &name("MID")))])); + m.poke(mem(0x1000), &[0x21, 0x43]); + m.log.clear(); + let r = i.evaluate(&mut m, "\\SET", &[]); + (m, r) + }; + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1000), Access::Word), Event::Write(mem(0x1000), Access::Word, 0x43F1)]); + + let (m, r) = store_into(&memory_region(WORD | ONES_RULE, &units), "MID", &int(0)); + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Write(mem(0x1000), Access::Word, 0xFF0F)]); + + let (m, r) = store_into(&memory_region(WORD | ZEROS_RULE, &units), "MID", &int(0xF)); + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Write(mem(0x1000), Access::Word, 0x00F0)]); + + // A unit the field covers whole is written without a read. + let (m, r) = store_into(&memory_region(BYTE, &[unit("ALL", 8)]), "ALL", &int(0x1FF)); + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Write(mem(0x1000), Access::Byte, 0xFF)]); +} + +#[test] +fn an_access_type_sets_the_unit_and_anyacc_takes_the_narrowest_natural_one() { + let (m, v) = read(&memory_region(DWORD, &[skip(8), unit("B1", 8)]), &[(mem(0x1000), &[0, 0x77, 0, 0])], "\\B1"); + assert_eq!(v, Ok(Value::Integer(0x77))); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1000), Access::DWord)]); + + let (m, _) = read(&memory_region(ANY, &[skip(16), unit("W", 16)]), &[], "\\W"); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1002), Access::Word)]); + let (m, _) = read(&memory_region(ANY, &[skip(8), unit("Q", 32)]), &[], "\\Q"); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1000), Access::QWord)]); + let (m, _) = read(&memory_region(ANY, &[skip(56), unit("X", 16)]), &[], "\\X"); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x1007), Access::Byte), Event::Read(mem(0x1008), Access::Byte)]); +} + +/// §19.6.47: "If the FieldUnit is larger than the size of an Integer, it +/// will be treated as a Buffer." +#[test] +fn a_field_wider_than_an_integer_is_a_buffer() { + let (_, v) = read(&memory_region(BYTE, &[unit("BIG", 72)]), &[(mem(0x1000), &[1, 2, 3, 4, 5, 6, 7, 8, 9])], "\\BIG"); + assert_eq!(v, Ok(Value::Buffer(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]))); + // A buffer longer than the field is written in pieces of its size, lower + // first; a string a character at a time (Table 19.7). + let (m, r) = store_into(&memory_region(BYTE, &[unit("TWO", 16)]), "TWO", &buffer(&int(3), &[1, 2, 3])); + r.unwrap(); + assert_eq!( + m.accesses(), + vec![ + Event::Write(mem(0x1000), Access::Byte, 1), + Event::Write(mem(0x1001), Access::Byte, 2), + Event::Write(mem(0x1000), Access::Byte, 3), + Event::Write(mem(0x1001), Access::Byte, 0), + ] + ); + let (m, r) = store_into(&memory_region(BYTE, &[unit("CH", 8)]), "CH", &string("AB")); + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Write(mem(0x1000), Access::Byte, 0x41), Event::Write(mem(0x1000), Access::Byte, 0x42)]); +} + +/// §19.6.47: "an access type of WordAcc cannot read the last byte of an +/// odd-length operation region". +#[test] +fn an_access_past_its_region_is_refused() { + let body = cat(&[&op_region("ODD", 0x00, &int(0), &int(3)), &field("ODD", WORD, &[skip(16), unit("LAST", 8)])]); + let (m, v) = read(&body, &[], "\\LAST"); + assert!(matches!(v, Err(Error::Rule(_)))); + assert_eq!(m.accesses(), vec![]); +} + +#[test] +fn system_io_and_the_embedded_controller_are_addressed_by_offset() { + let io = cat(&[&op_region("IO", 0x01, &int(0x62), &int(5)), &field("IO", BYTE, &[skip(32), unit("CMD", 8)])]); + let (m, _) = read(&io, &[], "\\CMD"); + assert_eq!(m.accesses(), vec![Event::Read(Address::Io(0x66), Access::Byte)]); + + let ec = cat(&[&op_region("ECOR", 0x03, &int(0), &int(0x100)), &field("ECOR", ANY, &[skip(0xA0 * 8), unit("TEMP", 16)])]); + let (m, v) = read(&ec, &[(Address::EmbeddedControl(0xA0), &[0x2C, 0x01])], "\\TEMP"); + assert_eq!(v, Ok(Value::Integer(0x12C))); + assert_eq!( + m.accesses(), + vec![Event::Read(Address::EmbeddedControl(0xA0), Access::Byte), Event::Read(Address::EmbeddedControl(0xA1), Access::Byte)] + ); + // Table 19.34: EmbeddedControl permits ByteAcc only. + let wide = cat(&[&op_region("ECOR", 0x03, &int(0), &int(0x100)), &field("ECOR", WORD, &[unit("W", 16)])]); + assert!(matches!(read(&wide, &[], "\\W").1, Err(Error::Type(_)))); + // §12: the controller's space is 256 bytes. + let past = cat(&[&op_region("ECOR", 0x03, &int(0xFF), &int(2)), &field("ECOR", BYTE, &[skip(8), unit("P", 8)])]); + assert!(matches!(read(&past, &[], "\\P").1, Err(Error::Rule(_)))); +} + +/// §6.1.1, §6.5.5, §6.5.6: the device's `_ADR`, the host bridge's `_BBN` and +/// `_SEG`. +#[test] +fn a_pci_config_region_addresses_its_devices_function() { + let lpc = |bridge: &[u8]| { + scope( + "\\_SB", + &device( + "PCI0", + &cat(&[ + bridge, + &device( + "LPCB", + &cat(&[ + &method("_ADR", 0, &ret(&int(0x001F_0003))), + &op_region("LPCR", 0x02, &int(0x40), &int(0x10)), + &field("LPCR", BYTE, &[skip(8), unit("R41", 8)]), + ]), + ), + ]), + ), + ) + }; + let bridge = cat(&[&def_name("_BBN", &int(0x80)), &def_name("_SEG", &int(1))]); + let (m, _) = read(&lpc(&bridge), &[], "\\_SB.PCI0.LPCB.R41"); + let at = Address::PciConfig { segment: 1, bus: 0x80, device: 0x1F, function: 3, offset: 0x41 }; + assert_eq!(m.accesses(), vec![Event::Read(at, Access::Byte)]); + let (_, v) = read(&lpc(&[]), &[], "\\_SB.PCI0.LPCB.R41"); + assert!(matches!(v, Err(Error::Unsupported(_)))); +} + +/// The example of §19.6.63: FET3, the high bit at indexed offset 0x2F. +#[test] +fn an_index_field_writes_its_offset_then_reaches_the_data() { + let body = cat(&[ + &op_region("GIO0", 0x01, &int(0x125), &int(0x100)), + &field("GIO0", BYTE | ZEROS_RULE, &[unit("IDX0", 8), unit("DAT0", 8)]), + &index_field("IDX0", "DAT0", BYTE, &[unit("FET0", 1), unit("FET1", 1), skip((0x2F * 8) - 2), skip(7), unit("FET3", 1)]), + &method("SET", 0, &store(&int(0), &name("FET3"))), + ]); + let (mut i, mut m) = loaded(&body); + m.poke(Address::Io(0x126), &[0xFF]); + m.log.clear(); + i.evaluate(&mut m, "\\SET", &[]).unwrap(); + assert_eq!( + m.accesses(), + vec![ + Event::Write(Address::Io(0x125), Access::Byte, 0x2F), + Event::Read(Address::Io(0x126), Access::Byte), + Event::Write(Address::Io(0x125), Access::Byte, 0x2F), + Event::Write(Address::Io(0x126), Access::Byte, 0x7F), + ] + ); +} + +/// §19.6.7: the bank value is written before the field is reached. +#[test] +fn a_bank_field_selects_its_bank_first() { + let body = cat(&[ + &op_region("GIO0", 0x01, &int(0x125), &int(0x100)), + &field("GIO0", BYTE, &[unit("GLB1", 1), unit("GLB2", 1), skip(6), unit("BNK1", 4)]), + &bank_field("GIO0", "BNK1", &int(1), BYTE, &[skip(0x30 * 8), unit("BLVL", 7), unit("BAC", 1)]), + ]); + let (m, _) = read(&body, &[(Address::Io(0x126), &[0xF0])], "\\BLVL"); + assert_eq!( + m.accesses(), + vec![ + Event::Read(Address::Io(0x126), Access::Byte), + Event::Write(Address::Io(0x126), Access::Byte, 0xF1), + Event::Read(Address::Io(0x155), Access::Byte), + ] + ); +} + +#[test] +fn a_lock_field_holds_the_global_lock_across_its_access() { + let (m, _) = read(&memory_region(BYTE | LOCK, &[unit("L", 16)]), &[], "\\L"); + assert_eq!( + m.log, + vec![ + Event::GlobalLock(true), + Event::Read(mem(0x1000), Access::Byte), + Event::Read(mem(0x1001), Access::Byte), + Event::GlobalLock(false), + ] + ); +} + +#[test] +fn a_host_refusal_and_a_space_not_carried_are_refused_by_name() { + let (mut i, mut m) = loaded(&memory_region(BYTE, &[unit("A", 8)])); + m.refuse = true; + assert_eq!(i.evaluate(&mut m, "\\A", &[]), Err(Error::Host("refused".into()))); + let smbus = cat(&[&op_region("SMB0", 0x04, &int(0), &int(0x100)), &field("SMB0", BYTE, &[unit("S", 8)])]); + assert_eq!(read(&smbus, &[], "\\S").1, Err(Error::Unsupported("the SMBus address space"))); + let mut m = Machine::default(); + let reserved = op_region("RSV", 0x0C, &int(0), &int(1)); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&reserved)), Err(Error::Malformed { .. }))); + for flags in [0x06, 0x60, 0x80] { + let bad = memory_region(flags, &[unit("A", 8)]); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&bad)), Err(Error::Malformed { .. })), "{flags:#x}"); + } +} + +/// §19.6.18-23 and Table 19.7: buffer fields see and change their buffer. +#[test] +fn buffer_fields_read_and_write_their_buffer() { + let create = |op: &[u8], at: u64, n: &str| cat(&[op, &name("BUF"), &int(at), &name(n)]); + let body = cat(&[ + &def_name("BUF", &buffer(&int(12), &[0x81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])), + &create(&[0x8D], 7, "BIT7"), + &create(&[0x8C], 1, "BYT1"), + &create(&[0x8B], 2, "WRD2"), + &create(&[0x8A], 4, "DWD4"), + &create(&[0x8F], 4, "QWD4"), + &cat(&[&[0x5B, 0x13], &name("BUF"), &int(0), &int(96), &name("ALL")]), + &method( + "SET", + 0, + &cat(&[ + &store(&int(0xFF), &name("BYT1")), + &store(&int(0x1_2345), &name("WRD2")), + &store(&string("AB"), &name("DWD4")), + ]), + ), + ]); + let (mut i, mut m) = loaded(&body); + assert_eq!(i.evaluate(&mut m, "\\BIT7", &[]), Ok(Value::Integer(1))); + i.evaluate(&mut m, "\\SET", &[]).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\BUF", &[]), Ok(Value::Buffer(vec![0x81, 0xFF, 0x45, 0x23, 0x41, 0x42, 0, 0, 0, 0, 0, 0]))); + assert_eq!(i.evaluate(&mut m, "\\QWD4", &[]), Ok(Value::Integer(0x4241))); + assert_eq!(i.evaluate(&mut m, "\\ALL", &[]), Ok(Value::Buffer(vec![0x81, 0xFF, 0x45, 0x23, 0x41, 0x42, 0, 0, 0, 0, 0, 0]))); + let mut m = Machine::default(); + let past = cat(&[&def_name("BUF", &buffer(&int(4), &[])), &cat(&[&[0x8A], &name("BUF"), &int(1), &name("X")])]); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&past)), Err(Error::Rule(_)))); + let none = cat(&[&def_name("BUF", &buffer(&int(4), &[])), &cat(&[&[0x5B, 0x13], &name("BUF"), &int(0), &int(0), &name("X")])]); + assert!(matches!(Interpreter::new().load(&mut m, &dsdt(&none)), Err(Error::Rule(_)))); +} + +#[test] +fn copy_object_into_a_field_writes_an_integer_or_buffer_alone() { + let (m, r) = { + let (mut i, mut m) = loaded(&cat(&[ + &memory_region(BYTE, &[unit("A", 8)]), + &method("CPY", 0, ©_object(&int(0x5A), &name("A"))), + &method("BAD", 0, ©_object(&string("x"), &name("A"))), + ])); + m.log.clear(); + let r = i.evaluate(&mut m, "\\CPY", &[]); + assert!(matches!(i.evaluate(&mut m, "\\BAD", &[]), Err(Error::Type(_)))); + (m, r) + }; + r.unwrap(); + assert_eq!(m.accesses(), vec![Event::Write(mem(0x1000), Access::Byte, 0x5A)]); +} + +/// A source that is not a Buffer converts to a new one (§19.6.18-23), and an +/// explicit conversion into a byte field takes an Integer or a Buffer alone +/// (Table 19.8). +#[test] +fn a_buffer_field_over_a_conversion_and_a_conversion_into_one() { + let (mut i, mut m) = loaded(&cat(&[ + &def_name("BUF", &buffer(&int(2), &[])), + &method("SRC", 0, &cat(&[&cat(&[&[0x8C], &string("AB"), &int(1), &name("CH")]), &ret(&name("CH"))])), + &method("CONV", 0, &cat(&[&[0x96], &string("Z"), &index(&name("BUF"), &int(0), &[0x00])])), + &method("TOHX", 0, &cat(&[&[0x98], &int(1), &index(&name("BUF"), &int(0), &[0x00])])), + ])); + assert_eq!(i.evaluate(&mut m, "\\SRC", &[]), Ok(Value::Integer(0x42))); + i.evaluate(&mut m, "\\CONV", &[]).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\BUF", &[]), Ok(Value::Buffer(vec![0x5A, 0]))); + assert!(matches!(i.evaluate(&mut m, "\\TOHX", &[]), Err(Error::Type(_)))); +} From 090c62a7a76aed40aea73d92492ec7cad5d0e6cc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:31:44 +0000 Subject: [PATCH 2/9] Processor parses by ACPI 6.3A, the table is toyos_acpi's, and QEMU's DSDT loads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 1, BLOCKERs 2 and 10. The owner ruled on 2026-10-05 to accept what real firmware ships: "Parse Processor and other legacy constructs real tables still contain, per their last spec definition". ProcessorOp now parses by ACPI 6.3 Errata A, the last edition to define it (§20.2.5.2, §19.6.108): a named object that opens a scope, ObjectType 12 (Table 19.36), a Notify target. QEMU 11.1.1's DSDT, already in the tree as toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin, holds two; it now loads, and its \_S5 gives SLP_TYPa 0, what its boot logged. A load takes a toyos_acpi::Table, whose open checks §5.2.6's length and checksum for every table; the interpreter's own copy of that check goes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C9qc7GuMaHZ9xhKsZ57RVz --- userland/Cargo.lock | 18 ++++++ userland/acpiserver/aml/Cargo.toml | 5 ++ userland/acpiserver/aml/src/exec.rs | 26 ++++++-- userland/acpiserver/aml/src/lib.rs | 43 ++++--------- userland/acpiserver/aml/src/object.rs | 8 ++- userland/acpiserver/aml/tests/common/mod.rs | 33 +++++++++- userland/acpiserver/aml/tests/evaluate.rs | 2 +- userland/acpiserver/aml/tests/hostile.rs | 6 +- userland/acpiserver/aml/tests/namespace.rs | 71 +++++++++++++-------- userland/acpiserver/aml/tests/regions.rs | 8 +-- 10 files changed, 143 insertions(+), 77 deletions(-) diff --git a/userland/Cargo.lock b/userland/Cargo.lock index 1856cfdfb36..1c3327bbc74 100644 --- a/userland/Cargo.lock +++ b/userland/Cargo.lock @@ -4181,9 +4181,20 @@ dependencies = [ name = "toyos-abi" version = "0.16.0" +[[package]] +name = "toyos-acpi" +version = "0.1.0" +dependencies = [ + "toyos-abi", + "toyos-bootmap", +] + [[package]] name = "toyos-aml" version = "0.1.0" +dependencies = [ + "toyos-acpi", +] [[package]] name = "toyos-blockhold" @@ -4197,6 +4208,13 @@ dependencies = [ "toyos-transport", ] +[[package]] +name = "toyos-bootmap" +version = "0.1.0" +dependencies = [ + "toyos-abi", +] + [[package]] name = "toyos-desktop" version = "0.1.0" diff --git a/userland/acpiserver/aml/Cargo.toml b/userland/acpiserver/aml/Cargo.toml index 3664c404c3a..46a797fa68e 100644 --- a/userland/acpiserver/aml/Cargo.toml +++ b/userland/acpiserver/aml/Cargo.toml @@ -8,5 +8,10 @@ license = "MIT OR Apache-2.0" [lib] doctest = false +[dependencies] +# The table a load takes, its §5.2.6 length and checksum already checked +# where every other table's are. +toyos-acpi = { path = "../../../toyos-acpi" } + [lints.rust] warnings = "deny" diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index 17fda394642..a7f9c1c3f58 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -96,6 +96,7 @@ fn type_name(code: u64) -> &'static [u8] { 9 => b"[Mutex]", 10 => b"[Operation Region]", 11 => b"[Power Resource]", + 12 => b"[Processor]", 13 => b"[Thermal Zone]", 14 => b"[Buffer Field]", 16 => b"[Debug Object]", @@ -415,8 +416,7 @@ impl<'a> Machine<'a> { 0x13 => self.def_create_field(f, c), 0x80 => self.def_region(f, c), 0x81 | 0x86 | 0x87 => self.def_field(f, c), - 0x82 | 0x84 | 0x85 => self.def_scoped(f, c), - 0x83 => Err(c.malformed("ProcessorOp, permanently reserved since ACPI 6.4 (§20.3)")), + 0x82..=0x85 => self.def_scoped(f, c), 0x88 => Err(Error::Unsupported("DataTableRegion")), 0x20 => Err(Error::Unsupported("Load")), 0x21 | 0x22 => self.delay(f, c), @@ -508,7 +508,10 @@ impl<'a> Machine<'a> { let id = self.resolve(f, &p)?; // §19.6.120: a Scope's location is a predefined scope, a Device, a // Processor, a Thermal Zone or a Power Resource. - if !matches!(self.node_object(id)?, Object::Scope | Object::Device | Object::ThermalZone | Object::PowerResource) { + if !matches!( + self.node_object(id)?, + Object::Scope | Object::Device | Object::Processor | Object::ThermalZone | Object::PowerResource + ) { return Err(Error::Type("a Scope names an object that opens no scope (§19.6.120)")); } let flow = self.within(f, id, &mut body)?; @@ -523,8 +526,8 @@ impl<'a> Machine<'a> { flow } - /// Device, PowerResource and ThermalZone (§20.2.5.2): a named object - /// whose term list runs in its own scope. + /// Device, Processor, PowerResource and ThermalZone (§20.2.5.2): a named + /// object whose term list runs in its own scope. fn def_scoped(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; let op = c.byte()?; @@ -534,6 +537,15 @@ impl<'a> Machine<'a> { let o = match op { 0x82 => Object::Device, 0x85 => Object::ThermalZone, + 0x83 => { + // DefProcessor := ProcessorOp PkgLength NameString ProcID + // PblkAddr PblkLen TermList (ACPI 6.3A §20.2.5.2), parsed by + // the owner's ruling to accept what real firmware ships. + body.byte()?; + body.dword()?; + body.byte()?; + Object::Processor + } _ => { // SystemLevel and ResourceOrder, which only OSPM's power // resource management reads. @@ -736,8 +748,8 @@ impl<'a> Machine<'a> { let v = self.int_arg(f, c)?; let id = self.node_of(f, &t)?; // §19.6.94: a device, processor, or thermal zone. - if !matches!(self.node_object(id)?, Object::Device | Object::ThermalZone) { - return Err(Error::Type("Notify of an object that is not a device or thermal zone (§19.6.94)")); + if !matches!(self.node_object(id)?, Object::Device | Object::Processor | Object::ThermalZone) { + return Err(Error::Type("Notify of an object that is not a device, processor or thermal zone (§19.6.94)")); } let path = self.ns.path_of(id, None); self.host.notify(&path, v); diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index 255ba2c7e8f..6b06eed1aca 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -87,8 +87,6 @@ pub(crate) const WINDOWS: &[&str] = &[ "Windows 2022", ]; -const HEADER: usize = 36; - /// Why a table or an evaluation was refused. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Error { @@ -202,20 +200,25 @@ impl Interpreter { Interpreter { ns, width: None } } - /// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. - pub fn load(&mut self, host: &mut dyn Host, table: &[u8]) -> Result<(), Error> { - let (signature, revision) = header(table)?; - let w = match (&signature, self.width) { + /// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. The + /// table's length and checksum are [`toyos_acpi::Table::open`]'s. + pub fn load(&mut self, host: &mut dyn Host, table: &toyos_acpi::Table

) -> Result<(), Error> { + let bytes: Vec = (0..table.len()).map_while(|i| table.byte(i)).collect(); + let (Some(signature), Some(&revision)) = (bytes.first_chunk::<4>(), bytes.get(toyos_acpi::SDT_REVISION)) else { + return Err(Error::Table("shorter than its header (§5.2.6)")); + }; + let w = match (signature, self.width) { (b"DSDT", None) => Width { bits: if revision < 2 { 32 } else { 64 } }, (b"DSDT", Some(_)) => return Err(Error::Table("a second DSDT")), - (_, Some(w)) => w, - (_, None) => return Err(Error::Table("an SSDT before the DSDT, whose revision sets every integer's width")), + (b"SSDT", Some(w)) => w, + (b"SSDT", None) => return Err(Error::Table("an SSDT before the DSDT, whose revision sets every integer's width")), + _ => return Err(Error::Table("not a DSDT or SSDT (§5.2.11)")), }; - let table: Rc<[u8]> = Rc::from(table); + let table: Rc<[u8]> = Rc::from(bytes); let root = self.ns.root(); let mut f = Frame::new(root, Vec::new(), table.clone(), 0); let mut m = Machine::new(&mut self.ns, host, w); - let mut c = stream::Cursor::new(&table, HEADER, table.len()); + let mut c = stream::Cursor::new(&table, toyos_acpi::SDT_HEADER_LEN, table.len()); let r = m.term_list(&mut f, &mut c).and_then(|flow| match flow { exec::Flow::Next => Ok(()), _ => Err(Error::Rule("a Return, Break or Continue at definition block level")), @@ -290,23 +293,3 @@ fn value_of(m: &mut Machine<'_>, o: Object, depth: usize) -> Result return Err(Error::Unsupported("a reference to an unnamed object, handed to the caller")), }) } - -/// The header of a definition block (§5.2.6): its signature and revision, -/// once its length and checksum agree with its bytes. -fn header(t: &[u8]) -> Result<([u8; 4], u8), Error> { - if t.len() < HEADER { - return Err(Error::Table("shorter than the 36-byte header (§5.2.6)")); - } - let len = u32::from_le_bytes([t[4], t[5], t[6], t[7]]); - if usize::try_from(len).ok() != Some(t.len()) { - return Err(Error::Table("its Length is not its size (§5.2.6)")); - } - if t.iter().fold(0u8, |s, &b| s.wrapping_add(b)) != 0 { - return Err(Error::Table("its bytes do not sum to zero (§5.2.6)")); - } - let signature = [t[0], t[1], t[2], t[3]]; - if &signature != b"DSDT" && &signature != b"SSDT" { - return Err(Error::Table("not a DSDT or SSDT (§5.2.11)")); - } - Ok((signature, t[8])) -} diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs index 99723cbe353..3860a9a29db 100644 --- a/userland/acpiserver/aml/src/object.rs +++ b/userland/acpiserver/aml/src/object.rs @@ -33,6 +33,8 @@ pub(crate) enum Object { /// A predefined scope such as `\_SB` (§5.3.1), typeless (§19.6.96). Scope, Device, + /// The Processor object ACPI 6.4 deprecated (ACPI 6.3A §19.6.108). + Processor, ThermalZone, PowerResource, Method(Rc), @@ -89,8 +91,9 @@ impl Object { Object::Buf(bytes(v)) } - /// The value ObjectType returns (§19.6.96, Table 19.36), a reference's - /// being its target's and found by the caller. + /// The value ObjectType returns (§19.6.96, Table 19.36; a Processor's 12 + /// from ACPI 6.3A's), a reference's being its target's and found by the + /// caller. pub(crate) fn type_code(&self) -> u64 { match self { Object::Uninit | Object::Scope | Object::Lazy(_) | Object::Ref(_) => 0, @@ -105,6 +108,7 @@ impl Object { Object::Mutex(_) => 9, Object::Region(_) => 10, Object::PowerResource => 11, + Object::Processor => 12, Object::ThermalZone => 13, Object::BufField(_) => 14, } diff --git a/userland/acpiserver/aml/tests/common/mod.rs b/userland/acpiserver/aml/tests/common/mod.rs index f44d04ace9b..8d1e850713d 100644 --- a/userland/acpiserver/aml/tests/common/mod.rs +++ b/userland/acpiserver/aml/tests/common/mod.rs @@ -5,7 +5,8 @@ use std::collections::BTreeMap; -use toyos_aml::{Access, Address, Denied, Host, Interpreter, Value}; +use toyos_acpi::{Phys, Table}; +use toyos_aml::{Access, Address, Denied, Error, Host, Interpreter, Value}; /// PkgLength (§20.2.4) of `n` bytes that follow it: the length counts its /// own encoding. @@ -394,11 +395,39 @@ impl Host for Machine { } } +/// A table's bytes as physical memory at address 0, for +/// [`toyos_acpi::Table::open`]. +#[derive(Clone, Copy)] +pub struct Image<'a>(pub &'a [u8]); + +impl Phys for Image<'_> { + fn readable(self, phys: u64, len: usize) -> bool { + usize::try_from(phys).ok().and_then(|p| p.checked_add(len)).is_some_and(|e| e <= self.0.len()) + } + + fn byte(self, phys: u64) -> u8 { + self.0[phys as usize] + } +} + +/// Loading from bytes, through the `Table::open` the server reaches a table by. +pub trait LoadBytes { + fn load_bytes(&mut self, m: &mut Machine, t: &[u8]) -> Result<(), Error>; +} + +impl LoadBytes for Interpreter { + fn load_bytes(&mut self, m: &mut Machine, t: &[u8]) -> Result<(), Error> { + let signature: [u8; 4] = t.get(..4).and_then(|s| s.try_into().ok()).expect("a test table has a signature"); + let table = Table::open(Image(t), 0, &signature, 0).expect("a test table opens"); + self.load(m, &table) + } +} + /// An interpreter with one DSDT of `body` loaded. pub fn loaded(body: &[u8]) -> (Interpreter, Machine) { let mut m = Machine::default(); let mut i = Interpreter::new(); - i.load(&mut m, &dsdt(body)).expect("the DSDT loads"); + i.load_bytes(&mut m, &dsdt(body)).expect("the DSDT loads"); (i, m) } diff --git a/userland/acpiserver/aml/tests/evaluate.rs b/userland/acpiserver/aml/tests/evaluate.rs index fe2cfef0ef9..43b53b1f252 100644 --- a/userland/acpiserver/aml/tests/evaluate.rs +++ b/userland/acpiserver/aml/tests/evaluate.rs @@ -466,7 +466,7 @@ fn osi_answers_like_windows() { // A DSDT of revision 1 answers Ones in 32 bits (§5.7.2). let mut m = Machine::default(); let mut ip = toyos_aml::Interpreter::new(); - ip.load(&mut m, &table(b"DSDT", 1, &method("Q", 0, &ret(&cat(&[&name("\\_OSI"), &string("Windows 2022")]))))).unwrap(); + ip.load_bytes(&mut m, &table(b"DSDT", 1, &method("Q", 0, &ret(&cat(&[&name("\\_OSI"), &string("Windows 2022")]))))).unwrap(); assert_eq!(ip.evaluate(&mut m, "\\Q", &[]), i(0xFFFF_FFFF)); } diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index 33f63df0ed4..1664cb31e5d 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -9,7 +9,7 @@ use toyos_aml::{Error, Interpreter, Value}; const ZERO: &[u8] = &[0x00]; fn load(body: &[u8]) -> Result<(), Error> { - Interpreter::new().load(&mut Machine::default(), &dsdt(body)) + Interpreter::new().load_bytes(&mut Machine::default(), &dsdt(body)) } #[test] @@ -209,7 +209,7 @@ fn mutated_tables_yield_a_value_or_a_refusal() { mutate(&mut body, &mut r); let mut m = Machine::default(); let mut i = Interpreter::new(); - if i.load(&mut m, &dsdt(&body)).is_err() { + if i.load_bytes(&mut m, &dsdt(&body)).is_err() { continue; } loads += 1; @@ -231,7 +231,7 @@ fn every_truncation_yields_a_value_or_a_refusal() { for n in 0..seed.len() { let mut m = Machine::default(); let mut i = Interpreter::new(); - if i.load(&mut m, &dsdt(&seed[..n])).is_ok() { + if i.load_bytes(&mut m, &dsdt(&seed[..n])).is_ok() { let _ = i.evaluate(&mut m, "\\MAIN", &[Value::Integer(1), Value::Integer(2)]); } } diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs index 680122dde5d..9852a6cf10d 100644 --- a/userland/acpiserver/aml/tests/namespace.rs +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -10,32 +10,22 @@ fn int_of(i: &mut Interpreter, m: &mut Machine, path: &str) -> Result Date: Sun, 4 Oct 2026 22:44:33 +0000 Subject: [PATCH 3/9] Every bound is real: objects metered live, steps charged by work, no reference chains Review round 1, BLOCKERs 3 to 8, and the NOTE on generations. - Every string, buffer and package is made by one of the machine's constructors, which bound its size (1 MiB, or 65536 elements), charge a step for every 64 bytes of it, and hold it against the interpreter's Meter until it drops: what one interpreter holds live is bounded at 16 MiB in sum. A conversion refuses an output too large before it builds it. - A step is charged in proportion to work: bytes made, copied or compared, a bit walked in a buffer field, a byte written out as digits. The worst evaluation measured, a buffer field of 8 Mi bits read in a loop, ends refused in 110 ms (release) or 1.46 s (debug). - A reference to a package element or to a LocalX or ArgX lives only in a LocalX or ArgX, which a method's exit clears: storing one into a package or a named object is refused, so no chain of references forms and no cycle outlives its evaluation. - An Alias is an object its table or method created like any other: a refused load and a method's exit remove it. - _BBN above 0xFF, _SEG above 0xFFFF, an _ADR naming no single function and a SystemIO port past 0xFFFF are refused, not truncated. - A namespace slot whose generation would wrap is retired. - \_OS is "Microsoft Windows NT", as the owner ruled on 2026-10-05. Each construction the review names has a hostile test, red before this commit: conversion output and proportional steps timed out at 120 s, the live fill and the reference chain aborted, the Alias and address tests failed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C9qc7GuMaHZ9xhKsZ57RVz --- userland/acpiserver/aml/src/exec.rs | 236 ++++++++++++++------ userland/acpiserver/aml/src/field.rs | 39 ++-- userland/acpiserver/aml/src/lib.rs | 40 ++-- userland/acpiserver/aml/src/namespace.rs | 12 +- userland/acpiserver/aml/src/object.rs | 192 +++++++++++----- userland/acpiserver/aml/tests/common/mod.rs | 2 +- userland/acpiserver/aml/tests/hostile.rs | 139 ++++++++++++ userland/acpiserver/aml/tests/namespace.rs | 3 +- userland/acpiserver/aml/tests/regions.rs | 37 +++ 9 files changed, 548 insertions(+), 152 deletions(-) diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index a7f9c1c3f58..6fe05f7fc90 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -23,15 +23,16 @@ use crate::field::{flags, BufField, Field, Kind, Region}; use crate::name::{text, Path, Seg}; use crate::namespace::{Namespace, NodeId}; use crate::object::{ - copy, decimal, fit, hex2, joined, to_buf, to_int, to_str, Body, Method, Mutex, Object, Ref, Slot, Width, + bounded, decimal, fit, hex2, joined, to_buf, to_int, to_str, Body, Meter, Method, Mutex, Object, Ref, Slot, Width, }; use crate::stream::{starts_name, Cursor}; -use crate::{Error, Host, MAX_BYTES, MAX_DEPTH, MAX_STEPS, MAX_WAIT_US, REVISION, WINDOWS}; +use crate::{Error, Host, MAX_DEPTH, MAX_NESTING, MAX_STEPS, MAX_WAIT_US, REVISION, WINDOWS, WORK_PER_STEP}; pub(crate) struct Machine<'a> { pub(crate) ns: &'a mut Namespace, pub(crate) host: &'a mut dyn Host, pub(crate) w: Width, + meter: Rc, steps: u64, depth: u32, waited_us: u64, @@ -74,6 +75,14 @@ fn slot(o: Object) -> Slot { } impl Frame { + /// Empties every LocalX and ArgX, which drops any reference among them + /// and so any cycle they formed (the module header of `object`). + pub(crate) fn clear(&self) { + for s in self.locals.iter().chain(&self.args) { + *s.borrow_mut() = Object::Uninit; + } + } + pub(crate) fn new(scope: NodeId, args: Vec, table: Rc<[u8]>, held: usize) -> Frame { Frame { locals: core::array::from_fn(|_| slot(Object::Uninit)), @@ -104,10 +113,6 @@ fn type_name(code: u64) -> &'static [u8] { } } -fn bounded(len: usize) -> Result<(), Error> { - if len > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } -} - /// A NameString written as ASL text, for DerefOf of a String (§19.6.30). fn path_of_text(s: &[u8]) -> Result { let bad = Error::Rule("DerefOf of a String that is not a name (§19.6.30)"); @@ -137,8 +142,71 @@ fn path_of_text(s: &[u8]) -> Result { } impl<'a> Machine<'a> { - pub(crate) fn new(ns: &'a mut Namespace, host: &'a mut dyn Host, w: Width) -> Self { - Machine { ns, host, w, steps: 0, depth: 0, waited_us: 0, held: Vec::new(), levels: Vec::new(), global: 0 } + pub(crate) fn new(ns: &'a mut Namespace, host: &'a mut dyn Host, w: Width, meter: Rc) -> Self { + Machine { ns, host, w, meter, steps: 0, depth: 0, waited_us: 0, held: Vec::new(), levels: Vec::new(), global: 0 } + } + + /// Steps for `bytes` of work done in one: a step a [`WORK_PER_STEP`]. + pub(crate) fn charge(&mut self, bytes: usize) -> Result<(), Error> { + self.steps = self.steps.saturating_add((bytes / WORK_PER_STEP) as u64); + self.step() + } + + pub(crate) fn new_str(&mut self, v: Vec) -> Result { + self.charge(v.len())?; + Ok(Object::Str(self.meter.bytes(v)?)) + } + + pub(crate) fn new_buf(&mut self, v: Vec) -> Result { + self.charge(v.len())?; + Ok(Object::Buf(self.meter.bytes(v)?)) + } + + fn new_pkg(&mut self, v: Vec) -> Result { + self.charge(v.len() * crate::object::ELEMENT)?; + Ok(Object::Pkg(self.meter.list(v)?)) + } + + /// A copy of an object for a store (§19.3.5.8): data is duplicated, + /// anything else is the same object again. Bounded in nesting, which a + /// table can grow without limit by storing a package into itself. + pub(crate) fn copy(&mut self, o: &Object) -> Result { + self.copy_in(o, 0) + } + + fn copy_in(&mut self, o: &Object, depth: usize) -> Result { + if depth > MAX_NESTING { + return Err(Error::Bound("a package nests deeper than this interpreter copies")); + } + match o { + Object::Str(s) => { + let v = s.borrow().clone(); + self.new_str(v) + } + Object::Buf(b) => { + let v = b.borrow().clone(); + self.new_buf(v) + } + Object::Pkg(p) => { + let elems = p.borrow().clone(); + let mut out = Vec::with_capacity(elems.len()); + for e in &elems { + out.push(self.copy_in(e, depth + 1)?); + } + self.new_pkg(out) + } + other => Ok(other.clone()), + } + } + + /// A copy for a package element or a named object, which a reference that + /// lives only in a LocalX or ArgX never enters (the module header of + /// `object`). + fn lasting(&mut self, v: &Object) -> Result { + if v.frame_bound() { + return Err(Error::Type("a reference to a package element, LocalX or ArgX stored where it would outlive its method")); + } + self.copy(v) } pub(crate) fn step(&mut self) -> Result<(), Error> { @@ -277,7 +345,7 @@ impl<'a> Machine<'a> { fn element(&mut self, scope: NodeId, p: &Path) -> Result { match self.ns.resolve(scope, p) { Some(id) => Ok(match self.node_value(id)? { - d @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_)) => copy(&d)?, + d @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_)) => self.copy(&d)?, o => o, }), None => Ok(Object::Lazy(Rc::new((p.clone(), scope)))), @@ -321,6 +389,7 @@ impl<'a> Machine<'a> { let mut f = Frame::new(node, args, table.clone(), self.held.len()); let mut c = Cursor::new(&table, start, end); let flow = self.term_list(&mut f, &mut c); + f.clear(); for &id in f.created.iter().rev() { self.ns.remove(id); } @@ -400,7 +469,7 @@ impl<'a> Machine<'a> { 0xA4 => { c.byte()?; let v = self.arg(f, c)?; - Ok(Flow::Return(copy(&v)?)) + Ok(Flow::Return(self.copy(&v)?)) } 0xA5 => { c.byte()?; @@ -488,7 +557,8 @@ impl<'a> Machine<'a> { let source = c.name()?; let alias = c.name()?; let target = self.resolve(f, &source)?; - self.ns.alias(f.scope, &alias, target)?; + let id = self.ns.alias(f.scope, &alias, target)?; + f.created.push(id); Ok(Flow::Next) } @@ -715,7 +785,13 @@ impl<'a> Machine<'a> { // one the field reaches into, anything else converts to a new one. let data = match self.arg(f, c)? { Object::Buf(b) => b, - o => crate::object::bytes(to_buf(&o, self.w)?), + o => { + let v = to_buf(&o, self.w)?; + match self.new_buf(v)? { + Object::Buf(b) => b, + _ => return Err(Error::Rule("a buffer that is not one")), + } + } }; let index = self.int_arg(f, c)?; let (bit, len) = match op { @@ -908,7 +984,7 @@ impl<'a> Machine<'a> { } bounded(s.len())?; } - Object::str(s) + self.new_str(s)? } 0x11 => { let end = c.pkg_end()?; @@ -922,14 +998,14 @@ impl<'a> Machine<'a> { v.resize(size, 0); } c.at = end; - Object::buf(v) + self.new_buf(v)? } op @ (0x12 | 0x13) => { let end = c.pkg_end()?; let mut p = Self::sub(c, end); let count = if op == 0x12 { u64::from(p.byte()?) } else { self.int_arg(f, &mut p)? }; let count = usize::try_from(count).map_err(|_| Error::Bound("a package larger than this interpreter holds"))?; - bounded(count)?; + crate::object::counted(count)?; let mut elems = Vec::new(); while !p.done() { self.step()?; @@ -949,7 +1025,7 @@ impl<'a> Machine<'a> { } elems.resize(count, Object::Uninit); c.at = end; - Object::Pkg(Rc::new(RefCell::new(elems))) + self.new_pkg(elems)? } 0x5B if c.byte()? == 0x30 => Object::Int(REVISION), _ => return Err(Error::Malformed { at: c.at.saturating_sub(1), why: "not a DataObject (§20.2.3)" }), @@ -1082,7 +1158,7 @@ impl<'a> Machine<'a> { match t { Target::None | Target::Debug => Ok(()), Target::Local(i) => { - let v = copy(&v)?; + let v = self.copy(&v)?; *f.locals[i].borrow_mut() = v; Ok(()) } @@ -1091,7 +1167,7 @@ impl<'a> Machine<'a> { match held { Object::Ref(r) => self.store_ref(&r, v), _ => { - let v = copy(&v)?; + let v = self.copy(&v)?; *f.args[i].borrow_mut() = v; Ok(()) } @@ -1106,15 +1182,13 @@ impl<'a> Machine<'a> { match r { Ref::Node(id) => self.store_node(*id, v), Ref::Slot(s) => { - let v = copy(&v)?; + let v = self.copy(&v)?; *s.borrow_mut() = v; Ok(()) } Ref::Elem(p, i) => { - let v = copy(&v)?; - let mut p = p.borrow_mut(); - *p.get_mut(*i).ok_or(Error::Rule("an Index reference past its package's end"))? = v; - Ok(()) + let v = self.lasting(&v)?; + p.set(*i, v) } Ref::BufField(b) => self.write_buf_field(b, v), } @@ -1126,28 +1200,32 @@ impl<'a> Machine<'a> { Object::Int(_) => self.ns.set(id, Object::Int(to_int(&v, w)?)), Object::Str(s) => { let n = to_str(&v, w)?; - *s.borrow_mut() = n; - Ok(()) + self.charge(n.len())?; + s.replace(n) } Object::Buf(b) => { // Table 19.7: a buffer that exists keeps its size. let n = to_buf(&v, w)?; let len = b.borrow().len(); - *b.borrow_mut() = fit(n, len); - Ok(()) + self.charge(len)?; + b.replace(fit(n, len)) } - Object::Pkg(p) => match copy(&v)? { + Object::Pkg(p) => match &v { Object::Pkg(src) => { - let elems = core::mem::take(&mut *src.borrow_mut()); - *p.borrow_mut() = elems; - Ok(()) + let elems = src.borrow().clone(); + let mut out = Vec::with_capacity(elems.len()); + for e in &elems { + out.push(self.copy_in(e, 1)?); + } + self.charge(out.len() * crate::object::ELEMENT)?; + p.replace(out) } _ => Err(Error::Type("a store to a package of an object that is not one (Table 19.6)")), }, Object::Field(x) => self.write_field(&x, v), Object::BufField(b) => self.write_buf_field(&b, v), Object::Ref(_) => { - let v = copy(&v)?; + let v = self.lasting(&v)?; self.ns.set(id, v) } _ => Err(Error::Type("a store to an object that is not data (Table 19.6)")), @@ -1171,7 +1249,7 @@ impl<'a> Machine<'a> { Object::Ref(Ref::Node(id)) => self.copy_node(id, v), Object::Ref(r) => self.store_ref(&r, v), _ => { - let v = copy(&v)?; + let v = self.copy(&v)?; *f.args[i].borrow_mut() = v; Ok(()) } @@ -1189,7 +1267,7 @@ impl<'a> Machine<'a> { Err(Error::Type("CopyObject to a field of an object that is not an Integer or Buffer (Table 19.8)")) } (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_) | Object::Ref(_), _) => { - let v = copy(&v)?; + let v = self.lasting(&v)?; self.ns.set(id, v) } _ => Err(Error::Type("CopyObject's destination is not a data object (§19.6.17)")), @@ -1500,7 +1578,12 @@ impl<'a> Machine<'a> { /// The logical comparisons' order (§19.6.69-72): the first operand's type /// is the one the second converts to; strings and buffers compare byte by /// byte, a shorter equal prefix the lesser. - fn compare(&self, a: &Object, b: &Object) -> Result { + fn compare(&mut self, a: &Object, b: &Object) -> Result { + let len = |o: &Object| match o { + Object::Str(x) | Object::Buf(x) => x.borrow().len(), + _ => 0, + }; + self.charge(len(a).max(len(b)))?; match a { Object::Int(x) => Ok((*x & self.w.ones()).cmp(&to_int(b, self.w)?)), Object::Str(x) => Ok(x.borrow().as_slice().cmp(to_str(b, self.w)?.as_slice())), @@ -1533,12 +1616,12 @@ impl<'a> Machine<'a> { Object::Int(x) => { let mut v = w.le(*x); v.extend(w.le(to_int(&b, w)?)); - Object::buf(v) + self.new_buf(v)? } Object::Str(x) => { let mut v = x.borrow().clone(); v.extend(tail_str(self, &b)?); - Object::str(v) + self.new_str(v)? } Object::Buf(x) => { let mut v = x.borrow().clone(); @@ -1548,18 +1631,14 @@ impl<'a> Machine<'a> { v.extend(named(self, &b)?); v.push(0); } - Object::buf(v) + self.new_buf(v)? } other => { let mut v = named(self, other)?; v.extend(tail_str(self, &b)?); - Object::str(v) + self.new_str(v)? } }; - match &r { - Object::Str(v) | Object::Buf(v) => bounded(v.borrow().len())?, - _ => {} - } self.store(f, t, r.clone())?; Ok(r) } @@ -1580,11 +1659,11 @@ impl<'a> Machine<'a> { _ => return Err(Error::Rule("ConcatenateResTemplate of a template without an End Tag (§6.4.2.9)")), } } + bounded(out.len())?; out.push(0x79); let sum = out.iter().fold(0u8, |s, &x| s.wrapping_add(x)); out.push(0u8.wrapping_sub(sum)); - bounded(out.len())?; - let r = Object::buf(out); + let r = self.new_buf(out)?; self.store(f, t, r.clone())?; Ok(r) } @@ -1594,18 +1673,39 @@ impl<'a> Machine<'a> { let w = self.w; let src = self.arg(f, c)?; let r = match op { - 0x96 => Object::buf(to_buf(&src, w)?), + 0x96 => { + let v = to_buf(&src, w)?; + self.new_buf(v)? + } 0x97 => match &src { - Object::Int(v) => Object::str(decimal(*v)), - Object::Str(s) => Object::str(s.borrow().clone()), - Object::Buf(b) => Object::str(joined(&b.borrow(), b',', |x| decimal(u64::from(x)))), + Object::Int(v) => self.new_str(decimal(*v))?, + Object::Str(s) => { + let v = s.borrow().clone(); + self.new_str(v)? + } + Object::Buf(b) => { + bounded(b.borrow().len().saturating_mul(4))?; + // Each byte written out as digits: the input's bytes are work too. + self.charge(b.borrow().len().saturating_mul(4))?; + let v = joined(&b.borrow(), b',', |x, out| out.extend(decimal(u64::from(x)))); + self.new_str(v)? + } _ => return Err(Error::Type("ToDecimalString of an object that is not an integer, string or buffer")), }, // §19.6.138 names no form for a buffer's values; each is written // in the two-digit form the Buffer to String rule uses (Table 19.7). 0x98 => match &src { - Object::Buf(b) => Object::str(joined(&b.borrow(), b',', hex2)), - o => Object::str(to_str(o, w)?), + Object::Buf(b) => { + bounded(b.borrow().len().saturating_mul(3))?; + // Each byte written out as digits: the input's bytes are work too. + self.charge(b.borrow().len().saturating_mul(4))?; + let v = joined(&b.borrow(), b',', hex2); + self.new_str(v)? + } + o => { + let v = to_str(o, w)?; + self.new_str(v)? + } }, 0x99 => Object::Int(match &src { Object::Str(s) => int_of_text(&s.borrow(), w)?, @@ -1615,7 +1715,7 @@ impl<'a> Machine<'a> { let b = to_buf(&src, w)?; let n = self.int_arg(f, c)?; let n = if n == w.ones() { usize::MAX } else { usize::try_from(n).unwrap_or(usize::MAX) }; - Object::str(b.iter().take(n).take_while(|&&x| x != 0).copied().collect()) + self.new_str(b.iter().take(n).take_while(|&&x| x != 0).copied().collect())? } _ => { let i = self.int_arg(f, c)?; @@ -1627,7 +1727,7 @@ impl<'a> Machine<'a> { let start = usize::try_from(i).unwrap_or(usize::MAX).min(data.len()); let end = start.saturating_add(usize::try_from(n).unwrap_or(usize::MAX)).min(data.len()); let part = data[start..end].to_vec(); - let r = if is_str { Object::str(part) } else { Object::buf(part) }; + let r = if is_str { self.new_str(part)? } else { self.new_buf(part)? }; let t = self.target(f, c)?; self.store(f, t, r.clone())?; return Ok(r); @@ -1668,7 +1768,7 @@ impl<'a> Machine<'a> { if !matches!(e, Object::Int(_) | Object::Str(_) | Object::Buf(_)) { continue; } - if self.matches(&e, op1, &m1) && self.matches(&e, op2, &m2) { + if self.matches(&e, op1, &m1)? && self.matches(&e, op2, &m2)? { return Ok(Object::Int(i as u64)); } } @@ -1676,24 +1776,34 @@ impl<'a> Machine<'a> { } /// One Match comparison: the element converted to the MatchObject's - /// type, an element that does not convert matching nothing. - fn matches(&self, e: &Object, op: u8, m: &Object) -> bool { + /// type, an element that does not convert matching nothing; a bound + /// reached on the way is a refusal. + fn matches(&mut self, e: &Object, op: u8, m: &Object) -> Result { if op == 0 { - return true; + return Ok(true); } - let e = match m { + let converted = match m { Object::Int(_) => to_int(e, self.w).map(Object::Int), - Object::Str(_) => to_str(e, self.w).map(Object::str), - _ => to_buf(e, self.w).map(Object::buf), + Object::Str(_) => to_str(e, self.w).and_then(|v| self.new_str(v)), + _ => to_buf(e, self.w).and_then(|v| self.new_buf(v)), }; - let Ok(o) = e.and_then(|e| self.compare(&e, m)) else { return false }; - match op { + let e = match converted { + Ok(e) => e, + Err(Error::Type(_)) => return Ok(false), + Err(other) => return Err(other), + }; + let o = match self.compare(&e, m) { + Ok(o) => o, + Err(Error::Type(_)) => return Ok(false), + Err(other) => return Err(other), + }; + Ok(match op { 1 => o == Ordering::Equal, 2 => o != Ordering::Greater, 3 => o == Ordering::Less, 4 => o != Ordering::Less, _ => o == Ordering::Greater, - } + }) } } diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs index 3479806629f..cfae5f7776b 100644 --- a/userland/acpiserver/aml/src/field.rs +++ b/userland/acpiserver/aml/src/field.rs @@ -17,7 +17,7 @@ use crate::exec::Machine; use crate::name::Seg; use crate::namespace::NodeId; use crate::object::{fit, to_buf, to_int, Bytes, Object}; -use crate::{Address, Error, Width, MAX_BYTES}; +use crate::{Address, Error, MAX_BYTES}; pub(crate) struct Region { pub(crate) space: u8, @@ -78,10 +78,6 @@ fn set_bit(b: &mut [u8], i: u64, on: bool) { } } -/// A field's value (§19.6.47): an Integer when it fits one, else a Buffer. -fn value(b: Vec, bits: u64, w: Width) -> Result { - if bits <= u64::from(w.bits) { Ok(Object::Int(w.int_of_bytes(&b)?)) } else { Ok(Object::buf(b)) } -} fn width(bytes: u64) -> crate::Access { match bytes { @@ -151,7 +147,10 @@ impl Machine<'_> { let at = r.base.checked_add(offset).ok_or_else(past)?; match r.space { 0x00 => Ok(Address::Memory(at)), - 0x01 => Ok(Address::Io(at)), + 0x01 => { + let port = u16::try_from(at).ok().filter(|&p| u64::from(p) + w <= 0x1_0000); + Ok(Address::Io(port.ok_or(Error::Rule("a SystemIO access past port 0xFFFF"))?)) + } 0x02 => { // PCI configuration space is 4096 bytes a function. let offset = u16::try_from(at).ok().filter(|&o| u64::from(o) + w <= 0x1000); @@ -199,11 +198,16 @@ impl Machine<'_> { let adr = self.named_int(device, Seg(*b"_ADR"))?.ok_or(Error::NotFound(self.ns.path_of(device, Some(Seg(*b"_ADR")))))?; let bus = self.named_int(bridge, bbn)?.unwrap_or(0); let segment = self.named_int(bridge, Seg(*b"_SEG"))?.unwrap_or(0); - let (dev, fun) = (adr >> 16 & 0xFFFF, adr & 0xFFFF); - if dev > 31 || fun > 7 { + // §6.5.5 and §6.5.6 give the bus in the low 8 bits and the segment + // group in the low 16, the rest reserved: a value outside them names + // no bus this access could reach. + let bus = u8::try_from(bus).map_err(|_| Error::Rule("a _BBN above 0xFF (§6.5.5)"))?; + let segment = u16::try_from(segment).map_err(|_| Error::Rule("a _SEG above 0xFFFF (§6.5.6)"))?; + let (dev, fun) = (adr >> 16, adr & 0xFFFF); + let (Ok(device @ 0..=31), Ok(function @ 0..=7)) = (u8::try_from(dev), u8::try_from(fun)) else { return Err(Error::Rule("an _ADR that names no single PCI function (§6.1.1)")); - } - let p = Pci { segment: segment as u16, bus: bus as u8, device: dev as u8, function: fun as u8 }; + }; + let p = Pci { segment, bus, device, function }; r.pci.set(Some(p)); Ok(p) } @@ -249,11 +253,17 @@ impl Machine<'_> { self.enter()?; let r = self.locked(f.lock, |m| m.read_units(f)); self.leave(); - value(r?, f.len, self.w) + self.value(r?, f.len) + } + + /// A field's value (§19.6.47): an Integer when it fits one, else a Buffer. + fn value(&mut self, b: Vec, bits: u64) -> Result { + if bits <= u64::from(self.w.bits) { Ok(Object::Int(self.w.int_of_bytes(&b)?)) } else { self.new_buf(b) } } fn read_units(&mut self, f: &Field) -> Result, Error> { let mut out = vec![0u8; bytes_for(f.len)?]; + self.charge(out.len())?; let w = self.unit(f)?; let span = 8 * w; for u in f.bit / span..=(f.bit + f.len - 1) / span { @@ -324,6 +334,8 @@ impl Machine<'_> { } pub(crate) fn read_buf_field(&mut self, f: &BufField) -> Result { + // A bit at a time: a byte of work for each. + self.charge(usize::try_from(f.len).unwrap_or(usize::MAX))?; let d = f.data.borrow(); if f.bit.saturating_add(f.len) > (d.len() as u64).saturating_mul(8) { return Err(Error::Rule("a buffer field reaches past its buffer, which shrank since")); @@ -333,7 +345,7 @@ impl Machine<'_> { set_bit(&mut out, i, bit(&d, f.bit + i)); } drop(d); - value(out, f.len, self.w) + self.value(out, f.len) } /// A store to a buffer field (Table 19.7): the source as bytes, truncated @@ -345,7 +357,8 @@ impl Machine<'_> { _ => return Err(Error::Type("a store to a buffer field of an object that is not an integer, buffer or string")), }; let src = fit(src, bytes_for(f.len)?); - let mut d = f.data.borrow_mut(); + self.charge(usize::try_from(f.len).unwrap_or(usize::MAX))?; + let mut d = f.data.bits(); if f.bit.saturating_add(f.len) > (d.len() as u64).saturating_mul(8) { return Err(Error::Rule("a buffer field reaches past its buffer, which shrank since")); } diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index 6b06eed1aca..60616e0860b 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -36,12 +36,12 @@ mod stream; use alloc::rc::Rc; use alloc::string::String; use alloc::vec::Vec; -use core::cell::{Cell, RefCell}; +use core::cell::Cell; use exec::{Frame, Machine}; use name::{Path, Seg}; use namespace::Namespace; -use object::{Body, Method, Mutex, Object, Ref}; +use object::{Body, Meter, Method, Mutex, Object, Ref}; pub(crate) use object::Width; @@ -53,8 +53,16 @@ pub(crate) const MAX_DEPTH: u32 = 256; /// How deep a package may nest within packages, where it is copied or /// handed to the caller. pub(crate) const MAX_NESTING: usize = 64; -/// The largest string, buffer, field or package, in bytes or elements. +/// The largest string, buffer or field, in bytes. pub(crate) const MAX_BYTES: usize = 1 << 20; +/// The largest package, in elements. +pub(crate) const MAX_ELEMENTS: usize = 1 << 16; +/// What one interpreter holds live across every string, buffer and package, +/// in bytes (`object::Meter`). +pub(crate) const MAX_LIVE: usize = 16 << 20; +/// The bytes of work one step stands for: a step for every this many bytes +/// an operation makes, copies, compares or walks. +pub(crate) const WORK_PER_STEP: usize = 64; /// The time one evaluation may ask to Sleep, Stall and Wait, together, in µs. pub(crate) const MAX_WAIT_US: u64 = 10_000_000; /// What the Revision opcode answers (§19.6.119): this interpreter's revision. @@ -132,7 +140,8 @@ pub enum Access { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Address { Memory(u64), - Io(u64), + /// A port in the x86 I/O space, which `in` and `out` address in 16 bits. + Io(u16), PciConfig { segment: u16, bus: u8, device: u8, function: u8, offset: u16 }, EmbeddedControl(u8), } @@ -169,6 +178,7 @@ pub enum Value { /// One machine's namespace, and what loaded it. pub struct Interpreter { ns: Namespace, + meter: Rc, /// Set by the DSDT's revision (§19.6.29), for every table after it. width: Option, } @@ -184,6 +194,7 @@ impl Interpreter { /// (§5.7). pub fn new() -> Self { let mut ns = Namespace::new(); + let meter = Meter::new(); let root = ns.root(); let mut put = |name: &[u8; 4], o: Object| { let p = Path { root: true, up: 0, segs: alloc::vec![Seg(*name)] }; @@ -195,9 +206,12 @@ impl Interpreter { } put(b"_GL_", Object::Mutex(Rc::new(Mutex { sync: 0, held: Cell::new(0), global: true }))); put(b"_OSI", Object::Method(Rc::new(Method { body: Body::Osi, args: 1, serialized: false, sync: 0 }))); - put(b"_OS_", Object::str(b"ToyOS".to_vec())); + // The owner's ruling (2026-10-05): "Microsoft Windows NT", as Windows answers. + if let Ok(os) = meter.bytes(b"Microsoft Windows NT".to_vec()) { + put(b"_OS_", Object::Str(os)); + } put(b"_REV", Object::Int(2)); - Interpreter { ns, width: None } + Interpreter { ns, meter, width: None } } /// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. The @@ -217,7 +231,7 @@ impl Interpreter { let table: Rc<[u8]> = Rc::from(bytes); let root = self.ns.root(); let mut f = Frame::new(root, Vec::new(), table.clone(), 0); - let mut m = Machine::new(&mut self.ns, host, w); + let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone()); let mut c = stream::Cursor::new(&table, toyos_acpi::SDT_HEADER_LEN, table.len()); let r = m.term_list(&mut f, &mut c).and_then(|flow| match flow { exec::Flow::Next => Ok(()), @@ -245,7 +259,7 @@ impl Interpreter { let p = Path::absolute(path)?; let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(String::from(path)))?; let args = args.iter().map(|a| self.object_of(a, w, 0)).collect::, _>>()?; - let mut m = Machine::new(&mut self.ns, host, w); + let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone()); let r = m.evaluate(id, args).and_then(|o| value_of(&mut m, o, 0)); m.finish(r) } @@ -258,11 +272,11 @@ impl Interpreter { Value::Uninitialized => Object::Uninit, Value::Integer(x) => Object::Int(x & w.ones()), Value::String(s) if s.contains(&0) => return Err(Error::Type("a String argument holds a NUL")), - Value::String(s) => Object::str(s.clone()), - Value::Buffer(b) => Object::buf(b.clone()), - Value::Package(p) => Object::Pkg(Rc::new(RefCell::new( - p.iter().map(|e| self.object_of(e, w, depth + 1)).collect::>()?, - ))), + Value::String(s) => Object::Str(self.meter.bytes(s.clone())?), + Value::Buffer(b) => Object::Buf(self.meter.bytes(b.clone())?), + Value::Package(p) => Object::Pkg( + self.meter.list(p.iter().map(|e| self.object_of(e, w, depth + 1)).collect::>()?)?, + ), Value::Reference(path) => { let p = Path::absolute(path)?; let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(path.clone()))?; diff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs index 0f6c72e118c..c51f589c218 100644 --- a/userland/acpiserver/aml/src/namespace.rs +++ b/userland/acpiserver/aml/src/namespace.rs @@ -135,7 +135,7 @@ impl Namespace { let id = match self.free.pop() { Some(index) => { let slot = &mut self.nodes[index as usize]; - let generation = slot.generation.wrapping_add(1); + let generation = slot.generation + 1; *slot = Node { generation, ..node }; NodeId { index, generation } } @@ -150,12 +150,12 @@ impl Namespace { Ok(id) } - pub(crate) fn alias(&mut self, scope: NodeId, path: &Path, target: NodeId) -> Result<(), Error> { + pub(crate) fn alias(&mut self, scope: NodeId, path: &Path, target: NodeId) -> Result { let id = self.create(scope, path, Object::Uninit)?; if let Some(n) = self.nodes.get_mut(id.index as usize) { n.alias = Some(target); } - Ok(()) + Ok(id) } /// Destroys an object and everything below it (§5.5.2.3). @@ -176,7 +176,11 @@ impl Namespace { n.live = false; n.object = Object::Uninit; doomed.extend(core::mem::take(&mut n.children).into_values()); - self.free.push(d.index); + // A slot whose generation would wrap is retired, so no stale + // NodeId ever names a live object again. + if n.generation < u32::MAX { + self.free.push(d.index); + } } } diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs index 3860a9a29db..a59795ebc7d 100644 --- a/userland/acpiserver/aml/src/object.rs +++ b/userland/acpiserver/aml/src/object.rs @@ -5,6 +5,13 @@ //! evaluates: a field created over a buffer (§19.6.21) and a reference made //! by Index (§19.6.62) see the object they were made from. A store copies //! (§19.3.5.8), and so does a return (§19.6.118). +//! +//! Every string, buffer and package is held against its interpreter's +//! [`Meter`] from its making to its drop, so what one interpreter holds live +//! is bounded in sum. A reference to a package element or to a LocalX or +//! ArgX lives only in a LocalX or ArgX, which its method's exit clears: none +//! enters a package or a named object, so no chain of references forms and +//! no cycle outlives its evaluation. use alloc::rc::Rc; use alloc::vec::Vec; @@ -13,12 +20,123 @@ use core::cell::{Cell, RefCell}; use crate::field::{BufField, Field, Region}; use crate::name::Path; use crate::namespace::NodeId; -use crate::{Error, MAX_BYTES, MAX_NESTING}; +use crate::{Error, MAX_BYTES, MAX_ELEMENTS, MAX_LIVE}; -pub(crate) type Bytes = Rc>>; -pub(crate) type Elems = Rc>>; +pub(crate) type Bytes = Rc; +pub(crate) type Elems = Rc; pub(crate) type Slot = Rc>; +/// What one interpreter holds live, in bytes: a string's or buffer's length, +/// a package's elements at [`ELEMENT`] bytes each. +pub(crate) struct Meter { + live: Cell, +} + +/// The bytes a package element is held at. +pub(crate) const ELEMENT: usize = core::mem::size_of::(); + +impl Meter { + pub(crate) fn new() -> Rc { + Rc::new(Meter { live: Cell::new(0) }) + } + + fn take(&self, n: usize) -> Result<(), Error> { + let live = self.live.get().checked_add(n).filter(|&l| l <= MAX_LIVE); + self.live.set(live.ok_or(Error::Bound("more held live than one interpreter holds"))?); + Ok(()) + } + + fn give(&self, n: usize) { + self.live.set(self.live.get().saturating_sub(n)); + } + + /// A string's or buffer's bytes, refused past [`MAX_BYTES`]. + pub(crate) fn bytes(self: &Rc, v: Vec) -> Result { + bounded(v.len())?; + self.take(v.len())?; + Ok(Rc::new(Data { v: RefCell::new(v), meter: self.clone() })) + } + + /// A package's elements, refused past [`MAX_ELEMENTS`]. + pub(crate) fn list(self: &Rc, v: Vec) -> Result { + counted(v.len())?; + self.take(v.len() * ELEMENT)?; + Ok(Rc::new(List { v: RefCell::new(v), meter: self.clone() })) + } +} + +/// A string's or buffer's bytes, held against a [`Meter`]. +pub(crate) struct Data { + v: RefCell>, + meter: Rc, +} + +impl Data { + pub(crate) fn borrow(&self) -> core::cell::Ref<'_, Vec> { + self.v.borrow() + } + + /// The bytes in place, to change and never to resize. + pub(crate) fn bits(&self) -> core::cell::RefMut<'_, Vec> { + self.v.borrow_mut() + } + + pub(crate) fn replace(&self, n: Vec) -> Result<(), Error> { + bounded(n.len())?; + self.meter.take(n.len())?; + let old = self.v.replace(n); + self.meter.give(old.len()); + Ok(()) + } +} + +impl Drop for Data { + fn drop(&mut self) { + self.meter.give(self.v.get_mut().len()); + } +} + +/// A package's elements, held against a [`Meter`]. +pub(crate) struct List { + v: RefCell>, + meter: Rc, +} + +impl List { + pub(crate) fn borrow(&self) -> core::cell::Ref<'_, Vec> { + self.v.borrow() + } + + /// One element, replaced; the count stays. + pub(crate) fn set(&self, i: usize, o: Object) -> Result<(), Error> { + let mut v = self.v.borrow_mut(); + *v.get_mut(i).ok_or(Error::Rule("an Index reference past its package's end"))? = o; + Ok(()) + } + + pub(crate) fn replace(&self, n: Vec) -> Result<(), Error> { + counted(n.len())?; + self.meter.take(n.len() * ELEMENT)?; + let old = self.v.replace(n); + self.meter.give(old.len() * ELEMENT); + Ok(()) + } +} + +impl Drop for List { + fn drop(&mut self) { + self.meter.give(self.v.get_mut().len() * ELEMENT); + } +} + +pub(crate) fn bounded(len: usize) -> Result<(), Error> { + if len > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } +} + +pub(crate) fn counted(len: usize) -> Result<(), Error> { + if len > MAX_ELEMENTS { Err(Error::Bound("a package larger than this interpreter holds")) } else { Ok(()) } +} + #[derive(Clone)] pub(crate) enum Object { Uninit, @@ -78,17 +196,11 @@ pub(crate) struct Mutex { pub(crate) global: bool, } -pub(crate) fn bytes(v: Vec) -> Bytes { - Rc::new(RefCell::new(v)) -} - impl Object { - pub(crate) fn str(v: Vec) -> Object { - Object::Str(bytes(v)) - } - - pub(crate) fn buf(v: Vec) -> Object { - Object::Buf(bytes(v)) + /// Whether this is a reference that may live only in a LocalX or ArgX + /// (the module header). + pub(crate) fn frame_bound(&self) -> bool { + matches!(self, Object::Ref(Ref::Slot(_) | Ref::Elem(..))) } /// The value ObjectType returns (§19.6.96, Table 19.36; a Processor's 12 @@ -115,44 +227,6 @@ impl Object { } } -/// A copy of an object for a store (§19.3.5.8): data is duplicated, anything -/// else is the same object again. Bounded in size and in nesting, both of -/// which a table can grow without limit by storing a package into itself. -pub(crate) fn copy(o: &Object) -> Result { - let mut weight = 0usize; - copy_in(o, &mut weight, 0) -} - -fn copy_in(o: &Object, weight: &mut usize, depth: usize) -> Result { - if depth > MAX_NESTING { - return Err(Error::Bound("a package nests deeper than this interpreter copies")); - } - let mut weigh = |n: usize| { - *weight = weight.saturating_add(n); - if *weight > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } - }; - Ok(match o { - Object::Str(s) => { - weigh(s.borrow().len())?; - Object::str(s.borrow().clone()) - } - Object::Buf(b) => { - weigh(b.borrow().len())?; - Object::buf(b.borrow().clone()) - } - Object::Pkg(p) => { - let p = p.borrow(); - weigh(p.len())?; - let mut out = Vec::with_capacity(p.len()); - for e in p.iter() { - out.push(copy_in(e, weight, depth + 1)?); - } - Object::Pkg(Rc::new(RefCell::new(out))) - } - other => other.clone(), - }) -} - /// The integer width a definition block's DSDT revision gives every integer /// (§19.6.29: "If the ComplianceRevision is less than 2, all integers are /// restricted to 32 bits"). @@ -226,6 +300,7 @@ pub(crate) fn to_buf(o: &Object, w: Width) -> Result, Error> { Object::Int(v) => Ok(w.le(*v)), Object::Str(s) => { let s = s.borrow(); + bounded(s.len() + 1)?; let mut b = s.clone(); if !s.is_empty() { b.push(0); @@ -245,23 +320,26 @@ pub(crate) fn to_str(o: &Object, w: Width) -> Result, Error> { match o { Object::Int(v) => Ok(w.hex(*v)), Object::Str(s) => Ok(s.borrow().clone()), - Object::Buf(b) => Ok(joined(&b.borrow(), b' ', hex2)), + Object::Buf(b) => { + bounded(b.borrow().len().saturating_mul(3))?; + Ok(joined(&b.borrow(), b' ', hex2)) + } Object::Uninit => Err(Error::Type("an uninitialized object is used as a source (Table 19.6)")), _ => Err(Error::Type("an operand that converts to no string (Table 19.6)")), } } -pub(crate) fn hex2(x: u8) -> Vec { - alloc::vec![HEX[usize::from(x >> 4)], HEX[usize::from(x & 0xF)]] +pub(crate) fn hex2(x: u8, out: &mut Vec) { + out.extend([HEX[usize::from(x >> 4)], HEX[usize::from(x & 0xF)]]); } -pub(crate) fn joined(b: &[u8], sep: u8, each: fn(u8) -> Vec) -> Vec { - let mut out = Vec::new(); +pub(crate) fn joined(b: &[u8], sep: u8, each: fn(u8, &mut Vec)) -> Vec { + let mut out = Vec::with_capacity(b.len() * 4); for (i, &x) in b.iter().enumerate() { if i > 0 { out.push(sep); } - out.extend(each(x)); + each(x, &mut out); } out } diff --git a/userland/acpiserver/aml/tests/common/mod.rs b/userland/acpiserver/aml/tests/common/mod.rs index 8d1e850713d..4f682327738 100644 --- a/userland/acpiserver/aml/tests/common/mod.rs +++ b/userland/acpiserver/aml/tests/common/mod.rs @@ -311,7 +311,7 @@ pub struct Machine { fn key(a: Address) -> (u8, u64) { match a { Address::Memory(x) => (0, x), - Address::Io(x) => (1, x), + Address::Io(x) => (1, u64::from(x)), Address::PciConfig { segment, bus, device, function, offset } => ( 2, (u64::from(segment) << 32) diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index 1664cb31e5d..775ee3e5746 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -236,3 +236,142 @@ fn every_truncation_yields_a_value_or_a_refusal() { } } } + +/// Review round 1, BLOCKER 3: every object an operator constructs is +/// bounded, a conversion's output as much as a literal's. +#[test] +fn a_conversion_is_bounded_in_what_it_constructs() { + assert!(matches!(returns(&ret(&op1(0x98, &buffer(&int(0x10_0000), &[]), ZERO))), Err(Error::Bound(_)))); + assert!(matches!(returns(&ret(&op1(0x97, &buffer(&int(0x10_0000), &[]), ZERO))), Err(Error::Bound(_)))); + // ToHexString (ToBuffer (...)), twenty deep, stored to Debug and so never copied. + let mut e = buffer(&int(0x1000), &[]); + for _ in 0..20 { + e = op1(0x98, &op1(0x96, &e, ZERO), ZERO); + } + assert!(matches!(returns(&store(&e, &debug())), Err(Error::Bound(_)))); +} + +/// BLOCKER 4: a step's cost is in proportion to the work it does, so one +/// that walks a mebibyte is charged for it. +#[test] +fn work_in_one_step_is_charged_in_proportion() { + let (mut i, mut m) = loaded(&cat(&[ + &def_name("BIG", &buffer(&int(0x10_0000), &[])), + &cat(&[&[0x5B, 0x13], &name("BIG"), &int(0), &int(0x80_0000), &name("FLD")]), + &method("WALK", 0, &while_(&int(1), &store(&name("FLD"), &local(0)))), + &method("PKGS", 0, &while_(&int(1), &store(&var_package(&int(0x1_0000), &[]), &local(0)))), + &method("BUFS", 0, &while_(&int(1), &store(&buffer(&int(0x10_0000), &[]), &local(0)))), + ])); + for p in ["\\WALK", "\\PKGS", "\\BUFS"] { + assert!(matches!(i.evaluate(&mut m, p, &[]), Err(Error::Bound(_))), "{p}"); + } +} + +/// BLOCKER 5: what an interpreter holds live is bounded in sum, not only +/// object by object. +#[test] +fn what_is_held_live_is_bounded_in_sum() { + let (mut i, mut m) = loaded(&cat(&[ + &def_name("GPKG", &var_package(&int(0x1_0000), &[])), + &method( + "FILL", + 0, + &cat(&[ + &store(&int(0), &local(0)), + &while_( + &int(1), + &cat(&[&store(&buffer(&int(0x10_0000), &[]), &index(&name("GPKG"), &local(0), ZERO)), &increment(&local(0))]), + ), + ]), + ), + ])); + assert!(matches!(i.evaluate(&mut m, "\\FILL", &[]), Err(Error::Bound(_)))); + // What the refused evaluation stored stays held, and the budget with it; + // the interpreter goes on answering. + assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); +} + +/// BLOCKER 6: a reference to a package element or to a LocalX or ArgX never +/// enters a package or a named object, so no chain of them forms and no +/// cycle outlives its evaluation. +#[test] +fn a_reference_chain_cannot_form() { + let chain = while_( + &int(1), + &cat(&[ + &store(&package(&[int(0)]), &local(1)), + &store(&local(0), &index(&local(1), &int(0), ZERO)), + &store(&index(&local(1), &int(0), ZERO), &local(0)), + ]), + ); + let seeded = cat(&[&store(&index(&package(&[int(0)]), &int(0), ZERO), &local(0)), &chain]); + assert!(matches!(returns(&seeded), Err(Error::Type(_)))); + let (mut i, mut m) = loaded(&cat(&[ + &def_name("NUM", &int(0)), + &method("NAME", 0, ©_object(&ref_of(&local(0)), &name("NUM"))), + &method("SELF", 0, &cat(&[&store(&package(&[int(0)]), &local(0)), &store(&index(&local(0), &int(0), ZERO), &index(&local(0), &int(0), ZERO))])), + ])); + assert!(matches!(i.evaluate(&mut m, "\\NAME", &[]), Err(Error::Type(_)))); + assert!(matches!(i.evaluate(&mut m, "\\SELF", &[]), Err(Error::Type(_)))); +} + +/// BLOCKER 9 (a) and (b): a Wait that times out and a Stall are charged to +/// the evaluation's time, as a Sleep is. +#[test] +fn waits_and_stalls_are_bounded_in_time_asked() { + let event = cat(&[&[0x5B, 0x02], &name("EVT")]); + let wait = cat(&[&[0x5B, 0x25], &name("EVT"), &int(0xFFFE)]); + let (mut i, mut m) = loaded(&cat(&[ + &event, + &method("WAIT", 0, &while_(&int(1), &wait)), + &method("STAL", 0, &while_(&int(1), &cat(&[&[0x5B, 0x21], &int(0xFF)]))), + ])); + assert!(matches!(i.evaluate(&mut m, "\\WAIT", &[]), Err(Error::Bound(_)))); + let waited: u64 = m.log.iter().map(|e| if let Event::Sleep(ms) = e { *ms * 1000 } else { 0 }).sum(); + assert!(waited <= 10_000_000, "{waited} µs"); + m.log.clear(); + assert!(matches!(i.evaluate(&mut m, "\\STAL", &[]), Err(Error::Bound(_)))); + let stalled: u64 = m.log.iter().map(|e| if let Event::Stall(us) = e { *us } else { 0 }).sum(); + assert!(stalled <= 10_000_000, "{stalled} µs"); +} + +/// BLOCKER 9 (c): a reference to an object a method created names nothing +/// once the method exits, even after its slot is reused. +#[test] +fn a_reference_outliving_its_object_names_nothing() { + let (mut i, mut m) = loaded(&cat(&[ + &method("MAKE", 0, &cat(&[&def_name("TMP", &int(7)), &ret(&ref_of(&name("TMP")))])), + &method("REUS", 0, &cat(&[&def_name("OTHR", &int(9)), &ret(&name("OTHR"))])), + &method( + "MAIN", + 0, + &cat(&[&store(&name("MAKE"), &local(0)), &store(&name("REUS"), &local(1)), &ret(&deref(&local(0)))]), + ), + ])); + assert!(matches!(i.evaluate(&mut m, "\\MAIN", &[]), Err(Error::NotFound(_)))); +} + +/// BLOCKER 9 (d): ConcatenateResTemplate is bounded in what it constructs. +#[test] +fn a_resource_template_join_is_bounded() { + let big = buffer(&int(0x10_0000), &[]); + let r = returns(&ret(&op2(0x84, &op2(0x73, &big, &buffer(&int(2), &[0x79, 0]), ZERO), &op2(0x73, &big, &buffer(&int(2), &[0x79, 0]), ZERO), ZERO))); + assert!(matches!(r, Err(Error::Bound(_)))); + let half = buffer(&int(0x8_0000), &[]); + let tail = buffer(&int(2), &[0x79, 0]); + let r = returns(&ret(&op2(0x84, &op2(0x73, &half, &tail, ZERO), &op2(0x73, &half, &tail, ZERO), ZERO))); + assert!(matches!(r, Err(Error::Bound(_)))); +} + +/// BLOCKER 7: an Alias is an object its table or method created like any +/// other. +#[test] +fn an_alias_goes_with_what_created_it() { + let alias = cat(&[&[0x06], &name("\\SRC"), &name("ALI")]); + let (mut i, mut m) = loaded(&cat(&[&def_name("SRC", &int(1)), &method("M", 0, &alias)])); + assert_eq!(i.evaluate(&mut m, "\\M", &[]), Ok(Value::Uninitialized)); + assert_eq!(i.evaluate(&mut m, "\\M", &[]), Ok(Value::Uninitialized)); + let ssdt = table(b"SSDT", 2, &cat(&[&[0x06], &name("\\SRC"), &name("\\B"), &def_name("\\SRC", &int(2))])); + assert!(matches!(i.load_bytes(&mut m, &ssdt), Err(Error::Exists(_)))); + assert!(matches!(i.evaluate(&mut m, "\\B", &[]), Err(Error::NotFound(_)))); +} diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs index 9852a6cf10d..f5ecd10af6f 100644 --- a/userland/acpiserver/aml/tests/namespace.rs +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -36,7 +36,8 @@ fn the_predefined_objects_are_there_before_any_table() { &method("TGL", 0, &ret(&object_type(&name("\\_GL")))), &method("TOSI", 0, &ret(&object_type(&name("\\_OSI")))), ])); - assert_eq!(i.evaluate(&mut m, "\\_OS", &[]), Ok(s("ToyOS"))); + // The owner's ruling (2026-10-05): "Microsoft Windows NT", as Windows answers. + assert_eq!(i.evaluate(&mut m, "\\_OS", &[]), Ok(s("Microsoft Windows NT"))); assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); // Table 19.36: a predefined scope is typeless, \_GL a Mutex, \_OSI a Method. assert_eq!(i.evaluate(&mut m, "\\TSB", &[]), Ok(Value::Integer(0))); diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs index def052a2b66..2fc7eeed728 100644 --- a/userland/acpiserver/aml/tests/regions.rs +++ b/userland/acpiserver/aml/tests/regions.rs @@ -323,3 +323,40 @@ fn a_buffer_field_over_a_conversion_and_a_conversion_into_one() { assert_eq!(i.evaluate(&mut m, "\\BUF", &[]), Ok(Value::Buffer(vec![0x5A, 0]))); assert!(matches!(i.evaluate(&mut m, "\\TOHX", &[]), Err(Error::Type(_)))); } + +/// BLOCKER 8: an address word firmware chose that its form cannot hold is +/// refused, never truncated. +#[test] +fn an_address_out_of_its_form_is_refused() { + let lpc = |bbn: u64, seg: u64, adr: u64, offset: u64| { + scope( + "\\_SB", + &device( + "PCI0", + &cat(&[ + &def_name("_BBN", &int(bbn)), + &def_name("_SEG", &int(seg)), + &device( + "DEV", + &cat(&[ + &def_name("_ADR", &int(adr)), + &op_region("CFG", 0x02, &int(offset), &int(0x10)), + &field("CFG", BYTE, &[unit("R0", 8)]), + ]), + ), + ]), + ), + ) + }; + let r0 = |bbn, seg, adr, offset| read(&lpc(bbn, seg, adr, offset), &[], "\\_SB.PCI0.DEV.R0").1; + assert!(r0(0, 0, 0x001F_0000, 0).is_ok()); + assert!(matches!(r0(0x100, 0, 0x001F_0000, 0), Err(Error::Rule(_)))); + assert!(matches!(r0(0, 0x1_0000, 0x001F_0000, 0), Err(Error::Rule(_)))); + assert!(matches!(r0(0, 0, 0x0020_0000, 0), Err(Error::Rule(_)))); + assert!(matches!(r0(0, 0, 0x001F_0008, 0), Err(Error::Rule(_)))); + assert!(matches!(r0(0, 0, 0x001F_0000, 0x1000), Err(Error::Rule(_)))); + let io = |base: u64| cat(&[&op_region("IO", 0x01, &int(base), &int(4)), &field("IO", BYTE, &[unit("P", 8)])]); + assert!(read(&io(0xFFFF), &[], "\\P").1.is_ok()); + assert!(matches!(read(&io(0x1_0000), &[], "\\P").1, Err(Error::Rule(_)))); +} + From c8489d505f10d52fa97f61f1b4d5419afe16aec1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:46:17 +0000 Subject: [PATCH 4/9] A stale reference is followed while its slot lives again; one bound, not two Review round 1, BLOCKER 9. Mutation (c), the generation check dropped, stayed green: the test's reusing object had gone with its method before the old reference was followed, so the slot was dead either way. MAIN now defines the reusing Name itself, alive when the reference is followed. Mutation (d), ConcatenateResTemplate's own bounded(), stayed green because the constructor bounds every object it makes; the operator's copy goes, and deleting the constructor's bound turns the test red. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C9qc7GuMaHZ9xhKsZ57RVz --- userland/acpiserver/aml/src/exec.rs | 1 - userland/acpiserver/aml/tests/hostile.rs | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index 6fe05f7fc90..984f5224953 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -1659,7 +1659,6 @@ impl<'a> Machine<'a> { _ => return Err(Error::Rule("ConcatenateResTemplate of a template without an End Tag (§6.4.2.9)")), } } - bounded(out.len())?; out.push(0x79); let sum = out.iter().fold(0u8, |s, &x| s.wrapping_add(x)); out.push(0u8.wrapping_sub(sum)); diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index 775ee3e5746..7652293d1c3 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -339,13 +339,14 @@ fn waits_and_stalls_are_bounded_in_time_asked() { /// once the method exits, even after its slot is reused. #[test] fn a_reference_outliving_its_object_names_nothing() { + // MAIN's own Name reuses TMP's slot and is alive when the old + // reference is followed. let (mut i, mut m) = loaded(&cat(&[ &method("MAKE", 0, &cat(&[&def_name("TMP", &int(7)), &ret(&ref_of(&name("TMP")))])), - &method("REUS", 0, &cat(&[&def_name("OTHR", &int(9)), &ret(&name("OTHR"))])), &method( "MAIN", 0, - &cat(&[&store(&name("MAKE"), &local(0)), &store(&name("REUS"), &local(1)), &ret(&deref(&local(0)))]), + &cat(&[&store(&name("MAKE"), &local(0)), &def_name("OTHR", &int(9)), &ret(&deref(&local(0)))]), ), ])); assert!(matches!(i.evaluate(&mut m, "\\MAIN", &[]), Err(Error::NotFound(_)))); From 0af9f96476f8468e249f2b4cb433d232d4447b1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:49:44 +0000 Subject: [PATCH 5/9] The owner's three rulings recorded and applied; the interpreter is a stage Review round 1, NOTEs and the implementer's readings. The track records the owner's rulings of 2026-10-05 verbatim (\_OS, _OSI feature groups, old opcodes) and the clean-room ruling as the orchestrator's brief carried it, and gains an interpreter stage whose exit a test and a run against the T14's tables can read. - _OSI answers every ACPI feature group no, which is how Windows answers: Microsoft's page says "Windows supports _OSI only for the use of identifying the host version of Windows". The interpreter so claims nothing it does not carry. - "Refuse only what is truly malformed": a multi-byte PkgLength's reserved bits, a zero-segment MultiNamePath, string bytes above AsciiChar, FieldFlags bit 7, SyncFlags' reserved bits and External's ArgumentCount no longer refuse a table. A reserved address space, AccessType or UpdateRule loads and is refused where an access needs its meaning. - One parser reads a NameString written as text, for DerefOf and for the caller; one function follows a reference to its object, for ObjectType, SizeOf and Concatenate's type names. - The contract states the stack an evaluation needs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C9qc7GuMaHZ9xhKsZ57RVz --- ...ine-in-acpi-mode-and-interprets-its-aml.md | 27 +++++++ userland/acpiserver/aml/src/exec.rs | 70 +++++-------------- userland/acpiserver/aml/src/field.rs | 26 +++---- userland/acpiserver/aml/src/lib.rs | 17 +++-- userland/acpiserver/aml/src/name.rs | 40 ++++++++--- userland/acpiserver/aml/src/stream.rs | 15 ++-- userland/acpiserver/aml/tests/hostile.rs | 12 +++- userland/acpiserver/aml/tests/namespace.rs | 6 +- userland/acpiserver/aml/tests/regions.rs | 16 +++-- 9 files changed, 124 insertions(+), 105 deletions(-) diff --git a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md index c1b3f863913..1de3463b591 100644 --- a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md +++ b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md @@ -71,6 +71,25 @@ The orchestrator's reading of the clean-room ruling, not his: uACPI and ACPICA are run only as black-box oracles, and whoever writes the interpreter never reads their source. +**Ruled** (owner, as the orchestrator's brief of 2026-10-04 carries it, not +verbatim): no clean-room machinery and no spec copies in the repository; the +interpreter's writer works from the ACPI Specification 6.5 directly and +never reads another AML implementation's source. + +**Ruled** (owner, 2026-10-05), on the interpreter (the option chosen, then +its text, verbatim): + +- **`\_OS`**: "\"Microsoft Windows NT\" (Recommended)" — "Same as Windows, + consistent with 'Like Windows, not Linux': firmware that branches on _OS + takes the tested Windows path." +- **`_OSI` feature groups**: "Like Windows answers (Recommended)" — "Answer + each feature group the way Windows does, so the T14 takes its tested path; + the interpreter must then actually support what it claims." +- **Old opcodes**: "Accept what real firmware ships (Recommended)" — "Parse + Processor and other legacy constructs real tables still contain, per their + last spec definition; refuse only what is truly malformed. Tested against + QEMU's table in the tree and your T14 tables locally." + **Stage 1: ACPI mode, its SCI served in userland.** The switch to ACPI mode, and a userland server that claims the SCI, handles the power button, a fixed event that needs no AML, and takes the EC's events. **Exit**: on the @@ -82,6 +101,14 @@ stop, and each EC query number once with its count: a T14 row reads them there. A second T14 row kills the server and reads `SCI_EN` clear in `PM1_CNT` afterwards, the kernel having written `ACPI_DISABLE` to `SMI_CMD`. +**Stage: the interpreter.** `userland/acpiserver/aml`, the AML +interpreter, pure and host-tested inside the server that uses it. **Exit**: +a host test loads QEMU 11.1.1's DSDT +(`toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin`) and evaluates `\_S5` to the +`SLP_TYPa` its boot logged, 0; and the T14's DSDT and SSDTs, read by a +check run outside the tree, load and evaluate `\_S5`, its pull request +recording the result. + **Stage: power-off through the server** (the orchestrator's placement of "Yes, one path"). The ACPI server evaluates `\_S5` and powers the machine off. Blocked on the interpreter's evaluation of `\_S5`. **Exit**: the kernel's `\_S5_` reader, `toyos-acpi/src/dsdt.rs`, and its caller diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index 984f5224953..75f8089ee0e 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -113,32 +113,9 @@ fn type_name(code: u64) -> &'static [u8] { } } -/// A NameString written as ASL text, for DerefOf of a String (§19.6.30). +/// DerefOf of a String names an object by ASL text (§19.6.30). fn path_of_text(s: &[u8]) -> Result { - let bad = Error::Rule("DerefOf of a String that is not a name (§19.6.30)"); - let mut rest = s; - let mut root = false; - let mut up = 0; - if let [b'\\', r @ ..] = rest { - root = true; - rest = r; - } - while let [b'^', r @ ..] = rest { - up += 1; - rest = r; - } - let mut segs = Vec::new(); - if !rest.is_empty() { - for part in rest.split(|&c| c == b'.') { - if part.is_empty() || part.len() > 4 { - return Err(bad); - } - let mut seg = [b'_'; 4]; - seg[..part.len()].copy_from_slice(part); - segs.push(Seg::new(seg).ok_or(Error::Rule("DerefOf of a String that is not a name (§19.6.30)"))?); - } - } - Ok(Path { root, up, segs }) + Path::text(s).ok_or(Error::Rule("DerefOf of a String that is not a name (§19.6.30)")) } impl<'a> Machine<'a> { @@ -653,9 +630,7 @@ impl<'a> Machine<'a> { c.byte()?; c.name()?; c.byte()?; - if c.byte()? > 7 { - return Err(c.malformed("an External's ArgumentCount is above 7 (§20.2.5.2)")); - } + c.byte()?; Ok(Flow::Next) } @@ -663,11 +638,9 @@ impl<'a> Machine<'a> { c.byte()?; c.byte()?; let p = c.name()?; - let flags = c.byte()?; - if flags & 0xF0 != 0 { - return Err(c.malformed("a Mutex's SyncFlags sets reserved bits 4-7 (§20.2.5.2)")); - } - self.define(f, &p, Object::Mutex(Rc::new(Mutex { sync: flags, held: Cell::new(0), global: false })))?; + // SyncFlags: the SyncLevel in bits 0-3, the rest reserved (§20.2.5.2). + let sync = c.byte()? & 0x0F; + self.define(f, &p, Object::Mutex(Rc::new(Mutex { sync, held: Cell::new(0), global: false })))?; Ok(Flow::Next) } @@ -684,10 +657,6 @@ impl<'a> Machine<'a> { c.byte()?; let p = c.name()?; let space = c.byte()?; - // Table 5.182: 0x0C-0x7E are reserved. - if (0x0C..=0x7E).contains(&space) { - return Err(c.malformed("an OperationRegion names a reserved address space (Table 5.182)")); - } let base = self.int_arg(f, c)?; let len = self.int_arg(f, c)?; let r = Region { space, base, len, scope: f.scope, pci: Cell::new(None) }; @@ -730,7 +699,7 @@ impl<'a> Machine<'a> { Kind::Bank { region, bank, value } } }; - let (mut access, lock, update) = flags(l.byte()?).map_err(|why| l.malformed(why))?; + let (mut access, lock, update) = flags(l.byte()?); let mut bit = 0u64; while !l.done() { self.step()?; @@ -741,7 +710,7 @@ impl<'a> Machine<'a> { } 0x01 | 0x03 => { let ext = l.byte()? == 0x03; - access = flags(l.byte()? & 0x0F).map_err(|why| l.malformed(why))?.0; + access = flags(l.byte()?).0; l.byte()?; if ext { l.byte()?; @@ -979,8 +948,9 @@ impl<'a> Machine<'a> { loop { match c.byte()? { 0 => break, - b @ 0x01..=0x7F => s.push(b), - _ => return Err(c.malformed("a String holds a byte AsciiChar does not allow (§20.2.3)")), + // AsciiChar is 0x01-0x7F (§20.2.3); a byte above it + // still ends nothing, and is kept. + b => s.push(b), } bounded(s.len())?; } @@ -1120,6 +1090,11 @@ impl<'a> Machine<'a> { Target::Node(id) => self.node_object(*id)?, Target::Ref(r) => Object::Ref(r.clone()), }; + self.followed(o) + } + + /// An object, a reference followed once to what it refers to. + fn followed(&mut self, o: Object) -> Result { Ok(match o { Object::Ref(Ref::Node(id)) => self.node_object(id)?, Object::Ref(Ref::Slot(s)) => s.borrow().clone(), @@ -1592,17 +1567,6 @@ impl<'a> Machine<'a> { } } - /// The ObjectType code of an object, a reference's being its target's. - fn type_of(&mut self, o: &Object) -> Result { - Ok(match o { - Object::Ref(Ref::Node(id)) => self.node_object(*id)?.type_code(), - Object::Ref(Ref::Slot(s)) => s.borrow().type_code(), - Object::Ref(Ref::Elem(p, i)) => p.borrow().get(*i).map_or(0, Object::type_code), - Object::Ref(Ref::BufField(_)) => 14, - o => o.type_code(), - }) - } - /// Concatenate (§19.6.12, Table 19.30). fn concat(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { let a = self.arg(f, c)?; @@ -1610,7 +1574,7 @@ impl<'a> Machine<'a> { let t = self.target(f, c)?; let w = self.w; let data = |o: &Object| matches!(o, Object::Int(_) | Object::Str(_) | Object::Buf(_)); - let named = |m: &mut Self, o: &Object| -> Result, Error> { Ok(type_name(m.type_of(o)?).to_vec()) }; + let named = |m: &mut Self, o: &Object| -> Result, Error> { Ok(type_name(m.followed(o.clone())?.type_code()).to_vec()) }; let tail_str = |m: &mut Self, o: &Object| if data(o) { to_str(o, w) } else { named(m, o) }; let r = match &a { Object::Int(x) => { diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs index cfae5f7776b..3e0b5cd5830 100644 --- a/userland/acpiserver/aml/src/field.rs +++ b/userland/acpiserver/aml/src/field.rs @@ -92,20 +92,11 @@ fn unit_ones(w: u64) -> u64 { if w >= 8 { u64::MAX } else { (1u64 << (8 * w)) - 1 } } -/// The FieldFlags byte (§20.2.5.2), refused where it sets what is reserved. -pub(crate) fn flags(b: u8) -> Result<(u8, bool, u8), &'static str> { - let access = b & 0x0F; - let update = (b >> 5) & 0x3; - if access > 5 { - return Err("a field's AccessType is reserved (§20.2.5.2)"); - } - if update == 3 { - return Err("a field's UpdateRule is reserved (§20.2.5.2)"); - } - if b & 0x80 != 0 { - return Err("a field's FieldFlags sets reserved bit 7 (§20.2.5.2)"); - } - Ok((access, b & 0x10 != 0, update)) +/// The FieldFlags byte (§20.2.5.2): AccessType, LockRule and UpdateRule. +/// Bit 7 is reserved and ignored; a reserved AccessType or UpdateRule is +/// refused where an access would need its meaning. +pub(crate) fn flags(b: u8) -> (u8, bool, u8) { + (b & 0x0F, b & 0x10 != 0, (b >> 5) & 0x3) } impl Machine<'_> { @@ -131,7 +122,8 @@ impl Machine<'_> { 2 => 2, 3 => 4, 4 => 8, - _ => return Err(Error::Unsupported("BufferAcc, which only the SMBus, IPMI and GenericSerialBus spaces use")), + 5 => return Err(Error::Unsupported("BufferAcc, which only the SMBus, IPMI and GenericSerialBus spaces use")), + _ => return Err(Error::Rule("an access by a reserved AccessType (§20.2.5.2)")), }; if bytes_only && w != 1 { return Err(Error::Type("a field wider than a byte in a space Table 19.34 permits ByteAcc alone")); @@ -172,6 +164,7 @@ impl Machine<'_> { 0x0A => Err(Error::Unsupported("the PCC address space")), 0x0B => Err(Error::Unsupported("the PlatformRtMechanism address space")), 0x7F => Err(Error::Unsupported("the FFixedHW address space")), + 0x0C..=0x7E => Err(Error::Unsupported("a reserved address space (Table 5.182)")), _ => Err(Error::Unsupported("an OEM-defined address space")), } } @@ -308,7 +301,8 @@ impl Machine<'_> { match f.update { 0 => self.unit_read(f, u * w, w)?, 1 => unit_ones(w), - _ => 0, + 2 => 0, + _ => return Err(Error::Rule("a write by a reserved UpdateRule (§20.2.5.2)")), } }; for b in s..e { diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index 60616e0860b..a90f9a5c193 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -15,11 +15,18 @@ //! and `DataTableRegion`. Only one invocation runs at a time, so a Mutex is //! never contended and an Event is never signalled by anyone else. //! -//! The predefined objects are the operating system's (§5.7): `\_OSI` answers -//! as the owner ruled ("Like Windows, not Linux"): yes to every Windows -//! version string Microsoft publishes for `_OSI`, no to anything else; -//! `\_OS` is this system's name and `\_REV` is 2, ACPI 2 or greater with -//! 64-bit integers (§5.7.4). +//! The predefined objects are the operating system's (§5.7), answered as +//! Windows answers them, by the owner's rulings ("Like Windows, not Linux"; +//! 2026-10-05 on `\_OS` and on feature groups): `\_OSI` says yes to every +//! Windows version string Microsoft publishes and no to anything else, the +//! ACPI feature groups included, which Windows does not answer ("Windows +//! supports _OSI only for the use of identifying the host version of +//! Windows"); `\_OS` is "Microsoft Windows NT"; `\_REV` is 2, ACPI 2 or +//! greater with 64-bit integers (§5.7.4). +//! +//! An evaluation nests at most [`MAX_DEPTH`] frames of this interpreter, each +//! measured at about 3.4 KiB of stack in a debug build: a caller runs it on a +//! stack of at least 1 MiB. #![no_std] #![forbid(unsafe_code)] diff --git a/userland/acpiserver/aml/src/name.rs b/userland/acpiserver/aml/src/name.rs index 38bf5c8f5ec..07d4c0bdeb4 100644 --- a/userland/acpiserver/aml/src/name.rs +++ b/userland/acpiserver/aml/src/name.rs @@ -42,23 +42,41 @@ impl Path { !self.root && self.up == 0 && self.segs.len() == 1 } - /// An absolute path written as text, `\_SB.PCI0._STA`; a segment shorter - /// than four characters is padded with `_`, as §5.3 says compilers pad. - pub(crate) fn absolute(text: &str) -> Result { - let rest = text.strip_prefix('\\').ok_or(Error::Rule("a path the caller names is not absolute"))?; + /// A path written as ASL text, `\_SB.PCI0._STA` or `^ABC`: a segment + /// shorter than four characters is padded with `_`, as §5.3 says + /// compilers pad. DerefOf of a String reads one (§19.6.30), and so does a + /// caller naming an object. + pub(crate) fn text(s: &[u8]) -> Option { + let mut rest = s; + let mut root = false; + let mut up = 0; + if let [b'\\', r @ ..] = rest { + root = true; + rest = r; + } + while let [b'^', r @ ..] = rest { + up += 1; + rest = r; + } let mut segs = Vec::new(); if !rest.is_empty() { - for part in rest.split('.') { - let b = part.as_bytes(); - if b.is_empty() || b.len() > 4 { - return Err(Error::Rule("a segment of the caller's path is not one to four characters")); + for part in rest.split(|&c| c == b'.') { + if part.is_empty() || part.len() > 4 { + return None; } let mut seg = [b'_'; 4]; - seg[..b.len()].copy_from_slice(b); - segs.push(Seg::new(seg).ok_or(Error::Rule("a segment of the caller's path is not a NameSeg"))?); + seg[..part.len()].copy_from_slice(part); + segs.push(Seg::new(seg)?); } } - Ok(Path { root: true, up: 0, segs }) + Some(Path { root, up, segs }) + } + + /// An absolute path written as text, as the caller names an object. + pub(crate) fn absolute(text: &str) -> Result { + Path::text(text.as_bytes()) + .filter(|p| p.root) + .ok_or(Error::Rule("a path the caller names is not an absolute path of NameSegs")) } } diff --git a/userland/acpiserver/aml/src/stream.rs b/userland/acpiserver/aml/src/stream.rs index 96b6b21afbd..c9cbacfd120 100644 --- a/userland/acpiserver/aml/src/stream.rs +++ b/userland/acpiserver/aml/src/stream.rs @@ -79,10 +79,9 @@ impl<'a> Cursor<'a> { if follow == 0 { return Ok((start, usize::from(lead & 0x3F))); } - // §20.2.4: bits 5-4 of a multi-byte lead are reserved and must be zero. - if lead & 0x30 != 0 { - return Err(Error::Malformed { at: start, why: "a multi-byte PkgLength sets bits 5-4 of its lead byte" }); - } + // §20.2.4 reserves bits 5-4 of a multi-byte lead: the length is the + // same whatever they hold, so they are ignored (the owner's ruling of + // 2026-10-05: "refuse only what is truly malformed"). let mut len = usize::from(lead & 0x0F); for i in 0..follow { len |= usize::from(self.byte()?) << (4 + 8 * i); @@ -132,11 +131,9 @@ impl<'a> Cursor<'a> { } 0x2F => { self.at += 1; - // §20.2.2: SegCount can be from 1 to 255. - match self.byte()? { - 0 => return Err(self.malformed("a MultiNamePath counts zero segments")), - n => usize::from(n), - } + // §20.2.2 gives SegCount as 1 to 255; zero names no segment, + // which is the NullName's meaning. + usize::from(self.byte()?) } _ => 1, }; diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index 7652293d1c3..b72e5e58f73 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -27,9 +27,17 @@ fn a_package_length_that_leaves_its_package_is_refused() { fn names_and_strings_hold_only_what_their_encoding_allows() { assert!(matches!(load(&cat(&[&[0x08], b"a___", &int(1)])), Err(Error::Malformed { .. }))); assert!(matches!(load(&cat(&[&[0x08], b"1___", &int(1)])), Err(Error::Malformed { .. }))); - assert!(matches!(load(&cat(&[&[0x08, 0x2F, 0x00], &int(1)])), Err(Error::Malformed { .. }))); - assert!(matches!(load(&def_name("S", &[0x0D, b'a', 0x80, 0x00])), Err(Error::Malformed { .. }))); + // A MultiNamePath of zero segments names what the NullName does: no + // object for Name to define. + assert!(matches!(load(&cat(&[&[0x08, 0x2F, 0x00], &int(1)])), Err(Error::Rule(_)))); + // A byte above AsciiChar's range ends nothing and is kept, by the owner's + // ruling to refuse only what is truly malformed; a string without its + // NullChar is. + let (mut i, mut m) = loaded(&def_name("S", &[0x0D, b'a', 0x80, 0x00])); + assert_eq!(i.evaluate(&mut m, "\\S", &[]), Ok(Value::String(vec![b'a', 0x80]))); assert!(matches!(load(&def_name("S", &[0x0D, b'a'])), Err(Error::Malformed { .. }))); + // A multi-byte PkgLength's reserved bits 5-4 change nothing. + assert!(load(&cat(&[&[0x10, 0x76, 0x00], &name("\\"), &[0xA3; 99]])).is_ok()); // A DataObject is all Name takes (§20.2.5.1). assert!(matches!(load(&def_name("L", &local(0))), Err(Error::Malformed { .. }))); // A term list holds terms; data alone is none (§20.2.5). diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs index f5ecd10af6f..e250db3e366 100644 --- a/userland/acpiserver/aml/tests/namespace.rs +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -171,8 +171,10 @@ fn an_external_defines_nothing() { let (mut i, mut m) = loaded(&external); assert!(matches!(int_of(&mut i, &mut m, "\\_SB.PCI0.XYZ"), Err(Error::NotFound(_)))); let mut m = Machine::default(); - let bad = cat(&[&[0x15], &name("XYZ"), &[0x08, 0x08]]); - assert!(matches!(Interpreter::new().load_bytes(&mut m, &dsdt(&bad)), Err(Error::Malformed { .. }))); + // An ArgumentCount above 7 tells a disassembler nothing it could use, + // and defines nothing either. + let odd = cat(&[&[0x15], &name("XYZ"), &[0x08, 0x08]]); + assert!(Interpreter::new().load_bytes(&mut m, &dsdt(&odd)).is_ok()); } /// The owner's ruling (2026-10-05): "Parse Processor and other legacy diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs index 2fc7eeed728..98a01a535a8 100644 --- a/userland/acpiserver/aml/tests/regions.rs +++ b/userland/acpiserver/aml/tests/regions.rs @@ -246,13 +246,15 @@ fn a_host_refusal_and_a_space_not_carried_are_refused_by_name() { assert_eq!(i.evaluate(&mut m, "\\A", &[]), Err(Error::Host("refused".into()))); let smbus = cat(&[&op_region("SMB0", 0x04, &int(0), &int(0x100)), &field("SMB0", BYTE, &[unit("S", 8)])]); assert_eq!(read(&smbus, &[], "\\S").1, Err(Error::Unsupported("the SMBus address space"))); - let mut m = Machine::default(); - let reserved = op_region("RSV", 0x0C, &int(0), &int(1)); - assert!(matches!(Interpreter::new().load_bytes(&mut m, &dsdt(&reserved)), Err(Error::Malformed { .. }))); - for flags in [0x06, 0x60, 0x80] { - let bad = memory_region(flags, &[unit("A", 8)]); - assert!(matches!(Interpreter::new().load_bytes(&mut m, &dsdt(&bad)), Err(Error::Malformed { .. })), "{flags:#x}"); - } + // A reserved space or flag loads, and is refused where an access needs + // its meaning; reserved bit 7 means nothing and is ignored. + let reserved = cat(&[&op_region("RSV", 0x0C, &int(0), &int(1)), &field("RSV", BYTE, &[unit("R", 8)])]); + assert_eq!(read(&reserved, &[], "\\R").1, Err(Error::Unsupported("a reserved address space (Table 5.182)"))); + assert!(matches!(read(&memory_region(0x06, &[unit("A", 8)]), &[], "\\A").1, Err(Error::Rule(_)))); + let (m, r) = store_into(&memory_region(BYTE | 0x60, &[unit("A", 4)]), "A", &int(1)); + assert!(matches!(r, Err(Error::Rule(_)))); + assert_eq!(m.accesses(), vec![]); + assert!(read(&memory_region(BYTE | 0x80, &[unit("A", 8)]), &[], "\\A").1.is_ok()); } /// §19.6.18-23 and Table 19.7: buffer fields see and change their buffer. From 488a05a57946b8ccfcdc2d928a69161c7e8c1974 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 7 Oct 2026 14:49:40 +0200 Subject: [PATCH 6/9] A LocalX reference ends with its frame; field stores stream; the meter counts tables and names Review round 2 of #739, an outside review of the same head, and the run against the T14's tables that round 1 left owed. A reference to a LocalX or ArgX held its slot by an Rc, so one returned from a method outlived the frame, could be stored through, and chained or pointed at itself: 50,000 iterations of the review's self-store left 2.4 MB behind per evaluation. Ref::Slot is now a Weak: the frame alone holds its slots, and a reference whose method has exited names nothing, as a NodeId whose object is gone does. Frame::clear, which emptied the slots to break cycles among them, has nothing left to break and goes. A String stored to a field unit built one field-sized Vec per character before writing any: 98,303 characters into a field of a mebibyte asked for 96 GiB, and under a 1 GiB heap cap the process died with "memory allocation of 1048576 bytes failed". write_field now hands write_units slices of the source, which reads past a slice's end as zeros; nothing is built, and the same store ends at the step bound with a peak heap of 168 KiB. The Meter no longer hides an under-count (give panics), Data::bits hands out a slice, and the Global Lock count and the predefined objects fail fast in the same way. The Meter counted strings, buffers and packages only. Measured at the old head: 24 tables of a mebibyte, each kept by one method, were all held, 25.2 MB and no refusal; one table naming 204,000 field units held 42.6 MB. A loaded table is now a metered Bytes, and every namespace node is taken from the Meter at creation and given back at removal. The T14's tables, read from outside the tree, found two things the interpreter refused that firmware ships. An AnyAcc field took the narrowest natural unit that held it even where that unit ran past its region's end, which refused a 16-bit field in the last two bytes of a 13-byte region; it now takes such a unit only inside the region, else bytes. A PCI_Config region reached its host bridge only from directly below it; a device below a bridge is now on the bus the bridge's Secondary Bus Number register names. load's "shorter than its header" branch was unreachable behind Table::open and goes. QEMU's DSDT is asserted whole and against toyos_acpi::s5_slp_typ, and two of its methods run against the registers they read. Co-Authored-By: Claude Opus 5.5 --- ...inks-are-refused-by-the-aml-interpreter.md | 27 ++++ ...ine-in-acpi-mode-and-interprets-its-aml.md | 40 ++++-- userland/acpiserver/aml/src/exec.rs | 30 ++--- userland/acpiserver/aml/src/field.rs | 95 +++++++++----- userland/acpiserver/aml/src/lib.rs | 35 +++--- userland/acpiserver/aml/src/namespace.rs | 22 ++-- userland/acpiserver/aml/src/object.rs | 52 +++++--- userland/acpiserver/aml/tests/hostile.rs | 117 +++++++++++++++++- userland/acpiserver/aml/tests/namespace.rs | 38 +++++- userland/acpiserver/aml/tests/regions.rs | 24 ++++ 10 files changed, 366 insertions(+), 114 deletions(-) create mode 100644 issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md diff --git a/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md b/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md new file mode 100644 index 00000000000..39f84c0b4cd --- /dev/null +++ b/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md @@ -0,0 +1,27 @@ +--- +status: open +kind: defect +opened: 2026-10-07 +--- + +# QEMU's interrupt links are refused by the AML interpreter + +`userland/acpiserver/aml/src/field.rs`'s `pci` finds the host bridge a +PCI_Config region is below as the nearest scope that names a `_BBN`. QEMU +11.1.1's DSDT (`toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin`) names none: its +`\_SB.PCI0` is a host bridge by its `_HID` and `_CID` alone, on bus 0. So a +field of the region its ISA bridge declares is refused as `Unsupported`, and +with it every method that reads one. + +Measured by evaluating each method that DSDT defines without an argument, +once, against a host that answers every read with zero: 39 methods, 15 a +value, 24 refused with `a PCI_Config region below no host bridge, which names +a _BBN` — the eight interrupt links' `_STA`, `_CRS` and `_DIS`. The T14's +host bridge names a `_BBN`, and none of its methods is refused this way. + +Nothing uses the interrupt links yet; whoever routes a PCI interrupt through +the interpreter on QEMU does. + +**Exit condition**: a host test in `userland/acpiserver/aml/tests/` evaluates +`\_SB.LNKA._CRS` on that fixture to a buffer, the host bridge found by what +the specification names one by (§6.1.5 `_HID`, §6.1.2 `_CID`). diff --git a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md index 1de3463b591..758c82f6176 100644 --- a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md +++ b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md @@ -67,14 +67,15 @@ writes its own, and the battery comes first (his direction of `0ee814f5a`). server; the kernel's power-off table reader is deleted. If the server is broken, power-off fails loudly in every test." -The orchestrator's reading of the clean-room ruling, not his: uACPI and -ACPICA are run only as black-box oracles, and whoever writes the interpreter -never reads their source. - -**Ruled** (owner, as the orchestrator's brief of 2026-10-04 carries it, not -verbatim): no clean-room machinery and no spec copies in the repository; the -interpreter's writer works from the ACPI Specification 6.5 directly and -never reads another AML implementation's source. +**Ruled** (owner, 2026-10-04, his words as the orchestrator's record of the +session holds them), asked where the specifications and a reference +implementation would be kept: "Nowhere why do we need existing c code ans +why do we need to persist prose. The specs exist we can reference them cant +we? Clean romm is only needed for reading code and writing using that code +in a transferred sense". The orchestrator's reading of it: no other +implementation is kept, as an oracle or otherwise, and no specification is +copied into a repository; whoever writes the interpreter works from the ACPI +Specification itself and never reads another AML implementation's source. **Ruled** (owner, 2026-10-05), on the interpreter (the option chosen, then its text, verbatim): @@ -109,6 +110,29 @@ a host test loads QEMU 11.1.1's DSDT check run outside the tree, load and evaluate `\_S5`, its pull request recording the result. +What that check found, which whoever builds on the interpreter would +otherwise pay to find again: + +- **The T14's tables load only against its own memory.** Their + definition-block code reads SystemMemory and PCI_Config while it loads, and + branches on what it reads: with every read answered zero the DSDT refers to + a device its own other branch never defined, and is refused. The check + answered one 16-bit word, the chipset series, and nothing else. A run on + the machine itself is still owed, by the power-off stage, which puts the + interpreter in the server. +- **The T14's processor objects need `Load`.** Its tables hold eight `Load` + opcodes and one `LoadTable`, none run while a table loads, and Linux lists eight tables + loaded that way; the interpreter refuses both as unsupported. +- **A refused evaluation keeps what it stored**, and nothing gives an + interpreter's 16 MiB back: after one method has filled it, a name's value + still evaluates, `\_S5`'s package among them, and every method that must + hold anything new is refused + (`what_is_held_live_is_bounded_in_sum`, `userland/acpiserver/aml/tests/hostile.rs`). + Owner: the power-off stage, which decides + what the server does with an interpreter that is full. **Exit**: a test + fills the budget through one method, and the server then evaluates a + method that builds a buffer. + **Stage: power-off through the server** (the orchestrator's placement of "Yes, one path"). The ACPI server evaluates `\_S5` and powers the machine off. Blocked on the interpreter's evaluation of `\_S5`. **Exit**: the kernel's `\_S5_` reader, `toyos-acpi/src/dsdt.rs`, and its caller diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index 75f8089ee0e..b4f33be548e 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -23,7 +23,8 @@ use crate::field::{flags, BufField, Field, Kind, Region}; use crate::name::{text, Path, Seg}; use crate::namespace::{Namespace, NodeId}; use crate::object::{ - bounded, decimal, fit, hex2, joined, to_buf, to_int, to_str, Body, Meter, Method, Mutex, Object, Ref, Slot, Width, + bounded, decimal, fit, hex2, joined, slot_of, to_buf, to_int, to_str, Body, Bytes, Meter, Method, Mutex, Object, Ref, + Slot, Width, }; use crate::stream::{starts_name, Cursor}; use crate::{Error, Host, MAX_DEPTH, MAX_NESTING, MAX_STEPS, MAX_WAIT_US, REVISION, WINDOWS, WORK_PER_STEP}; @@ -48,7 +49,7 @@ pub(crate) struct Frame { locals: [Slot; 8], args: Vec, scope: NodeId, - table: Rc<[u8]>, + table: Bytes, /// Objects this frame created (§5.5.2.3), destroyed when a method exits. pub(crate) created: Vec, held: usize, @@ -75,15 +76,7 @@ fn slot(o: Object) -> Slot { } impl Frame { - /// Empties every LocalX and ArgX, which drops any reference among them - /// and so any cycle they formed (the module header of `object`). - pub(crate) fn clear(&self) { - for s in self.locals.iter().chain(&self.args) { - *s.borrow_mut() = Object::Uninit; - } - } - - pub(crate) fn new(scope: NodeId, args: Vec, table: Rc<[u8]>, held: usize) -> Frame { + pub(crate) fn new(scope: NodeId, args: Vec, table: Bytes, held: usize) -> Frame { Frame { locals: core::array::from_fn(|_| slot(Object::Uninit)), args: args.into_iter().map(slot).collect(), @@ -234,7 +227,7 @@ impl<'a> Machine<'a> { } pub(crate) fn drop_global(&mut self) -> Result<(), Error> { - self.global = self.global.saturating_sub(1); + self.global = self.global.checked_sub(1).expect("the Global Lock is given back only by who took it"); if self.global == 0 { self.host.global_lock(false).map_err(|d| Error::Host(d.0))?; } @@ -303,7 +296,7 @@ impl<'a> Machine<'a> { fn deref(&mut self, r: &Ref) -> Result { match r { Ref::Node(id) => self.node_value(*id), - Ref::Slot(s) => Ok(s.borrow().clone()), + Ref::Slot(s) => Ok(slot_of(s)?.borrow().clone()), Ref::Elem(p, i) => { let e = p.borrow().get(*i).cloned().ok_or(Error::Rule("an Index reference past its package's end"))?; match e { @@ -364,9 +357,9 @@ impl<'a> Machine<'a> { self.levels.push(m.sync); } let mut f = Frame::new(node, args, table.clone(), self.held.len()); - let mut c = Cursor::new(&table, start, end); + let bytes = table.borrow(); + let mut c = Cursor::new(&bytes, start, end); let flow = self.term_list(&mut f, &mut c); - f.clear(); for &id in f.created.iter().rev() { self.ns.remove(id); } @@ -1097,7 +1090,7 @@ impl<'a> Machine<'a> { fn followed(&mut self, o: Object) -> Result { Ok(match o { Object::Ref(Ref::Node(id)) => self.node_object(id)?, - Object::Ref(Ref::Slot(s)) => s.borrow().clone(), + Object::Ref(Ref::Slot(s)) => slot_of(&s)?.borrow().clone(), Object::Ref(Ref::Elem(p, i)) => { let e = p.borrow().get(i).cloned().ok_or(Error::Rule("an Index reference past its package's end"))?; self.resolve_lazy(e)? @@ -1157,6 +1150,7 @@ impl<'a> Machine<'a> { match r { Ref::Node(id) => self.store_node(*id, v), Ref::Slot(s) => { + let s = slot_of(s)?; let v = self.copy(&v)?; *s.borrow_mut() = v; Ok(()) @@ -1521,8 +1515,8 @@ impl<'a> Machine<'a> { fn ref_of(&self, f: &Frame, t: Target) -> Result { match t { Target::Node(id) => Ok(Ref::Node(id)), - Target::Local(i) => Ok(Ref::Slot(f.locals[i].clone())), - Target::Arg(i) => Ok(Ref::Slot(f.args[i].clone())), + Target::Local(i) => Ok(Ref::Slot(Rc::downgrade(&f.locals[i]))), + Target::Arg(i) => Ok(Ref::Slot(Rc::downgrade(&f.args[i]))), Target::Ref(r) => Ok(r), Target::None | Target::Debug => Err(Error::Type("a reference to the Debug object")), } diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs index 3e0b5cd5830..7611be29825 100644 --- a/userland/acpiserver/aml/src/field.rs +++ b/userland/acpiserver/aml/src/field.rs @@ -101,22 +101,26 @@ pub(crate) fn flags(b: u8) -> (u8, bool, u8) { impl Machine<'_> { /// The bytes of one access unit, by the access type and, for an access - /// type of AnyAcc, the narrowest naturally aligned unit holding the whole - /// field (§19.6.47: "accesses within the parent object are performed - /// naturally aligned"), else bytes. + /// type of AnyAcc, the narrowest naturally aligned unit that holds the + /// whole field and lies within its region (§19.6.47: "accesses within + /// the parent object are performed naturally aligned"), else bytes. fn unit(&self, f: &Field) -> Result { - let space = match &f.kind { - Kind::Region(r) | Kind::Bank { region: r, .. } => Some(r.space), + let region = match &f.kind { + Kind::Region(r) | Kind::Bank { region: r, .. } => Some(r), Kind::Index { .. } => None, }; // Table 19.34: EmbeddedControl, SystemCMOS, GeneralPurposeIO and PCC // permit byte access only. - let bytes_only = matches!(space, Some(0x03 | 0x05 | 0x08 | 0x0A)); + let bytes_only = matches!(region.map(|r| r.space), Some(0x03 | 0x05 | 0x08 | 0x0A)); + let within = |end: u64| region.is_none_or(|r| end <= r.len); let w = match f.access { 0 if bytes_only => 1, 0 => [1u64, 2, 4, 8] .into_iter() - .find(|&w| f.bit / (8 * w) == (f.bit + f.len - 1) / (8 * w)) + .find(|&w| { + let unit = f.bit / (8 * w); + unit == (f.bit + f.len - 1) / (8 * w) && within((unit + 1) * w) + }) .unwrap_or(1), 1 => 1, 2 => 2, @@ -170,39 +174,50 @@ impl Machine<'_> { } /// The function a PCI_Config region addresses: its device's `_ADR` - /// (§6.1.1: device in the high word, function in the low), on the bus a - /// host bridge's `_BBN` names (§6.5.5), in the segment group its `_SEG` - /// names or 0 without one (§6.5.6). The region is declared in the host - /// bridge itself or in a device directly below it. + /// (§6.1.1: device in the high word, function in the low), in the + /// segment group the host bridge's `_SEG` names or 0 without one + /// (§6.5.6). The host bridge is the nearest scope naming a `_BBN`, which + /// is the bus directly below it (§6.5.5); each device between it and the + /// region's is a bridge, whose Secondary Bus Number register is the bus + /// below it (PCI-to-PCI Bridge Architecture Specification 1.2, §3.2.5.4). + /// A region declared in the host bridge itself addresses the bridge. fn pci(&mut self, r: &Region) -> Result { if let Some(p) = r.pci.get() { return Ok(p); } - let device = r.scope; let bbn = Seg(*b"_BBN"); - let bridge = if self.ns.child(device, bbn).is_some() { - device - } else { - match self.ns.parent(device) { - Some(p) if self.ns.child(p, bbn).is_some() => p, - _ => return Err(Error::Unsupported("a PCI_Config region not on a host bridge's bus, which names a _BBN")), - } - }; - let adr = self.named_int(device, Seg(*b"_ADR"))?.ok_or(Error::NotFound(self.ns.path_of(device, Some(Seg(*b"_ADR")))))?; + let mut path = Vec::new(); + let mut bridge = r.scope; + while self.ns.child(bridge, bbn).is_none() { + self.step()?; + path.push(bridge); + let above = self.ns.parent(bridge); + bridge = above.ok_or(Error::Unsupported("a PCI_Config region below no host bridge, which names a _BBN"))?; + } let bus = self.named_int(bridge, bbn)?.unwrap_or(0); let segment = self.named_int(bridge, Seg(*b"_SEG"))?.unwrap_or(0); // §6.5.5 and §6.5.6 give the bus in the low 8 bits and the segment // group in the low 16, the rest reserved: a value outside them names // no bus this access could reach. - let bus = u8::try_from(bus).map_err(|_| Error::Rule("a _BBN above 0xFF (§6.5.5)"))?; + let mut bus = u8::try_from(bus).map_err(|_| Error::Rule("a _BBN above 0xFF (§6.5.5)"))?; let segment = u16::try_from(segment).map_err(|_| Error::Rule("a _SEG above 0xFFFF (§6.5.6)"))?; - let (dev, fun) = (adr >> 16, adr & 0xFFFF); - let (Ok(device @ 0..=31), Ok(function @ 0..=7)) = (u8::try_from(dev), u8::try_from(fun)) else { + for &above in path.iter().skip(1).rev() { + let b = self.function(above, segment, bus)?; + let secondary = Address::PciConfig { segment, bus, device: b.device, function: b.function, offset: 0x19 }; + bus = self.host.read(secondary, crate::Access::Byte).map_err(|d| Error::Host(d.0))? as u8; + } + let at = self.function(path.first().copied().unwrap_or(bridge), segment, bus)?; + r.pci.set(Some(at)); + Ok(at) + } + + /// The function a device's `_ADR` names on `bus` (§6.1.1). + fn function(&mut self, device: NodeId, segment: u16, bus: u8) -> Result { + let adr = self.named_int(device, Seg(*b"_ADR"))?.ok_or(Error::NotFound(self.ns.path_of(device, Some(Seg(*b"_ADR")))))?; + let (Ok(device @ 0..=31), Ok(function @ 0..=7)) = (u8::try_from(adr >> 16), u8::try_from(adr & 0xFFFF)) else { return Err(Error::Rule("an _ADR that names no single PCI function (§6.1.1)")); }; - let p = Pci { segment, bus, device, function }; - r.pci.set(Some(p)); - Ok(p) + Ok(Pci { segment, bus, device, function }) } fn unit_read(&mut self, f: &Field, offset: u64, w: u64) -> Result { @@ -272,22 +287,34 @@ impl Machine<'_> { /// A store to a field unit (Table 19.7): an Integer overwrites the whole /// field; a Buffer is written in pieces of the field's size, lower first, - /// each zero-extended; a String is written a character at a time. + /// each zero-extended, and an empty one as zeros; a String is written a + /// character at a time. The pieces are slices of the source, each + /// written before the next is taken. pub(crate) fn write_field(&mut self, f: &Field, v: Object) -> Result<(), Error> { let n = bytes_for(f.len)?; - let pieces: Vec> = match &v { - Object::Int(x) => vec![fit(x.to_le_bytes().to_vec(), n)], - Object::Buf(b) if b.borrow().is_empty() => vec![vec![0; n]], - Object::Buf(b) => b.borrow().chunks(n).map(|c| fit(c.to_vec(), n)).collect(), - Object::Str(s) => s.borrow().iter().map(|&c| fit(vec![c], n)).collect(), + let (int, held); + let (source, piece): (&[u8], usize) = match &v { + Object::Int(x) => { + int = x.to_le_bytes(); + (&int, int.len()) + } + Object::Buf(b) => { + held = b.borrow(); + if held.is_empty() { (&[0], 1) } else { (&held, n) } + } + Object::Str(s) => { + held = s.borrow(); + (&held, 1) + } _ => return Err(Error::Type("a store to a field unit of an object that is not an integer, buffer or string")), }; self.enter()?; - let r = self.locked(f.lock, |m| pieces.iter().try_for_each(|p| m.write_units(f, p))); + let r = self.locked(f.lock, |m| source.chunks(piece).try_for_each(|p| m.write_units(f, p))); self.leave(); r } + /// Writes the field from `data`, which is zeros past its end. fn write_units(&mut self, f: &Field, data: &[u8]) -> Result<(), Error> { let w = self.unit(f)?; let span = 8 * w; diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index a90f9a5c193..153131087ea 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -5,8 +5,10 @@ //! A definition block is firmware's, and so untrusted: whatever its bytes, //! [`Interpreter::load`] and [`Interpreter::evaluate`] return a value or a //! named [`Error`], never panic, and never run unbounded — every evaluation -//! is bounded in steps, nesting, object size and time asked to sleep. A load -//! refused leaves the namespace without anything that table created. +//! is bounded in steps, nesting, object size and time asked to sleep, and +//! what an interpreter holds of tables, namespace and objects is bounded in +//! sum. A load refused leaves the namespace without anything that table +//! created. //! //! The library touches no hardware. An operation region's field is read and //! written through the [`Host`] the caller passes, in SystemMemory, @@ -64,8 +66,8 @@ pub(crate) const MAX_NESTING: usize = 64; pub(crate) const MAX_BYTES: usize = 1 << 20; /// The largest package, in elements. pub(crate) const MAX_ELEMENTS: usize = 1 << 16; -/// What one interpreter holds live across every string, buffer and package, -/// in bytes (`object::Meter`). +/// What one interpreter holds live across every table, namespace node, +/// string, buffer and package, in bytes (`object::Meter`). pub(crate) const MAX_LIVE: usize = 16 << 20; /// The bytes of work one step stands for: a step for every this many bytes /// an operation makes, copies, compares or walks. @@ -200,13 +202,12 @@ impl Interpreter { /// An empty namespace holding the predefined scopes (§5.3.1) and objects /// (§5.7). pub fn new() -> Self { - let mut ns = Namespace::new(); let meter = Meter::new(); + let mut ns = Namespace::new(meter.clone()); let root = ns.root(); let mut put = |name: &[u8; 4], o: Object| { let p = Path { root: true, up: 0, segs: alloc::vec![Seg(*name)] }; - // The root is empty and every name differs. - let _ = ns.create(root, &p, o); + ns.create(root, &p, o).expect("the root is empty and every predefined name differs"); }; for scope in [b"_GPE", b"_PR_", b"_SB_", b"_SI_", b"_TZ_"] { put(scope, Object::Scope); @@ -214,32 +215,30 @@ impl Interpreter { put(b"_GL_", Object::Mutex(Rc::new(Mutex { sync: 0, held: Cell::new(0), global: true }))); put(b"_OSI", Object::Method(Rc::new(Method { body: Body::Osi, args: 1, serialized: false, sync: 0 }))); // The owner's ruling (2026-10-05): "Microsoft Windows NT", as Windows answers. - if let Ok(os) = meter.bytes(b"Microsoft Windows NT".to_vec()) { - put(b"_OS_", Object::Str(os)); - } + let os = meter.bytes(b"Microsoft Windows NT".to_vec()).expect("an empty meter holds twenty bytes"); + put(b"_OS_", Object::Str(os)); put(b"_REV", Object::Int(2)); Interpreter { ns, meter, width: None } } /// Loads a DSDT or SSDT (§5.4.2): the DSDT first, then each SSDT. The - /// table's length and checksum are [`toyos_acpi::Table::open`]'s. + /// table's header, length and checksum are [`toyos_acpi::Table::open`]'s. pub fn load(&mut self, host: &mut dyn Host, table: &toyos_acpi::Table

) -> Result<(), Error> { let bytes: Vec = (0..table.len()).map_while(|i| table.byte(i)).collect(); - let (Some(signature), Some(&revision)) = (bytes.first_chunk::<4>(), bytes.get(toyos_acpi::SDT_REVISION)) else { - return Err(Error::Table("shorter than its header (§5.2.6)")); - }; - let w = match (signature, self.width) { - (b"DSDT", None) => Width { bits: if revision < 2 { 32 } else { 64 } }, + let w = match (&bytes[..4], self.width) { + (b"DSDT", None) => Width { bits: if bytes[toyos_acpi::SDT_REVISION] < 2 { 32 } else { 64 } }, (b"DSDT", Some(_)) => return Err(Error::Table("a second DSDT")), (b"SSDT", Some(w)) => w, (b"SSDT", None) => return Err(Error::Table("an SSDT before the DSDT, whose revision sets every integer's width")), _ => return Err(Error::Table("not a DSDT or SSDT (§5.2.11)")), }; - let table: Rc<[u8]> = Rc::from(bytes); + // Held for as long as a method it defines refers to it. + let table = self.meter.bytes(bytes)?; let root = self.ns.root(); let mut f = Frame::new(root, Vec::new(), table.clone(), 0); let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone()); - let mut c = stream::Cursor::new(&table, toyos_acpi::SDT_HEADER_LEN, table.len()); + let bytes = table.borrow(); + let mut c = stream::Cursor::new(&bytes, toyos_acpi::SDT_HEADER_LEN, bytes.len()); let r = m.term_list(&mut f, &mut c).and_then(|flow| match flow { exec::Flow::Next => Ok(()), _ => Err(Error::Rule("a Return, Break or Continue at definition block level")), diff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs index c51f589c218..c9b91e7c55c 100644 --- a/userland/acpiserver/aml/src/namespace.rs +++ b/userland/acpiserver/aml/src/namespace.rs @@ -4,15 +4,21 @@ //! reference to an object a method created and its exit destroyed //! (§5.5.2.3) resolves to nothing rather than to whatever reused its slot. //! Nothing here recurses over the tree's depth, which a table chooses. +//! Every node is held against the interpreter's [`Meter`] from its creation +//! to its removal. use alloc::collections::BTreeMap; +use alloc::rc::Rc; use alloc::string::String; use alloc::vec::Vec; use crate::name::{Path, Seg}; -use crate::object::Object; +use crate::object::{Meter, Object}; use crate::Error; +/// The bytes a node is held at. +const NODE: usize = core::mem::size_of::(); + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) struct NodeId { index: u32, @@ -33,10 +39,11 @@ struct Node { pub(crate) struct Namespace { nodes: Vec, free: Vec, + meter: Rc, } impl Namespace { - pub(crate) fn new() -> Self { + pub(crate) fn new(meter: Rc) -> Self { let root = Node { seg: Seg(*b"\\___"), parent: None, @@ -46,7 +53,7 @@ impl Namespace { generation: 0, live: true, }; - Namespace { nodes: alloc::vec![root], free: Vec::new() } + Namespace { nodes: alloc::vec![root], free: Vec::new(), meter } } pub(crate) fn root(&self) -> NodeId { @@ -132,6 +139,8 @@ impl Namespace { generation: 0, live: true, }; + let fresh = u32::try_from(self.nodes.len()).map_err(|_| Error::Bound("the namespace's node count"))?; + self.meter.take(NODE)?; let id = match self.free.pop() { Some(index) => { let slot = &mut self.nodes[index as usize]; @@ -140,13 +149,11 @@ impl Namespace { NodeId { index, generation } } None => { - let index = u32::try_from(self.nodes.len()).map_err(|_| Error::Bound("the namespace's node count"))?; self.nodes.push(node); - NodeId { index, generation: 0 } + NodeId { index: fresh, generation: 0 } } }; - let parent = self.nodes.get_mut(at.index as usize).ok_or(Error::Rule("a parent vanished"))?; - parent.children.insert(*last, id); + self.nodes[at.index as usize].children.insert(*last, id); Ok(id) } @@ -175,6 +182,7 @@ impl Namespace { }; n.live = false; n.object = Object::Uninit; + self.meter.give(NODE); doomed.extend(core::mem::take(&mut n.children).into_values()); // A slot whose generation would wrap is retired, so no stale // NodeId ever names a live object again. diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs index a59795ebc7d..5d423ba168a 100644 --- a/userland/acpiserver/aml/src/object.rs +++ b/userland/acpiserver/aml/src/object.rs @@ -6,14 +6,19 @@ //! by Index (§19.6.62) see the object they were made from. A store copies //! (§19.3.5.8), and so does a return (§19.6.118). //! -//! Every string, buffer and package is held against its interpreter's -//! [`Meter`] from its making to its drop, so what one interpreter holds live -//! is bounded in sum. A reference to a package element or to a LocalX or -//! ArgX lives only in a LocalX or ArgX, which its method's exit clears: none -//! enters a package or a named object, so no chain of references forms and -//! no cycle outlives its evaluation. - -use alloc::rc::Rc; +//! Every string, buffer and package, every loaded table a method still runs +//! from and every namespace node is held against its interpreter's [`Meter`] +//! from its making to its end, so what one interpreter holds live is bounded +//! in sum. +//! +//! A reference to a LocalX or ArgX does not hold it: the frame alone does, +//! and once its method exits the reference names nothing. A reference to a +//! package element holds its package, and lives only in a LocalX or ArgX. +//! Neither kind enters a package or a named object, so no reference owns +//! another, no chain of them forms, and none is part of a cycle. + +use alloc::rc::{Rc, Weak}; +use alloc::string::String; use alloc::vec::Vec; use core::cell::{Cell, RefCell}; @@ -26,8 +31,10 @@ pub(crate) type Bytes = Rc; pub(crate) type Elems = Rc; pub(crate) type Slot = Rc>; -/// What one interpreter holds live, in bytes: a string's or buffer's length, -/// a package's elements at [`ELEMENT`] bytes each. +/// What one interpreter holds live, in bytes: a string's, buffer's or +/// table's length, a package's elements at [`ELEMENT`] bytes each, a +/// namespace node at the size of one. It counts those alone, not what a +/// node or an element points at beside them, nor the allocator's overhead. pub(crate) struct Meter { live: Cell, } @@ -40,17 +47,17 @@ impl Meter { Rc::new(Meter { live: Cell::new(0) }) } - fn take(&self, n: usize) -> Result<(), Error> { + pub(crate) fn take(&self, n: usize) -> Result<(), Error> { let live = self.live.get().checked_add(n).filter(|&l| l <= MAX_LIVE); self.live.set(live.ok_or(Error::Bound("more held live than one interpreter holds"))?); Ok(()) } - fn give(&self, n: usize) { - self.live.set(self.live.get().saturating_sub(n)); + pub(crate) fn give(&self, n: usize) { + self.live.set(self.live.get().checked_sub(n).expect("the meter gives back only what it took")); } - /// A string's or buffer's bytes, refused past [`MAX_BYTES`]. + /// A string's, buffer's or table's bytes, refused past [`MAX_BYTES`]. pub(crate) fn bytes(self: &Rc, v: Vec) -> Result { bounded(v.len())?; self.take(v.len())?; @@ -76,9 +83,9 @@ impl Data { self.v.borrow() } - /// The bytes in place, to change and never to resize. - pub(crate) fn bits(&self) -> core::cell::RefMut<'_, Vec> { - self.v.borrow_mut() + /// The bytes in place, to change: a slice, which cannot be resized. + pub(crate) fn bits(&self) -> core::cell::RefMut<'_, [u8]> { + core::cell::RefMut::map(self.v.borrow_mut(), Vec::as_mut_slice) } pub(crate) fn replace(&self, n: Vec) -> Result<(), Error> { @@ -170,14 +177,19 @@ pub(crate) enum Object { pub(crate) enum Ref { Node(NodeId), /// A method's LocalX or ArgX (§19.3.5.8.1: "RefOf (ArgX) returns a - /// reference to ArgX"). - Slot(Slot), + /// reference to ArgX"), which its frame alone holds. + Slot(Weak>), Elem(Elems, usize), BufField(Rc), } +/// The LocalX or ArgX a reference names, while its method runs. +pub(crate) fn slot_of(s: &Weak>) -> Result { + s.upgrade().ok_or_else(|| Error::NotFound(String::from("a LocalX or ArgX its method's exit destroyed"))) +} + pub(crate) enum Body { - Aml { table: Rc<[u8]>, start: usize, end: usize }, + Aml { table: Bytes, start: usize, end: usize }, /// `\_OSI`, which the operating system implements (§5.7.2). Osi, } diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index b72e5e58f73..fe47146a11d 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -4,7 +4,7 @@ mod common; use common::*; -use toyos_aml::{Error, Interpreter, Value}; +use toyos_aml::{Access, Address, Denied, Error, Host, Interpreter, Value}; const ZERO: &[u8] = &[0x00]; @@ -281,6 +281,8 @@ fn work_in_one_step_is_charged_in_proportion() { fn what_is_held_live_is_bounded_in_sum() { let (mut i, mut m) = loaded(&cat(&[ &def_name("GPKG", &var_package(&int(0x1_0000), &[])), + &def_name("_S5", &package(&[int(7), int(7), int(0), int(0)])), + &method("MAKE", 0, &ret(&buffer(&int(0x10_0000), &[]))), &method( "FILL", 0, @@ -293,10 +295,119 @@ fn what_is_held_live_is_bounded_in_sum() { ]), ), ])); + assert_eq!(i.evaluate(&mut m, "\\MAKE", &[]).map(|v| v == Value::Buffer(vec![0; 0x10_0000])), Ok(true)); assert!(matches!(i.evaluate(&mut m, "\\FILL", &[]), Err(Error::Bound(_)))); - // What the refused evaluation stored stays held, and the budget with it; - // the interpreter goes on answering. + // What the refused evaluation stored stays held: the interpreter goes on + // answering what needs nothing new held, a named package among it, and + // refuses what does. + let seven = Value::Integer(7); + let zero = Value::Integer(0); + assert_eq!(i.evaluate(&mut m, "\\_S5", &[]), Ok(Value::Package(vec![seven.clone(), seven, zero.clone(), zero]))); + assert!(matches!(i.evaluate(&mut m, "\\MAKE", &[]), Err(Error::Bound(_)))); +} + +/// A table a method still runs from and every namespace node count against +/// what an interpreter holds live, as its strings, buffers and packages do. +#[test] +fn tables_and_names_are_held_against_the_live_bound() { + // Tables of a mebibyte, each kept whole by the one method it defines. + let (mut i, mut m) = loaded(&[]); + let kept = |n: usize| table(b"SSDT", 2, &method(&format!("M{n:03}"), 0, &vec![0xA3; (1 << 20) - 52])); + let refused = (0..17).find_map(|n| i.load_bytes(&mut m, &kept(n)).err().map(|e| (n, e))); + let Some((n, Error::Bound(_))) = refused else { panic!("seventeen mebibytes of tables are held: {refused:?}") }; + assert!(matches!(i.evaluate(&mut m, &format!("\\M{n:03}"), &[]), Err(Error::NotFound(_)))); assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); + + // Field units, five bytes of table each: 204,000 names a table. + let digits = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let units: Vec> = (0..4000) + .map(|u| unit(std::str::from_utf8(&[b'A' + (u / 1296) as u8, digits[u / 36 % 36], digits[u % 36]]).unwrap(), 8)) + .collect(); + let dense = |t: usize| { + let devices: Vec> = (0..51).map(|d| device(&format!("D{t}{d:02}"), &field("\\MEM", 0x01, &units))).collect(); + table(b"SSDT", 2, &devices.concat()) + }; + let (mut i, mut m) = loaded(&op_region("MEM", 0x00, &int(0), &int(0x1000))); + let refused = (0..3).find_map(|t| i.load_bytes(&mut m, &dense(t)).err().map(|e| (t, e))); + let Some((t, Error::Bound(_))) = refused else { panic!("612,000 names are held: {refused:?}") }; + assert!(matches!(i.evaluate(&mut m, &format!("\\D{t}00.AAA"), &[]), Err(Error::NotFound(_)))); + assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); +} + +/// A host that takes every access and keeps none, for a store whose bound is +/// a million accesses away. +struct Sink; + +impl Host for Sink { + fn read(&mut self, _: Address, _: Access) -> Result { + Ok(0) + } + fn write(&mut self, _: Address, _: Access, _: u64) -> Result<(), Denied> { + Ok(()) + } + fn sleep(&mut self, _: u64) {} + fn stall(&mut self, _: u64) {} + fn timer(&mut self) -> u64 { + 0 + } + fn notify(&mut self, _: &str, _: u64) {} + fn global_lock(&mut self, _: bool) -> Result<(), Denied> { + Ok(()) + } +} + +/// A String stored to a field is written a character at a time, each charged +/// as it is written: 98,303 characters into a field of a mebibyte end at the +/// step bound, having made no piece ahead of its write. +#[test] +fn a_string_stored_to_a_field_is_bounded_as_it_is_written() { + let body = cat(&[ + &op_region("MEM", 0x00, &int(0), &int(0x10_0000)), + &field("MEM", 0x01, &[unit("HUGE", 0x80_0000)]), + &method("MAIN", 0, &store(&op1(0x98, &buffer(&int(0x8000), &[]), ZERO), &name("HUGE"))), + ]); + let mut i = Interpreter::new(); + i.load_bytes(&mut Machine::default(), &dsdt(&body)).unwrap(); + assert!(matches!(i.evaluate(&mut Sink, "\\MAIN", &[]), Err(Error::Bound(_)))); +} + +/// A reference to a LocalX or ArgX does not hold it: it reaches it while its +/// method runs, and names nothing once that method has exited, whichever way +/// the reference left. +#[test] +fn a_reference_to_a_local_ends_with_its_method() { + let give = method("GIVE", 0, &ret(&ref_of(&local(0)))); + let put = method("PUT", 1, &store(&ref_of(&local(1)), &arg(0))); + let set = method("SET", 1, &store(&int(5), &arg(0))); + // Each GIVE hands back a LocalX of a frame that is gone; storing through + // it would chain them. + let chain = while_( + &int(1), + &cat(&[&store(&name("GIVE"), &local(2)), &store(&local(0), &deref(&local(2))), &store(&local(2), &local(0))]), + ); + // Storing it through itself would make it hold itself. + let cycle = cat(&[&store(&name("GIVE"), &local(2)), &store(&local(2), &deref(&local(2)))]); + // A callee's LocalX, left behind through an ArgX that refers to the caller's. + let through = cat(&[&cat(&[&name("PUT"), &ref_of(&local(2))]), &ret(&deref(&local(2)))]); + let live = cat(&[ + &store(&ref_of(&local(0)), &local(1)), + &store(&int(2), &deref(&local(1))), + &cat(&[&name("SET"), &ref_of(&local(3))]), + &ret(&add(&local(0), &local(3), ZERO)), + ]); + let (mut i, mut m) = loaded(&cat(&[ + &give, + &put, + &set, + &method("CHAN", 0, &chain), + &method("CYCL", 0, &cycle), + &method("THRU", 0, &through), + &method("LIVE", 0, &live), + ])); + for p in ["\\CHAN", "\\CYCL", "\\THRU"] { + assert!(matches!(i.evaluate(&mut m, p, &[]), Err(Error::NotFound(_))), "{p}"); + } + assert_eq!(i.evaluate(&mut m, "\\LIVE", &[]), Ok(Value::Integer(7))); } /// BLOCKER 6: a reference to a package element or to a LocalX or ArgX never diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs index e250db3e366..fc184583a05 100644 --- a/userland/acpiserver/aml/tests/namespace.rs +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -4,7 +4,7 @@ mod common; use common::*; -use toyos_aml::{Error, Interpreter, Value}; +use toyos_aml::{Access, Address, Error, Interpreter, Value}; fn int_of(i: &mut Interpreter, m: &mut Machine, path: &str) -> Result { i.evaluate(m, path, &[]) @@ -197,16 +197,42 @@ fn a_processor_parses_opens_a_scope_and_takes_notify() { assert_eq!(m.log, vec![Event::Notify("\\_PR_.CPU0".into(), 0x80)]); } +const QEMU_DSDT: &[u8] = include_bytes!("../../../../toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin"); + /// QEMU 11.1.1's DSDT, as `toyos-acpi/fixtures/qemu-11.1.1/SOURCE` records -/// it: a boot of it logged `ACPI: PM1a=0x604 SLP_TYPa=0`. +/// it: a boot of it logged `ACPI: PM1a=0x604 SLP_TYPa=0`, which the kernel +/// read by `toyos_acpi::s5_slp_typ`'s byte scan. #[test] fn qemus_dsdt_loads_and_its_s5_is_what_its_boot_logged() { - let dsdt = include_bytes!("../../../../toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin"); let mut m = Machine::default(); let mut i = Interpreter::new(); - i.load_bytes(&mut m, dsdt).unwrap(); - let Ok(Value::Package(s5)) = i.evaluate(&mut m, "\\_S5", &[]) else { panic!("\\_S5 is no package") }; - assert_eq!(s5.first(), Some(&Value::Integer(0))); + i.load_bytes(&mut m, QEMU_DSDT).unwrap(); + let zero = Value::Integer(0); + assert_eq!(i.evaluate(&mut m, "\\_S5", &[]), Ok(Value::Package(vec![zero.clone(), zero.clone(), zero.clone(), zero]))); + let scanned = toyos_acpi::s5_slp_typ(&toyos_acpi::Table::open(Image(QEMU_DSDT), 0, b"DSDT", 0).unwrap()); + assert_eq!(scanned, toyos_acpi::S5::SlpTyp(0)); + assert_eq!(m.log, vec![]); +} + +/// QEMU's own methods, run against the registers they read: a CPU's `_STA` +/// selects it and reads its enabled bit (QEMU's `docs/specs/acpi_cpu_hotplug.rst`), +/// the HPET's reads its vendor and its period (IA-PC HPET 1.0a §2.3.4). +#[test] +fn qemus_methods_run_against_the_registers_they_read() { + let (select, flags) = (Address::Io(0xCD8), Address::Io(0xCDC)); + let mut m = Machine::default(); + let mut i = Interpreter::new(); + i.load_bytes(&mut m, QEMU_DSDT).unwrap(); + assert_eq!(i.evaluate(&mut m, "\\_SB.CPUS.C001._STA", &[]), Ok(Value::Integer(0))); + assert_eq!(m.accesses(), vec![Event::Write(select, Access::DWord, 1), Event::Read(flags, Access::Byte)]); + m.poke(flags, &[1]); + assert_eq!(i.evaluate(&mut m, "\\_SB.CPUS.C001._STA", &[]), Ok(Value::Integer(0xF))); + + let hpet = Address::Memory(0xFED0_0000); + assert_eq!(i.evaluate(&mut m, "\\_SB.HPET._STA", &[]), Ok(Value::Integer(0))); + // Vendor 0x8086 in bits 31:16, a period of 10 ns in femtoseconds above them. + m.poke(hpet, &[0x01, 0xA2, 0x86, 0x80, 0x80, 0x96, 0x98, 0x00]); + assert_eq!(i.evaluate(&mut m, "\\_SB.HPET._STA", &[]), Ok(Value::Integer(0xF))); } #[test] diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs index 98a01a535a8..1258569ef53 100644 --- a/userland/acpiserver/aml/tests/regions.rs +++ b/userland/acpiserver/aml/tests/regions.rs @@ -94,6 +94,12 @@ fn an_access_type_sets_the_unit_and_anyacc_takes_the_narrowest_natural_one() { assert_eq!(m.accesses(), vec![Event::Read(mem(0x1000), Access::QWord)]); let (m, _) = read(&memory_region(ANY, &[skip(56), unit("X", 16)]), &[], "\\X"); assert_eq!(m.accesses(), vec![Event::Read(mem(0x1007), Access::Byte), Event::Read(mem(0x1008), Access::Byte)]); + // A unit that would hold the field whole but runs past its region is not + // taken: the last two bytes of a 13-byte region are read as bytes. + let odd = cat(&[&op_region("ODD", 0x00, &int(0x2000), &int(13)), &field("ODD", ANY, &[skip(88), unit("END", 16)])]); + let (m, v) = read(&odd, &[(mem(0x200B), &[0x34, 0x12])], "\\END"); + assert_eq!(v, Ok(Value::Integer(0x1234))); + assert_eq!(m.accesses(), vec![Event::Read(mem(0x200B), Access::Byte), Event::Read(mem(0x200C), Access::Byte)]); } /// §19.6.47: "If the FieldUnit is larger than the size of an Integer, it @@ -182,6 +188,24 @@ fn a_pci_config_region_addresses_its_devices_function() { assert!(matches!(v, Err(Error::Unsupported(_)))); } +/// A device below a bridge is on the bus the bridge's Secondary Bus Number +/// register names (PCI-to-PCI Bridge Architecture Specification 1.2, +/// §3.2.5.4), read from the bridge on the bus above it. +#[test] +fn a_pci_config_region_below_a_bridge_is_on_its_secondary_bus() { + let endpoint = cat(&[ + &def_name("_ADR", &int(0x0000_0001)), + &op_region("CFG", 0x02, &int(0), &int(0x10)), + &field("CFG", BYTE, &[unit("VEN", 8)]), + ]); + let port = cat(&[&def_name("_ADR", &int(0x001C_0002)), &device("PXSX", &endpoint)]); + let body = scope("\\_SB", &device("PCI0", &cat(&[&def_name("_BBN", &int(0x40)), &device("RP03", &port)]))); + let secondary = Address::PciConfig { segment: 0, bus: 0x40, device: 0x1C, function: 2, offset: 0x19 }; + let (m, _) = read(&body, &[(secondary, &[0x45])], "\\_SB.PCI0.RP03.PXSX.VEN"); + let at = Address::PciConfig { segment: 0, bus: 0x45, device: 0, function: 1, offset: 0 }; + assert_eq!(m.accesses(), vec![Event::Read(secondary, Access::Byte), Event::Read(at, Access::Byte)]); +} + /// The example of §19.6.63: FET3, the high bit at indexed offset 0x2F. #[test] fn an_index_field_writes_its_offset_then_reaches_the_data() { From 98ffa87532643554296f98317f4f45685ec4d04f Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 7 Oct 2026 15:54:18 +0200 Subject: [PATCH 7/9] A field store owns its source; names pay for their walk; an unset bridge names no bus Review round 3 of #739. Each finding was measured at 488a05a57 before it was changed, by the out-of-tree harness and by the new tests run against the old source. A store to a field held a borrow of its source across the write, and a write to a PCI_Config field runs firmware's _ADR, _BBN and _SEG: one that stores to the source panicked, "RefCell already borrowed". The store now writes from a copy of its own, charged and held against the meter, so it is of what the source held when it began. A package element naming an object not yet defined kept its Path outside the meter. Filled with 255-segment names the heap behind a full meter was 713,799,584 bytes. The element is now an Unresolved, metered at its own size and its segments': the same fill holds 15,953,360, and one-segment names 19,549,520 where they held 63,533,264. The bus below a bridge was whatever byte its Secondary Bus Number register answered, kept for the region's life. A register that answers a bus not above the bridge's own, as an unconfigured bridge's 0 does, now names no bus and is refused; and nothing is kept: every access asks the bridges and firmware's methods again, so a bridge renumbered since is seen. A region declared in a method addresses the device the method is in: the walk counts devices alone. A name cost one step however long it was and however far it was searched for. Every namespace walk now pays a step for each scope climbed and each segment looked up (resolve, create, path_of), and a NameString is charged for its bytes. A lone name that is nowhere, looked for from 5,000 scopes down in a loop, ran 11.9 s before the step bound and now runs 3.4 ms. The same defect on the paths it left, found by reading every operator for work a table sizes: a long buffer stored to a short one or to a buffer field, ToString and Mid of a long buffer's first byte, ToInteger and DerefOf of a long string. Each is charged for what it reads. ToInteger of a 64 KiB string of zeros in a loop ran 49 s and now runs 73 ms. _OSI copied its argument to compare it and no longer does. The tests name nothing that the local T14 tables hold and main did not, beyond what the specification itself names. Co-Authored-By: Claude Opus 5.5 --- ...ine-in-acpi-mode-and-interprets-its-aml.md | 19 +- userland/acpiserver/aml/src/exec.rs | 177 +++++++++++------- userland/acpiserver/aml/src/field.rs | 99 +++++----- userland/acpiserver/aml/src/lib.rs | 20 +- userland/acpiserver/aml/src/namespace.rs | 50 +++-- userland/acpiserver/aml/src/object.rs | 45 ++++- userland/acpiserver/aml/tests/evaluate.rs | 4 +- userland/acpiserver/aml/tests/hostile.rs | 91 ++++++++- userland/acpiserver/aml/tests/namespace.rs | 8 +- userland/acpiserver/aml/tests/regions.rs | 95 ++++++++-- 10 files changed, 433 insertions(+), 175 deletions(-) diff --git a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md index 758c82f6176..ad0658693b5 100644 --- a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md +++ b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md @@ -117,8 +117,11 @@ otherwise pay to find again: definition-block code reads SystemMemory and PCI_Config while it loads, and branches on what it reads: with every read answered zero the DSDT refers to a device its own other branch never defined, and is refused. The check - answered one 16-bit word, the chipset series, and nothing else. A run on - the machine itself is still owed, by the power-off stage, which puts the + answered one 16-bit word, the chipset series, and each bridge's Secondary + Bus Number register as a configured bridge does, and nothing else: one + SSDT reads a field below a bridge while it loads, and the interpreter + refuses a secondary bus that is not above the bridge's own. A run on the + machine itself is still owed, by the power-off stage, which puts the interpreter in the server. - **The T14's processor objects need `Load`.** Its tables hold eight `Load` opcodes and one `LoadTable`, none run while a table loads, and Linux lists eight tables @@ -132,6 +135,18 @@ otherwise pay to find again: what the server does with an interpreter that is full. **Exit**: a test fills the budget through one method, and the server then evaluates a method that builds a buffer. +- **The interpreter's 16 MiB is its meter's count, not its heap.** The meter + counts whatever a table sizes; each namespace node and package element + carries a constant beside that which it does not count (`object::Meter`, + `userland/acpiserver/aml/src/object.rs`). With the meter full, the heap an + interpreter held was 16,776,232 bytes when buffers filled it, 19,549,520 + when package elements naming objects not yet defined did, and 41,091,744 + when field units did. A load refused at the bound also leaves the + namespace's arena at the capacity it grew to: 24,115,888 bytes held after + one table naming 204,000 field units was refused. Owner: the power-off + stage, which gives the server its memory. **Exit**: the server states its + interpreter's bound in heap bytes, and a host test under a counting + allocator holds each of those three fills and that refused load to it. **Stage: power-off through the server** (the orchestrator's placement of "Yes, one path"). The ACPI server evaluates `\_S5` and powers the machine off. diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index b4f33be548e..0aa14d718d7 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -24,7 +24,7 @@ use crate::name::{text, Path, Seg}; use crate::namespace::{Namespace, NodeId}; use crate::object::{ bounded, decimal, fit, hex2, joined, slot_of, to_buf, to_int, to_str, Body, Bytes, Meter, Method, Mutex, Object, Ref, - Slot, Width, + Slot, Unresolved, Width, }; use crate::stream::{starts_name, Cursor}; use crate::{Error, Host, MAX_DEPTH, MAX_NESTING, MAX_STEPS, MAX_WAIT_US, REVISION, WINDOWS, WORK_PER_STEP}; @@ -106,9 +106,10 @@ fn type_name(code: u64) -> &'static [u8] { } } -/// DerefOf of a String names an object by ASL text (§19.6.30). -fn path_of_text(s: &[u8]) -> Result { - Path::text(s).ok_or(Error::Rule("DerefOf of a String that is not a name (§19.6.30)")) +/// One step more, refused past the bound. +fn tick(steps: &mut u64) -> Result<(), Error> { + *steps += 1; + if *steps > MAX_STEPS { Err(Error::Bound("more steps than one evaluation may take")) } else { Ok(()) } } impl<'a> Machine<'a> { @@ -122,14 +123,18 @@ impl<'a> Machine<'a> { self.step() } - pub(crate) fn new_str(&mut self, v: Vec) -> Result { + /// Bytes made, charged for and held against the meter. + pub(crate) fn bytes(&mut self, v: Vec) -> Result { self.charge(v.len())?; - Ok(Object::Str(self.meter.bytes(v)?)) + self.meter.bytes(v) + } + + pub(crate) fn new_str(&mut self, v: Vec) -> Result { + Ok(Object::Str(self.bytes(v)?)) } pub(crate) fn new_buf(&mut self, v: Vec) -> Result { - self.charge(v.len())?; - Ok(Object::Buf(self.meter.bytes(v)?)) + Ok(Object::Buf(self.bytes(v)?)) } fn new_pkg(&mut self, v: Vec) -> Result { @@ -180,8 +185,34 @@ impl<'a> Machine<'a> { } pub(crate) fn step(&mut self) -> Result<(), Error> { - self.steps += 1; - if self.steps > MAX_STEPS { Err(Error::Bound("more steps than one evaluation may take")) } else { Ok(()) } + tick(&mut self.steps) + } + + /// A NameString read (§20.2.2), charged for its bytes: a table writes as + /// many parent prefixes and segments as it likes. + fn name(&mut self, c: &mut Cursor<'_>) -> Result { + let at = c.at; + let p = c.name()?; + self.charge(c.at - at)?; + Ok(p) + } + + /// The object a path names from `scope` (§5.3), if it names one. + fn find(&mut self, scope: NodeId, p: &Path) -> Result, Error> { + let steps = &mut self.steps; + self.ns.resolve(scope, p, &mut || tick(steps)) + } + + /// The absolute path of a node, and of `child` below it when given. + pub(crate) fn path_of(&mut self, id: NodeId, child: Option) -> Result { + let steps = &mut self.steps; + self.ns.path_of(id, child, &mut || tick(steps)) + } + + /// DerefOf of a String names an object by ASL text (§19.6.30), read whole. + fn path_of_text(&mut self, s: &Bytes) -> Result { + self.charge(s.borrow().len())?; + Path::text(&s.borrow()).ok_or(Error::Rule("DerefOf of a String that is not a name (§19.6.30)")) } pub(crate) fn enter(&mut self) -> Result<(), Error> { @@ -243,13 +274,14 @@ impl<'a> Machine<'a> { } fn define(&mut self, f: &mut Frame, p: &Path, o: Object) -> Result { - let id = self.ns.create(f.scope, p, o)?; + let steps = &mut self.steps; + let id = self.ns.create(f.scope, p, o, &mut || tick(steps))?; f.created.push(id); Ok(id) } - fn resolve(&self, f: &Frame, p: &Path) -> Result { - self.ns.resolve(f.scope, p).ok_or_else(|| Error::NotFound(text(p))) + fn resolve(&mut self, f: &Frame, p: &Path) -> Result { + self.find(f.scope, p)?.ok_or_else(|| Error::NotFound(text(p))) } fn node_object(&self, id: NodeId) -> Result { @@ -309,24 +341,18 @@ impl<'a> Machine<'a> { } } - /// A package element a name gave (§19.6.101): data is resolved to its - /// value, anything else is a reference; a name not yet defined is - /// resolved when read. - fn element(&mut self, scope: NodeId, p: &Path) -> Result { - match self.ns.resolve(scope, p) { - Some(id) => Ok(match self.node_value(id)? { - d @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_)) => self.copy(&d)?, - o => o, - }), - None => Ok(Object::Lazy(Rc::new((p.clone(), scope)))), - } + /// A package element a name gave (§19.6.101), if `scope` resolves the + /// name: data is resolved to its value, anything else is a reference. + fn element(&mut self, scope: NodeId, p: &Path) -> Result, Error> { + let Some(id) = self.find(scope, p)? else { return Ok(None) }; + Ok(Some(match self.node_value(id)? { + d @ (Object::Int(_) | Object::Str(_) | Object::Buf(_) | Object::Pkg(_)) => self.copy(&d)?, + o => o, + })) } - fn lazy(&mut self, l: &(Path, NodeId)) -> Result { - match self.element(l.1, &l.0)? { - Object::Lazy(_) => Err(Error::NotFound(text(&l.0))), - o => Ok(o), - } + fn lazy(&mut self, l: &Unresolved) -> Result { + self.element(l.scope, &l.path)?.ok_or_else(|| Error::NotFound(text(&l.path))) } pub(crate) fn resolve_lazy(&mut self, o: Object) -> Result { @@ -380,11 +406,10 @@ impl<'a> Machine<'a> { /// `\_OSI` (§5.7.2), answered as the owner ruled: yes to every Windows /// version string Microsoft publishes, no to anything else. fn osi(&mut self, args: Vec) -> Result { - let s = match args.first() { - Some(Object::Str(s)) => s.borrow().clone(), - _ => return Err(Error::Type("_OSI's argument is not a String (§5.7.2)")), + let Some(Object::Str(s)) = args.first() else { + return Err(Error::Type("_OSI's argument is not a String (§5.7.2)")); }; - Ok(Object::Int(self.w.bool(WINDOWS.iter().any(|w| w.as_bytes() == s.as_slice())))) + Ok(Object::Int(self.w.bool(WINDOWS.iter().any(|w| w.as_bytes() == s.borrow().as_slice())))) } // ---- term lists ------------------------------------------------------- @@ -411,7 +436,7 @@ impl<'a> Machine<'a> { let op = c.peek()?; if starts_name(op) { // MethodInvocation (§20.2.5): a name alone in a term list. - let p = c.name()?; + let p = self.name(c)?; let id = self.resolve(f, &p)?; let Object::Method(m) = self.node_object(id)? else { return Err(c.malformed("a name in a term list names no method (§20.2.5)")); @@ -524,17 +549,18 @@ impl<'a> Machine<'a> { fn def_alias(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; - let source = c.name()?; - let alias = c.name()?; + let source = self.name(c)?; + let alias = self.name(c)?; let target = self.resolve(f, &source)?; - let id = self.ns.alias(f.scope, &alias, target)?; + let steps = &mut self.steps; + let id = self.ns.alias(f.scope, &alias, target, &mut || tick(steps))?; f.created.push(id); Ok(Flow::Next) } fn def_name(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; - let p = c.name()?; + let p = self.name(c)?; let v = self.data_object(f, c)?; self.define(f, &p, v)?; Ok(Flow::Next) @@ -544,7 +570,7 @@ impl<'a> Machine<'a> { c.byte()?; let end = c.pkg_end()?; let mut body = Self::sub(c, end); - let p = body.name()?; + let p = self.name(&mut body)?; let id = self.resolve(f, &p)?; // §19.6.120: a Scope's location is a predefined scope, a Device, a // Processor, a Thermal Zone or a Power Resource. @@ -573,7 +599,7 @@ impl<'a> Machine<'a> { let op = c.byte()?; let end = c.pkg_end()?; let mut body = Self::sub(c, end); - let p = body.name()?; + let p = self.name(&mut body)?; let o = match op { 0x82 => Object::Device, 0x85 => Object::ThermalZone, @@ -604,7 +630,7 @@ impl<'a> Machine<'a> { c.byte()?; let end = c.pkg_end()?; let mut head = Self::sub(c, end); - let p = head.name()?; + let p = self.name(&mut head)?; let flags = head.byte()?; let m = Method { body: Body::Aml { table: f.table.clone(), start: head.at, end }, @@ -621,7 +647,7 @@ impl<'a> Machine<'a> { /// and defines nothing. fn def_external(&mut self, c: &mut Cursor<'_>) -> Result { c.byte()?; - c.name()?; + self.name(c)?; c.byte()?; c.byte()?; Ok(Flow::Next) @@ -630,7 +656,7 @@ impl<'a> Machine<'a> { fn def_mutex(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; c.byte()?; - let p = c.name()?; + let p = self.name(c)?; // SyncFlags: the SyncLevel in bits 0-3, the rest reserved (§20.2.5.2). let sync = c.byte()? & 0x0F; self.define(f, &p, Object::Mutex(Rc::new(Mutex { sync, held: Cell::new(0), global: false })))?; @@ -640,7 +666,7 @@ impl<'a> Machine<'a> { fn def_event(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; c.byte()?; - let p = c.name()?; + let p = self.name(c)?; self.define(f, &p, Object::Event(Rc::new(Cell::new(0))))?; Ok(Flow::Next) } @@ -648,24 +674,28 @@ impl<'a> Machine<'a> { fn def_region(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { c.byte()?; c.byte()?; - let p = c.name()?; + let p = self.name(c)?; let space = c.byte()?; let base = self.int_arg(f, c)?; let len = self.int_arg(f, c)?; - let r = Region { space, base, len, scope: f.scope, pci: Cell::new(None) }; + let r = Region { space, base, len, scope: f.scope }; self.define(f, &p, Object::Region(Rc::new(r)))?; Ok(Flow::Next) } - fn field_of(&self, f: &Frame, p: &Path) -> Result, Error> { - match self.node_object(self.resolve(f, p)?)? { + fn field_of(&mut self, f: &Frame, c: &mut Cursor<'_>) -> Result, Error> { + let p = self.name(c)?; + let id = self.resolve(f, &p)?; + match self.node_object(id)? { Object::Field(x) => Ok(x), _ => Err(Error::Type("an IndexField's or BankField's register is not a field unit (§19.6.63, §19.6.7)")), } } - fn region_of(&self, f: &Frame, p: &Path) -> Result, Error> { - match self.node_object(self.resolve(f, p)?)? { + fn region_of(&mut self, f: &Frame, c: &mut Cursor<'_>) -> Result, Error> { + let p = self.name(c)?; + let id = self.resolve(f, &p)?; + match self.node_object(id)? { Object::Region(r) => Ok(r), _ => Err(Error::Type("a field's RegionName is not an operation region (§19.6.47)")), } @@ -679,15 +709,15 @@ impl<'a> Machine<'a> { let end = c.pkg_end()?; let mut l = Self::sub(c, end); let kind = match op { - 0x81 => Kind::Region(self.region_of(f, &l.name()?)?), + 0x81 => Kind::Region(self.region_of(f, &mut l)?), 0x86 => { - let index = self.field_of(f, &l.name()?)?; - let data = self.field_of(f, &l.name()?)?; + let index = self.field_of(f, &mut l)?; + let data = self.field_of(f, &mut l)?; Kind::Index { index, data } } _ => { - let region = self.region_of(f, &l.name()?)?; - let bank = self.field_of(f, &l.name()?)?; + let region = self.region_of(f, &mut l)?; + let bank = self.field_of(f, &mut l)?; let value = self.int_arg(f, &mut l)?; Kind::Bank { region, bank, value } } @@ -717,7 +747,7 @@ impl<'a> Machine<'a> { if l.peek()? == 0x11 { self.data_object(f, &mut l)?; } else { - l.name()?; + self.name(&mut l)?; } } _ => { @@ -749,10 +779,7 @@ impl<'a> Machine<'a> { Object::Buf(b) => b, o => { let v = to_buf(&o, self.w)?; - match self.new_buf(v)? { - Object::Buf(b) => b, - _ => return Err(Error::Rule("a buffer that is not one")), - } + self.bytes(v)? } }; let index = self.int_arg(f, c)?; @@ -770,7 +797,7 @@ impl<'a> Machine<'a> { (Some(index), n) } }; - let p = c.name()?; + let p = self.name(c)?; let size = (data.borrow().len() as u64).saturating_mul(8); let bit = bit.filter(|b| b.checked_add(len).is_some_and(|e| e <= size)); let bit = bit.ok_or(Error::Rule("a buffer field reaches past its buffer (§19.6.18-23)"))?; @@ -789,7 +816,7 @@ impl<'a> Machine<'a> { if !matches!(self.node_object(id)?, Object::Device | Object::Processor | Object::ThermalZone) { return Err(Error::Type("Notify of an object that is not a device, processor or thermal zone (§19.6.94)")); } - let path = self.ns.path_of(id, None); + let path = self.path_of(id, None)?; self.host.notify(&path, v); Ok(Flow::Next) } @@ -904,7 +931,7 @@ impl<'a> Machine<'a> { /// A name in an argument position: a method it names is invoked. fn named(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { - let p = c.name()?; + let p = self.name(c)?; let id = self.resolve(f, &p)?; match self.node_object(id)? { Object::Method(m) => { @@ -976,8 +1003,11 @@ impl<'a> Machine<'a> { return Err(p.malformed("a package holds more elements than its NumElements (§19.6.101)")); } let e = if starts_name(p.peek()?) { - let path = p.name()?; - self.element(f.scope, &path)? + let path = self.name(&mut p)?; + match self.element(f.scope, &path)? { + Some(e) => e, + None => Object::Lazy(self.meter.unresolved(path, f.scope)?), + } } else { self.enter()?; let e = self.data_object(f, &mut p); @@ -1009,8 +1039,8 @@ impl<'a> Machine<'a> { fn super_name_or(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result, Error> { let op = c.peek()?; if starts_name(op) { - let p = c.name()?; - return Ok(self.ns.resolve(f.scope, &p).map(Target::Node).ok_or(p)); + let p = self.name(c)?; + return Ok(self.find(f.scope, &p)?.map(Target::Node).ok_or(p)); } Ok(Ok(match op { 0x60..=0x67 => { @@ -1036,7 +1066,7 @@ impl<'a> Machine<'a> { match self.arg(f, c)? { Object::Ref(r) => Target::Ref(r), Object::Str(s) => { - let p = path_of_text(&s.borrow())?; + let p = self.path_of_text(&s)?; Target::Node(self.resolve(f, &p)?) } _ => return Err(Error::Type("DerefOf of an object that is not a reference or a name (§19.6.30)")), @@ -1176,7 +1206,7 @@ impl<'a> Machine<'a> { // Table 19.7: a buffer that exists keeps its size. let n = to_buf(&v, w)?; let len = b.borrow().len(); - self.charge(len)?; + self.charge(n.len().max(len))?; b.replace(fit(n, len)) } Object::Pkg(p) => match &v { @@ -1383,7 +1413,7 @@ impl<'a> Machine<'a> { match self.arg(f, c)? { Object::Ref(r) => self.deref(&r), Object::Str(s) => { - let p = path_of_text(&s.borrow())?; + let p = self.path_of_text(&s)?; let id = self.resolve(f, &p)?; self.node_value(id) } @@ -1665,11 +1695,15 @@ impl<'a> Machine<'a> { } }, 0x99 => Object::Int(match &src { - Object::Str(s) => int_of_text(&s.borrow(), w)?, + Object::Str(s) => { + self.charge(s.borrow().len())?; + int_of_text(&s.borrow(), w)? + } o => to_int(o, w)?, }), 0x9C => { let b = to_buf(&src, w)?; + self.charge(b.len())?; let n = self.int_arg(f, c)?; let n = if n == w.ones() { usize::MAX } else { usize::try_from(n).unwrap_or(usize::MAX) }; self.new_str(b.iter().take(n).take_while(|&&x| x != 0).copied().collect())? @@ -1681,6 +1715,7 @@ impl<'a> Machine<'a> { Object::Str(s) => (s.borrow().clone(), true), o => (to_buf(o, w)?, false), }; + self.charge(data.len())?; let start = usize::try_from(i).unwrap_or(usize::MAX).min(data.len()); let end = start.saturating_add(usize::try_from(n).unwrap_or(usize::MAX)).min(data.len()); let part = data[start..end].to_vec(); diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs index 7611be29825..9d1f5f4f8eb 100644 --- a/userland/acpiserver/aml/src/field.rs +++ b/userland/acpiserver/aml/src/field.rs @@ -11,7 +11,6 @@ use alloc::rc::Rc; use alloc::vec; use alloc::vec::Vec; -use core::cell::Cell; use crate::exec::Machine; use crate::name::Seg; @@ -24,13 +23,11 @@ pub(crate) struct Region { pub(crate) base: u64, pub(crate) len: u64, /// The scope the region was declared in: for PCI_Config, the device it - /// addresses. + /// addresses, or something inside that device. pub(crate) scope: NodeId, - pub(crate) pci: Cell>, } -#[derive(Clone, Copy)] -pub(crate) struct Pci { +struct Pci { segment: u16, bus: u8, device: u8, @@ -173,47 +170,60 @@ impl Machine<'_> { } } - /// The function a PCI_Config region addresses: its device's `_ADR` - /// (§6.1.1: device in the high word, function in the low), in the - /// segment group the host bridge's `_SEG` names or 0 without one - /// (§6.5.6). The host bridge is the nearest scope naming a `_BBN`, which - /// is the bus directly below it (§6.5.5); each device between it and the - /// region's is a bridge, whose Secondary Bus Number register is the bus - /// below it (PCI-to-PCI Bridge Architecture Specification 1.2, §3.2.5.4). - /// A region declared in the host bridge itself addresses the bridge. + /// The function a PCI_Config region addresses: that of the nearest device + /// its scope is or lies in, by the device's `_ADR` (§6.1.1: device in + /// the high word, function in the low), in the segment group the host + /// bridge's `_SEG` names or 0 without one (§6.5.6). The host bridge is + /// the nearest device naming a `_BBN`, which is the bus directly below it + /// (§6.5.5); each device between it and the region's is a bridge, whose + /// Secondary Bus Number register is the bus below it (PCI-to-PCI Bridge + /// Architecture Specification 1.2, §3.2.5.4). A region declared in the + /// host bridge itself addresses the bridge. + /// + /// Every access asks again, firmware's methods and the bridges both: + /// nothing is kept that a bridge renumbered since would make stale. fn pci(&mut self, r: &Region) -> Result { - if let Some(p) = r.pci.get() { - return Ok(p); - } let bbn = Seg(*b"_BBN"); - let mut path = Vec::new(); - let mut bridge = r.scope; - while self.ns.child(bridge, bbn).is_none() { + let mut below = Vec::new(); + let mut host = r.scope; + loop { self.step()?; - path.push(bridge); - let above = self.ns.parent(bridge); - bridge = above.ok_or(Error::Unsupported("a PCI_Config region below no host bridge, which names a _BBN"))?; + if matches!(self.ns.object(host), Some(Object::Device)) { + if self.ns.child(host, bbn).is_some() { + break; + } + below.push(host); + } + let above = self.ns.parent(host); + host = above.ok_or(Error::Unsupported("a PCI_Config region below no host bridge, which names a _BBN"))?; } - let bus = self.named_int(bridge, bbn)?.unwrap_or(0); - let segment = self.named_int(bridge, Seg(*b"_SEG"))?.unwrap_or(0); + let bus = self.named_int(host, bbn)?.unwrap_or(0); + let segment = self.named_int(host, Seg(*b"_SEG"))?.unwrap_or(0); // §6.5.5 and §6.5.6 give the bus in the low 8 bits and the segment // group in the low 16, the rest reserved: a value outside them names // no bus this access could reach. let mut bus = u8::try_from(bus).map_err(|_| Error::Rule("a _BBN above 0xFF (§6.5.5)"))?; let segment = u16::try_from(segment).map_err(|_| Error::Rule("a _SEG above 0xFFFF (§6.5.6)"))?; - for &above in path.iter().skip(1).rev() { - let b = self.function(above, segment, bus)?; + let Some((&device, bridges)) = below.split_first() else { return self.function(host, segment, bus) }; + for &bridge in bridges.iter().rev() { + let b = self.function(bridge, segment, bus)?; let secondary = Address::PciConfig { segment, bus, device: b.device, function: b.function, offset: 0x19 }; - bus = self.host.read(secondary, crate::Access::Byte).map_err(|d| Error::Host(d.0))? as u8; + let answered = self.host.read(secondary, crate::Access::Byte).map_err(|d| Error::Host(d.0))? as u8; + // The register resets to 0, and a configured bridge's secondary + // bus is above the bus the bridge is on: any other answer would + // address a device that is not below this bridge. + if answered <= bus { + return Err(Error::Rule("a bridge's Secondary Bus Number is not above its own bus, and names no bus below it")); + } + bus = answered; } - let at = self.function(path.first().copied().unwrap_or(bridge), segment, bus)?; - r.pci.set(Some(at)); - Ok(at) + self.function(device, segment, bus) } /// The function a device's `_ADR` names on `bus` (§6.1.1). fn function(&mut self, device: NodeId, segment: u16, bus: u8) -> Result { - let adr = self.named_int(device, Seg(*b"_ADR"))?.ok_or(Error::NotFound(self.ns.path_of(device, Some(Seg(*b"_ADR")))))?; + let adr = Seg(*b"_ADR"); + let Some(adr) = self.named_int(device, adr)? else { return Err(Error::NotFound(self.path_of(device, Some(adr))?)) }; let (Ok(device @ 0..=31), Ok(function @ 0..=7)) = (u8::try_from(adr >> 16), u8::try_from(adr & 0xFFFF)) else { return Err(Error::Rule("an _ADR that names no single PCI function (§6.1.1)")); }; @@ -288,23 +298,27 @@ impl Machine<'_> { /// A store to a field unit (Table 19.7): an Integer overwrites the whole /// field; a Buffer is written in pieces of the field's size, lower first, /// each zero-extended, and an empty one as zeros; a String is written a - /// character at a time. The pieces are slices of the source, each - /// written before the next is taken. + /// character at a time. The pieces are slices of the store's own copy of + /// the source, each written before the next is taken: a write runs + /// firmware's methods, which may store to the source, and the store is of + /// what the source held when it began. pub(crate) fn write_field(&mut self, f: &Field, v: Object) -> Result<(), Error> { let n = bytes_for(f.len)?; - let (int, held); + let (int, copy, held); let (source, piece): (&[u8], usize) = match &v { Object::Int(x) => { int = x.to_le_bytes(); (&int, int.len()) } - Object::Buf(b) => { - held = b.borrow(); - if held.is_empty() { (&[0], 1) } else { (&held, n) } - } - Object::Str(s) => { - held = s.borrow(); - (&held, 1) + Object::Buf(b) | Object::Str(b) => { + let bytes = b.borrow().clone(); + copy = self.bytes(bytes)?; + held = copy.borrow(); + match &v { + Object::Str(_) => (&held, 1), + _ if held.is_empty() => (&[0], 1), + _ => (&held, n), + } } _ => return Err(Error::Type("a store to a field unit of an object that is not an integer, buffer or string")), }; @@ -377,8 +391,9 @@ impl Machine<'_> { Object::Buf(_) | Object::Str(_) => to_buf(&v, self.w)?, _ => return Err(Error::Type("a store to a buffer field of an object that is not an integer, buffer or string")), }; + // The source is read whole, and the field written a bit at a time. + self.charge(src.len().saturating_add(usize::try_from(f.len).unwrap_or(usize::MAX)))?; let src = fit(src, bytes_for(f.len)?); - self.charge(usize::try_from(f.len).unwrap_or(usize::MAX))?; let mut d = f.data.bits(); if f.bit.saturating_add(f.len) > (d.len() as u64).saturating_mul(8) { return Err(Error::Rule("a buffer field reaches past its buffer, which shrank since")); diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index 153131087ea..0025002e4a8 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -207,7 +207,7 @@ impl Interpreter { let root = ns.root(); let mut put = |name: &[u8; 4], o: Object| { let p = Path { root: true, up: 0, segs: alloc::vec![Seg(*name)] }; - ns.create(root, &p, o).expect("the root is empty and every predefined name differs"); + ns.create(root, &p, o, &mut || Ok(())).expect("the root is empty and every predefined name differs"); }; for scope in [b"_GPE", b"_PR_", b"_SB_", b"_SI_", b"_TZ_"] { put(scope, Object::Scope); @@ -262,8 +262,7 @@ impl Interpreter { /// method is invoked with `args`, anything else is its value. pub fn evaluate(&mut self, host: &mut dyn Host, path: &str, args: &[Value]) -> Result { let w = self.width.ok_or(Error::Table("nothing is loaded"))?; - let p = Path::absolute(path)?; - let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(String::from(path)))?; + let id = self.named(path)?; let args = args.iter().map(|a| self.object_of(a, w, 0)).collect::, _>>()?; let mut m = Machine::new(&mut self.ns, host, w, self.meter.clone()); let r = m.evaluate(id, args).and_then(|o| value_of(&mut m, o, 0)); @@ -283,13 +282,16 @@ impl Interpreter { Value::Package(p) => Object::Pkg( self.meter.list(p.iter().map(|e| self.object_of(e, w, depth + 1)).collect::>()?)?, ), - Value::Reference(path) => { - let p = Path::absolute(path)?; - let id = self.ns.resolve(self.ns.root(), &p).ok_or_else(|| Error::NotFound(path.clone()))?; - Object::Ref(Ref::Node(id)) - } + Value::Reference(path) => Object::Ref(Ref::Node(self.named(path)?)), }) } + + /// The object at an absolute path the caller wrote, whose length is the + /// caller's and costs no evaluation a step. + fn named(&self, path: &str) -> Result { + let p = Path::absolute(path)?; + self.ns.resolve(self.ns.root(), &p, &mut || Ok(()))?.ok_or_else(|| Error::NotFound(String::from(path))) + } } fn value_of(m: &mut Machine<'_>, o: Object, depth: usize) -> Result { @@ -309,7 +311,7 @@ fn value_of(m: &mut Machine<'_>, o: Object, depth: usize) -> Result Value::Reference(m.ns.path_of(id, None)), + Object::Ref(Ref::Node(id)) => Value::Reference(m.path_of(id, None)?), _ => return Err(Error::Unsupported("a reference to an unnamed object, handed to the caller")), }) } diff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs index c9b91e7c55c..d749ec86812 100644 --- a/userland/acpiserver/aml/src/namespace.rs +++ b/userland/acpiserver/aml/src/namespace.rs @@ -3,7 +3,9 @@ //! Nodes live in an arena and are named by index and generation, so a //! reference to an object a method created and its exit destroyed //! (§5.5.2.3) resolves to nothing rather than to whatever reused its slot. -//! Nothing here recurses over the tree's depth, which a table chooses. +//! Nothing here recurses over the tree's depth, which a table chooses, as it +//! does a path's length: every walk pays its caller's [`Toll`] for each scope +//! it climbs and each segment it looks up. //! Every node is held against the interpreter's [`Meter`] from its creation //! to its removal. @@ -16,6 +18,10 @@ use crate::name::{Path, Seg}; use crate::object::{Meter, Object}; use crate::Error; +/// What a walk pays for a scope or a segment: a step of the evaluation it is +/// part of, which refuses the walk at its bound. +pub(crate) type Toll<'a> = &'a mut dyn FnMut() -> Result<(), Error>; + /// The bytes a node is held at. const NODE: usize = core::mem::size_of::(); @@ -87,48 +93,55 @@ impl Namespace { /// The scope a path's segments are walked from: the root, or `scope` /// raised by its parent prefixes. A prefix above the root finds nothing /// (§5.3). - fn start(&self, scope: NodeId, path: &Path) -> Option { + fn start(&self, scope: NodeId, path: &Path, toll: Toll<'_>) -> Result, Error> { if path.root { - return Some(self.root()); + return Ok(Some(self.root())); } let mut at = scope; for _ in 0..path.up { - at = self.parent(at)?; + toll()?; + let Some(above) = self.parent(at) else { return Ok(None) }; + at = above; } - Some(at) + Ok(Some(at)) } /// The object a path names from `scope`, by §5.3's rules: a lone NameSeg /// is searched for in the scope and then each parent up to the root; /// anything else is looked up exactly. - pub(crate) fn resolve(&self, scope: NodeId, path: &Path) -> Option { - let mut at = self.start(scope, path)?; + pub(crate) fn resolve(&self, scope: NodeId, path: &Path, toll: Toll<'_>) -> Result, Error> { + let Some(mut at) = self.start(scope, path, toll)? else { return Ok(None) }; if path.searches() { loop { + toll()?; if let Some(found) = self.child(at, path.segs[0]) { - return Some(found); + return Ok(Some(found)); } - at = self.parent(at)?; + let Some(above) = self.parent(at) else { return Ok(None) }; + at = above; } } for &seg in &path.segs { - at = self.child(at, seg)?; + toll()?; + let Some(below) = self.child(at, seg) else { return Ok(None) }; + at = below; } - Some(at) + Ok(Some(at)) } /// Creates the object a path names: every segment but the last must /// exist, and the last must not (§5.3: "a name collision ... is /// considered fatal"). - pub(crate) fn create(&mut self, scope: NodeId, path: &Path, object: Object) -> Result { + pub(crate) fn create(&mut self, scope: NodeId, path: &Path, object: Object, toll: Toll<'_>) -> Result { let (last, parents) = path.segs.split_last().ok_or(Error::Rule("a definition names no object"))?; let missing = || Error::NotFound(crate::name::text(path)); - let mut at = self.start(scope, path).ok_or_else(missing)?; + let mut at = self.start(scope, path, toll)?.ok_or_else(missing)?; for &seg in parents { + toll()?; at = self.child(at, seg).ok_or_else(missing)?; } if self.node(at).is_some_and(|n| n.children.contains_key(last)) { - return Err(Error::Exists(self.path_of(at, Some(*last)))); + return Err(Error::Exists(self.path_of(at, Some(*last), toll)?)); } let node = Node { seg: *last, @@ -157,8 +170,8 @@ impl Namespace { Ok(id) } - pub(crate) fn alias(&mut self, scope: NodeId, path: &Path, target: NodeId) -> Result { - let id = self.create(scope, path, Object::Uninit)?; + pub(crate) fn alias(&mut self, scope: NodeId, path: &Path, target: NodeId, toll: Toll<'_>) -> Result { + let id = self.create(scope, path, Object::Uninit, toll)?; if let Some(n) = self.nodes.get_mut(id.index as usize) { n.alias = Some(target); } @@ -193,15 +206,16 @@ impl Namespace { } /// The absolute path of a node, and of `child` below it when given. - pub(crate) fn path_of(&self, id: NodeId, child: Option) -> String { + pub(crate) fn path_of(&self, id: NodeId, child: Option, toll: Toll<'_>) -> Result { let mut segs: Vec = child.into_iter().collect(); let mut at = id; while let Some(n) = self.node(at) { let Some(p) = n.parent else { break }; + toll()?; segs.push(n.seg); at = p; } segs.reverse(); - crate::name::text(&Path { root: true, up: 0, segs }) + Ok(crate::name::text(&Path { root: true, up: 0, segs })) } } diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs index 5d423ba168a..c7beaabaa82 100644 --- a/userland/acpiserver/aml/src/object.rs +++ b/userland/acpiserver/aml/src/object.rs @@ -7,9 +7,9 @@ //! (§19.3.5.8), and so does a return (§19.6.118). //! //! Every string, buffer and package, every loaded table a method still runs -//! from and every namespace node is held against its interpreter's [`Meter`] -//! from its making to its end, so what one interpreter holds live is bounded -//! in sum. +//! from, every namespace node and every package element's name not yet +//! defined is held against its interpreter's [`Meter`] from its making to its +//! end, so what one interpreter holds live is bounded in sum. //! //! A reference to a LocalX or ArgX does not hold it: the frame alone does, //! and once its method exits the reference names nothing. A reference to a @@ -33,8 +33,11 @@ pub(crate) type Slot = Rc>; /// What one interpreter holds live, in bytes: a string's, buffer's or /// table's length, a package's elements at [`ELEMENT`] bytes each, a -/// namespace node at the size of one. It counts those alone, not what a -/// node or an element points at beside them, nor the allocator's overhead. +/// namespace node at the size of one, and an element's name not yet defined +/// at its own size and its segments'. Whatever a table sizes is counted; +/// what is not is bounded by a constant for each node and element: a field's +/// or method's own record, a node's entry among its parent's children, a +/// shared object's counts, the allocator's overhead. pub(crate) struct Meter { live: Cell, } @@ -70,6 +73,12 @@ impl Meter { self.take(v.len() * ELEMENT)?; Ok(Rc::new(List { v: RefCell::new(v), meter: self.clone() })) } + + /// A package element's name that `scope` does not resolve yet. + pub(crate) fn unresolved(self: &Rc, path: Path, scope: NodeId) -> Result, Error> { + self.take(Unresolved::held(&path))?; + Ok(Rc::new(Unresolved { path, scope, meter: self.clone() })) + } } /// A string's or buffer's bytes, held against a [`Meter`]. @@ -136,6 +145,27 @@ impl Drop for List { } } +/// A package element named by a path that did not resolve when the package +/// was evaluated, held against a [`Meter`]: a path is as long as its table +/// wrote it. +pub(crate) struct Unresolved { + pub(crate) path: Path, + pub(crate) scope: NodeId, + meter: Rc, +} + +impl Unresolved { + fn held(path: &Path) -> usize { + core::mem::size_of::() + core::mem::size_of_val(path.segs.as_slice()) + } +} + +impl Drop for Unresolved { + fn drop(&mut self) { + self.meter.give(Self::held(&self.path)); + } +} + pub(crate) fn bounded(len: usize) -> Result<(), Error> { if len > MAX_BYTES { Err(Error::Bound("an object larger than this interpreter holds")) } else { Ok(()) } } @@ -167,9 +197,8 @@ pub(crate) enum Object { /// An Event's pending signal count (§19.6.147). Event(Rc>), Region(Rc), - /// A package element named by a path that did not resolve when the - /// package was evaluated; it is resolved when read (§19.6.101). - Lazy(Rc<(Path, NodeId)>), + /// A package element whose name is resolved when read (§19.6.101). + Lazy(Rc), } /// An object reference (§19.6.113, §19.6.62). diff --git a/userland/acpiserver/aml/tests/evaluate.rs b/userland/acpiserver/aml/tests/evaluate.rs index 43b53b1f252..c36740a0e4b 100644 --- a/userland/acpiserver/aml/tests/evaluate.rs +++ b/userland/acpiserver/aml/tests/evaluate.rs @@ -130,8 +130,8 @@ fn methods_take_their_declared_arguments_and_return_a_copy() { &method("SUM3", 3, &ret(&add(&add(&arg(0), &arg(1), ZERO), &arg(2), ZERO))), &method("MAIN", 0, &ret(&cat(&[&name("SUM3"), &int(1), &int(2), &int(3)]))), &def_name("BUF", &buffer(&int(2), &[7, 8])), - &method("GETB", 0, &ret(&name("BUF"))), - &method("POKE", 0, &cat(&[&store(&name("GETB"), &local(0)), &store(&int(9), &index(&local(0), &int(0), ZERO))])), + &method("SAME", 0, &ret(&name("BUF"))), + &method("POKE", 0, &cat(&[&store(&name("SAME"), &local(0)), &store(&int(9), &index(&local(0), &int(0), ZERO))])), ])); assert_eq!(ip.evaluate(&mut m, "\\MAIN", &[]), i(6)); ip.evaluate(&mut m, "\\POKE", &[]).unwrap(); diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index fe47146a11d..d72c792411f 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -122,11 +122,11 @@ fn seed() -> Vec { &cat(&[ &def_name("_BBN", &int(0)), &device( - "LPCB", + "ISAB", &cat(&[ &def_name("_ADR", &int(0x001F_0000)), - &op_region("LPCR", 0x02, &int(0x40), &int(0x10)), - &field("LPCR", 1, &[unit("R40", 8), unit("R41", 8)]), + &op_region("ISAR", 0x02, &int(0x40), &int(0x10)), + &field("ISAR", 1, &[unit("R40", 8), unit("R41", 8)]), ]), ), ]), @@ -209,7 +209,7 @@ fn mutated_tables_yield_a_value_or_a_refusal() { let (mut i, mut m) = loaded(&seed); assert_eq!(i.evaluate(&mut m, "\\MAIN", &[Value::Integer(1), Value::Integer(2)]), Ok(Value::Integer(0))); } - let paths = ["\\MAIN", "\\OSI", "\\_S5", "\\_SB.PKG", "\\_SB.BF", "\\_SB.A", "\\_SB.F1", "\\_SB.PCI0.LPCB.R41"]; + let paths = ["\\MAIN", "\\OSI", "\\_S5", "\\_SB.PKG", "\\_SB.BF", "\\_SB.A", "\\_SB.F1", "\\_SB.PCI0.ISAB.R41"]; let mut r = Rng(0x2545_F491_4F6C_DD1D); let (mut loads, mut values) = (0, 0); for _ in 0..20_000 { @@ -275,6 +275,77 @@ fn work_in_one_step_is_charged_in_proportion() { } } +/// How often `While (One) { op Increment (\CNT) }` ran, in a method `scopes` +/// devices down, before the step bound refused it. +fn iterations(setup: &[u8], scopes: usize, op: &[u8]) -> u64 { + let mut body = method("MAIN", 0, &while_(&int(1), &cat(&[op, &increment(&name("\\CNT"))]))); + for d in (0..scopes).rev() { + body = device(&format!("N{d:03}"), &body); + } + let main: String = (0..scopes).map(|d| format!("N{d:03}.")).collect(); + let (mut i, mut m) = loaded(&cat(&[&def_name("CNT", &int(0)), setup, &body])); + assert!(matches!(i.evaluate(&mut m, &format!("\\{main}MAIN"), &[]), Err(Error::Bound(_)))); + match i.evaluate(&mut m, "\\CNT", &[]) { + Ok(Value::Integer(n)) => n, + other => panic!("{other:?}"), + } +} + +/// The work one step may hide is a table's to size wherever a name is read +/// or walked and wherever an operator reads more of an object than it makes: +/// each is charged for all of it, so a loop of one gets no further than the +/// step bound over that charge. +#[test] +fn a_walk_or_a_read_a_table_sizes_is_charged_for_all_of_it() { + const STEPS: u64 = 1 << 20; + const SCOPES: usize = 200; + const BYTES: usize = 1 << 14; + let cond_ref_of = |n: &[u8]| cat(&[&[0x5B, 0x12], n, &local(0)]); + let deep: Vec = (0..SCOPES).map(|d| format!("N{d:03}")).collect(); + let deep = format!("\\{}", deep.join(".")); + let chain = (0..SCOPES).rev().fold(Vec::new(), |inner, d| device(&format!("N{d:03}"), &inner)); + // A string an operator makes, which Name does not take: made at load. + let made = |text: &[u8]| { + let to_string = cat(&[&[0x9C], &buffer(&int(text.len() as u64), text), &ones(), ZERO]); + cat(&[&def_name("BIG", &string("")), &store(&to_string, &name("BIG"))]) + }; + let big = def_name("BIG", &buffer(&int(BYTES as u64), &[])); + let small = def_name("SMAL", &buffer(&int(1), &[])); + let per_scope = STEPS / SCOPES as u64; + let per_byte = STEPS / (BYTES / 64) as u64; + let cases: Vec<(&str, u64, u64)> = vec![ + ("a lone name that is nowhere, searched to the root", per_scope, iterations(&[], SCOPES, &cond_ref_of(b"ZZZZ"))), + ("a path of that many segments", per_scope, iterations(&chain, 0, &cond_ref_of(&name(&deep)))), + ("a name behind parent prefixes", per_byte, iterations(&[], 0, &cond_ref_of(&cat(&[&vec![b'^'; BYTES], b"ZZZZ"])))), + ("parent prefixes, each a scope climbed", per_scope, iterations(&[], SCOPES, &cond_ref_of(&cat(&[&[b'^'; SCOPES], b"ZZZZ"])))), + ( + "a definition by a path of that many segments", + per_scope, + iterations(&cat(&[&chain, &method("MAKE", 0, &def_name(&format!("{deep}.TMP"), &int(0)))]), 0, &name("MAKE")), + ), + ("Notify, which names its device by its path", per_scope, iterations(&[], SCOPES, &cat(&[&[0x86], &name("^"), &int(0x80)]))), + ("a long buffer stored to a short one", per_byte, iterations(&cat(&[&big, &small]), 0, &store(&name("BIG"), &name("SMAL")))), + ( + "a long buffer stored to a buffer field of a bit", + per_byte, + iterations(&cat(&[&big, &small, &[0x8D], &name("SMAL"), &int(0), &name("BIT0")]), 0, &store(&name("BIG"), &name("BIT0"))), + ), + ("ToString of a long buffer's first character", per_byte, iterations(&big, 0, &cat(&[&[0x9C], &name("BIG"), &int(1), &local(0)]))), + ("Mid of a long buffer's first byte", per_byte, iterations(&big, 0, &cat(&[&[0x9E], &name("BIG"), &int(0), &int(1), &local(0)]))), + ("ToInteger of a long string of zeros", per_byte, iterations(&made(&vec![b'0'; BYTES]), 0, &cat(&[&[0x99], &name("BIG"), &local(0)]))), + ( + "DerefOf of a long string that names the root", + per_byte, + iterations(&made(&cat(&[b"\\", &vec![b'^'; BYTES - 1]])), 0, &store(&deref(&name("BIG")), &local(0))), + ), + ]; + let over: Vec<_> = cases.iter().filter(|(_, most, ran)| ran > most).collect(); + assert!(over.is_empty(), "ran more often than the step bound over its charge, as (what, at most, ran): {over:#?}"); + // Each loop did run: the bound is what ended it, not a refusal of its first pass. + let idle: Vec<_> = cases.iter().filter(|(_, _, ran)| *ran == 0).collect(); + assert!(idle.is_empty(), "{idle:#?}"); +} + /// BLOCKER 5: what an interpreter holds live is bounded in sum, not only /// object by object. #[test] @@ -332,6 +403,18 @@ fn tables_and_names_are_held_against_the_live_bound() { let Some((t, Error::Bound(_))) = refused else { panic!("612,000 names are held: {refused:?}") }; assert!(matches!(i.evaluate(&mut m, &format!("\\D{t}00.AAA"), &[]), Err(Error::NotFound(_)))); assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); + + // Package elements naming objects no table defines, each by a path of 255 + // segments: 1,020 a table, which holds no method and so is not kept. + let long = cat(&[&[b'\\', 0x2F, 255], &b"ZZZZ".repeat(255)]); + let unresolved = |t: usize| { + let packages: Vec> = (0..4).map(|p| def_name(&format!("P{p}"), &package(&vec![long.clone(); 255]))).collect(); + table(b"SSDT", 2, &device(&format!("L{t:03}"), &packages.concat())) + }; + let (mut i, mut m) = loaded(&[]); + let refused = (0..17).find_map(|t| i.load_bytes(&mut m, &unresolved(t)).err().map(|e| (t, e))); + let Some((t, Error::Bound(_))) = refused else { panic!("seventeen mebibytes of names are held: {refused:?}") }; + assert!(matches!(i.evaluate(&mut m, &format!("\\L{t:03}"), &[]), Err(Error::NotFound(_)))); } /// A host that takes every access and keeps none, for a store whose bound is diff --git a/userland/acpiserver/aml/tests/namespace.rs b/userland/acpiserver/aml/tests/namespace.rs index fc184583a05..8c67472cb78 100644 --- a/userland/acpiserver/aml/tests/namespace.rs +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -33,7 +33,7 @@ fn a_dsdt_loads_first_and_then_ssdts() { fn the_predefined_objects_are_there_before_any_table() { let (mut i, mut m) = loaded(&cat(&[ &method("TSB", 0, &ret(&object_type(&name("\\_SB")))), - &method("TGL", 0, &ret(&object_type(&name("\\_GL")))), + &method("GLTY", 0, &ret(&object_type(&name("\\_GL")))), &method("TOSI", 0, &ret(&object_type(&name("\\_OSI")))), ])); // The owner's ruling (2026-10-05): "Microsoft Windows NT", as Windows answers. @@ -41,7 +41,7 @@ fn the_predefined_objects_are_there_before_any_table() { assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); // Table 19.36: a predefined scope is typeless, \_GL a Mutex, \_OSI a Method. assert_eq!(i.evaluate(&mut m, "\\TSB", &[]), Ok(Value::Integer(0))); - assert_eq!(i.evaluate(&mut m, "\\TGL", &[]), Ok(Value::Integer(9))); + assert_eq!(i.evaluate(&mut m, "\\GLTY", &[]), Ok(Value::Integer(9))); assert_eq!(i.evaluate(&mut m, "\\TOSI", &[]), Ok(Value::Integer(8))); } @@ -291,11 +291,11 @@ fn a_package_names_its_elements() { let (mut i, mut m) = loaded(&cat(&[ &def_name("INT1", &int(0x1234)), &device("DEV0", &[]), - &def_name("PKG1", &package(&[int(0x3400), name("\\INT1"), name("\\DEV0"), name("\\LATE")])), + &def_name("MIXD", &package(&[int(0x3400), name("\\INT1"), name("\\DEV0"), name("\\LATE")])), &def_name("LATE", &int(5)), ])); assert_eq!( - int_of(&mut i, &mut m, "\\PKG1"), + int_of(&mut i, &mut m, "\\MIXD"), Ok(Value::Package(vec![ Value::Integer(0x3400), Value::Integer(0x1234), diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs index 1258569ef53..bb9c9e8ab24 100644 --- a/userland/acpiserver/aml/tests/regions.rs +++ b/userland/acpiserver/aml/tests/regions.rs @@ -169,11 +169,11 @@ fn a_pci_config_region_addresses_its_devices_function() { &cat(&[ bridge, &device( - "LPCB", + "ISAB", &cat(&[ &method("_ADR", 0, &ret(&int(0x001F_0003))), - &op_region("LPCR", 0x02, &int(0x40), &int(0x10)), - &field("LPCR", BYTE, &[skip(8), unit("R41", 8)]), + &op_region("ISAR", 0x02, &int(0x40), &int(0x10)), + &field("ISAR", BYTE, &[skip(8), unit("R41", 8)]), ]), ), ]), @@ -181,29 +181,94 @@ fn a_pci_config_region_addresses_its_devices_function() { ) }; let bridge = cat(&[&def_name("_BBN", &int(0x80)), &def_name("_SEG", &int(1))]); - let (m, _) = read(&lpc(&bridge), &[], "\\_SB.PCI0.LPCB.R41"); + let (m, _) = read(&lpc(&bridge), &[], "\\_SB.PCI0.ISAB.R41"); let at = Address::PciConfig { segment: 1, bus: 0x80, device: 0x1F, function: 3, offset: 0x41 }; assert_eq!(m.accesses(), vec![Event::Read(at, Access::Byte)]); - let (_, v) = read(&lpc(&[]), &[], "\\_SB.PCI0.LPCB.R41"); + let (_, v) = read(&lpc(&[]), &[], "\\_SB.PCI0.ISAB.R41"); assert!(matches!(v, Err(Error::Unsupported(_)))); } -/// A device below a bridge is on the bus the bridge's Secondary Bus Number -/// register names (PCI-to-PCI Bridge Architecture Specification 1.2, -/// §3.2.5.4), read from the bridge on the bus above it. +/// A device below bridges is on the bus the nearest one's Secondary Bus +/// Number register names (PCI-to-PCI Bridge Architecture Specification 1.2, +/// §3.2.5.4), each bridge read on the bus the one above it named, the first +/// on the host bridge's `_BBN`. #[test] -fn a_pci_config_region_below_a_bridge_is_on_its_secondary_bus() { +fn a_pci_config_region_below_bridges_is_on_the_nearest_ones_secondary_bus() { + let below = |endpoint_scope: &[u8]| { + let lower = cat(&[&def_name("_ADR", &int(0x0000_0000)), &device("END0", endpoint_scope)]); + let upper = cat(&[&def_name("_ADR", &int(0x0003_0001)), &device("BRG1", &lower)]); + scope("\\_SB", &device("PCI0", &cat(&[&def_name("_BBN", &int(0x40)), &device("BRG0", &upper)]))) + }; let endpoint = cat(&[ &def_name("_ADR", &int(0x0000_0001)), &op_region("CFG", 0x02, &int(0), &int(0x10)), &field("CFG", BYTE, &[unit("VEN", 8)]), ]); - let port = cat(&[&def_name("_ADR", &int(0x001C_0002)), &device("PXSX", &endpoint)]); - let body = scope("\\_SB", &device("PCI0", &cat(&[&def_name("_BBN", &int(0x40)), &device("RP03", &port)]))); - let secondary = Address::PciConfig { segment: 0, bus: 0x40, device: 0x1C, function: 2, offset: 0x19 }; - let (m, _) = read(&body, &[(secondary, &[0x45])], "\\_SB.PCI0.RP03.PXSX.VEN"); - let at = Address::PciConfig { segment: 0, bus: 0x45, device: 0, function: 1, offset: 0 }; - assert_eq!(m.accesses(), vec![Event::Read(secondary, Access::Byte), Event::Read(at, Access::Byte)]); + let upper = Address::PciConfig { segment: 0, bus: 0x40, device: 3, function: 1, offset: 0x19 }; + let lower = Address::PciConfig { segment: 0, bus: 0x45, device: 0, function: 0, offset: 0x19 }; + let at = Address::PciConfig { segment: 0, bus: 0x47, device: 0, function: 1, offset: 0 }; + let ven = "\\_SB.PCI0.BRG0.BRG1.END0.VEN"; + let (m, v) = read(&below(&endpoint), &[(upper, &[0x45]), (lower, &[0x47]), (at, &[0x86])], ven); + assert_eq!(v, Ok(Value::Integer(0x86))); + assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte), Event::Read(lower, Access::Byte), Event::Read(at, Access::Byte)]); + + // A bridge that is not configured answers 0, its register's reset value, + // and one may answer anything: a secondary bus that is not above the + // bridge's own names no bus below it, and nothing is accessed there. + for unset in [0x00, 0x40, 0x3F] { + let (m, v) = read(&below(&endpoint), &[(upper, &[unset])], ven); + assert!(matches!(v, Err(Error::Rule(_))), "{unset:#x}: {v:?}"); + assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte)], "{unset:#x}"); + } + + // A region a method declares addresses the device the method is in. + let get = method( + "GET", + 0, + &cat(&[&op_region("TMP", 0x02, &int(0), &int(0x10)), &field("TMP", BYTE, &[unit("TVEN", 8)]), &ret(&name("TVEN"))]), + ); + let (m, v) = read( + &below(&cat(&[&def_name("_ADR", &int(0x0000_0001)), &get])), + &[(upper, &[0x45]), (lower, &[0x47]), (at, &[0x86])], + "\\_SB.PCI0.BRG0.BRG1.END0.GET", + ); + assert_eq!(v, Ok(Value::Integer(0x86))); + assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte), Event::Read(lower, Access::Byte), Event::Read(at, Access::Byte)]); +} + +/// A store to a field is of its source as the store found it: the function a +/// PCI_Config field addresses is asked of firmware's own methods while the +/// store is under way, and one that stores to the source changes nothing of +/// what is written. +#[test] +fn a_field_store_writes_its_source_as_it_was_when_firmware_changes_it() { + let body = |source: &[u8]| { + device( + "PCI0", + &cat(&[ + &def_name("_BBN", &int(0)), + &def_name("BUFF", source), + &method("_ADR", 0, &cat(&[&store(&int(0), &name("BUFF")), &ret(&int(0))])), + &op_region("CFG", 0x02, &int(0), &int(0x10)), + &field("CFG", DWORD, &[unit("FLD", 32)]), + &method("MAIN", 0, &store(&name("BUFF"), &name("FLD"))), + ]), + ) + }; + let fld = Address::PciConfig { segment: 0, bus: 0, device: 0, function: 0, offset: 0 }; + let written = |source: &[u8]| { + let (mut i, mut m) = loaded(&body(source)); + let r = i.evaluate(&mut m, "\\PCI0.MAIN", &[]); + (r, m.accesses(), i.evaluate(&mut m, "\\PCI0.BUFF", &[])) + }; + let (r, accesses, after) = written(&buffer(&int(4), &[0x44, 0x33, 0x22, 0x11])); + assert!(r.is_ok(), "{r:?}"); + assert_eq!(accesses, vec![Event::Write(fld, Access::DWord, 0x1122_3344)]); + assert_eq!(after, Ok(Value::Buffer(vec![0; 4]))); + let (r, accesses, after) = written(&string("AB")); + assert!(r.is_ok(), "{r:?}"); + assert_eq!(accesses, vec![Event::Write(fld, Access::DWord, 0x41), Event::Write(fld, Access::DWord, 0x42)]); + assert_eq!(after, Ok(s("0000000000000000"))); } /// The example of §19.6.63: FET3, the high bit at indexed offset 0x2F. From 7d130196a27939af486fc07696b823ba3bb0e45f Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 7 Oct 2026 16:34:14 +0200 Subject: [PATCH 8/9] A bridge is asked for its Header Type before its secondary bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The walk from a host bridge down to a PCI_Config region's device read offset 0x19 of every Device between them as a Secondary Bus Number. That register exists only in header layout 1. A function that is absent, as a root port the firmware disabled and hid is, answers all ones: 0xFF is above every bus, so the region's device was addressed on bus 255. A Device that is no bridge answers a byte of something else, and any value above its bus was taken. Each bridge is now asked for its Header Type first (offset 0x0E, the low seven bits), and a region below a function of any other layout is refused, Rule, with that one register read and nothing accessed below. ACPI 6.5 §6.5.4 was read for the rule and is cited at it: OSPM guarantees a PCI_Config region always accessible only "on a PCI root bus containing a _BBN object", and one below a bridge is ready "as soon the host controller or bridge controller has been programmed with a bus number". The bridge test's passing arm answers layout 1 at both bridges, the upper with the multi-function bit set; it gains four refusals: layout 0 with a plausible byte at 0x19, all ones at both, layout 2, and the multi-function bit alone. The track's bullet on the T14's tables gains an owner's exit a row can fail and what the out-of-tree check found with the bridges answered five ways. The QEMU issue's "nearest scope" is the nearest Device. Co-Authored-By: Claude Opus 5.5 --- ...inks-are-refused-by-the-aml-interpreter.md | 2 +- ...ine-in-acpi-mode-and-interprets-its-aml.md | 27 ++++++++--- userland/acpiserver/aml/src/field.rs | 35 ++++++++++---- userland/acpiserver/aml/tests/regions.rs | 46 +++++++++++++------ 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md b/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md index 39f84c0b4cd..2c356bde80d 100644 --- a/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md +++ b/issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md @@ -7,7 +7,7 @@ opened: 2026-10-07 # QEMU's interrupt links are refused by the AML interpreter `userland/acpiserver/aml/src/field.rs`'s `pci` finds the host bridge a -PCI_Config region is below as the nearest scope that names a `_BBN`. QEMU +PCI_Config region is below as the nearest Device that names a `_BBN`. QEMU 11.1.1's DSDT (`toyos-acpi/fixtures/qemu-11.1.1/dsdt.bin`) names none: its `\_SB.PCI0` is a host bridge by its `_HID` and `_CID` alone, on bus 0. So a field of the region its ISA bridge declares is refused as `Unsupported`, and diff --git a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md index ad0658693b5..3048ddfcdc3 100644 --- a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md +++ b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md @@ -113,16 +113,29 @@ recording the result. What that check found, which whoever builds on the interpreter would otherwise pay to find again: -- **The T14's tables load only against its own memory.** Their +- **The T14's tables load only against its own memory and its own + bridges.** Their definition-block code reads SystemMemory and PCI_Config while it loads, and branches on what it reads: with every read answered zero the DSDT refers to a device its own other branch never defined, and is refused. The check - answered one 16-bit word, the chipset series, and each bridge's Secondary - Bus Number register as a configured bridge does, and nothing else: one - SSDT reads a field below a bridge while it loads, and the interpreter - refuses a secondary bus that is not above the bridge's own. A run on the - machine itself is still owed, by the power-off stage, which puts the - interpreter in the server. + answered one 16-bit word of memory, the chipset series, and nothing else + of it. One SSDT reads a field below a bridge while it loads, and the + interpreter refuses a PCI_Config region below a function that is no + PCI-to-PCI bridge by its Header Type, or whose Secondary Bus Number is not + above its own bus (`pci`, `userland/acpiserver/aml/src/field.rs`). With + the bridges answering as present and numbered all 14 tables load; with + them answering zero, as bridges at reset, or as absent, that SSDT is + refused and 13 load. With every function answering its Header Type and + bus registers as Linux on the T14 reads them, all 14 load, and 42 methods + are refused for a function above their region that is not there. That + reading was taken after Linux enumerated the buses, and Linux may number a + bridge the firmware left unnumbered: it does not show what the firmware + leaves at boot, and nothing here does. Owner: the power-off stage, which + puts the interpreter in the server. **Exit**: on the T14 the server logs + the load result of each of the 14 tables and a T14 row reads all 14 + there; a table refused for a bridge's answer is brought to the owner with + that bridge's Header Type and bus registers as the firmware left them, + and he rules whether a table real firmware ships may be refused for it. - **The T14's processor objects need `Load`.** Its tables hold eight `Load` opcodes and one `LoadTable`, none run while a table loads, and Linux lists eight tables loaded that way; the interpreter refuses both as unsupported. diff --git a/userland/acpiserver/aml/src/field.rs b/userland/acpiserver/aml/src/field.rs index 9d1f5f4f8eb..304ac184621 100644 --- a/userland/acpiserver/aml/src/field.rs +++ b/userland/acpiserver/aml/src/field.rs @@ -175,10 +175,16 @@ impl Machine<'_> { /// the high word, function in the low), in the segment group the host /// bridge's `_SEG` names or 0 without one (§6.5.6). The host bridge is /// the nearest device naming a `_BBN`, which is the bus directly below it - /// (§6.5.5); each device between it and the region's is a bridge, whose - /// Secondary Bus Number register is the bus below it (PCI-to-PCI Bridge - /// Architecture Specification 1.2, §3.2.5.4). A region declared in the - /// host bridge itself addresses the bridge. + /// (§6.5.5); each device between it and the region's is a PCI-to-PCI + /// bridge by its Header Type register, whose Secondary Bus Number + /// register is the bus below it (PCI-to-PCI Bridge Architecture + /// Specification 1.2, §3.2.5.4). A region declared in the host bridge + /// itself addresses the bridge. + /// + /// §6.5.4 holds a PCI_Config region accessible always only on a root bus + /// naming a `_BBN`, and one below a bridge once "the bridge controller + /// has been programmed with a bus number": a region below anything else + /// is refused, where any bus chosen for it would be another device's. /// /// Every access asks again, firmware's methods and the bridges both: /// nothing is kept that a bridge renumbered since would make stale. @@ -207,11 +213,22 @@ impl Machine<'_> { let Some((&device, bridges)) = below.split_first() else { return self.function(host, segment, bus) }; for &bridge in bridges.iter().rev() { let b = self.function(bridge, segment, bus)?; - let secondary = Address::PciConfig { segment, bus, device: b.device, function: b.function, offset: 0x19 }; - let answered = self.host.read(secondary, crate::Access::Byte).map_err(|d| Error::Host(d.0))? as u8; - // The register resets to 0, and a configured bridge's secondary - // bus is above the bus the bridge is on: any other answer would - // address a device that is not below this bridge. + let register = |m: &mut Self, offset| { + let at = Address::PciConfig { segment, bus, device: b.device, function: b.function, offset }; + m.host.read(at, crate::Access::Byte).map(|v| v as u8).map_err(|d| Error::Host(d.0)) + }; + // Offset 0x19 is a Secondary Bus Number only in header layout 1, + // the low seven bits of the Header Type: a function that is + // absent answers all ones, and any other layout a byte of + // something else. + if register(self, 0x0E)? & 0x7F != 0x01 { + return Err(Error::Rule("a device above a PCI_Config region's is no PCI-to-PCI bridge by its Header Type, and has no bus below it")); + } + let answered = register(self, 0x19)?; + // §6.5.4: the region is ready once its bridge has a bus number. + // The register resets to 0, and a bridge's secondary bus is + // above the bus the bridge is on: any other answer would address + // a device that is not below this bridge. if answered <= bus { return Err(Error::Rule("a bridge's Secondary Bus Number is not above its own bus, and names no bus below it")); } diff --git a/userland/acpiserver/aml/tests/regions.rs b/userland/acpiserver/aml/tests/regions.rs index bb9c9e8ab24..a532d7eb36b 100644 --- a/userland/acpiserver/aml/tests/regions.rs +++ b/userland/acpiserver/aml/tests/regions.rs @@ -190,8 +190,10 @@ fn a_pci_config_region_addresses_its_devices_function() { /// A device below bridges is on the bus the nearest one's Secondary Bus /// Number register names (PCI-to-PCI Bridge Architecture Specification 1.2, -/// §3.2.5.4), each bridge read on the bus the one above it named, the first -/// on the host bridge's `_BBN`. +/// §3.2.5.4), each bridge asked first for its Header Type, on the bus the +/// one above it named, the first on the host bridge's `_BBN`. §6.5.4: the +/// region is ready once "the bridge controller has been programmed with a +/// bus number". #[test] fn a_pci_config_region_below_bridges_is_on_the_nearest_ones_secondary_bus() { let below = |endpoint_scope: &[u8]| { @@ -204,21 +206,41 @@ fn a_pci_config_region_below_bridges_is_on_the_nearest_ones_secondary_bus() { &op_region("CFG", 0x02, &int(0), &int(0x10)), &field("CFG", BYTE, &[unit("VEN", 8)]), ]); - let upper = Address::PciConfig { segment: 0, bus: 0x40, device: 3, function: 1, offset: 0x19 }; - let lower = Address::PciConfig { segment: 0, bus: 0x45, device: 0, function: 0, offset: 0x19 }; + let upper = |offset| Address::PciConfig { segment: 0, bus: 0x40, device: 3, function: 1, offset }; + let lower = |offset| Address::PciConfig { segment: 0, bus: 0x45, device: 0, function: 0, offset }; + let (header, secondary) = (0x0E, 0x19); let at = Address::PciConfig { segment: 0, bus: 0x47, device: 0, function: 1, offset: 0 }; + // The upper bridge is a multi-function device: bit 7 is not the layout. + let configured: &[(Address, &[u8])] = + &[(upper(header), &[0x81]), (upper(secondary), &[0x45]), (lower(header), &[0x01]), (lower(secondary), &[0x47]), (at, &[0x86])]; + let walked = vec![ + Event::Read(upper(header), Access::Byte), + Event::Read(upper(secondary), Access::Byte), + Event::Read(lower(header), Access::Byte), + Event::Read(lower(secondary), Access::Byte), + Event::Read(at, Access::Byte), + ]; let ven = "\\_SB.PCI0.BRG0.BRG1.END0.VEN"; - let (m, v) = read(&below(&endpoint), &[(upper, &[0x45]), (lower, &[0x47]), (at, &[0x86])], ven); + let (m, v) = read(&below(&endpoint), configured, ven); assert_eq!(v, Ok(Value::Integer(0x86))); - assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte), Event::Read(lower, Access::Byte), Event::Read(at, Access::Byte)]); + assert_eq!(m.accesses(), walked); // A bridge that is not configured answers 0, its register's reset value, // and one may answer anything: a secondary bus that is not above the // bridge's own names no bus below it, and nothing is accessed there. for unset in [0x00, 0x40, 0x3F] { - let (m, v) = read(&below(&endpoint), &[(upper, &[unset])], ven); + let (m, v) = read(&below(&endpoint), &[(upper(header), &[0x01]), (upper(secondary), &[unset])], ven); assert!(matches!(v, Err(Error::Rule(_))), "{unset:#x}: {v:?}"); - assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte)], "{unset:#x}"); + assert_eq!(m.accesses(), vec![Event::Read(upper(header), Access::Byte), Event::Read(upper(secondary), Access::Byte)], "{unset:#x}"); + } + + // Offset 0x19 is a Secondary Bus Number in a bridge's header alone: a + // function of another layout answers a byte of something else there, and + // one that is absent all ones, which is above every bus. Neither is asked. + for (layout, at_0x19) in [(0x00, 0x45), (0xFF, 0xFF), (0x02, 0x45), (0x80, 0x45)] { + let (m, v) = read(&below(&endpoint), &[(upper(header), &[layout]), (upper(secondary), &[at_0x19])], ven); + assert!(matches!(v, Err(Error::Rule(_))), "{layout:#x}: {v:?}"); + assert_eq!(m.accesses(), vec![Event::Read(upper(header), Access::Byte)], "{layout:#x}"); } // A region a method declares addresses the device the method is in. @@ -227,13 +249,9 @@ fn a_pci_config_region_below_bridges_is_on_the_nearest_ones_secondary_bus() { 0, &cat(&[&op_region("TMP", 0x02, &int(0), &int(0x10)), &field("TMP", BYTE, &[unit("TVEN", 8)]), &ret(&name("TVEN"))]), ); - let (m, v) = read( - &below(&cat(&[&def_name("_ADR", &int(0x0000_0001)), &get])), - &[(upper, &[0x45]), (lower, &[0x47]), (at, &[0x86])], - "\\_SB.PCI0.BRG0.BRG1.END0.GET", - ); + let (m, v) = read(&below(&cat(&[&def_name("_ADR", &int(0x0000_0001)), &get])), configured, "\\_SB.PCI0.BRG0.BRG1.END0.GET"); assert_eq!(v, Ok(Value::Integer(0x86))); - assert_eq!(m.accesses(), vec![Event::Read(upper, Access::Byte), Event::Read(lower, Access::Byte), Event::Read(at, Access::Byte)]); + assert_eq!(m.accesses(), walked); } /// A store to a field is of its source as the store found it: the function a From eea9ec35cb1208b98dc9142c5d27af5a394f90b7 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 7 Oct 2026 16:46:27 +0200 Subject: [PATCH 9/9] The bridge bullet's exit reads refused methods too The exit read table loads alone, so a method the server evaluates on the machine and has refused for a bridge's answer reached nobody by it. The same T14 row now reads that none was, and one that was goes to the owner with the table refusal. Review round 5's NOTE on #739. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A --- ...yos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md index 3048ddfcdc3..0a0894f0f51 100644 --- a/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md +++ b/issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md @@ -136,6 +136,9 @@ otherwise pay to find again: there; a table refused for a bridge's answer is brought to the owner with that bridge's Header Type and bus registers as the firmware left them, and he rules whether a table real firmware ships may be refused for it. + The same row reads that no method the server evaluated was refused for a + bridge's answer, and one that was goes to the owner with the table + refusal. - **The T14's processor objects need `Load`.** Its tables hold eight `Load` opcodes and one `LoadTable`, none run while a table loads, and Linux lists eight tables loaded that way; the interpreter refuses both as unsupported.