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 2ba30d45d93..58bc61134fa 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 @@ -220,18 +220,40 @@ otherwise pay to find again: map leaf of 104 bytes for its entry in its parent and the arena at its doubled capacity: an interpreter filled with field units refuses with 11,111,460 bytes of heap held, and one filled with devices of one child - with 8,900,612. The real machine's tables have not been loaded since the - meter came to count a node's slot, entry and record, a wide field's read - came to hold its buffer and the arena's move to cost steps: that reading - is owed before the server links the crate, and with it the slots the - arena has free after the last table, since a method that defines more - names than that moves the arena each time it runs, at a step for every - 64 bytes of it. 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 the most a load - and an evaluation hold while they run to it; and the real machine's - tables load under the meter as it stands, the arena's free slots after - the last read with them. + with 8,900,612. The real machine's tables, loaded outside the tree under + the meter as it stands, with the one word of memory answered and every + function answering as Linux reads it: all 14 load in 39,419 steps, 32,946 of them the + DSDT's, and leave the meter at 2,412,886 bytes over 1,819,038 of heap by + a counting allocator, in 6,891 nodes. Nothing reads the arena's + capacity: by its doubling it is 8,192 slots, 1,301 of them free, and a + method that defines more names than are free moves the arena each time + it runs, at a step for every 64 bytes of it. 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 the most a load and an evaluation hold while they run to + it; and the arena's capacity after the real machine's last table is + read, not derived. +- **The T14's tables are not in name order.** The interpreter's walk of + the namespace (`Interpreter::walk`, `userland/acpiserver/aml/src/lib.rs`) + reads siblings as their tables declared them. Of the 267 objects of the + T14's tables that hold others, 217 declare them against the order their + names sort in. What its initialisation needs of the order was read only + so far: the tables define 45 `_INI`, all methods; a dry run that answered + every read zero but one word of memory and each function's header found + two of them writing anything, the PCI root bridge's and the embedded + controller's; and that controller's device is below that bridge, so + under those answers the two run parent before child whichever order + siblings take. What the 45 do under the machine's own answers, and + whether any hangs on a sibling's, nothing has read. +- **An evaluation may ask to wait ten seconds, and no caller names another + limit.** The limit counts the time asked of Sleep, Stall and Wait, a + load's too (`MAX_WAIT_US`, `userland/acpiserver/aml/src/lib.rs`), and + `Interpreter::usage` says what a call asked, the request that was refused + included. The T14's 14 tables asked for none while they loaded, and no + method of them has been read refused at it. Owner: this stage. **Exit**: + the slice that carries a reading of a method refused at ten seconds, on + the T14 and by `usage`, brings the limit its caller names with that + reading; until one does, ten seconds stands and nothing names more. What the server's load of the tables, on the T14 and through the kernel's mediated access, leaves open: diff --git a/userland/acpiserver/aml/src/exec.rs b/userland/acpiserver/aml/src/exec.rs index 278402fb8fa..20143bcb625 100644 --- a/userland/acpiserver/aml/src/exec.rs +++ b/userland/acpiserver/aml/src/exec.rs @@ -33,7 +33,7 @@ use crate::object::{ 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}; +use crate::{Error, Host, Usage, 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, @@ -239,7 +239,9 @@ impl<'a> Machine<'a> { /// 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 { + /// What it took goes to `took`, whichever way it ends. + pub(crate) fn finish(mut self, r: Result, took: &mut Usage) -> Result { + (took.steps, took.waited_us) = (self.steps, self.waited_us); let held = !self.held.is_empty(); for m in self.held.drain(..) { m.held.set(0); diff --git a/userland/acpiserver/aml/src/lib.rs b/userland/acpiserver/aml/src/lib.rs index b5233010f94..f75947a350f 100644 --- a/userland/acpiserver/aml/src/lib.rs +++ b/userland/acpiserver/aml/src/lib.rs @@ -9,7 +9,12 @@ //! what an interpreter holds of tables, namespace and objects is bounded in //! sum, in bytes of heap ([`MAX_LIVE`]). A load refused leaves the namespace //! without anything that table created, and the interpreter holding what it -//! held before. +//! held before. What the last load or evaluation took of those bounds is +//! [`Interpreter::usage`]'s, a refused one's too. +//! +//! [`Interpreter::walk`] reads the namespace an interpreter holds, and runs +//! nothing of it: which objects a caller then evaluates, and in which order, +//! is the caller's. //! //! The library touches no hardware. An operation region's field is read and //! written through the [`Host`] the caller passes, in SystemMemory, @@ -193,12 +198,105 @@ pub enum Value { Reference(String), } +/// What a load or an evaluation took of its bounds, a refused one as well. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Usage { + /// The steps it took: terms, arguments, loop iterations, field access + /// units, and the bytes it made, copied, compared or walked. + pub steps: u64, + /// The time it asked to Sleep, Stall and Wait, in µs, the request its + /// limit refused included. + pub waited_us: u64, + /// The heap the interpreter held at its end, of [`MAX_LIVE`]. + pub live: usize, +} + +/// What a named object is: the types of Table 19.36, a predefined scope, +/// which has none, and a reference a Name holds. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + Uninitialized, + Integer, + String, + Buffer, + Package, + FieldUnit, + Device, + Event, + Method, + Mutex, + OperationRegion, + PowerResource, + /// The Processor object ACPI 6.4 deprecated (ACPI 6.3A §19.6.108). + Processor, + ThermalZone, + BufferField, + /// `\_SB` and the other scopes of §5.3.1: descended, and no device. + Scope, + Reference, +} + +/// One object of a [`Walk`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Entry { + /// How many names its path has: 1 directly below the root. Everything + /// below an object follows it at a greater depth, so a caller leaves a + /// subtree out by skipping to the next entry no deeper than its top. + pub depth: u32, + pub name: [u8; 4], + pub kind: Kind, +} + +/// The namespace read from the root down ([`Interpreter::walk`]). A table +/// chooses how deep it nests, and a path is as long as its depth: an entry +/// is as small at any depth, and is what a caller keeps of a whole +/// namespace, where the paths of one nested as deep as [`MAX_LIVE`] admits +/// are gigabytes. +pub struct Walk<'a>(namespace::Walk<'a>); + +impl Walk<'_> { + /// The absolute path of the entry last returned, as + /// [`Interpreter::evaluate`] takes it. + pub fn path(&self) -> &str { + self.0.path() + } +} + +impl Iterator for Walk<'_> { + type Item = Entry; + + fn next(&mut self) -> Option { + let (depth, seg, object) = self.0.step()?; + let kind = match object { + Object::Uninit => Kind::Uninitialized, + Object::Int(_) => Kind::Integer, + Object::Str(_) => Kind::String, + Object::Buf(_) => Kind::Buffer, + Object::Pkg(_) => Kind::Package, + Object::Field(_) => Kind::FieldUnit, + Object::BufField(_) => Kind::BufferField, + Object::Ref(_) | Object::Lazy(_) => Kind::Reference, + Object::Scope => Kind::Scope, + Object::Device => Kind::Device, + Object::Processor => Kind::Processor, + Object::ThermalZone => Kind::ThermalZone, + Object::PowerResource => Kind::PowerResource, + Object::Method(_) => Kind::Method, + Object::Mutex(_) => Kind::Mutex, + Object::Event(_) => Kind::Event, + Object::Region(_) => Kind::OperationRegion, + }; + Some(Entry { depth, name: seg.0, kind }) + } +} + /// 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, + last: Usage, } impl Default for Interpreter { @@ -228,12 +326,20 @@ impl Interpreter { let os = meter.bytes(b"Microsoft Windows NT".to_vec()).expect(held); put(b"_OS_", Object::Str(os)); put(b"_REV", Object::Int(2)); - Interpreter { ns, meter, width: None } + let last = Usage { live: meter.live(), ..Usage::default() }; + Interpreter { ns, meter, width: None, last } } /// 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> { + self.last = Usage::default(); + let r = self.load_in(host, table); + self.last.live = self.meter.live(); + r + } + + fn load_in(&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 } }, @@ -253,22 +359,25 @@ impl Interpreter { 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) => { - self.ns.unwind(made); - Err(e) - } + let r = m.finish(r, &mut self.last); + if r.is_ok() { + self.width = Some(w); + } else { + self.ns.unwind(made); } + r } /// 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 { + self.last = Usage::default(); + let r = self.evaluate_in(host, path, args); + self.last.live = self.meter.live(); + r + } + + fn evaluate_in(&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::, _>>()?; @@ -278,7 +387,23 @@ impl Interpreter { let r = m.evaluate(id, args).and_then(|o| value_of(&mut m, &meter, &mut handed, o, 0)); // The value is the caller's from here, and no longer this interpreter's. meter.give(handed); - m.finish(r) + m.finish(r, &mut self.last) + } + + /// What the last load or evaluation took, and what the interpreter held + /// at its end. + pub fn usage(&self) -> Usage { + self.last + } + + /// Every object the loaded tables and this interpreter defined, read-only + /// and without evaluating any: each after the object it is in, siblings + /// in the order they were declared, a table's after those of the tables + /// loaded before it. An Alias is not among them; what it names is, where + /// it was defined. The walk is held against [`MAX_LIVE`] while it lasts, + /// and refused where that has no room for it. + pub fn walk(&self) -> Result, Error> { + self.ns.walk().map(Walk) } fn object_of(&self, v: &Value, w: Width, depth: usize) -> Result { diff --git a/userland/acpiserver/aml/src/namespace.rs b/userland/acpiserver/aml/src/namespace.rs index a4c12976317..cf888b4c159 100644 --- a/userland/acpiserver/aml/src/namespace.rs +++ b/userland/acpiserver/aml/src/namespace.rs @@ -17,6 +17,10 @@ //! and a parent whose last child it took no map. The arena therefore moves //! as often as a method that grows it runs, and a move pays the [`Toll`] for //! the bytes it copies. +//! +//! The arena's order is the order its nodes were declared in, and a [`Walk`] +//! reads sibling order from it alone: a parent's map keeps its children by +//! name. use alloc::collections::BTreeMap; use alloc::rc::Rc; @@ -241,3 +245,103 @@ impl Namespace { Ok(crate::name::text(&Path { root: true, up: 0, segs })) } } + +/// Every object below the root, each after its parent and siblings in the +/// order they were declared, an Alias left out: its source is met at its own +/// place, and an Alias of an ancestor would otherwise never end. +/// +/// It is held against the meter while it lasts, at eight bytes a node and +/// five a level of the deepest: both are as many as the tables chose. +pub(crate) struct Walk<'a> { + ns: &'a Namespace, + /// A node's first child and its next sibling, by index; 0, the root's, + /// is none. + first: Vec, + next: Vec, + at: u32, + depth: u32, + path: String, + held: usize, +} + +/// The bytes of a path `depth` names long, `\AAAA.BBBB`. +const fn path_len(depth: usize) -> usize { + 5 * depth +} + +impl Namespace { + pub(crate) fn walk(&self) -> Result, Error> { + let count = self.nodes.len(); + let links = 2 * count * core::mem::size_of::(); + self.meter.take(links)?; + let mut walk = Walk { ns: self, first: Vec::new(), next: Vec::new(), at: 0, depth: 1, path: String::new(), held: links }; + for links in [&mut walk.first, &mut walk.next] { + links.reserve_exact(count); + links.resize(count, 0); + } + // A parent is older than its child, so its depth is known first: + // `next` holds each node's depth until the links are made. + let mut deepest = 0; + for (i, n) in self.nodes.iter().enumerate() { + if let Some(p) = n.parent { + walk.next[i] = walk.next[p as usize] + 1; + deepest = deepest.max(walk.next[i]); + } + } + let path = path_len(deepest as usize); + self.meter.take(path)?; + walk.held += path; + walk.path.reserve_exact(path); + // Newest first, each node goes to the front of its parent's list, + // which leaves the oldest there. + for (i, n) in self.nodes.iter().enumerate().rev() { + walk.next[i] = 0; + if let (Some(p), None) = (n.parent, n.alias) { + walk.next[i] = walk.first[p as usize]; + walk.first[p as usize] = i as u32; + } + } + walk.at = walk.first[0]; + Ok(walk) + } +} + +impl Drop for Walk<'_> { + fn drop(&mut self) { + self.ns.meter.give(self.held); + } +} + +impl Walk<'_> { + /// The next object's depth, a child of the root's being 1, its name and + /// the object. + pub(crate) fn step(&mut self) -> Option<(u32, Seg, &Object)> { + let i = self.at as usize; + if i == 0 { + return None; + } + let (n, depth) = (&self.ns.nodes[i], self.depth); + self.path.truncate(path_len(depth as usize - 1)); + self.path.push(if depth == 1 { '\\' } else { '.' }); + self.path.extend(n.seg.0.iter().map(|&c| char::from(c))); + if self.first[i] != 0 { + self.at = self.first[i]; + self.depth += 1; + } else { + // The next sibling of this node or of the nearest ancestor that + // has one: each node is climbed from once in a whole walk. + let mut up = i; + while up != 0 && self.next[up] == 0 { + up = self.ns.nodes[up].parent.map_or(0, |p| p as usize); + self.depth -= 1; + } + self.at = self.next[up]; + } + Some((depth, n.seg, &n.object)) + } + + /// The absolute path of the object last stepped to. + pub(crate) fn path(&self) -> &str { + &self.path + } +} diff --git a/userland/acpiserver/aml/src/object.rs b/userland/acpiserver/aml/src/object.rs index aef674790a2..c81041391b5 100644 --- a/userland/acpiserver/aml/src/object.rs +++ b/userland/acpiserver/aml/src/object.rs @@ -39,8 +39,9 @@ pub(crate) type Kept = Rc>; /// for: a string, buffer, table or package at its vector's capacity and its /// shared record, a fixed record at its size, the namespace's arena at its /// capacity and a node's entry among its parent's children at the most a -/// parent allocates for one (`namespace`), and the value an evaluation is -/// building for its caller, until the caller has it. What it does not count +/// parent allocates for one (`namespace`), the value an evaluation is +/// building for its caller, until the caller has it, and a walk of the +/// namespace while its caller holds it. What it does not count /// is what a load or an evaluation holds while it runs and lets go at its /// end: its frames and the mutexes it has acquired, the names it is /// reading, the bytes one operator has made and not yet held (`exec`) and @@ -69,6 +70,10 @@ impl Meter { Ok(()) } + pub(crate) fn live(&self) -> usize { + self.live.get() + } + 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")); } diff --git a/userland/acpiserver/aml/tests/heap.rs b/userland/acpiserver/aml/tests/heap.rs index c7cf8226281..f4ad8696f8d 100644 --- a/userland/acpiserver/aml/tests/heap.rs +++ b/userland/acpiserver/aml/tests/heap.rs @@ -240,6 +240,34 @@ fn a_parent_whose_last_child_is_unwound_holds_no_map() { assert_eq!(kept, held, "bytes held after the two evaluations, and before them"); } +/// A walk of 40,000 devices holds a third of a mebibyte, which is the +/// interpreter's while the walk lasts: one that is full refuses the walk, +/// and one that is not holds afterwards what it held before. +#[test] +fn a_walk_is_held_against_the_bound_while_it_lasts() { + let devices: Vec> = (0..40_000).map(|d| device(&seg(d), &[])).collect(); + let (fill, fills) = fillers(8, &buffer(&int(0), &[])); + let (mut i, before) = start(&fill); + i.load_bytes(&mut Sink, &table(b"SSDT", 2, &devices.concat())).expect("the table loads"); + let live = i.usage().live; + let held = heap() - before; + let walk = i.walk().expect("an interpreter with room is walked"); + // The root is no entry, and has its links as every node has. + let nodes = walk.count() as isize + 1; + assert!(nodes > 40_000, "{nodes} nodes"); + let walking = PEAK.get() - before - held; + // Two links a node, and a path one name long. + assert_eq!(walking, 8 * nodes + 5, "bytes a walk of {nodes} nodes held"); + assert_eq!(HELD.get() - before, held, "bytes held after the walk, and before it"); + assert_eq!(i.evaluate(&mut Sink, "\\_REV", &[]), Ok(Value::Integer(2))); + assert_eq!(i.usage().live, live); + + let refused = fills.iter().find_map(|m| i.evaluate(&mut Sink, m, &[]).err()); + Filled::of(before, refused).within_the_bound(); + assert_eq!(i.walk().err(), Some(Error::Bound(FULL))); + assert_eq!(i.evaluate(&mut Sink, "\\_REV", &[]), Ok(Value::Integer(2))); +} + /// One table naming 204,000 field units is refused, and the interpreter /// then holds what it held before, to the byte: its arena has the slots it /// had, and what it had room for it has room for again. diff --git a/userland/acpiserver/aml/tests/hostile.rs b/userland/acpiserver/aml/tests/hostile.rs index 5f5ff0f09f6..aaac51f7d98 100644 --- a/userland/acpiserver/aml/tests/hostile.rs +++ b/userland/acpiserver/aml/tests/hostile.rs @@ -524,6 +524,99 @@ fn waits_and_stalls_are_bounded_in_time_asked() { assert!(stalled <= 10_000_000, "{stalled} µs"); } +fn sleep(ms: u64) -> Vec { + cat(&[&[0x5B, 0x22], &int(ms)]) +} + +fn stall(us: u64) -> Vec { + cat(&[&[0x5B, 0x21], &int(us)]) +} + +/// Ten seconds is the most an evaluation may ask, to the microsecond, and a +/// load too. What crosses it is not asked of the host. +#[test] +fn the_wait_limit_is_exact_and_a_loads_too() { + let (mut i, mut m) = loaded(&cat(&[ + &method("TEN0", 0, &cat(&[&sleep(9_999), &stall(250), &stall(250), &stall(250), &stall(250), &ret(&int(1))])), + &method("TEN1", 0, &cat(&[&sleep(10_000), &stall(1), &ret(&int(1))])), + ])); + let asleep = Error::Bound("more time asleep than one evaluation may spend"); + let over = Err(asleep.clone()); + + assert_eq!(i.evaluate(&mut m, "\\TEN0", &[]), Ok(Value::Integer(1))); + assert_eq!(i.usage().waited_us, 10_000_000); + m.log.clear(); + assert_eq!(i.evaluate(&mut m, "\\TEN1", &[]), over); + assert_eq!(i.usage().waited_us, 10_000_001); + assert_eq!(m.log, vec![Event::Sleep(10_000)]); + + let mut m = Machine::default(); + assert_eq!(Interpreter::new().load_bytes(&mut m, &dsdt(&cat(&[&sleep(10_000), &def_name("A", &int(1))]))), Ok(())); + assert_eq!(Interpreter::new().load_bytes(&mut m, &dsdt(&cat(&[&sleep(10_000), &stall(1)]))), Err(asleep)); +} + +/// What a load or an evaluation took is read after it, a refused one's too, +/// and is that call's alone. +#[test] +fn usage_is_each_calls_own_and_a_refusals_too() { + let mut m = Machine::default(); + let mut i = Interpreter::new(); + let empty = i.usage(); + assert_eq!((empty.steps, empty.waited_us), (0, 0)); + assert!(empty.live > 0 && empty.live < 4096, "{empty:?}"); + + let body = cat(&[ + &def_name("PKG0", &package(&[int(0)])), + &method("SPIN", 0, &while_(&int(1), &[0xA3])), + &method("NAP0", 0, &cat(&[&sleep(3), &stall(5)])), + &method("KEEP", 0, &store(&buffer(&int(1000), &[]), &index(&name("PKG0"), &int(0), ZERO))), + &method("DROP", 0, &store(&int(0), &index(&name("PKG0"), &int(0), ZERO))), + &method("RETB", 0, &ret(&buffer(&int(1000), &[]))), + ]); + i.load_bytes(&mut m, &dsdt(&body)).unwrap(); + let loaded = i.usage(); + // Six terms and what they hold: a load is counted as an evaluation is. + assert!(loaded.steps >= 6 && loaded.steps < 64, "{loaded:?}"); + assert_eq!(loaded.waited_us, 0); + assert!(loaded.live > empty.live + body.len(), "{loaded:?}"); + + // The step bound's refusal has taken the bound and the step it refused. + assert!(matches!(i.evaluate(&mut m, "\\SPIN", &[]), Err(Error::Bound(_)))); + assert_eq!(i.usage(), toyos_aml::Usage { steps: (1 << 20) + 1, waited_us: 0, live: loaded.live }); + + i.evaluate(&mut m, "\\NAP0", &[]).unwrap(); + let napped = i.usage(); + assert_eq!((napped.waited_us, napped.live), (3_005, loaded.live)); + assert!(napped.steps >= 4 && napped.steps < 16, "{napped:?}"); + + // What an evaluation leaves stored is held after it; what it hands its + // caller is not. + i.evaluate(&mut m, "\\KEEP", &[]).unwrap(); + let kept = i.usage(); + assert!(kept.live >= loaded.live + 1000 && kept.live < loaded.live + 1100, "{kept:?}"); + assert_eq!(kept.waited_us, 0); + assert_eq!(i.evaluate(&mut m, "\\RETB", &[]), Ok(Value::Buffer(vec![0; 1000]))); + assert_eq!(i.usage().live, kept.live); + i.evaluate(&mut m, "\\DROP", &[]).unwrap(); + assert_eq!(i.usage().live, loaded.live); + + // A refusal before anything ran took nothing. + assert!(matches!(i.evaluate(&mut m, "\\NONE", &[]), Err(Error::NotFound(_)))); + assert_eq!(i.usage(), toyos_aml::Usage { steps: 0, waited_us: 0, live: loaded.live }); + + // A refused load took its steps, and holds what was held before it. + let refused = table(b"SSDT", 2, &cat(&[&def_name("\\NEW0", &buffer(&int(5000), &[])), &sleep(7), &def_name("\\PKG0", &int(1))])); + assert!(matches!(i.load_bytes(&mut m, &refused), Err(Error::Exists(_)))); + let after = i.usage(); + assert_eq!((after.waited_us, after.live), (7_000, loaded.live)); + assert!(after.steps >= 3 && after.steps < 256, "{after:?}"); + + // A load refused before anything ran took nothing, whatever the call + // before it took. + assert_eq!(i.load_bytes(&mut m, &dsdt(&[])), Err(Error::Table("a second DSDT"))); + assert_eq!(i.usage(), toyos_aml::Usage { steps: 0, waited_us: 0, live: loaded.live }); +} + /// BLOCKER 9 (c): a reference to an object a method created names nothing /// once the method exits, even after its slot is reused. #[test] diff --git a/userland/acpiserver/aml/tests/walk.rs b/userland/acpiserver/aml/tests/walk.rs new file mode 100644 index 00000000000..3fd282899f6 --- /dev/null +++ b/userland/acpiserver/aml/tests/walk.rs @@ -0,0 +1,295 @@ +//! The namespace read from the root down (§5.3): every object once, after +//! the object it is in, siblings in the order their tables declared them, +//! whatever a table's names sort as, however deep it nests, and whatever a +//! refused load or a method's exit took out of the arena before; and what a +//! walk takes of the interpreter's bound, to the byte. + +mod common; + +use common::*; +use toyos_aml::{Error, Interpreter, Kind, Value, MAX_LIVE}; + +/// What §5.3.1 and §5.7 predefine, in the order `Interpreter::new` makes them. +const PREDEFINED: [(&str, Kind); 9] = [ + ("\\_GPE", Kind::Scope), + ("\\_PR_", Kind::Scope), + ("\\_SB_", Kind::Scope), + ("\\_SI_", Kind::Scope), + ("\\_TZ_", Kind::Scope), + ("\\_GL_", Kind::Mutex), + ("\\_OSI", Kind::Method), + ("\\_OS_", Kind::String), + ("\\_REV", Kind::Integer), +]; + +/// Every entry's path and kind, after the predefined ones, each checked +/// against its depth and its name. +fn walked(i: &Interpreter) -> Vec<(String, Kind)> { + let mut w = i.walk().unwrap(); + let mut out = Vec::new(); + while let Some(e) = w.next() { + let path = w.path(); + assert_eq!(path.len(), 5 * e.depth as usize, "{path}"); + assert_eq!(&path.as_bytes()[path.len() - 4..], &e.name, "{path}"); + out.push((path.to_string(), e.kind)); + } + let predefined: Vec<(String, Kind)> = PREDEFINED.iter().map(|&(p, k)| (p.to_string(), k)).collect(); + assert_eq!(out[..PREDEFINED.len()], predefined); + out.split_off(PREDEFINED.len()) +} + +fn listed(entries: &[(&str, Kind)]) -> Vec<(String, Kind)> { + entries.iter().map(|&(p, k)| (p.to_string(), k)).collect() +} + +fn alias(source: &str, name_: &str) -> Vec { + cat(&[&[0x06], &name(source), &name(name_)]) +} + +/// Names that sort against the order they are declared in, at the root and +/// below it; a second table that adds to a device of the first; a table +/// refused after it made objects, and a method that made some and exited, +/// both before the table whose objects take their slots. +#[test] +fn siblings_are_read_in_the_order_they_were_declared() { + let (mut i, mut m) = loaded(&cat(&[ + &device("ZZZZ", &cat(&[&def_name("YYYY", &int(1)), &def_name("BBBB", &int(2))])), + &device("MMMM", &[]), + &method("MAKE", 0, &cat(&[&device("\\ZZZZ.TMP0", &[]), &def_name("\\AAA0", &int(0))])), + ])); + let before = walked(&i); + assert_eq!( + before, + listed(&[ + ("\\ZZZZ", Kind::Device), + ("\\ZZZZ.YYYY", Kind::Integer), + ("\\ZZZZ.BBBB", Kind::Integer), + ("\\MMMM", Kind::Device), + ("\\MAKE", Kind::Method), + ]) + ); + + // A table that defines three objects and then collides: all three go. + let refused = table( + b"SSDT", + 2, + &cat(&[&device("\\ZZZZ.GONE", &def_name("X", &int(1))), &def_name("\\AAAA", &int(1)), &def_name("\\MMMM", &int(1))]), + ); + assert_eq!(i.load_bytes(&mut m, &refused), Err(Error::Exists("\\MMMM".into()))); + assert_eq!(walked(&i), before); + assert_eq!(i.evaluate(&mut m, "\\MAKE", &[]), Ok(Value::Uninitialized)); + assert_eq!(walked(&i), before); + + let ssdt = table( + b"SSDT", + 2, + &cat(&[ + &def_name("\\ZZZZ.CCCC", &int(3)), + &def_name("\\AAAA", &int(4)), + &scope("\\ZZZZ", &def_name("AAAA", &int(5))), + &def_name("\\MMMM.KKKK", &int(6)), + ]), + ); + i.load_bytes(&mut m, &ssdt).unwrap(); + let after = walked(&i); + assert_eq!( + after, + listed(&[ + ("\\ZZZZ", Kind::Device), + ("\\ZZZZ.YYYY", Kind::Integer), + ("\\ZZZZ.BBBB", Kind::Integer), + ("\\ZZZZ.CCCC", Kind::Integer), + ("\\ZZZZ.AAAA", Kind::Integer), + ("\\MMMM", Kind::Device), + ("\\MMMM.KKKK", Kind::Integer), + ("\\MAKE", Kind::Method), + ("\\AAAA", Kind::Integer), + ]) + ); + // A walked path is one `evaluate` takes. + let values: Vec = + after.iter().filter(|(_, k)| *k == Kind::Integer).map(|(p, _)| i.evaluate(&mut m, p, &[]).unwrap()).collect(); + assert_eq!(values, [1, 2, 3, 5, 6, 4].map(Value::Integer)); +} + +/// An Alias is no second object: not of a device, whose `_INI` would run +/// twice, and not of an ancestor, below which a walk that followed it would +/// find itself again. +#[test] +fn an_alias_is_not_walked() { + let (mut i, mut m) = loaded(&cat(&[ + &device( + "DEV0", + &cat(&[&def_name("_INI", &int(7)), &device("SUB0", &alias("\\DEV0", "LOOP")), &alias("SUB0", "TWIN")]), + ), + &alias("\\DEV0", "ALI0"), + ])); + assert_eq!( + walked(&i), + listed(&[("\\DEV0", Kind::Device), ("\\DEV0._INI", Kind::Integer), ("\\DEV0.SUB0", Kind::Device)]) + ); + // The aliases are there, and name what the walk met once. + assert_eq!(i.evaluate(&mut m, "\\ALI0._INI", &[]), Ok(Value::Integer(7))); + assert_eq!(i.evaluate(&mut m, "\\DEV0.SUB0.LOOP.TWIN.LOOP._INI", &[]), Ok(Value::Integer(7))); +} + +/// §5.3.1's scopes are descended and are no device; every other type is +/// what Table 19.36 calls it. +#[test] +fn every_object_is_walked_as_what_it_is() { + let processor = cat(&[&[0x5B, 0x83], &pkg(&cat(&[&name("CPU0"), &[0x01, 0x10, 0x04, 0x00, 0x00, 0x06]]))]); + let (i, _) = loaded(&cat(&[ + &scope("\\_SB", &device("PCI0", &method("_INI", 0, &[]))), + &scope("\\_PR", &processor), + &thermal_zone("\\_TZ.TZ00", &[]), + &power_resource("PWR0", &[]), + &def_name("STR0", &string("s")), + &def_name("BUF0", &buffer(&int(4), &[])), + &def_name("PKG0", &package(&[int(1)])), + &op_region("REG0", 0x00, &int(0), &int(8)), + &field("REG0", 0x01, &[unit("FLD0", 8)]), + &cat(&[&[0x8C], &name("BUF0"), &int(0), &name("BFL0")]), + &cat(&[&[0x5B, 0x01], &name("MTX0"), &[0x00]]), + &cat(&[&[0x5B, 0x02], &name("EVT0")]), + &def_name("REF0", &int(0)), + &method("MKRF", 0, ©_object(&ref_of(&name("STR0")), &name("REF0"))), + ])); + let mut i = i; + i.evaluate(&mut Machine::default(), "\\MKRF", &[]).unwrap(); + let mut w = i.walk().unwrap(); + let mut seen = Vec::new(); + while let Some(e) = w.next() { + seen.push((w.path().to_string(), e.depth, e.kind)); + } + let at = |path: &str| { + let (_, depth, kind) = seen.iter().find(|(p, ..)| p == path).unwrap_or_else(|| panic!("{path} is not walked")); + (*depth, *kind) + }; + assert_eq!(at("\\_SB_"), (1, Kind::Scope)); + assert_eq!(at("\\_SB_.PCI0"), (2, Kind::Device)); + assert_eq!(at("\\_SB_.PCI0._INI"), (3, Kind::Method)); + assert_eq!(at("\\_PR_.CPU0"), (2, Kind::Processor)); + assert_eq!(at("\\_TZ_.TZ00"), (2, Kind::ThermalZone)); + assert_eq!(at("\\PWR0"), (1, Kind::PowerResource)); + assert_eq!(at("\\STR0"), (1, Kind::String)); + assert_eq!(at("\\BUF0"), (1, Kind::Buffer)); + assert_eq!(at("\\PKG0"), (1, Kind::Package)); + assert_eq!(at("\\REG0"), (1, Kind::OperationRegion)); + assert_eq!(at("\\FLD0"), (1, Kind::FieldUnit)); + assert_eq!(at("\\BFL0"), (1, Kind::BufferField)); + assert_eq!(at("\\MTX0"), (1, Kind::Mutex)); + assert_eq!(at("\\EVT0"), (1, Kind::Event)); + assert_eq!(at("\\REF0"), (1, Kind::Reference)); + // What a scope holds follows it, before the scope declared after it. + let order: Vec<&str> = seen.iter().map(|(p, ..)| p.as_str()).filter(|p| p.starts_with("\\_")).collect(); + assert_eq!( + order, + ["\\_GPE", "\\_PR_", "\\_PR_.CPU0", "\\_SB_", "\\_SB_.PCI0", "\\_SB_.PCI0._INI", "\\_SI_", "\\_TZ_", "\\_TZ_.TZ00", "\\_GL_", "\\_OSI", "\\_OS_", "\\_REV"] + ); +} + +/// A device in a device, `levels` deep, below the `above` that are there: +/// the devices are reached through Scopes of at most 200 names each, so a +/// table nests no deeper in terms than the interpreter goes. +fn deeper(above: usize, levels: usize) -> Vec { + let mut body = Vec::new(); + for _ in 0..levels { + body = device("D", &body); + } + let mut left = above; + while left > 0 { + let chunk = if left.is_multiple_of(200) { 200 } else { left % 200 }; + left -= chunk; + let path = vec!["D"; chunk].join("."); + body = scope(&if left == 0 { format!("\\{path}") } else { path }, &body); + } + body +} + +/// A table nests its namespace as deep as it likes, far past what a walk +/// that recursed, or one that kept a path an entry, could follow. +#[test] +fn a_namespace_nested_deep_is_walked_whole() { + const TABLES: usize = 120; + const LEVELS: usize = 100; + let mut m = Machine::default(); + let mut i = Interpreter::new(); + i.load_bytes(&mut m, &dsdt(&deeper(0, LEVELS))).unwrap(); + for t in 1..TABLES { + i.load_bytes(&mut m, &table(b"SSDT", 2, &deeper(t * LEVELS, LEVELS))).unwrap(); + } + let mut w = i.walk().unwrap(); + let mut depths = Vec::new(); + while let Some(e) = w.next() { + if e.name == *b"D___" { + assert_eq!(e.kind, Kind::Device); + assert_eq!(w.path().len(), 5 * e.depth as usize); + depths.push(e.depth as usize); + } + } + assert_eq!(depths, (1..=TABLES * LEVELS).collect::>()); +} + +/// Leaves the interpreter `room` bytes of its bound, by the buffers `\SET_` +/// stores in `\PKG0`: one an element while that is far off, then the last +/// element's, resized to the byte. `fill` is the next element and the last +/// one's bytes. +fn leave(i: &mut Interpreter, m: &mut Machine, fill: &mut (u64, usize), room: usize) { + const LAST: u64 = 63; + let mut set = |at: u64, bytes: usize| { + assert_eq!(i.evaluate(&mut *m, "\\SET_", &[Value::Integer(at), Value::Integer(bytes as u64)]), Ok(Value::Uninitialized)); + MAX_LIVE - i.usage().live + }; + let mut free = set(LAST, fill.1); + while free > room + 4096 { + assert!(fill.0 < LAST, "{free} bytes free with every element stored"); + free = set(fill.0, ((free - room) / 2).min(1 << 20)); + fill.0 += 1; + } + fill.1 = fill.1 + free - room; + assert_eq!(set(LAST, fill.1), room, "bytes of the bound left"); +} + +/// A walk takes two links a node and its deepest path from the bound, and +/// not a byte less: an interpreter with room for both is walked, and one +/// with a byte less, which is room for the links, refuses and holds +/// afterwards what it held before. +#[test] +fn a_walk_takes_its_links_and_its_deepest_path_from_the_bound() { + const TABLES: usize = 120; + const LEVELS: usize = 100; + let mut m = Machine::default(); + let mut i = Interpreter::new(); + let set = method("SET_", 2, &store(&buffer(&arg(1), &[]), &index(&name("PKG0"), &arg(0), &[0x00]))); + i.load_bytes(&mut m, &dsdt(&cat(&[&def_name("PKG0", &var_package(&int(64), &[])), &set, &deeper(0, LEVELS)]))).unwrap(); + for t in 1..TABLES { + i.load_bytes(&mut m, &table(b"SSDT", 2, &deeper(t * LEVELS, LEVELS))).unwrap(); + } + // The root is no entry, and has its links as every node has. + let nodes = i.walk().unwrap().count() + 1; + let (links, path) = (8 * nodes, 5 * TABLES * LEVELS); + let mut fill = (0, 8192); + + leave(&mut i, &mut m, &mut fill, links + path); + let held = i.usage().live; + assert_eq!(i.walk().map(|w| w.count()), Ok(nodes - 1)); + assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); + assert_eq!(i.usage().live, held, "bytes held after a walk, and before it"); + + leave(&mut i, &mut m, &mut fill, links + path - 1); + let held = i.usage().live; + assert_eq!(i.walk().map(|w| w.count()), Err(Error::Bound("more held live than one interpreter holds"))); + assert_eq!(i.evaluate(&mut m, "\\_REV", &[]), Ok(Value::Integer(2))); + assert_eq!(i.usage().live, held, "bytes held after a walk refused for its path, and before it"); +} + +/// A parent of thousands, declared against the order their names sort in. +#[test] +fn a_parent_of_thousands_is_walked_in_their_order() { + let names: Vec = (0..4000u32).rev().map(|n| format!("{:04X}", 0xA000 + n)).collect(); + let units: Vec> = names.iter().map(|n| unit(n, 8)).collect(); + let (i, _) = loaded(&cat(&[&op_region("MEM", 0x00, &int(0), &int(0x1000)), &device("WIDE", &field("\\MEM", 0x01, &units))])); + let got: Vec = walked(&i).into_iter().filter(|(_, k)| *k == Kind::FieldUnit).map(|(p, _)| p).collect(); + let want: Vec = names.iter().map(|n| format!("\\WIDE.{n}")).collect(); + assert_eq!(got, want); +}