Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions issues/qemus-interrupt-links-are-refused-by-the-aml-interpreter.md
Original file line number Diff line number Diff line change
@@ -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`).
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions userland/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions userland/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"acpiserver",
"acpiserver/aml",
"calc",
"compositor",
"compositor/desktop",
Expand Down
17 changes: 17 additions & 0 deletions userland/acpiserver/aml/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Loading
Loading