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..2c356bde80d --- /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 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 +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 9f9eaa84e56..54b19b7cd9e 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 @@ -73,9 +73,29 @@ button, and not to power the machine on again. 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, 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): + +- **`\_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 @@ -115,12 +135,71 @@ written. **Stage: the interpreter** (the orchestrator's placement of "The AML stage closes it"). ToyOS's own AML interpreter, written from the specification, run -by the ACPI server, the battery first. It owns +by the ACPI server, the battery first: `userland/acpiserver/aml`, pure and +host-tested, beside the server, which does not link it yet. It owns `issues/the-t14s-power-button-event-came-up-to-17-s-after-ec-query-0x28.md`. -**Exit**: on the T14 the server evaluates the method of each embedded-controller -query the tables define, a T14 row reads the battery's state as the -interpreter evaluated it beside Linux's reading of the same machine, and the -press issue is closed by its own exit. +**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; and on the T14 the server evaluates the method of each +embedded-controller query the tables define, a T14 row reads the battery's +state as the interpreter evaluated it beside Linux's reading of the same +machine, and the press issue is closed by its own exit. + +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 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 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 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. +- **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. +- **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/Cargo.lock b/userland/Cargo.lock index cb6a7aef398..d0dfba9d56a 100644 --- a/userland/Cargo.lock +++ b/userland/Cargo.lock @@ -4189,6 +4189,21 @@ 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" version = "0.1.0" @@ -4201,6 +4216,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/Cargo.toml b/userland/Cargo.toml index f3db7a14419..71ab4811e43 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "acpiserver", + "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..46a797fa68e --- /dev/null +++ b/userland/acpiserver/aml/Cargo.toml @@ -0,0 +1,17 @@ +[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 + +[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 new file mode 100644 index 00000000000..0aa14d718d7 --- /dev/null +++ b/userland/acpiserver/aml/src/exec.rs @@ -0,0 +1,1822 @@ +//! 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::{ + bounded, decimal, fit, hex2, joined, slot_of, to_buf, to_int, to_str, Body, Bytes, Meter, Method, Mutex, Object, Ref, + 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}; + +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, + /// 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: Bytes, + /// 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: Bytes, 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]", + 12 => b"[Processor]", + 13 => b"[Thermal Zone]", + 14 => b"[Buffer Field]", + 16 => b"[Debug Object]", + _ => b"[Uninitialized Object]", + } +} + +/// 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> { + 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() + } + + /// Bytes made, charged for and held against the meter. + pub(crate) fn bytes(&mut self, v: Vec) -> Result { + self.charge(v.len())?; + 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 { + Ok(Object::Buf(self.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> { + 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> { + 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.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))?; + } + 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 steps = &mut self.steps; + let id = self.ns.create(f.scope, p, o, &mut || tick(steps))?; + f.created.push(id); + Ok(id) + } + + 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 { + 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(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 { + 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), 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: &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 { + 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 bytes = table.borrow(); + let mut c = Cursor::new(&bytes, 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 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.borrow().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 = 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)")); + }; + 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(self.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..=0x85 => self.def_scoped(f, c), + 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 = self.name(c)?; + let alias = self.name(c)?; + let target = self.resolve(f, &source)?; + 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 = self.name(c)?; + 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 = 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. + 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)?; + 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, 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()?; + let end = c.pkg_end()?; + let mut body = Self::sub(c, end); + let p = self.name(&mut body)?; + 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. + 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 = self.name(&mut head)?; + 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()?; + self.name(c)?; + c.byte()?; + c.byte()?; + Ok(Flow::Next) + } + + fn def_mutex(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + 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 })))?; + Ok(Flow::Next) + } + + fn def_event(&mut self, f: &mut Frame, c: &mut Cursor<'_>) -> Result { + c.byte()?; + c.byte()?; + let p = self.name(c)?; + 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 = 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 }; + self.define(f, &p, Object::Region(Rc::new(r)))?; + Ok(Flow::Next) + } + + 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(&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)")), + } + } + + /// 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, &mut l)?), + 0x86 => { + 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, &mut l)?; + let bank = self.field_of(f, &mut l)?; + let value = self.int_arg(f, &mut l)?; + Kind::Bank { region, bank, value } + } + }; + let (mut access, lock, update) = flags(l.byte()?); + 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()?).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 { + self.name(&mut l)?; + } + } + _ => { + 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 => { + let v = to_buf(&o, self.w)?; + self.bytes(v)? + } + }; + 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 = 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)"))?; + 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::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.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 = self.name(c)?; + 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, + // AsciiChar is 0x01-0x7F (§20.2.3); a byte above it + // still ends nothing, and is kept. + b => s.push(b), + } + bounded(s.len())?; + } + self.new_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; + 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"))?; + crate::object::counted(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 = 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); + self.leave(); + e? + }; + elems.push(e); + } + elems.resize(count, Object::Uninit); + c.at = end; + 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)" }), + }) + } + + // ---- 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 = self.name(c)?; + return Ok(self.find(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 = 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)")), + } + } + 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()), + }; + 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)) => 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)? + } + 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 = self.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 = self.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 s = slot_of(s)?; + let v = self.copy(&v)?; + *s.borrow_mut() = v; + Ok(()) + } + Ref::Elem(p, i) => { + let v = self.lasting(&v)?; + p.set(*i, v) + } + 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)?; + 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(); + self.charge(n.len().max(len))?; + b.replace(fit(n, len)) + } + Object::Pkg(p) => match &v { + Object::Pkg(src) => { + 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 = self.lasting(&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 = self.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 = self.lasting(&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 = self.path_of_text(&s)?; + 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(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")), + } + } + + /// 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(&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())), + 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)")), + } + } + + /// 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.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) => { + let mut v = w.le(*x); + v.extend(w.le(to_int(&b, w)?)); + self.new_buf(v)? + } + Object::Str(x) => { + let mut v = x.borrow().clone(); + v.extend(tail_str(self, &b)?); + self.new_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); + } + self.new_buf(v)? + } + other => { + let mut v = named(self, other)?; + v.extend(tail_str(self, &b)?); + self.new_str(v)? + } + }; + 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)); + let r = self.new_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 => { + let v = to_buf(&src, w)?; + self.new_buf(v)? + } + 0x97 => match &src { + 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) => { + 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) => { + 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())? + } + _ => { + 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), + }; + 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(); + 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); + } + }; + 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; a bound + /// reached on the way is a refusal. + fn matches(&mut self, e: &Object, op: u8, m: &Object) -> Result { + if op == 0 { + return Ok(true); + } + let converted = match m { + Object::Int(_) => to_int(e, self.w).map(Object::Int), + 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 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, + }) + } +} + +/// 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..304ac184621 --- /dev/null +++ b/userland/acpiserver/aml/src/field.rs @@ -0,0 +1,423 @@ +//! 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 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, 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, or something inside that device. + pub(crate) scope: NodeId, +} + +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 } + } +} + + +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): 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<'_> { + /// The bytes of one access unit, by the access type and, for an access + /// 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 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!(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| { + 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, + 3 => 4, + 4 => 8, + 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")); + } + 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 => { + 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); + 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")), + 0x0C..=0x7E => Err(Error::Unsupported("a reserved address space (Table 5.182)")), + _ => Err(Error::Unsupported("an OEM-defined address space")), + } + } + + /// 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 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. + fn pci(&mut self, r: &Region) -> Result { + let bbn = Seg(*b"_BBN"); + let mut below = Vec::new(); + let mut host = r.scope; + loop { + self.step()?; + 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(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)"))?; + 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 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")); + } + bus = answered; + } + 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 = 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)")); + }; + Ok(Pci { segment, bus, device, function }) + } + + 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(); + 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 { + 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, and an empty one as zeros; a String is written a + /// 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, copy, held); + let (source, piece): (&[u8], usize) = match &v { + Object::Int(x) => { + int = x.to_le_bytes(); + (&int, int.len()) + } + 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")), + }; + self.enter()?; + 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; + 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), + 2 => 0, + _ => return Err(Error::Rule("a write by a reserved UpdateRule (§20.2.5.2)")), + } + }; + 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 { + // 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")); + } + 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); + self.value(out, f.len) + } + + /// 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")), + }; + // 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)?); + 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")); + } + 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..0025002e4a8 --- /dev/null +++ b/userland/acpiserver/aml/src/lib.rs @@ -0,0 +1,317 @@ +//! 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, 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, +//! 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), 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)] + +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; + +use exec::{Frame, Machine}; +use name::{Path, Seg}; +use namespace::Namespace; +use object::{Body, Meter, 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 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 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. +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. +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", +]; + +/// 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), + /// 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), +} + +/// 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, + meter: Rc, + /// 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 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)] }; + 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); + } + 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. + 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 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 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)")), + }; + // 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 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")), + }); + 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 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)); + 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(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) => 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 { + 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.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/name.rs b/userland/acpiserver/aml/src/name.rs new file mode 100644 index 00000000000..07d4c0bdeb4 --- /dev/null +++ b/userland/acpiserver/aml/src/name.rs @@ -0,0 +1,105 @@ +//! 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 + } + + /// 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(|&c| c == b'.') { + if part.is_empty() || part.len() > 4 { + return None; + } + let mut seg = [b'_'; 4]; + seg[..part.len()].copy_from_slice(part); + segs.push(Seg::new(seg)?); + } + } + 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")) + } +} + +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..d749ec86812 --- /dev/null +++ b/userland/acpiserver/aml/src/namespace.rs @@ -0,0 +1,221 @@ +//! 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, 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. + +use alloc::collections::BTreeMap; +use alloc::rc::Rc; +use alloc::string::String; +use alloc::vec::Vec; + +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::(); + +#[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, + meter: Rc, +} + +impl Namespace { + pub(crate) fn new(meter: Rc) -> 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(), meter } + } + + 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, toll: Toll<'_>) -> Result, Error> { + if path.root { + return Ok(Some(self.root())); + } + let mut at = scope; + for _ in 0..path.up { + toll()?; + let Some(above) = self.parent(at) else { return Ok(None) }; + at = above; + } + 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, 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 Ok(Some(found)); + } + let Some(above) = self.parent(at) else { return Ok(None) }; + at = above; + } + } + for &seg in &path.segs { + toll()?; + let Some(below) = self.child(at, seg) else { return Ok(None) }; + at = below; + } + 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, 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, 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), toll)?)); + } + let node = Node { + seg: *last, + parent: Some(at), + children: BTreeMap::new(), + alias: None, + object, + 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]; + let generation = slot.generation + 1; + *slot = Node { generation, ..node }; + NodeId { index, generation } + } + None => { + self.nodes.push(node); + NodeId { index: fresh, generation: 0 } + } + }; + self.nodes[at.index as usize].children.insert(*last, id); + Ok(id) + } + + 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); + } + Ok(id) + } + + /// 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; + 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. + if n.generation < u32::MAX { + 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, 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(); + 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 new file mode 100644 index 00000000000..c7beaabaa82 --- /dev/null +++ b/userland/acpiserver/aml/src/object.rs @@ -0,0 +1,396 @@ +//! 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). +//! +//! Every string, buffer and package, every loaded table a method still runs +//! 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 +//! 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}; + +use crate::field::{BufField, Field, Region}; +use crate::name::Path; +use crate::namespace::NodeId; +use crate::{Error, MAX_BYTES, MAX_ELEMENTS, MAX_LIVE}; + +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, buffer's or +/// table's length, a package's elements at [`ELEMENT`] bytes each, a +/// 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, +} + +/// 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) }) + } + + 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(()) + } + + 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, 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())?; + 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 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`]. +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: 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> { + 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); + } +} + +/// 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(()) } +} + +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, + 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, + /// The Processor object ACPI 6.4 deprecated (ACPI 6.3A §19.6.108). + Processor, + ThermalZone, + PowerResource, + Method(Rc), + Mutex(Rc), + /// An Event's pending signal count (§19.6.147). + Event(Rc>), + Region(Rc), + /// A package element whose name is resolved when read (§19.6.101). + Lazy(Rc), +} + +/// 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"), 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: Bytes, 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, +} + +impl Object { + /// 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 + /// 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, + 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::Processor => 12, + Object::ThermalZone => 13, + Object::BufField(_) => 14, + } + } +} + +/// 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(); + bounded(s.len() + 1)?; + 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) => { + 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, 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, &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); + } + each(x, &mut out); + } + 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..c9cbacfd120 --- /dev/null +++ b/userland/acpiserver/aml/src/stream.rs @@ -0,0 +1,151 @@ +//! 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 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); + } + 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 gives SegCount as 1 to 255; zero names no segment, + // which is the NullName's meaning. + usize::from(self.byte()?) + } + _ => 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..4f682327738 --- /dev/null +++ b/userland/acpiserver/aml/tests/common/mod.rs @@ -0,0 +1,449 @@ +//! 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_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. +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, u64::from(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(()) + } +} + +/// 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_bytes(&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..c36740a0e4b --- /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("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(); + 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_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)); +} + +/// `\_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..d72c792411f --- /dev/null +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -0,0 +1,580 @@ +//! 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::{Access, Address, Denied, Error, Host, Interpreter, Value}; + +const ZERO: &[u8] = &[0x00]; + +fn load(body: &[u8]) -> Result<(), Error> { + Interpreter::new().load_bytes(&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 { .. }))); + // 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). + 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( + "ISAB", + &cat(&[ + &def_name("_ADR", &int(0x001F_0000)), + &op_region("ISAR", 0x02, &int(0x40), &int(0x10)), + &field("ISAR", 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.ISAB.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_bytes(&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_bytes(&mut m, &dsdt(&seed[..n])).is_ok() { + let _ = i.evaluate(&mut m, "\\MAIN", &[Value::Integer(1), Value::Integer(2)]); + } + } +} + +/// 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}"); + } +} + +/// 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] +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, + &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_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: 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))); + + // 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 +/// 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 +/// 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() { + // 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( + "MAIN", + 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(_)))); +} + +/// 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 new file mode 100644 index 00000000000..8c67472cb78 --- /dev/null +++ b/userland/acpiserver/aml/tests/namespace.rs @@ -0,0 +1,318 @@ +//! 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::{Access, Address, Error, Interpreter, Value}; + +fn int_of(i: &mut Interpreter, m: &mut Machine, path: &str) -> Result { + i.evaluate(m, path, &[]) +} + +/// A table's length and checksum are `toyos_acpi::Table::open`'s; what the +/// interpreter decides is which tables load, and in which order. +#[test] +fn a_dsdt_loads_first_and_then_ssdts() { + let mut m = Machine::default(); + let good = dsdt(&def_name("A", &int(1))); + let facp = table(b"FACP", 2, &def_name("A", &int(1))); + assert!(matches!(Interpreter::new().load_bytes(&mut m, &facp), Err(Error::Table(_)))); + + let ssdt = table(b"SSDT", 2, &def_name("A", &int(1))); + assert!(matches!(Interpreter::new().load_bytes(&mut m, &ssdt), Err(Error::Table(_)))); + + let mut i = Interpreter::new(); + i.load_bytes(&mut m, &good).unwrap(); + assert!(matches!(i.load_bytes(&mut m, &good), Err(Error::Table(_)))); + i.load_bytes(&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("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. + 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))); + assert_eq!(i.evaluate(&mut m, "\\GLTY", &[]), 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_bytes(&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_bytes(&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_bytes(&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_bytes(&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_bytes(&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_bytes(&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(); + // 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 +/// constructs real tables still contain, per their last spec definition"; +/// ACPI 6.3A §20.2.5.2, §19.6.108, Table 19.36. +#[test] +fn a_processor_parses_opens_a_scope_and_takes_notify() { + let processor = cat(&[ + &[0x5B, 0x83], + &pkg(&cat(&[&name("CPU0"), &[0x01, 0x10, 0x04, 0x00, 0x00, 0x06], &def_name("_PPC", &int(3))])), + ]); + let (mut i, mut m) = loaded(&cat(&[ + &scope("\\_PR", &processor), + &method("TYPE", 0, &ret(&object_type(&name("\\_PR.CPU0")))), + &method("NOTE", 0, &cat(&[&[0x86], &name("\\_PR.CPU0"), &int(0x80)])), + ])); + assert_eq!(i.evaluate(&mut m, "\\_PR.CPU0._PPC", &[]), Ok(Value::Integer(3))); + assert_eq!(i.evaluate(&mut m, "\\TYPE", &[]), Ok(Value::Integer(12))); + i.evaluate(&mut m, "\\NOTE", &[]).unwrap(); + 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`, 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 mut m = Machine::default(); + let mut i = Interpreter::new(); + 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] +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("MIXD", &package(&[int(0x3400), name("\\INT1"), name("\\DEV0"), name("\\LATE")])), + &def_name("LATE", &int(5)), + ])); + assert_eq!( + int_of(&mut i, &mut m, "\\MIXD"), + 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..a532d7eb36b --- /dev/null +++ b/userland/acpiserver/aml/tests/regions.rs @@ -0,0 +1,471 @@ +//! 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)]); + // 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 +/// 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( + "ISAB", + &cat(&[ + &method("_ADR", 0, &ret(&int(0x001F_0003))), + &op_region("ISAR", 0x02, &int(0x40), &int(0x10)), + &field("ISAR", 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.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.ISAB.R41"); + assert!(matches!(v, Err(Error::Unsupported(_)))); +} + +/// 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 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]| { + 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 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), configured, ven); + assert_eq!(v, Ok(Value::Integer(0x86))); + 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(header), &[0x01]), (upper(secondary), &[unset])], ven); + assert!(matches!(v, Err(Error::Rule(_))), "{unset:#x}: {v:?}"); + 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. + 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])), configured, "\\_SB.PCI0.BRG0.BRG1.END0.GET"); + assert_eq!(v, Ok(Value::Integer(0x86))); + assert_eq!(m.accesses(), walked); +} + +/// 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. +#[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"))); + // 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. +#[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_bytes(&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_bytes(&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(_)))); +} + +/// 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(_)))); +} +