Skip to content

toyos-acpi decodes the IORT; toyos-smmu and toyos-its hold the SMMUv3's and the ITS's encodings, over toyos-phys's one address type - #798

Merged
Japabu merged 5 commits into
mainfrom
wt/toyos-arm-g1
Oct 9, 2026
Merged

Japabu merged 5 commits into
mainfrom
wt/toyos-arm-g1

Conversation

@Japabu

@Japabu Japabu commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Stage G1 of the ARM desktop, on issues/toyos-runs-on-arm64.md's stage 6: host-only, and no file under kernel/. The owner's words for the order: "No, isolation first" — the IOMMU is built before any ARM userland driver touches a device. This is what the kernel's SMMUv3 unit (G2) and its ITS (G3) are built on.

What was measured first

QEMU 11.1.1's virt under HVF with edk2-stable202408 and no operating system in the guest: the firmware was left to reach its shell, guest RAM was written out with the monitor's pmemsave, and the tables were walked host-side from the RSDP. Both QEMUs exited on quit. toyos-acpi/fixtures/qemu-11.1.1-virt/SOURCE has the commands.

gic-version=3,iommu=smmuv3 (the hypervisor's GIC) gic-version=3,its=on,iommu=smmuv3,kernel-irqchip=off
IORT revision 5, 172 bytes: SMMUv3 (node rev 4), root complex (rev 3) revision 5, 236 bytes: ITS group of one (rev 1), SMMUv3, root complex
SMMUv3 node base 0x09050000, COHACC override, GSIVs event 106, PRI 107, GERR 109, sync 108, DeviceID mapping index 0, no ID mapping the same, and one mapping: streams 0..=0xffff to the ITS, DeviceID = StreamID
root complex segment 0, RIDs 0x00..=0xff to the SMMU, StreamID = RID; nothing else mapped the same, and RIDs 0x100..=0xffff straight to the ITS, DeviceID = RID
MADT ends in a GIC MSI Frame (type 0xD) ends in a GIC ITS (type 0xF): id 0 at 0x08080000

The plan's table read the four GSIVs as "106 to 109 (event, PRI, gerror, sync)"; the node's order is event, PRI, GERR, sync and its values are 106, 107, 109, 108. No node type beyond the ITS, the root complex and the SMMUv3 appears in either mode, so the stop condition did not arise.

What changed, by decision

toyos-acpi: the IORT (src/iort.rs). iort(phys, rsdp) answers an Iort every node of which it has walked, or a named refusal. Iort::nodes() gives each node as Its { id }, RootComplex { segment }, Smmuv3(Smmuv3) or Other(type); Iort::route(segment, rid) gives Translated { smmu, stream, its: Option<ItsDevice> }, Untranslated(ItsDevice) or Unmapped.

  • The refusals when the table is opened, each by name: a table revision outside 5..=7; a node array inside the header; a node too short for its type or past the table (a node count that overruns ends there); a node revision not read against DEN 0049 (ITS 1, root complex 3..=4, SMMUv3 4..=5); an ID array over its node's own fields or past its end, or on an ITS node; a mapping range past the 32-bit ID space; a single mapping that is not its SMMUv3's own; an ITS group of other than one; an SMMUv3 whose valid DeviceID mapping index is at or past its ID array's count (OwnMapping).
  • The refusals of a route: an output reference to no node, or to one its source may not reach (root complex to SMMUv3 or ITS, SMMUv3 to ITS); an ID two mappings claim; a stream another mapping also puts out to the same SMMUv3 (SharedStream).
  • A translated stream is the requester's alone, or the route is refused. Overlap is judged on input IDs within the asked segment, so before this round two RID ranges of one root complex, two segments, or a named component mapped onto the same streams of one unit each answered Translated with the same stream, and G2 attaching a claim's domain there would hand the other requester the claim's DMA. The IORT holds every mapping that reaches a unit, so route walks every node and its mappings once more and refuses SharedStream { stream, at } where any mapping but the requester's own puts that stream out to that unit, whatever the other node's type: a single mapping (a named component's) counts its output base alone, and a mapping to an ITS or to another SMMUv3 is no sharer. A node type check does not decode has its ID array held to Table 4's header and its own length before that walk reads it, refused as Mappings otherwise. The stream is judged last, after the SMMUv3's onward reference, so a table that is malformed there still answers Output.
  • An SMMUv3's own DeviceID mapping is the one its index names (Table 13, offset 64). The index holds where Table 14's bit 4 says so, from node revision 5 (issue E.e); in a revision 4 node it holds wherever one of the four control interrupts has no GSIV, which is issue E.d's wording: "If all the SMMU control interrupts are GSIV based, this field is ignored." That entry routes no stream and its input base and length are not read ("Regardless of the flags, the OSPM must ignore the input base and length fields"). virt's node is revision 4 with all four wired, so its index is ignored and its one mapping routes streams. A revision 4 unit with no PRI interrupt has a zero PRI GSIV and so an index that holds, which is what E.e's flag was added to end: the entry it names stops routing, a claim behind it finds its: None and is refused. That is fail-closed, and it is not on virt. Where the index holds by either rule and names no entry, the table is OwnMapping: E.g calls it "an index into the array of ID mapping", and one past the array is malformed firmware, not a node with no mapping of its own.
  • Two shapes the specification allows are refused whole, by name, and neither is on virt. A single mapping in a root complex, or in an SMMUv3 beside its own: Table 6 names both node types among those the flag is valid in, and has it put every input ID out as one. And an ITS group of more than one ITS under a table of revision 5 or 6, which was legal until issue E.g fixed the count at 1. Nothing in the gate routes either; each is SingleMapping or ItsGroup and not a silent skip.
  • An output reference is followed by the route that needs it, and no longer by the open. Resolving one costs a walk of the nodes, and the crate has no allocation to index them in, so judging every reference at open cost nodes times mappings. Now iort reads each node and each mapping once, and a route walks the nodes once to find its root complex and once for each of the two references it may follow. A reference nothing routes through is not judged. opening_a_table_and_routing_a_requester_each_read_it_a_bounded_number_of_times counts the bytes read of a 417,236-byte table of 500 nodes and 19,960 mappings: 824,594 to open and 14,812 to route, held to three times its length and an eighth of it. With each reference followed at open again the open reads 120,744,274. Both of the review's named ways (resolve once at open, index the nodes) need storage for node offsets, which here would be an array with a cap on the nodes of a table; I took neither for that reason.
  • Other node types are answered, not refused. A named component or a PMCG is no reason to refuse the SMMU. G2 rules on them, and is expected to refuse a reserved memory range (type 6), which says firmware left DMA running.
  • Overlap is judged per query. A second mapping holding the asked ID is Overlap whether it sits in the same node, a second root complex of the segment, or the SMMU's own array.

toyos-acpi/src/madt.rs is main's. git diff origin/main -- toyos-acpi/src/madt.rs prints nothing. msi_controllers, MsiController, MsiFrame, SpiRange and the Structure split are deleted with their three tests. The MADT's ITS entry is MadtEntry::Its on main already; two tests here read it out of virt's two MADTs with madt_entries as it stands. The stage that owns the kernel's exhaustive matches adds a MadtEntry variant if it needs the MSI frame at all.

toyos-phys, new: Phys<const ALIGN> has one home. Both crates declared it. Neither may depend on the other (an ITS is no part of an SMMU), toyos-bootmap may change only by a re-export, and toyos-abi is outside the fence, so the home is a third crate of one type: an address aligned to 1 << ALIGN and below 2^48. It gains Phys::of(word), the address a word holds in its bits [47:ALIGN], which toyos-smmu reads a table descriptor back with.

toyos-smmu. One configuration and no other: a stream aborts, or is translated by stage 1 alone through one context descriptor under NS-EL1, with 4 KiB-granule VMSAv8-64 tables over a 48-bit input.

  • The unit's output size bounds everything it is given. IDR5.OAS is private to Unit, and stream_table, command_queue and event_queue answer None for a table or queue any byte of which is at or past 1 << OAS; Ste::stage1, Cd::new, table::next and table::plan answer None for an S1ContextPtr, a TTB0, a next table or a leaf's output past it. The check takes the unit each time, so two units of different sizes cannot lend each other an address.
  • No bypass can be spelled. Ste and Cd hold their words privately; Ste::ABORT and Ste::stage1 are the only ways to an Ste, and words() reads one.
  • The stage 1 descriptors are toyos-bootmap's. table::plan builds a leaf from toyos_bootmap::aarch64::block and page, next from table, and MAIR0 is the low word of its MAIR; toyos-bootmap changes by one word, pub on PXN, which changes no byte anything builds. What toyos-bootmap does not declare stays here: AP[1], AP[2], nG, the two bits a descriptor is read back by, and the level index.
  • The queues are two types the unit makes. Unit::command_queue answers its base register and a Commands; Unit::event_queue a base and an Events. Events::after(cons, prod) is what SMMU_EVENTQ_CONS is written with: the next position and OVACKFLG equal to the OVFLG that prod was read with, which is §7.4's acknowledgement and can never forge an overflow; Events::overflowed(prod, cons) says records were lost. Commands::after cannot be handed an event index.
  • Coherence is carried once. probe(idr0, idr1, idr5, coherent_override) takes the IORT's Smmuv3::coherent_override, which stands for IDR0.COHACC where it is set (DEN 0049 Table 14), and refuses a unit neither calls coherent as Lacks::CoherentAccess: CR1_WRITE_BACK and every cacheability written here assume it.
  • Deleted, as the gate never issues or reads them: Signal::Sev (valid only under IDR0.SEV, which Unit does not carry), Command::ForgetContext (CMD_CFGI_CD: with one context descriptor behind each entry, CMD_CFGI_STE forgets both), the public output_bits and coherent. Signal::None and Signal::Irq both stay: a CMD_SYNC is waited on by the sync GSIV the IORT gives, or by reading SMMU_CMDQ_CONS where that interrupt is not yet routed, and G2 deletes the one it does not issue.

toyos-its. Physical LPIs through flat tables. The device table's and the ITT's entries are IMPLEMENTATION DEFINED (IHI 0069 §5.2.3, §5.2.4) and written only by the ITS, so no entry is encoded.

  • An event, a collection and an LPI are types. EventBits::event(id) answers an Event inside its device's table; MAPTI, INV and DISCARD take one. Its::collections(in_table) answers how many collections there are — the collection table's entries, with GITS_TYPER.HCC where there is no table or CCT counts both, and no more than an ICID of 16 bits or of CIDbits under CIL numbers (§5.2.2, §5.3.1) — and Collections::collection(id) a Collection; MAPC and MAPTI take one. lpi::Layout::new(gicd_typer, bits) is None unless the distributor takes LPIs (LPIS) and has that many INTID bits (IDbits), and counts the LPIs by num_LPIs; Layout::space(propbaser) is the Space of a redistributor that read GICR_PROPBASER back with the IDbits written, and only a Space makes an Lpi. The 24-bit cap of round 1, a bound of mine, is gone.
  • One form of queue emptiness in both crates: CommandQueue::is_empty, is_full and after take GITS_CWRITER and GITS_CREADR as read and mask them to Offset, so Stalled or a reserved bit beside an offset is the same offset.
  • A table register is a Backing: what GITS_BASER<n> backs and its page size, with entries(pages), pages(entries) and baser(at, pages), so the device table's and the collection table's sizes are not arithmetic in the driver.
  • Deleted: Its::collections_held as a public field, CTLR_QUIESCENT, Lpi::pending_bit (the pending table is zeroed and PTZ says so; nothing writes a bit of it), and GICR_CTLR's and GICR_TYPER's offsets, which kernel/src/arch/aarch64/irqchip.rs declares and G3 edits.

Siblings that stay, recorded. issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md: the stage 1 bits and level index beside kernel/src/arch/aarch64/paging.rs, and GICR_CTLR.EnableLPIs, GICR_TYPER.PLPIS and Processor_Number beside kernel/gicv3 and irqchip.rs. Unifying either means editing kernel/; the owner is issues/toyos-runs-on-arm64.md's stage 6 (its SMMUv3 unit and its ITS), and the exit is two rg commands that print nothing.

Left out on purpose, since nothing in the plan's gate needs it: the two-level stream table (a linear one is always supported, and a StreamID past it aborts with C_BAD_STREAMID), CMD_TLBI_NH_VA (an unmap invalidates its domain's ASID), stalls, substreams, ATS, PRI, stage 2, the unit's own MSIs, and the ITS's MAPI, MOVI, INVALL and every vPE command.

What G2 and G3 build on each type

Type Who For
toyos_acpi::Iort::nodes, Smmuv3 { base, coherent_override, event, gerror, sync } G2 finds the unit, its register window and its three wired GSIVs (Option<NonZeroU32>: a unit that signals by MSI has none, and is refused there)
Iort::route → Route::Translated { stream, .. } G2 the stream table index a claimed function's domain is attached at, which no other requester reaches
Route::Untranslated, Route::Unmapped, IortRefused::SharedStream G2 the refusal of a claim on a function the IORT routes past or around the SMMU (bus 1 and up on virt), or under a stream another requester shares
Route::Translated { its: Some(ItsDevice { its, device }) } G3 the DeviceID MAPD and MAPTI are issued under; its: None (every function in the hypervisor-GIC mode) is the claim's refusal
toyos_acpi::madt_entries → MadtEntry::Its { id, base } (main's) G3 the ITS's base, matched to the IORT's by id; a MADT with none is the refusal
toyos_smmu::unit::probe(idr0, idr1, idr5, coherent_override) → Unit / Lacks G2 what the unit can do, refused by name; Unit is then the only way to an Asid, a stream table or queue base, a translating Ste, a Cd and a table plan
unit::* offsets and values, active_errors, command_error G2 bring-up (GBPA abort, CR1, CR2, the bases, CR0), GERROR service
config::Ste::ABORT, Ste::stage1(Phys<6>, &Unit) G2 every stream entry at boot; a claimed stream's entry
config::Cd::new(Phys<12>, Asid, &Unit) G2 one per claim slot
Unit::command_queue → Commands; queue::Command, Signal G2 the command queue and CMD_SYNC
Unit::event_queue → Events; queue::event → Event { stream, code, attempt } G2 event-queue records into pcidev::note_fault, each consumed with its overflow acknowledged
table::plan(at, Leaf::Memory(Phys<21>, Access), &Unit), next, entry G2 a grant's 2 MiB leaves in a domain's tables
table::plan(at, Leaf::Doorbell(Phys<12>), &Unit) G3 the one 4 KiB leaf over GITS_TRANSLATER's page in each claimed domain
toyos_its::probe → Its; table → Backing; CommandQueue G3 ITS bring-up: the device and collection tables, the command queue
Its::events → EventBits → Event; Its::itt_bytes; Its::collections → Collection; Its::target → Target G3 a claim's ITT and its events; a CPU's collection and its redistributor as this ITS names it
command::Command G3 MAPC per CPU, MAPD/MAPTI/INV per claim, DISCARD and unmap on release, SYNC
lpi::Layout → Space → Lpi; configuration, pendbaser G3 the LPI configuration table and each CPU's pending table
TRANSLATION_FRAME, GITS_TRANSLATER G3 the address a claim's MSI-X entry is written with, inside its domain

The checks of this high-risk code

It is an isolation boundary and a device's and firmware's input.

Independent oracles. The specifications, fetched from Arm's documentation service this round: IHI 0070 H.a (SMMUv3), IHI 0069 H.b (GIC), DEN 0049 E.b, E.c, E.d, E.e, E.f and E.g (IORT), DDI 0487 M.d (Tables D8-50, D8-52 and D8-63, for the descriptor bits). ACPI 6.5 is not cited by anything left in the diff. And a differential implementation for the decoder: QEMU's own table generator, whose bytes are the fixtures. No other kernel's source was read.

What each issue of DEN 0049 states, read from the Revision row of each node's table:

Issue Table SMMUv3 node Root complex node ITS node
E.b 3 4 3 1
E.c (deprecated by E.d) 4 4 4 1
E.d 5 4 4 1
E.e 6 5 4 1
E.f 6 5 4 1
E.g 7 5 4 1

The decoder takes tables 5..=7, SMMUv3 nodes 4..=5, root complex nodes 3..=4 (QEMU publishes a revision 3 node under a revision 5 table) and ITS node 1. Round 1's body said no older issue could be read; that was false, and all six are read here.

How each position was confirmed. The PDFs are read as text extracted with pypdf; no renderer is on the machine. A field heading and a register-summary row are text and unambiguous. A figure comes out as labels, so its positions are read from each label's x coordinate beside the bit ruler's.

What Where in the specification How it was read
SMMU register offsets, page 0: IDR0 0x0, IDR1 0x4, IDR5 0x14, CR0 0x20, CR0ACK 0x24, CR1 0x28, CR2 0x2C, GBPA 0x44, IRQ_CTRL 0x50, IRQ_CTRLACK 0x54, GERROR 0x60, GERRORN 0x64, STRTAB_BASE 0x80, STRTAB_BASE_CFG 0x88, CMDQ_BASE 0x90, CMDQ_PROD 0x98, CMDQ_CONS 0x9C, EVENTQ_BASE 0xA0 IHI 0070 §6.2.1, "Registers in Page 0" (the table has no number) each row of the table ("0x0020 SMMU_CR0 32-bit, RW"), and a second place: each register's own "Accessible at offset 0x… from SMMUv3_PAGE_0" in §6.3.1, .2, .6, .9, .10, .11, .12, .15, .17, .18, .19, .20, .24, .25, .26, .27, .28, .29
EVENTQ_PROD 0x100A8, EVENTQ_CONS 0x100AC §6.2.2, "Registers in Page 1": "Offsets are relative to the base of Page 1, 0x10000" the two rows, and "Accessible at offset 0x00A8 / 0x00AC from SMMUv3_PAGE_1" in §6.3.130 and §6.3.131
OVFLG [31], OVACKFLG [31]; IDR0.COHACC [4]; IDR5.OAS [2:0] §6.3.130, §6.3.131, §6.3.1, §6.3.6 field headings ("OVFLG, bit [31]"); §7.4's prose states the acknowledgement
ITS control frame: GITS_CTLR 0x0, GITS_TYPER 0x8, GITS_CBASER 0x80, GITS_CWRITER 0x88, GITS_CREADR 0x90, GITS_BASER<n> 0x100..0x138 IHI 0069 §12.18, Table 12-33 each row, and each register's "GIC ITS control 0x… " line in §12.19.4, .13, .2, .5, .3 and .1 ("0x0100 + (8 * n)")
GITS_TRANSLATER 0x40 of the frame at ITS_base + 0x010000 §12.18's prose and Table 12-34 the row, and "GIC ITS translation 0x0040" in §12.19.12
GICR_PROPBASER 0x70, GICR_PENDBASER 0x78 §12.10, Table 12-27 the rows, and "GIC Redistributor RD_base 0x0070 / 0x0078" in §12.11.33 and §12.11.32
GITS_TYPER: CCT [2], HCC [31:24], CIDbits [35:32], CIL [36]; GICD_TYPER: num_LPIs [15:11], LPIS [17], IDbits [23:19]; GICR_PROPBASER.IDbits [4:0]; GITS_CREADR/CWRITER.Offset [19:5], Stalled [0] §12.19.13, §12.9.38, §12.11.33, §12.19.3, §12.19.5 field headings
DISCARD: DW0 DeviceID [63:32], RES0 [31:8], 0x0F [7:0]; DW1 RES0 [63:32], EventID [31:0] §5.3.4, Figure 5-7 (PDF page 106) label coordinates: the ruler has "63" at x=159 and "32 31" at x=328; on DW0's row DeviceID is at x=231, RES0 at 396, 0x0F at 488; on DW1's row RES0 is at x=237. pypdf reports no position for the EventID label, so its text matrix was read from the page's content stream: x=414.6 on DW1's row (y=502.5), right of the 32/31 mark. The prose gives the arguments as "DISCARD DeviceID, EventID"
INV: the same layout under 0x0C §5.3.6, Figure 5-9 (PDF page 108) the same reading: DeviceID x=231, RES0 396, 0x0C 488 on DW0; RES0 x=237 and, from the content stream, EventID x=414.6 on DW1's row (y=132.8)
DEN 0049: DeviceID mapping index at 64; flags bit 4 E.g Table 13, Table 14 table rows ("4 64", "1 4"); E.d's and E.b's rows for the revision 4 wording
The descriptor: valid [0], table or page [1], AttrIndx [4:2], AP[1] [6], AP[2] [7], SH [9:8], AF [10], nG [11], PXN [53], UXN [54]; AP[2:1] 01 read-write at both levels and 11 read-only at both DDI 0487 M.d Table D8-52, Table D8-63 table rows ("[11] … Not global (nG)")

Each has a test that states the layout: the_registers_sit_where_the_register_overview_has_them (toyos-smmu), the_its_registers_sit_where_tables_12_33_and_12_34_have_them, the_redistributors_lpi_registers_sit_where_table_12_27_has_them and inv_and_discard_hold_a_device_and_an_event_and_nothing_else (toyos-its). Every section number the three crates cite was looked up against its heading. The encoding tests write the specification's positions as (msb, lsb, value) triples and compare the whole value: they hold a constant against drift, and the oracle is the reading above until G2 and G3 put the encodings to QEMU's units.

Negative controls.

  • Red first, at the reviewed head. The two IORT tests and the output-size test were added to 286536fb8 with nothing else and run there: toyos-acpi 2 failed of 29 (Range { at: 72, base: 4294967295, last: 4294967295 } where the table must open), toyos-smmu 1 failed of 23 (Some(..) where None is owed), exits 101 and 101. The patch and the log are in the comment below; the step-log directory's copies went when that directory was deleted. The same three tests are green at this head.
  • Mutations, round 3. Ten new mutations of the two refusals this round adds, each a checked patch applied to 6193b7376, built, run and reverted: ten red (r3-mutations.log, EXIT=0). The patches, results and the tests each turned red are in the comment below. iort-own-past-accepted and iort-own-past-by-one drop the OwnMapping check or make it >; iort-shared-unchecked drops the call; iort-shared-any-unit counts a mapping to any node; iort-shared-counts-itself and iort-shared-by-index-alone count the requester's own mapping, or skip index by.1 of every node; iort-shared-single-spans and iort-shared-span-exclusive read a single mapping's span and drop the last ID; iort-shared-array-floor and iort-shared-array-end drop either half of the placement check of an undecoded node's ID array.
  • Mutations, round 2. 230 named mutations, each a checked patch, built, run and reverted: 230 red at f8022b7e2 (39 toyos-acpi; 108 under toyos-smmu's tests, 10 of them mutating toyos-bootmap/src/aarch64.rs; 3 toyos-phys; 80 toyos-its). Every file a round 1 mutation lived in changed, so each was written again against the new text, and the four madt-* went with their code. The patches and results are in five comments below. None stayed green. They were not run again at this head.
  • Never a panic. no_corruption_of_a_published_iort_panics_or_hangs_the_decoder puts seven values in every byte of both captured IORTs and declares every shorter length, then asks for 512 requesters each time. probe, event, the queues' arithmetic and Its::collections are total over their inputs and are fed extreme register values.

Gates, at 6193b73

Each is the command's own exit code, run in sequence from the committed tree by one script; git status --porcelain --ignore-submodules=none printed nothing before the first and after the last. The logs are in the brief's step-log directory under the names given. 6193b7376 is bf56c79ef (this round's change) with origin/main at 558283168 merged in; the merge was clean and git diff origin/main HEAD -- toyos-acpi/src/madt.rs prints nothing.

Check Command Exit Log
The four crates' suites cargo test -p toyos-acpi -p toyos-smmu -p toyos-its -p toyos-phys 0 r3-suites.log: toyos-acpi 104 tests (31 of them in tests/iort.rs), toyos-smmu 27, toyos-its 21, toyos-phys 2
This round's mutations each patch in the comment below: git apply --check, apply, cargo test -p toyos-acpi --no-run, cargo test -p toyos-acpi, git apply -R 0 r3-mutations.log: 10 mutations, 10 red
Clippy cargo run -- --clippy 0 r3-clippy.log: "24 invocations clean"
The source gates cargo test --lib 0 r3-buildsystem.log: 340 passed, 15 ignored
Host cargo run -- --ci host 0 r3-ci-host.log: "Host: 78 step(s), all green", the iort tests, both encodings suites and phys in it; its 114 FAILED lines all fall inside the harness's own [ci] control blocks, 50 of which report "verdict(s) reached"
The image cargo run -- --build-only 0 r3-build-only.log

The host's load when each started, from uptime: 38, 39, 42, 36, 37 and 25 (one-minute averages; the machine is shared). A first run of the same script was stopped by me during --ci host when the directory its logs were written to was deleted by something outside this branch's work; every result above is from the second, complete run.

No guest test is added, changed or reached. git diff origin/main -- toyos-acpi/src/madt.rs is empty, so the MADT walk the kernel boots through is main's. What the kernel and the loader link that differs from main: toyos-acpi gains a module nothing in kernel/ calls, and toyos-bootmap's PXN gains pub. Neither is a line a boot runs. --metal was not run in any form.

What I am unsure of

  • No SMMU or ITS register was read. G1 boots no ToyOS, so probe is judged on crafted registers only. What QEMU's units report (IDR0.STALL_MODEL, IDR0.COHACC, IDR1.SIDSIZE, IDR5.OAS, GITS_TYPER.PTA, HCC and CIL, the GITS_BASER types, whether GICR_PROPBASER.IDbits reads back as written) is G2's and G3's first measurement.
  • Not measured, as the plan says: that QEMU translates the doorbell write through the function's stage 1, which is what the 4 KiB leaf exists for.
  • An output reference nothing routes through is no longer judged. That is the price of the linear open. Every reference a route follows is.
  • The revision 4 rule reads all four GSIVs, PRI's among them, as E.d's "all the SMMU control interrupts" says. A reading that leaves PRI out would route streams this one refuses to route.
  • Its::collections reads §5.2.2 and CCT together. §5.3.1 gives the total as HCC plus the table's entries, and GITS_TYPER.CCT says the table's alone where it is clear; I took the smaller where they differ.
  • The EventID label of Figures 5-7 and 5-9 has its position from the page's content stream and not from pypdf's text visitor, which reports none for it.
  • A revision 4 SMMUv3 with an unwired control interrupt and no ID mapping now refuses the whole table as OwnMapping { index: 0, count: 0 }, where round 2 opened it: by E.d's wording its index holds and names nothing. A unit whose PRI GSIV is zero only because it has no PRI would be one; it is not on virt. The node-revision test's SMMUv3 fixture was one, and is given four GSIVs this round so that it tests revisions alone.
  • Every node type counts as a sharer of a stream, a reserved memory range (type 6) among them: a stream an RMR node also maps is refused to a claim. That is fail-closed and is the review's rule as written; whether G2 wants an RMR stream reported otherwise is G2's ruling.
  • The 230 round-2 mutations were not run again at 6193b7376. This round changes iort.rs's route, hits and check, which some of the 39 toyos-acpi ones touch.
  • toyos-its beside kernel/gicv3's toyos-gicv3, and the stage 1 bits beside the kernel's: recorded in the issue named above, for the stages that touch those files.

Fence

toyos-acpi/src/iort.rs, src/lib.rs and tests/iort.rs with the six fixtures; toyos-smmu, toyos-its and toyos-phys; root Cargo.toml's members and Cargo.lock; toyos-bootmap/src/aarch64.rs by one pub; six COMMITTED_FILES rows in src/licence.rs, because src/sourcegate.rs reds on a committed binary without one; one issue file. NOTICE is unchanged: the fixtures are this project's capture of firmware output, as the q35 tables are.

Size

git diff --shortstat origin/main...HEAD: 30 files changed, 3852 insertions(+), 2 deletions(-). Production Rust +1845 −2 (the IORT decoder 453, toyos-acpi/src/lib.rs 4, toyos-smmu 793, toyos-its 521, toyos-phys 33, six licence rows 40, one word in toyos-bootmap); tests +1856; manifests, lockfile, SOURCE and the issue +151; six fixtures, 1396 bytes.

Against round 1, production is +141: the MADT's controllers went (−83) and the two Phys became one, while each of the review's findings that asked for a type or a check added lines — the own-mapping index (toyos-acpi +33), the output-size checks and the two queue types (toyos-smmu +72), and Event, Collection, Layout/Space and Backing (toyos-its +90). None has a consumer before G2 and G3. Round 3 adds 37 lines of production, all in iort.rs, for the two refusals.

All of it is new decoding and encoding with no predecessor to delete; kernel/src/arch/aarch64/iommu_unit.rs's stub goes in G2.

🤖 Generated with Claude Code

https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C

…u and toyos-its hold the SMMUv3's and the ITS's encodings

Stage G1 of the ARM desktop: host-only, no kernel file. The kernel's SMMUv3
unit (G2) and its ITS (G3) are built on these.

toyos-acpi: `iort` answers an IORT every node of which it walked, with the
SMMUv3 node's base and four GSIVs, the ITS node's id, and `route(segment,
rid)`: through which SMMUv3 under which StreamID and on to which ITS under
which DeviceID, straight to an ITS past every SMMU, or mapped nowhere. The
refusals are by name: a table or node revision not read against DEN 0049, a
node the table cannot hold, an ID array over its node's fields or past its
end, a range past the ID space, an output reference to no node or to one its
source may not reach, a root complex single mapping, an ITS group of other
than one, and an ID two mappings claim. `msi_controllers` walks the MADT for
its ITS and GIC MSI Frame structures; the frame is not a `MadtEntry` variant
because the kernel matches that enum exhaustively in two files this stage does
not touch.

The fixtures are QEMU 11.1.1 `virt`'s RSDP, XSDT, MADT and IORT in both GIC
modes, captured with the firmware alone in the guest (`SOURCE` beside them).

toyos-smmu: the identification registers judged (`probe`), the stream table
entry that aborts and the one that translates by stage 1 through one context
descriptor, the context descriptor, the register values, the queue index
arithmetic, the commands, the event records, and the stage 1 table plan with
its 2 MiB memory block and its one 4 KiB doorbell page. No bypass is spelled.

toyos-its: the type register judged, the table and command queue registers,
the commands, and the LPI configuration and pending tables with the
redistributor registers that name them. The device and translation table
entries are IMPLEMENTATION DEFINED and the ITS's own, so only their sizes are
here.

Every encoding is tested against the specification's field positions (IHI
0070 H.a, IHI 0069 H.b, DDI 0487 M.d, ACPI 6.5), written in the tests as
`(msb, lsb, value)` triples and compared whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations of toyos-acpi at 286536f: each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p toyos-acpi --no-run), run (cargo test -p toyos-acpi) and reverted in the same script, the tree clean before and after each. 33 mutations, 33 red.

iort-table-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-array-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-past-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-too-short: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-its-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-array-over-fields: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-rev4-fields: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-array-past-node: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-its-with-array: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-range-wraps: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-smmu-to-smmu: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-output-any-kind: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-output-mid-node: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-single: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-single-routes: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-single-range-judged: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-its-group: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-overlap-root: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-overlap-smmu: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-span-exclusive: build EXIT=0 test EXIT=101 RED (4 tests failed)
iort-output-not-offset: build EXIT=0 test EXIT=101 RED (3 tests failed)
iort-any-segment: build EXIT=0 test EXIT=101 RED (3 tests failed)
iort-gsiv-order: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-cohacc-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-segment-offset: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-smmu-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-untranslated-id: build EXIT=0 test EXIT=101 RED (1 tests failed)
madt-frame-select: build EXIT=0 test EXIT=101 RED (1 tests failed)
madt-frame-length: build EXIT=0 test EXIT=101 RED (1 tests failed)
madt-frame-spi-fields: build EXIT=0 test EXIT=101 RED (2 tests failed)
madt-halt-swallowed: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# iort-table-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -177,7 +177,7 @@
     let table = find_table(phys, rsdp_addr, b"IORT", IORT_NEEDED)?;
     let short = TableError::Length { declared: table.len() as u32, needed: IORT_NEEDED };
     let revision = table.byte(SDT_REVISION).ok_or(short)?;
-    if !REVISIONS.contains(&revision) {
+    if false {
         return Err(IortRefused::Revision(revision));
     }
     let nodes = table.u32_at(NODE_COUNT).ok_or(short)?;
# iort-node-array-floor
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -182,7 +182,7 @@
     }
     let nodes = table.u32_at(NODE_COUNT).ok_or(short)?;
     let offset = table.u32_at(NODE_ARRAY).ok_or(short)?;
-    if (offset as usize) < IORT_NEEDED {
+    if false {
         return Err(IortRefused::NodeArray { offset });
     }
     let iort = Iort { table, nodes, array: offset as usize };
# iort-node-past-table
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -271,7 +271,7 @@
             SMMUV3 => SMMU_LEN,
             _ => NODE_HEADER,
         };
-        if len < floor || at + len > t.len() {
+        if len < floor {
             return Err(IortRefused::Node { at, declared: len });
         }
         Ok(Raw { at, kind, len, revision, mappings, array })
# iort-node-too-short
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -271,7 +271,7 @@
             SMMUV3 => SMMU_LEN,
             _ => NODE_HEADER,
         };
-        if len < floor || at + len > t.len() {
+        if at + len > t.len() {
             return Err(IortRefused::Node { at, declared: len });
         }
         Ok(Raw { at, kind, len, revision, mappings, array })
# iort-node-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -327,7 +327,7 @@
                 Node::RootComplex { segment: t.u32_at(at + RC_SEGMENT).ok_or(short)? }
             }
             SMMUV3 => {
-                revised(4..=5)?;
+                revised(0..=255)?;
                 let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * i).map(NonZeroU32::new).ok_or(short);
                 Node::Smmuv3(Smmuv3 {
                     base: t.u64_at(at + SMMU_BASE).ok_or(short)?,
# iort-root-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -323,7 +323,7 @@
                 Node::Its { id: t.u32_at(at + NODE_HEADER + 4).ok_or(short)? }
             }
             ROOT_COMPLEX => {
-                revised(3..=4)?;
+                revised(0..=255)?;
                 Node::RootComplex { segment: t.u32_at(at + RC_SEGMENT).ok_or(short)? }
             }
             SMMUV3 => {
# iort-its-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -315,7 +315,7 @@
         };
         Ok(match kind {
             ITS => {
-                revised(1..=1)?;
+                revised(0..=255)?;
                 let count = t.u32_at(at + NODE_HEADER).ok_or(short)?;
                 if count != 1 {
                     return Err(IortRefused::ItsGroup { at, count });
# iort-array-over-fields
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -355,7 +355,7 @@
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-        let misplaced = (offset as usize) < fields || end > raw.len as u64;
+        let misplaced = end > raw.len as u64;
         if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
# iort-root-rev4-fields
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -350,7 +350,7 @@
             (Node::Other(_), _) => return Ok(()),
             (Node::Its { .. }, _) => ITS_LEN,
             (Node::RootComplex { .. }, 3) => 36,
-            (Node::RootComplex { .. }, _) => 40,
+            (Node::RootComplex { .. }, _) => 36,
             (Node::Smmuv3(_), _) => SMMU_LEN,
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
# iort-array-past-node
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -355,7 +355,7 @@
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-        let misplaced = (offset as usize) < fields || end > raw.len as u64;
+        let misplaced = (offset as usize) < fields;
         if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
# iort-its-with-array
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -356,7 +356,7 @@
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
         let misplaced = (offset as usize) < fields || end > raw.len as u64;
-        if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
+        if count != 0 && (misplaced) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
         for index in 0..count {
# iort-range-wraps
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -367,7 +367,7 @@
             }
             if !mapping.single {
                 for base in [mapping.input, mapping.output] {
-                    if base.checked_add(mapping.span).is_none() {
+                    if false {
                         return Err(IortRefused::Range { at, base, last: mapping.span });
                     }
                 }
# iort-smmu-to-smmu
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -374,7 +374,7 @@
             }
             match self.output(raw, mapping)?.1 {
                 Node::Its { .. } => {}
-                Node::Smmuv3(_) if from_root => {}
+                Node::Smmuv3(_) => {}
                 _ => return Err(IortRefused::Output { at, reference: mapping.reference }),
             }
         }
# iort-output-any-kind
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -375,7 +375,7 @@
             match self.output(raw, mapping)?.1 {
                 Node::Its { .. } => {}
                 Node::Smmuv3(_) if from_root => {}
-                _ => return Err(IortRefused::Output { at, reference: mapping.reference }),
+                _ => {}
             }
         }
         Ok(())
# iort-output-mid-node
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -295,7 +295,7 @@
         let refused = IortRefused::Output { at: from.at, reference: mapping.reference };
         for raw in self.raw_nodes() {
             let raw = raw?;
-            if raw.at == mapping.reference as usize {
+            if raw.at <= mapping.reference as usize && raw.at + raw.len > mapping.reference as usize {
                 return Ok((raw, self.decode(raw)?));
             }
         }
# iort-root-single
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -362,7 +362,7 @@
         for index in 0..count {
             let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
             let from_root = matches!(node, Node::RootComplex { .. });
-            if mapping.single && from_root {
+            if false {
                 return Err(IortRefused::SingleMapping { at });
             }
             if !mapping.single {
# iort-single-routes
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -160,7 +160,7 @@
 impl Mapping {
     fn maps(&self, id: u32) -> Option<u32> {
         // `output + span` was checked not to wrap when the table was opened.
-        (!self.single && id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
+        (id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
     }
 }
 
# iort-single-range-judged
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -365,7 +365,7 @@
             if mapping.single && from_root {
                 return Err(IortRefused::SingleMapping { at });
             }
-            if !mapping.single {
+            if true {
                 for base in [mapping.input, mapping.output] {
                     if base.checked_add(mapping.span).is_none() {
                         return Err(IortRefused::Range { at, base, last: mapping.span });
# iort-its-group
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -317,7 +317,7 @@
             ITS => {
                 revised(1..=1)?;
                 let count = t.u32_at(at + NODE_HEADER).ok_or(short)?;
-                if count != 1 {
+                if false {
                     return Err(IortRefused::ItsGroup { at, count });
                 }
                 Node::Its { id: t.u32_at(at + NODE_HEADER + 4).ok_or(short)? }
# iort-overlap-root
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -224,7 +224,7 @@
                     }
                     _ => return Err(IortRefused::Output { at: raw.at, reference: mapping.reference }),
                 };
-                if route.replace(hop).is_some() {
+                if route.replace(hop).is_some() && false {
                     return Err(IortRefused::Overlap { id: u32::from(rid) });
                 }
             }
# iort-overlap-smmu
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -216,7 +216,7 @@
                             let (_, Node::Its { id: to }) = self.output(smmu_raw, onward)? else {
                                 return Err(IortRefused::Output { at: smmu_raw.at, reference: onward.reference });
                             };
-                            if its.replace(ItsDevice { its: to, device }).is_some() {
+                            if its.replace(ItsDevice { its: to, device }).is_some() && false {
                                 return Err(IortRefused::Overlap { id });
                             }
                         }
# iort-span-exclusive
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -160,7 +160,7 @@
 impl Mapping {
     fn maps(&self, id: u32) -> Option<u32> {
         // `output + span` was checked not to wrap when the table was opened.
-        (!self.single && id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
+        (!self.single && id >= self.input && id - self.input < self.span).then(|| self.output + (id - self.input))
     }
 }
 
# iort-output-not-offset
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -160,7 +160,7 @@
 impl Mapping {
     fn maps(&self, id: u32) -> Option<u32> {
         // `output + span` was checked not to wrap when the table was opened.
-        (!self.single && id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
+        (!self.single && id >= self.input && id - self.input <= self.span).then(|| self.output.wrapping_add(id))
     }
 }
 
# iort-any-segment
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -204,7 +204,7 @@
         let mut route = None;
         for raw in self.raw_nodes() {
             let raw = raw?;
-            if self.decode(raw)? != (Node::RootComplex { segment }) {
+            if !matches!(self.decode(raw)?, Node::RootComplex { .. }) {
                 continue;
             }
             for (mapping, id) in self.hits(raw, u32::from(rid)) {
# iort-gsiv-order
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -328,7 +328,7 @@
             }
             SMMUV3 => {
                 revised(4..=5)?;
-                let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * i).map(NonZeroU32::new).ok_or(short);
+                let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * (3 - i)).map(NonZeroU32::new).ok_or(short);
                 Node::Smmuv3(Smmuv3 {
                     base: t.u64_at(at + SMMU_BASE).ok_or(short)?,
                     coherent: t.u32_at(at + SMMU_FLAGS).ok_or(short)? & SMMU_COHACC != 0,
# iort-cohacc-bit
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -52,7 +52,7 @@
 const SMMU_GSIVS: usize = 44;
 const SMMU_LEN: usize = 68;
 /// Table 14, bit 0: `COHACC override`.
-const SMMU_COHACC: u32 = 1;
+const SMMU_COHACC: u32 = 2;
 
 /// Why an IORT cannot be used. `at` is a node's offset in the table.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# iort-segment-offset
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -43,7 +43,7 @@
 /// Table 18: memory access properties (16..24), ATS attribute (24..28), PCI
 /// segment number (28..32), then the address size limit and, from node
 /// revision 4, the PASID capabilities and flags.
-const RC_SEGMENT: usize = 28;
+const RC_SEGMENT: usize = 24;
 /// Table 13: base address (16..24), flags (24..28), a reserved word, VATOS
 /// (32..40), model (40..44), the event, PRI, GERR and sync GSIVs (44..60),
 /// proximity domain (60..64), DeviceID mapping index (64..68).
# iort-smmu-floor
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -50,7 +50,7 @@
 const SMMU_BASE: usize = 16;
 const SMMU_FLAGS: usize = 24;
 const SMMU_GSIVS: usize = 44;
-const SMMU_LEN: usize = 68;
+const SMMU_LEN: usize = 64;
 /// Table 14, bit 0: `COHACC override`.
 const SMMU_COHACC: u32 = 1;
 
# iort-untranslated-id
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -209,7 +209,7 @@
             }
             for (mapping, id) in self.hits(raw, u32::from(rid)) {
                 let hop = match self.output(raw, mapping)? {
-                    (_, Node::Its { id: its }) => Route::Untranslated(ItsDevice { its, device: id }),
+                    (_, Node::Its { id: its }) => Route::Untranslated(ItsDevice { its, device: u32::from(rid) }),
                     (smmu_raw, Node::Smmuv3(smmu)) => {
                         let mut its = None;
                         for (onward, device) in self.hits(smmu_raw, id) {
# madt-frame-select
--- a/toyos-acpi/src/madt.rs
+++ b/toyos-acpi/src/madt.rs
@@ -183,7 +183,7 @@
         (entry_type == 0xD && declared >= 24).then(|| MsiFrame {
             id: u32le(phys, base + 4),
             base: u64le(phys, base + 8),
-            spis: (u32le(phys, base + 16) & 1 != 0)
+            spis: (u32le(phys, base + 16) != 0)
                 .then(|| SpiRange { count: u16le(phys, base + 20), base: u16le(phys, base + 22) }),
         })
     }
# madt-frame-length
--- a/toyos-acpi/src/madt.rs
+++ b/toyos-acpi/src/madt.rs
@@ -180,7 +180,7 @@
     /// Count (20..22), SPI Base (22..24).
     fn msi_frame(self) -> Option<MsiFrame> {
         let Self { phys, base, entry_type, declared } = self;
-        (entry_type == 0xD && declared >= 24).then(|| MsiFrame {
+        (entry_type == 0xD).then(|| MsiFrame {
             id: u32le(phys, base + 4),
             base: u64le(phys, base + 8),
             spis: (u32le(phys, base + 16) & 1 != 0)
# madt-frame-spi-fields
--- a/toyos-acpi/src/madt.rs
+++ b/toyos-acpi/src/madt.rs
@@ -184,7 +184,7 @@
             id: u32le(phys, base + 4),
             base: u64le(phys, base + 8),
             spis: (u32le(phys, base + 16) & 1 != 0)
-                .then(|| SpiRange { count: u16le(phys, base + 20), base: u16le(phys, base + 22) }),
+                .then(|| SpiRange { count: u16le(phys, base + 22), base: u16le(phys, base + 20) }),
         })
     }
 
# madt-halt-swallowed
--- a/toyos-acpi/src/madt.rs
+++ b/toyos-acpi/src/madt.rs
@@ -162,7 +162,7 @@
         loop {
             let structure = match entries.structure()? {
                 Ok(structure) => structure,
-                Err(halt) => return Some(Err(halt)),
+                Err(_) => return None,
             };
             if let Some(frame) = structure.msi_frame() {
                 return Some(Ok(MsiController::Frame(frame)));

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations of toyos-smmu at 286536f: each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p toyos-smmu --no-run), run (cargo test -p toyos-smmu) and reverted in the same script, the tree clean before and after each. 81 mutations, 81 red.

ste-abort-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-bypass: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stage2: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-always: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-never: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-cd-attributes: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-ttb1-walked: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-shared-asid-set: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-raz-wi: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-unrecorded: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-stalls: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-asid-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
cd-t0sz: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-aarch32: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-ips-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
cd-ttb0-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-mair-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
unit-ips-52: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-global: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-privileged-only: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-no-access-flag: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-executable: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-read-is-writable: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-write-is-read-only: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-memory-as-device: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-as-block: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-as-memory: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-at-level-2: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-unaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-past-input: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-index-shift: build EXIT=0 test EXIT=101 RED (2 tests failed)
plan-index-width: build EXIT=0 test EXIT=101 RED (2 tests failed)
entry-page-as-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
entry-invalid-as-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
next-not-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
mair-order: build EXIT=0 test EXIT=101 RED (2 tests failed)
queue-full-is-empty: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-no-wrap-flag: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-after-overflows: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-empty-ignores-wrap: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-size-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-sync-signal-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-sync-irq-is-sev: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-stream-nonleaf: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-stream-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-all-range: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-asid-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-invalidate-all-opcode: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-rnw-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-rnw-polarity: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-address-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-stream-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
event-permission-unnamed: build EXIT=0 test EXIT=101 RED (2 tests failed)
event-attempt-on-any: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-no-stage1: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-ttf-bit: build EXIT=0 test EXIT=101 RED (10 tests failed)
probe-big-endian: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-forced-stall: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-no-4k: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-preset: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-stream-bits-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-queue-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-eventq-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
probe-cohacc-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
asid-wider-than-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-misaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-past-streams: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-no-read-allocate: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmdq-past-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmdq-misaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
eventq-entry-size: build EXIT=0 test EXIT=101 RED (1 tests failed)
eventq-past-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
errors-not-toggled: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-error-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cr1-table-shareability: build EXIT=0 test EXIT=101 RED (1 tests failed)
cr2-broadcast: build EXIT=0 test EXIT=101 RED (1 tests failed)
gbpa-abort-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
phys-past-48: build EXIT=0 test EXIT=101 RED (1 tests failed)
phys-unaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# ste-abort-not-valid
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -24,7 +24,7 @@
     /// `V` set and `Config` `0b000`: every transaction of the stream is
     /// aborted, and no event is recorded for it. An entry of all zeroes
     /// aborts too, and records `C_BAD_STE` each time.
-    pub const ABORT: Self = Self([STE_V, 0, 0, 0, 0, 0, 0, 0]);
+    pub const ABORT: Self = Self([0, 0, 0, 0, 0, 0, 0, 0]);
 
     /// The stream translated by stage 1 through the one context descriptor
     /// at `context`: `S1ContextPtr` [55:6], `S1CDMax` [63:59] zero so a
# ste-bypass
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -12,7 +12,7 @@
 /// `V`, bit [0].
 const STE_V: u64 = 1 << 0;
 /// `Config`, bits [3:1]: `0b101` is stage 1 translate, stage 2 bypass.
-const STE_STAGE1: u64 = 0b101 << 1;
+const STE_STAGE1: u64 = 0b100 << 1;
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
# ste-stage2
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -12,7 +12,7 @@
 /// `V`, bit [0].
 const STE_V: u64 = 1 << 0;
 /// `Config`, bits [3:1]: `0b101` is stage 1 translate, stage 2 bypass.
-const STE_STAGE1: u64 = 0b101 << 1;
+const STE_STAGE1: u64 = 0b110 << 1;
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
# ste-stalld-bit
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -18,7 +18,7 @@
 /// reads the context descriptor.
 const STE_CD_CACHED: u64 = 0b01 << 2 | 0b01 << 4 | 0b11 << 6;
 /// `S1STALLD`, bit [91].
-const STE_S1STALLD: u64 = 1 << 27;
+const STE_S1STALLD: u64 = 1 << 26;
 
 impl Ste {
     /// `V` set and `Config` `0b000`: every transaction of the stream is
# ste-stalld-always
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -32,7 +32,7 @@
     /// the Non-secure EL1 regime, `EATS` [93:92] zero so ATS is refused, and
     /// no stage 2 field.
     pub const fn stage1(context: Phys<6>, unit: &Unit) -> Self {
-        let stall = if unit.stall_disable() { STE_S1STALLD } else { 0 };
+        let stall = STE_S1STALLD;
         Self([STE_V | STE_STAGE1 | context.get(), STE_CD_CACHED | stall, 0, 0, 0, 0, 0, 0])
     }
 }
# ste-stalld-never
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -32,7 +32,7 @@
     /// the Non-secure EL1 regime, `EATS` [93:92] zero so ATS is refused, and
     /// no stage 2 field.
     pub const fn stage1(context: Phys<6>, unit: &Unit) -> Self {
-        let stall = if unit.stall_disable() { STE_S1STALLD } else { 0 };
+        let stall = 0;
         Self([STE_V | STE_STAGE1 | context.get(), STE_CD_CACHED | stall, 0, 0, 0, 0, 0, 0])
     }
 }
# ste-cd-attributes
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -16,7 +16,7 @@
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
-const STE_CD_CACHED: u64 = 0b01 << 2 | 0b01 << 4 | 0b11 << 6;
+const STE_CD_CACHED: u64 = 0b01 << 0 | 0b01 << 2 | 0b11 << 4;
 /// `S1STALLD`, bit [91].
 const STE_S1STALLD: u64 = 1 << 27;
 
# cd-ttb1-walked
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -70,7 +70,6 @@
     pub const fn new(root: Phys<12>, asid: Asid, unit: &Unit) -> Self {
         let word0 = CD_T0SZ
             | CD_WALK
-            | CD_EPD1
             | CD_V
             | unit.ips() << 32
             | CD_AA64
# cd-not-valid
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -71,7 +71,6 @@
         let word0 = CD_T0SZ
             | CD_WALK
             | CD_EPD1
-            | CD_V
             | unit.ips() << 32
             | CD_AA64
             | CD_FAULTS
# cd-shared-asid-set
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -75,7 +75,6 @@
             | unit.ips() << 32
             | CD_AA64
             | CD_FAULTS
-            | CD_ASET
             | (asid.get() as u64) << 48;
         Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0])
     }
# cd-raz-wi
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -57,7 +57,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 45;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-unrecorded
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -57,7 +57,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 46;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-stalls
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -57,7 +57,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 44 | 1 << 45 | 1 << 46;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-asid-field
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -76,7 +76,7 @@
             | CD_AA64
             | CD_FAULTS
             | CD_ASET
-            | (asid.get() as u64) << 48;
+            | (asid.get() as u64) << 32;
         Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0])
     }
 }
# cd-t0sz
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -42,7 +42,7 @@
 pub struct Cd(pub [u64; 8]);
 
 /// `T0SZ`, bits [5:0]: a 48-bit input, walked from level 0.
-const CD_T0SZ: u64 = 64 - crate::table::INPUT_BITS as u64;
+const CD_T0SZ: u64 = crate::table::INPUT_BITS as u64;
 /// `TG0` [7:6] zero is the 4 KiB granule; `IR0` [9:8] and `OR0` [11:10]
 /// `0b01`, write-back write-allocate, and `SH0` [13:12] `0b11`, inner
 /// shareable — how the unit walks the tables; `EPD0` [14] and `ENDI` [15]
# cd-aarch32
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -73,7 +73,6 @@
             | CD_EPD1
             | CD_V
             | unit.ips() << 32
-            | CD_AA64
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
# cd-ips-field
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -72,7 +72,7 @@
             | CD_WALK
             | CD_EPD1
             | CD_V
-            | unit.ips() << 32
+            | unit.ips() << 33
             | CD_AA64
             | CD_FAULTS
             | CD_ASET
# cd-ttb0-word
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -77,6 +77,6 @@
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
-        Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0])
+        Self([word0, 0, root.get(), crate::table::MAIR0 as u64, 0, 0, 0, 0])
     }
 }
# cd-mair-word
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -77,6 +77,6 @@
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
-        Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0])
+        Self([word0, root.get(), crate::table::MAIR0 as u64, 0, 0, 0, 0, 0])
     }
 }
# unit-ips-52
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -211,7 +211,7 @@
             40 => 0b010,
             42 => 0b011,
             44 => 0b100,
-            _ => 0b101,
+            _ => 0b110,
         }
     }
 
# leaf-global
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -40,7 +40,7 @@
 /// `PXN` [53] and `UXN` [54]: never executable.
 const NEVER_EXECUTED: u64 = 1 << 53 | 1 << 54;
 
-const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NOT_GLOBAL | NEVER_EXECUTED;
+const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NEVER_EXECUTED;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-privileged-only
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -40,7 +40,7 @@
 /// `PXN` [53] and `UXN` [54]: never executable.
 const NEVER_EXECUTED: u64 = 1 << 53 | 1 << 54;
 
-const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NOT_GLOBAL | NEVER_EXECUTED;
+const LEAF: u64 = VALID | AF | NOT_GLOBAL | NEVER_EXECUTED;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-no-access-flag
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -40,7 +40,7 @@
 /// `PXN` [53] and `UXN` [54]: never executable.
 const NEVER_EXECUTED: u64 = 1 << 53 | 1 << 54;
 
-const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NOT_GLOBAL | NEVER_EXECUTED;
+const LEAF: u64 = VALID | AP_UNPRIVILEGED | NOT_GLOBAL | NEVER_EXECUTED;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-executable
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -40,7 +40,7 @@
 /// `PXN` [53] and `UXN` [54]: never executable.
 const NEVER_EXECUTED: u64 = 1 << 53 | 1 << 54;
 
-const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NOT_GLOBAL | NEVER_EXECUTED;
+const LEAF: u64 = VALID | AP_UNPRIVILEGED | AF | NOT_GLOBAL;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-read-is-writable
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -89,7 +89,7 @@
     let (tables, size, descriptor) = match leaf {
         Leaf::Memory(block, access) => {
             let access = match access {
-                Access::Read => AP_READ_ONLY,
+                Access::Read => 0,
                 Access::ReadWrite => 0,
             };
             (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
# leaf-write-is-read-only
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -90,7 +90,7 @@
         Leaf::Memory(block, access) => {
             let access = match access {
                 Access::Read => AP_READ_ONLY,
-                Access::ReadWrite => 0,
+                Access::ReadWrite => AP_READ_ONLY,
             };
             (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
         }
# leaf-memory-as-device
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -92,7 +92,7 @@
                 Access::Read => AP_READ_ONLY,
                 Access::ReadWrite => 0,
             };
-            (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
+            (3, 1u64 << 21, block.get() | LEAF | ATTR_DEVICE | INNER_SHAREABLE | access)
         }
         Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
     };
# doorbell-as-block
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -94,7 +94,7 @@
             };
             (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
         }
-        Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
+        Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | ATTR_DEVICE),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
         return None;
# doorbell-as-memory
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -94,7 +94,7 @@
             };
             (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
         }
-        Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
+        Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_MEMORY),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
         return None;
# doorbell-at-level-2
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -94,7 +94,7 @@
             };
             (3, 1u64 << 21, block.get() | LEAF | ATTR_MEMORY | INNER_SHAREABLE | access)
         }
-        Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
+        Leaf::Doorbell(page) => (3, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
         return None;
# plan-unaligned
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -96,7 +96,7 @@
         }
         Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
     };
-    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
+    if at >> INPUT_BITS != 0 {
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
# plan-past-input
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -96,7 +96,7 @@
         }
         Leaf::Doorbell(page) => (4, 1u64 << 12, page.get() | LEAF | TABLE_OR_PAGE | ATTR_DEVICE),
     };
-    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
+    if at & (size - 1) != 0 {
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
# plan-index-shift
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -100,7 +100,7 @@
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
-        (at >> (39 - 9 * level) & 0x1FF) as usize
+        (at >> (38 - 9 * level) & 0x1FF) as usize
     }
     Some(Path { indices: [index(at, 0), index(at, 1), index(at, 2), index(at, 3)], tables, descriptor })
 }
# plan-index-width
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -100,7 +100,7 @@
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
-        (at >> (39 - 9 * level) & 0x1FF) as usize
+        (at >> (39 - 9 * level) & 0xFF) as usize
     }
     Some(Path { indices: [index(at, 0), index(at, 1), index(at, 2), index(at, 3)], tables, descriptor })
 }
# entry-page-as-table
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -121,7 +121,7 @@
 pub const fn entry(descriptor: u64, level: usize) -> Entry {
     if descriptor & VALID == 0 {
         Entry::Invalid
-    } else if level < LEVELS - 1 && descriptor & TABLE_OR_PAGE != 0 {
+    } else if descriptor & TABLE_OR_PAGE != 0 {
         Entry::Table(Phys(descriptor & 0x0000_FFFF_FFFF_F000))
     } else {
         Entry::Mapped
# entry-invalid-as-table
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -119,7 +119,7 @@
 /// `descriptor` as the walk reads it at `level`: bit [1] names a table above
 /// level 3 and a page at it, and a table's address is bits [47:12].
 pub const fn entry(descriptor: u64, level: usize) -> Entry {
-    if descriptor & VALID == 0 {
+    if descriptor == 0 {
         Entry::Invalid
     } else if level < LEVELS - 1 && descriptor & TABLE_OR_PAGE != 0 {
         Entry::Table(Phys(descriptor & 0x0000_FFFF_FFFF_F000))
# next-not-table
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -61,7 +61,7 @@
 /// The descriptor naming the next table down, at levels 0 to 2. Its
 /// hierarchical fields stay clear: the leaf alone decides the access.
 pub const fn next(table: Phys<12>) -> u64 {
-    table.get() | TABLE_OR_PAGE | VALID
+    table.get() | VALID
 }
 
 /// One mapping, planned: the index to follow in each table from the root,
# mair-order
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -15,7 +15,7 @@
 
 /// `CD.MAIR0`: attribute 0 is Device-nGnRE (`0x04`), which the message page
 /// is mapped with, and attribute 1 Normal write-back memory (`0xFF`).
-pub const MAIR0: u32 = 0x04 | 0xFF << 8;
+pub const MAIR0: u32 = 0xFF | 0x04 << 8;
 const ATTR_DEVICE: u64 = 0 << 2;
 const ATTR_MEMORY: u64 = 1 << 2;
 
# queue-full-is-empty
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -40,7 +40,7 @@
 
     /// Nothing can be produced: the indexes are equal and the wrap flags differ.
     pub const fn is_full(self, prod: u32, cons: u32) -> bool {
-        self.position(prod) ^ self.position(cons) == 1 << self.log2size
+        self.position(prod) == self.position(cons)
     }
 
     /// The position after `index`, wrap flag toggled where it passes the
# queue-no-wrap-flag
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -46,7 +46,7 @@
     /// The position after `index`, wrap flag toggled where it passes the
     /// top: what the owner of an index writes back, index and flag at once.
     pub const fn after(self, index: u32) -> u32 {
-        self.position(self.position(index) + 1)
+        (self.position(index) + 1) & ((1 << self.log2size) - 1)
     }
 }
 
# queue-after-overflows
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -46,7 +46,7 @@
     /// The position after `index`, wrap flag toggled where it passes the
     /// top: what the owner of an index writes back, index and flag at once.
     pub const fn after(self, index: u32) -> u32 {
-        self.position(self.position(index) + 1)
+        self.position(index + 1)
     }
 }
 
# queue-empty-ignores-wrap
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -35,7 +35,7 @@
 
     /// Nothing to consume: the indexes and their wrap flags are equal.
     pub const fn is_empty(self, prod: u32, cons: u32) -> bool {
-        self.position(prod) == self.position(cons)
+        self.slot(prod) == self.slot(cons)
     }
 
     /// Nothing can be produced: the indexes are equal and the wrap flags differ.
# queue-size-bound
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -15,7 +15,7 @@
     /// `None` past the 19 bits an index register's `WR` and `RD` fields with
     /// their wrap flag can hold (`IDR1.CMDQS`, `EVENTQS`).
     pub const fn new(log2size: u8) -> Option<Self> {
-        if log2size <= 19 {
+        if log2size <= 31 {
             Some(Self { log2size })
         } else {
             None
# cmd-sync-signal-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -102,7 +102,7 @@
                     Signal::Irq => 0b01,
                     Signal::Sev => 0b10,
                 };
-                [0x46 | cs << 12, 0]
+                [0x46 | cs << 11, 0]
             }
         }
     }
# cmd-sync-irq-is-sev
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -99,7 +99,7 @@
             Self::Sync(signal) => {
                 let cs = match signal {
                     Signal::None => 0b00,
-                    Signal::Irq => 0b01,
+                    Signal::Irq => 0b10,
                     Signal::Sev => 0b10,
                 };
                 [0x46 | cs << 12, 0]
# cmd-forget-stream-nonleaf
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -87,7 +87,7 @@
     pub const fn words(self) -> [u64; 2] {
         match self {
             // StreamID [63:32]; Leaf [64].
-            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 1],
+            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 0],
             // StreamID [63:32], SubstreamID [31:12] zero; Leaf [64].
             Self::ForgetContext(stream) => [0x05 | (stream as u64) << 32, 1],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
# cmd-forget-stream-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -87,7 +87,7 @@
     pub const fn words(self) -> [u64; 2] {
         match self {
             // StreamID [63:32]; Leaf [64].
-            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 1],
+            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 12, 1],
             // StreamID [63:32], SubstreamID [31:12] zero; Leaf [64].
             Self::ForgetContext(stream) => [0x05 | (stream as u64) << 32, 1],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
# cmd-forget-all-range
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -91,7 +91,7 @@
             // StreamID [63:32], SubstreamID [31:12] zero; Leaf [64].
             Self::ForgetContext(stream) => [0x05 | (stream as u64) << 32, 1],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
-            Self::ForgetAll => [0x04, 31],
+            Self::ForgetAll => [0x04, 0],
             // ASID [63:48], VMID [47:32].
             Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
             Self::InvalidateAll => [0x30, 0],
# cmd-asid-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -93,7 +93,7 @@
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
-            Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
+            Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 32, 0],
             Self::InvalidateAll => [0x30, 0],
             // CS [13:12].
             Self::Sync(signal) => {
# cmd-invalidate-all-opcode
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -94,7 +94,7 @@
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
             Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
-            Self::InvalidateAll => [0x30, 0],
+            Self::InvalidateAll => [0x10, 0],
             // CS [13:12].
             Self::Sync(signal) => {
                 let cs = match signal {
# event-rnw-bit
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -197,7 +197,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[2], write: record[1] & 1 << 34 == 0 })
         }
         _ => None,
     };
# event-rnw-polarity
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -197,7 +197,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[2], write: record[1] & 1 << 35 != 0 })
         }
         _ => None,
     };
# event-address-word
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -197,7 +197,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[3], write: record[1] & 1 << 35 == 0 })
         }
         _ => None,
     };
# event-stream-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -201,5 +201,5 @@
         }
         _ => None,
     };
-    Event { stream: (record[0] >> 32) as u32, code, attempt }
+    Event { stream: (record[0] >> 12) as u32, code, attempt }
 }
# event-permission-unnamed
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -188,7 +188,6 @@
         0x10 => Code::Translation,
         0x11 => Code::AddressSize,
         0x12 => Code::AccessFlag,
-        0x13 => Code::Permission,
         0x20 => Code::TlbConflict,
         0x21 => Code::ConfigurationConflict,
         0x24 => Code::PageRequest,
# event-attempt-on-any
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -199,7 +199,7 @@
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
             Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
         }
-        _ => None,
+        _ => Some(Attempt { address: record[2], write: true }),
     };
     Event { stream: (record[0] >> 32) as u32, code, attempt }
 }
# probe-no-stage1
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -140,7 +140,7 @@
 
 /// §6.3.1, §6.3.2 and §6.3.6: the three identification registers, judged.
 pub const fn probe(idr0: u32, idr1: u32, idr5: u32) -> Result<Unit, Lacks> {
-    if idr0 & 1 << 1 == 0 {
+    if false {
         return Err(Lacks::Stage1);
     }
     if idr0 & 1 << 3 == 0 {
# probe-ttf-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -143,7 +143,7 @@
     if idr0 & 1 << 1 == 0 {
         return Err(Lacks::Stage1);
     }
-    if idr0 & 1 << 3 == 0 {
+    if idr0 & 1 << 2 == 0 {
         return Err(Lacks::Aarch64Tables);
     }
     // `TTENDIAN` [22:21]: `0b00` mixed and `0b10` little-endian can be set to little-endian.
# probe-big-endian
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -147,7 +147,7 @@
         return Err(Lacks::Aarch64Tables);
     }
     // `TTENDIAN` [22:21]: `0b00` mixed and `0b10` little-endian can be set to little-endian.
-    if idr0 >> 21 & 0b11 == 0b11 {
+    if false {
         return Err(Lacks::LittleEndianTables);
     }
     // `STALL_MODEL` [25:24]: `0b00` stall and terminate, `0b01` terminate only.
# probe-forced-stall
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -153,7 +153,7 @@
     // `STALL_MODEL` [25:24]: `0b00` stall and terminate, `0b01` terminate only.
     let stall = match idr0 >> 24 & 0b11 {
         0b00 => true,
-        0b01 => false,
+        0b01 | 0b10 => false,
         other => return Err(Lacks::Termination(other as u8)),
     };
     if idr5 & 1 << 4 == 0 {
# probe-no-4k
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -156,7 +156,7 @@
         0b01 => false,
         other => return Err(Lacks::Termination(other as u8)),
     };
-    if idr5 & 1 << 4 == 0 {
+    if false {
         return Err(Lacks::Granule4K);
     }
     if idr1 & (1 << 30 | 1 << 29) != 0 {
# probe-preset
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -159,7 +159,7 @@
     if idr5 & 1 << 4 == 0 {
         return Err(Lacks::Granule4K);
     }
-    if idr1 & (1 << 30 | 1 << 29) != 0 {
+    if idr1 & (1 << 30) != 0 {
         return Err(Lacks::FixedStructures);
     }
     let stream_bits = (idr1 & 0x3F) as u8;
# probe-stream-bits-bound
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -165,7 +165,7 @@
     let stream_bits = (idr1 & 0x3F) as u8;
     let command_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
     let event_queue_log2 = (idr1 >> 16 & 0x1F) as u8;
-    if stream_bits > 32 {
+    if stream_bits > 63 {
         return Err(Lacks::Size(stream_bits));
     }
     if command_queue_log2 > 19 {
# probe-queue-bound
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -168,7 +168,7 @@
     if stream_bits > 32 {
         return Err(Lacks::Size(stream_bits));
     }
-    if command_queue_log2 > 19 {
+    if command_queue_log2 > 31 {
         return Err(Lacks::Size(command_queue_log2));
     }
     if event_queue_log2 > 19 {
# probe-eventq-field
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -164,7 +164,7 @@
     }
     let stream_bits = (idr1 & 0x3F) as u8;
     let command_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
-    let event_queue_log2 = (idr1 >> 16 & 0x1F) as u8;
+    let event_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
     if stream_bits > 32 {
         return Err(Lacks::Size(stream_bits));
     }
# probe-cohacc-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -179,7 +179,7 @@
         command_queue_log2,
         event_queue_log2,
         output_bits: [32, 36, 40, 42, 44, 48, 52, 56][(idr5 & 0b111) as usize],
-        coherent: idr0 & 1 << 4 != 0,
+        coherent: idr0 & 1 << 5 != 0,
         asid16: idr0 & 1 << 12 != 0,
         stall,
     })
# asid-wider-than-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -189,7 +189,7 @@
     /// `tag` as an ASID of this unit: eight bits where `IDR0.ASID16` is
     /// clear, under which a wider one makes its context descriptor ILLEGAL.
     pub const fn asid(&self, tag: u16) -> Option<Asid> {
-        if self.asid16 || tag <= 0xFF {
+        if true {
             Some(Asid(tag))
         } else {
             None
# strtab-misaligned
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -221,7 +221,7 @@
     /// where the unit takes fewer StreamID bits, or the table is not aligned
     /// to its own size as the unit reads its address.
     pub const fn stream_table(&self, table: Phys<6>, log2size: u8) -> Option<(u64, u32)> {
-        if log2size > self.stream_bits || table.get() & ((64 << log2size) - 1) != 0 {
+        if log2size > self.stream_bits {
             return None;
         }
         Some((1 << 62 | table.get(), log2size as u32))
# strtab-past-streams
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -221,7 +221,7 @@
     /// where the unit takes fewer StreamID bits, or the table is not aligned
     /// to its own size as the unit reads its address.
     pub const fn stream_table(&self, table: Phys<6>, log2size: u8) -> Option<(u64, u32)> {
-        if log2size > self.stream_bits || table.get() & ((64 << log2size) - 1) != 0 {
+        if table.get() & ((64 << log2size) - 1) != 0 {
             return None;
         }
         Some((1 << 62 | table.get(), log2size as u32))
# strtab-no-read-allocate
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -224,7 +224,7 @@
         if log2size > self.stream_bits || table.get() & ((64 << log2size) - 1) != 0 {
             return None;
         }
-        Some((1 << 62 | table.get(), log2size as u32))
+        Some((table.get(), log2size as u32))
     }
 
     /// `SMMU_CMDQ_BASE` (§6.3.26) for a queue of `1 << log2size` 16-byte
# cmdq-past-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -232,7 +232,7 @@
     /// past the unit's `CMDQS`, or where the queue is not aligned to the
     /// larger of its size and 32 bytes.
     pub const fn command_queue(&self, queue: Phys<5>, log2size: u8) -> Option<u64> {
-        if log2size > self.command_queue_log2 || queue.get() & ((16 << log2size) - 1) != 0 {
+        if queue.get() & ((16 << log2size) - 1) != 0 {
             return None;
         }
         Some(1 << 62 | queue.get() | log2size as u64)
# cmdq-misaligned
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -232,7 +232,7 @@
     /// past the unit's `CMDQS`, or where the queue is not aligned to the
     /// larger of its size and 32 bytes.
     pub const fn command_queue(&self, queue: Phys<5>, log2size: u8) -> Option<u64> {
-        if log2size > self.command_queue_log2 || queue.get() & ((16 << log2size) - 1) != 0 {
+        if log2size > self.command_queue_log2 {
             return None;
         }
         Some(1 << 62 | queue.get() | log2size as u64)
# eventq-entry-size
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -241,7 +241,7 @@
     /// `SMMU_EVENTQ_BASE` (§6.3.29) for a queue of `1 << log2size` 32-byte
     /// records: `WA` [62], the address, `LOG2SIZE` [4:0].
     pub const fn event_queue(&self, queue: Phys<5>, log2size: u8) -> Option<u64> {
-        if log2size > self.event_queue_log2 || queue.get() & ((32 << log2size) - 1) != 0 {
+        if log2size > self.event_queue_log2 || queue.get() & ((16 << log2size) - 1) != 0 {
             return None;
         }
         Some(1 << 62 | queue.get() | log2size as u64)
# eventq-past-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -241,7 +241,7 @@
     /// `SMMU_EVENTQ_BASE` (§6.3.29) for a queue of `1 << log2size` 32-byte
     /// records: `WA` [62], the address, `LOG2SIZE` [4:0].
     pub const fn event_queue(&self, queue: Phys<5>, log2size: u8) -> Option<u64> {
-        if log2size > self.event_queue_log2 || queue.get() & ((32 << log2size) - 1) != 0 {
+        if queue.get() & ((32 << log2size) - 1) != 0 {
             return None;
         }
         Some(1 << 62 | queue.get() | log2size as u64)
# errors-not-toggled
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -59,7 +59,7 @@
 /// `SMMU_GERROR` (§6.3.19): an error is active while its bit differs from
 /// `SMMU_GERRORN`'s, and writing `GERRORN` equal acknowledges it (§7.5).
 pub const fn active_errors(gerror: u32, gerrorn: u32) -> u32 {
-    gerror ^ gerrorn
+    gerror & !gerrorn
 }
 
 /// `SMMU_GERROR.CMDQ_ERR`: the command queue stopped on [`CommandError`].
# cmd-error-field
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -85,7 +85,7 @@
 }
 
 pub const fn command_error(cons: u32) -> CommandError {
-    match (cons >> 24 & 0x7F) as u8 {
+    match (cons >> 24) as u8 {
         0 => CommandError::None,
         1 => CommandError::Illegal,
         2 => CommandError::Abort,
# cr1-table-shareability
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -38,7 +38,7 @@
 /// inner-shareable write-back memory — `TABLE_SH` [11:10], `TABLE_OC` [9:8],
 /// `TABLE_IC` [7:6], `QUEUE_SH` [5:4], `QUEUE_OC` [3:2], `QUEUE_IC` [1:0],
 /// each shareability `0b11` and each cacheability `0b01`.
-pub const CR1_WRITE_BACK: u32 = 0b11 << 10 | 0b01 << 8 | 0b01 << 6 | 0b11 << 4 | 0b01 << 2 | 0b01;
+pub const CR1_WRITE_BACK: u32 = 0b11 << 12 | 0b01 << 8 | 0b01 << 6 | 0b11 << 4 | 0b01 << 2 | 0b01;
 
 /// `SMMU_CR2` (§6.3.12): `RECINVSID` [1], so a transaction under a StreamID
 /// past the stream table is recorded as well as aborted, and `PTM` [2], so
# cr2-broadcast
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -44,7 +44,7 @@
 /// past the stream table is recorded as well as aborted, and `PTM` [2], so
 /// no CPU's broadcast TLB invalidation reaches the unit's entries: its ASIDs
 /// are its own. `E2H` [0] stays clear.
-pub const CR2_RECORD_PRIVATE: u32 = 1 << 1 | 1 << 2;
+pub const CR2_RECORD_PRIVATE: u32 = 1 << 1;
 
 /// `SMMU_GBPA` (§6.3.15): what happens to every transaction while `SMMUEN`
 /// is clear. `ABORT` [20] aborts them; a write takes effect by carrying
# gbpa-abort-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -49,7 +49,7 @@
 /// `SMMU_GBPA` (§6.3.15): what happens to every transaction while `SMMUEN`
 /// is clear. `ABORT` [20] aborts them; a write takes effect by carrying
 /// `Update` [31], which reads back set until the unit has taken it (§6.3.15.1).
-pub const GBPA_ABORT: u32 = 1 << 20;
+pub const GBPA_ABORT: u32 = 1 << 19;
 pub const GBPA_UPDATE: u32 = 1 << 31;
 
 /// `SMMU_IRQ_CTRL` (§6.3.17), and `SMMU_IRQ_CTRLACK` as `SMMU_CR0ACK`.
# phys-past-48
--- a/toyos-smmu/src/lib.rs
+++ b/toyos-smmu/src/lib.rs
@@ -32,7 +32,7 @@
 
 impl<const ALIGN: u32> Phys<ALIGN> {
     pub const fn new(address: u64) -> Option<Self> {
-        if address >> 48 == 0 && address & ((1 << ALIGN) - 1) == 0 {
+        if address & ((1 << ALIGN) - 1) == 0 {
             Some(Self(address))
         } else {
             None
# phys-unaligned
--- a/toyos-smmu/src/lib.rs
+++ b/toyos-smmu/src/lib.rs
@@ -32,7 +32,7 @@
 
 impl<const ALIGN: u32> Phys<ALIGN> {
     pub const fn new(address: u64) -> Option<Self> {
-        if address >> 48 == 0 && address & ((1 << ALIGN) - 1) == 0 {
+        if address >> 48 == 0 {
             Some(Self(address))
         } else {
             None

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations of toyos-its at 286536f: each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p toyos-its --no-run), run (cargo test -p toyos-its) and reverted in the same script, the tree clean before and after each. 48 mutations, 48 red.

its-mapd-size: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-mapd-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-unmapd-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapc-rdbase-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapc-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapti-fields-swapped: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapti-collection-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-device-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-sync-rdbase-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-inv-is-discard: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-target-form-swapped: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-events-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-events-zero: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-itt-bytes: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-no-physical-lpis: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-minus-one: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-typer-devbits-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-pta-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-type-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-collections: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-entry-size: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-pages-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-size-not-minus-one: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-page-size-64k: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-cache-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-full-is-empty: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-no-wrap: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-after-overflows: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-offset-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-offset-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-cbaser-size: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-phys-unaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-phys-past-48: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-below-8192: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-past-space: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-res1: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-priority-bits: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-index: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-pending-from-8192: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-pending-bytes: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-bytes: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-space-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-space-ceiling: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-propbaser-idbits: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-pendbaser-not-zeroed: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-cache-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-processor-number: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# its-mapd-size
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -39,7 +39,7 @@
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
             Self::MapDevice { device: id, table, events } => {
-                [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
+                [device(0x08, id), events.0 as u64, V | table.get(), 0]
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
# its-mapd-not-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -39,7 +39,7 @@
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
             Self::MapDevice { device: id, table, events } => {
-                [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
+                [device(0x08, id), (events.0 - 1) as u64, table.get(), 0]
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
# its-unmapd-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -41,7 +41,7 @@
             Self::MapDevice { device: id, table, events } => {
                 [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
             }
-            Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
+            Self::UnmapDevice { device: id } => [device(0x08, id), 0, V, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
# its-mapc-rdbase-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -43,7 +43,7 @@
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
-            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection as u64, 0],
+            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 32 | collection as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, collection as u64, 0]
# its-mapc-not-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -43,7 +43,7 @@
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
-            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection as u64, 0],
+            Self::MapCollection { collection, target } => [0x09, 0, target.0 << 16 | collection as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, collection as u64, 0]
# its-mapti-fields-swapped
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -46,7 +46,7 @@
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
-                [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, collection as u64, 0]
+                [device(0x0A, id), (event as u64) << 32 | lpi.intid() as u64, collection as u64, 0]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event as u64, 0, 0],
# its-mapti-collection-word
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -46,7 +46,7 @@
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
-                [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, collection as u64, 0]
+                [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, 0, collection as u64]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event as u64, 0, 0],
# its-device-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -34,7 +34,7 @@
     pub const fn words(self) -> [u64; 4] {
         // DeviceID is bits [63:32] of doubleword 0, and EventID bits [31:0] of doubleword 1.
         const fn device(number: u64, device: u32) -> u64 {
-            number | (device as u64) << 32
+            number | (device as u64) << 8
         }
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
# its-sync-rdbase-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -50,7 +50,7 @@
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event as u64, 0, 0],
-            Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
+            Self::Sync(target) => [0x05, 0, target.0, 0],
         }
     }
 }
# its-inv-is-discard
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -48,7 +48,7 @@
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event as u64, collection as u64, 0]
             }
-            Self::Reconfigure { device: id, event } => [device(0x0C, id), event as u64, 0, 0],
+            Self::Reconfigure { device: id, event } => [device(0x0F, id), event as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event as u64, 0, 0],
             Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
         }
# its-target-form-swapped
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -136,7 +136,7 @@
     /// The redistributor at `frame` whose `GICR_TYPER.Processor_Number` is
     /// `number`, as this ITS names it.
     pub const fn target(&self, number: u16, frame: Phys<16>) -> Target {
-        Target(if self.physical_targets { frame.get() >> 16 } else { number as u64 })
+        Target(if !self.physical_targets { frame.get() >> 16 } else { number as u64 })
     }
 }
 
# its-events-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -120,7 +120,7 @@
     /// A translation table of `1 << bits` events. `None` for no bit, or more
     /// than the ITS's EventIDs have.
     pub const fn events(&self, bits: u8) -> Option<EventBits> {
-        if bits >= 1 && bits <= self.event_bits {
+        if bits >= 1 {
             Some(EventBits(bits))
         } else {
             None
# its-events-zero
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -120,7 +120,7 @@
     /// A translation table of `1 << bits` events. `None` for no bit, or more
     /// than the ITS's EventIDs have.
     pub const fn events(&self, bits: u8) -> Option<EventBits> {
-        if bits >= 1 && bits <= self.event_bits {
+        if bits <= self.event_bits {
             Some(EventBits(bits))
         } else {
             None
# its-itt-bytes
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -130,7 +130,7 @@
     /// The zeroed bytes `MAPD` is given for that table, at a 256-byte
     /// aligned address.
     pub const fn itt_bytes(&self, events: EventBits) -> u64 {
-        (self.itt_entry_bytes as u64) << events.0
+        (self.itt_entry_bytes as u64) << (events.0 - 1)
     }
 
     /// The redistributor at `frame` whose `GICR_TYPER.Processor_Number` is
# its-no-physical-lpis
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -94,7 +94,7 @@
 }
 
 pub const fn probe(typer: u64) -> Result<Its, Lacks> {
-    if typer & 1 == 0 {
+    if false {
         return Err(Lacks::PhysicalLpis);
     }
     Ok(Its {
# its-typer-minus-one
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -98,7 +98,7 @@
         return Err(Lacks::PhysicalLpis);
     }
     Ok(Its {
-        event_bits: (typer >> 8 & 0x1F) as u8 + 1,
+        event_bits: (typer >> 8 & 0x1F) as u8,
         device_bits: (typer >> 13 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         collections_held: (typer >> 24) as u8,
# its-typer-devbits-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -99,7 +99,7 @@
     }
     Ok(Its {
         event_bits: (typer >> 8 & 0x1F) as u8 + 1,
-        device_bits: (typer >> 13 & 0x1F) as u8 + 1,
+        device_bits: (typer >> 12 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         collections_held: (typer >> 24) as u8,
         physical_targets: typer & 1 << 19 != 0,
# its-typer-pta-bit
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -102,7 +102,7 @@
         device_bits: (typer >> 13 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         collections_held: (typer >> 24) as u8,
-        physical_targets: typer & 1 << 19 != 0,
+        physical_targets: typer & 1 << 18 != 0,
     })
 }
 
# its-table-type-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -175,7 +175,7 @@
 /// entry (`Entry_Size` [52:48] plus one) and the page size it holds — which
 /// an ITS may fix, so it is read back after [`baser`] is written.
 pub const fn table(baser: u64) -> (Table, u8, PageSize) {
-    let kind = match baser >> 56 & 0b111 {
+    let kind = match baser >> 59 & 0b111 {
         0b000 => Table::Unimplemented,
         0b001 => Table::Devices,
         0b100 => Table::Collections,
# its-table-collections
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -178,7 +178,7 @@
     let kind = match baser >> 56 & 0b111 {
         0b000 => Table::Unimplemented,
         0b001 => Table::Devices,
-        0b100 => Table::Collections,
+        0b010 => Table::Collections,
         other => Table::Other(other as u8),
     };
     let page = match baser >> 8 & 0b11 {
# its-table-entry-size
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -187,7 +187,7 @@
         // `0b11` is reserved and treated as `0b10`.
         _ => PageSize::K64,
     };
-    (kind, (baser >> 48 & 0x1F) as u8 + 1, page)
+    (kind, (baser >> 48 & 0x1F) as u8, page)
 }
 
 /// `InnerCache` `0b111`, read-allocate write-allocate write-back, with
# its-baser-pages-bound
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -201,7 +201,7 @@
 /// `Page_Size` [9:8], `Size` [7:0] the pages minus one. `None` for no page or
 /// more than the 256 the field counts.
 pub const fn baser(at: Phys<16>, page: PageSize, pages: u16) -> Option<u64> {
-    if pages == 0 || pages > 256 {
+    if pages == 0 {
         return None;
     }
     let size = match page {
# its-baser-size-not-minus-one
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -209,7 +209,7 @@
         PageSize::K16 => 0b01,
         PageSize::K64 => 0b10,
     };
-    Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
+    Some(VALID | CACHED | at.get() | size << 8 | (pages as u64 & 0xFF))
 }
 
 /// The ITS's command queue: `pages` 4 KiB pages of 32-byte commands
# its-baser-page-size-64k
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -207,7 +207,7 @@
     let size = match page {
         PageSize::K4 => 0b00,
         PageSize::K16 => 0b01,
-        PageSize::K64 => 0b10,
+        PageSize::K64 => 0b11,
     };
     Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
 }
# its-baser-not-valid
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -209,7 +209,7 @@
         PageSize::K16 => 0b01,
         PageSize::K64 => 0b10,
     };
-    Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
+    Some(CACHED | at.get() | size << 8 | (pages - 1) as u64)
 }
 
 /// The ITS's command queue: `pages` 4 KiB pages of 32-byte commands
# its-cache-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -193,7 +193,7 @@
 /// `InnerCache` `0b111`, read-allocate write-allocate write-back, with
 /// `OuterCache` zero, as the inner; `Shareability` [11:10] `0b01`, inner
 /// shareable. `GITS_BASER<n>` and `GITS_CBASER` hold them at the same bits.
-const CACHED: u64 = 0b111 << 59 | 0b01 << 10;
+const CACHED: u64 = 0b111 << 56 | 0b01 << 10;
 const VALID: u64 = 1 << 63;
 
 /// `GITS_BASER<n>` for a flat table of `pages` pages of `page` at `at`:
# its-queue-full-is-empty
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -259,6 +259,6 @@
     /// One more command would make the writer equal the reader, which reads
     /// as empty: the queue holds one command fewer than its bytes do.
     pub const fn is_full(self, writer: u32, reader: u32) -> bool {
-        self.after(writer) == reader
+        writer == reader && false
     }
 }
# its-queue-no-wrap
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -248,7 +248,7 @@
 
     /// The offset after `offset`, wrapping at the queue's end.
     pub const fn after(self, offset: u32) -> u32 {
-        (self.offset(offset as u64) + 32) % self.bytes
+        self.offset(offset as u64) + 32
     }
 
     /// The ITS has read every command: the two offsets are equal.
# its-queue-after-overflows
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -248,7 +248,7 @@
 
     /// The offset after `offset`, wrapping at the queue's end.
     pub const fn after(self, offset: u32) -> u32 {
-        (self.offset(offset as u64) + 32) % self.bytes
+        (offset + 32) % self.bytes
     }
 
     /// The ITS has read every command: the two offsets are equal.
# its-queue-offset-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -243,7 +243,7 @@
     /// The byte offset an offset register names: its `Offset` field alone,
     /// and inside the queue whatever the ITS wrote there.
     pub const fn offset(self, register: u64) -> u32 {
-        (register & 0xF_FFE0) as u32 % self.bytes
+        (register & 0xF_FFFF) as u32 % self.bytes
     }
 
     /// The offset after `offset`, wrapping at the queue's end.
# its-queue-offset-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -243,7 +243,7 @@
     /// The byte offset an offset register names: its `Offset` field alone,
     /// and inside the queue whatever the ITS wrote there.
     pub const fn offset(self, register: u64) -> u32 {
-        (register & 0xF_FFE0) as u32 % self.bytes
+        (register & 0xF_FFE0) as u32
     }
 
     /// The offset after `offset`, wrapping at the queue's end.
# its-cbaser-size
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -237,7 +237,7 @@
     /// `Physical_Address` [51:12] with its low four bits zero, `Size` [7:0]
     /// the pages minus one.
     pub const fn cbaser(self, at: Phys<16>) -> u64 {
-        VALID | CACHED | at.get() | (self.bytes / 4096 - 1) as u64
+        VALID | CACHED | at.get() | (self.bytes / 4096) as u64 & 0xFF
     }
 
     /// The byte offset an offset register names: its `Offset` field alone,
# its-phys-unaligned
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -31,7 +31,7 @@
 
 impl<const ALIGN: u32> Phys<ALIGN> {
     pub const fn new(address: u64) -> Option<Self> {
-        if address >> 48 == 0 && address & ((1 << ALIGN) - 1) == 0 {
+        if address >> 48 == 0 {
             Some(Self(address))
         } else {
             None
# its-phys-past-48
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -31,7 +31,7 @@
 
 impl<const ALIGN: u32> Phys<ALIGN> {
     pub const fn new(address: u64) -> Option<Self> {
-        if address >> 48 == 0 && address & ((1 << ALIGN) - 1) == 0 {
+        if address & ((1 << ALIGN) - 1) == 0 {
             Some(Self(address))
         } else {
             None
# lpi-below-8192
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -79,7 +79,7 @@
 
     /// `intid` as an LPI of this space.
     pub const fn lpi(self, intid: u32) -> Option<Lpi> {
-        if intid >= FIRST && intid >> self.id_bits == 0 {
+        if intid >> self.id_bits == 0 {
             Some(Lpi(intid))
         } else {
             None
# lpi-past-space
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -79,7 +79,7 @@
 
     /// `intid` as an LPI of this space.
     pub const fn lpi(self, intid: u32) -> Option<Lpi> {
-        if intid >= FIRST && intid >> self.id_bits == 0 {
+        if intid >= FIRST {
             Some(Lpi(intid))
         } else {
             None
# lpi-config-res1
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -52,7 +52,7 @@
 /// One LPI's byte in the configuration table (Table 5-1): the priority's
 /// upper six bits in [7:2], bit [1] RES1, and the enable in bit [0].
 pub const fn configuration(priority: u8, enabled: bool) -> u8 {
-    priority & 0xFC | 0b10 | enabled as u8
+    priority & 0xFC | enabled as u8
 }
 
 /// The INTIDs of `id_bits` bits the tables are laid out for.
# lpi-config-priority-bits
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -52,7 +52,7 @@
 /// One LPI's byte in the configuration table (Table 5-1): the priority's
 /// upper six bits in [7:2], bit [1] RES1, and the enable in bit [0].
 pub const fn configuration(priority: u8, enabled: bool) -> u8 {
-    priority & 0xFC | 0b10 | enabled as u8
+    priority | 0b10 | enabled as u8
 }
 
 /// The INTIDs of `id_bits` bits the tables are laid out for.
# lpi-config-index
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -39,7 +39,7 @@
     /// Where its byte is in the configuration table: the table's first byte
     /// is INTID 8192's (§5.1.1).
     pub const fn configuration_index(self) -> usize {
-        (self.0 - FIRST) as usize
+        self.0 as usize
     }
 
     /// Where its bit is in a pending table, which is indexed by the INTID
# lpi-pending-from-8192
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -45,7 +45,7 @@
     /// Where its bit is in a pending table, which is indexed by the INTID
     /// itself: the byte, and the bit in it (§5.1.2).
     pub const fn pending_bit(self) -> (usize, u8) {
-        ((self.0 / 8) as usize, (self.0 % 8) as u8)
+        (((self.0 - FIRST) / 8) as usize, (self.0 % 8) as u8)
     }
 }
 
# lpi-pending-bytes
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -93,7 +93,7 @@
 
     /// One bit for every INTID, the first 8192 included.
     pub const fn pending_bytes(self) -> usize {
-        (1 << self.id_bits) / 8
+        ((1 << self.id_bits) - FIRST as usize) / 8
     }
 
     /// `GICR_PROPBASER` (§12.11.33) for the configuration table at `table`:
# lpi-config-bytes
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -88,7 +88,7 @@
 
     /// One byte for every LPI.
     pub const fn configuration_bytes(self) -> usize {
-        (1 << self.id_bits) - FIRST as usize
+        1 << self.id_bits
     }
 
     /// One bit for every INTID, the first 8192 included.
# lpi-space-floor
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -70,7 +70,7 @@
     /// `None` below 14 bits, which reach no LPI, and above the 24 that keep
     /// the configuration table within 16 MiB.
     pub const fn new(id_bits: u8) -> Option<Self> {
-        if id_bits >= 14 && id_bits <= 24 {
+        if id_bits <= 24 {
             Some(Self { id_bits })
         } else {
             None
# lpi-space-ceiling
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -70,7 +70,7 @@
     /// `None` below 14 bits, which reach no LPI, and above the 24 that keep
     /// the configuration table within 16 MiB.
     pub const fn new(id_bits: u8) -> Option<Self> {
-        if id_bits >= 14 && id_bits <= 24 {
+        if id_bits >= 14 && id_bits <= 31 {
             Some(Self { id_bits })
         } else {
             None
# lpi-propbaser-idbits
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -99,7 +99,7 @@
     /// `GICR_PROPBASER` (§12.11.33) for the configuration table at `table`:
     /// `Physical_Address` [51:12] and `IDbits` [4:0], the bits minus one.
     pub const fn propbaser(self, table: Phys<12>) -> u64 {
-        CACHED | table.get() | (self.id_bits - 1) as u64
+        CACHED | table.get() | self.id_bits as u64
     }
 }
 
# lpi-pendbaser-not-zeroed
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -107,5 +107,5 @@
 /// all zeroes: `Physical_Address` [51:16], and `PTZ` [62], which says so and
 /// lets the redistributor not read it.
 pub const fn pendbaser(table: Phys<16>) -> u64 {
-    1 << 62 | CACHED | table.get()
+    CACHED | table.get()
 }
# lpi-cache-field
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -64,7 +64,7 @@
 /// `InnerCache` [9:7] `0b111`, read-allocate write-allocate write-back, with
 /// `OuterCache` [58:56] zero, as the inner; `Shareability` [11:10] `0b01`,
 /// inner shareable. Both base registers hold them at the same bits.
-const CACHED: u64 = 0b111 << 7 | 0b01 << 10;
+const CACHED: u64 = 0b111 << 8 | 0b01 << 10;
 
 impl Space {
     /// `None` below 14 bits, which reach no LPI, and above the 24 that keep
# lpi-processor-number
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -24,7 +24,7 @@
 /// `GICR_TYPER.Processor_Number` [23:8]: how an ITS whose `GITS_TYPER.PTA`
 /// is clear names this redistributor.
 pub const fn processor_number(typer: u64) -> u16 {
-    (typer >> 8) as u16
+    (typer >> 16) as u16
 }
 
 /// An LPI's INTID, inside the space it was made in: [`Space::lpi`].

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #798 at 286536fb8, round 1, against .claude/agents/reviewer.md. Read: git log origin/main..286536fb8 (one commit), git diff origin/main...286536fb8, every changed file whole, the body and its three mutation comments, the step logs. No build, test or QEMU was run.

Oracles read for this review, fetched from Arm's documentation service: IHI 0070 H.a, IHI 0069 H.b, and DEN 0049 issues E.b, E.c, E.d, E.e, E.f and E.g. No renderer here either, so figures were read as labels with page coordinates, independently of the implementer's extraction. No other kernel's source was read.

Growth. git diff --shortstat origin/main...286536fb8: 26 files, +3133 −10. Production Rust +1667 −10, tests +1394, manifests, lockfile and SOURCE +72. No consumer exists in kernel/, bootloader/ or userland/ for any of the new production lines.

BLOCKER

  • toyos-acpi/src/iort.rs:329-340, :161-164, :362-374 — the SMMUv3 node's own DeviceID mapping is recognised by its single-mapping flag; DeviceID mapping index (offset 64) and the flag that validates it (Table 14 bit 4, node revision 5) are never read — DEN 0049 E.g Table 13: the entry the index names is the unit's own, and "regardless of the flags, the OSPM must ignore the input base and length fields"; E.d (node revision 4) says the index holds whenever a control interrupt is not GSIV based. At this head an indexed entry without the flag has its input base and length range-checked (Range refuses the table on fields the specification says to ignore) and routes streams to the unit's own DeviceID, which G3 would issue MAPD under. The test that must be red at this head: a revision 5 SMMUv3 node, flags bit 4 set, index 0, mapping [0xffff_ffff, 0xffff_ffff, 0x30, ITS, 0]: iort answers Ok, and route of a stream gives its: None; and the revision 4 arm with a zero GSIV.
  • toyos-smmu/src/unit.rs:223-248 — stream_table, command_queue and event_queue hold a Unit and answer Some for an address past its output_bits — IHI 0070 H.a §6.3.24: "High-order bits of the ADDR field above the system physical address size, as reported by SMMU_IDR5.OAS, are RES0", and an implementation need not store them, so the unit reads its stream table or queue at the truncated address: other memory read as stream table entries. The body says "output_bits: no address the unit is given may reach past it" and nothing enforces it. Must be red at this head: on a unit of IDR5.OAS 0b000, each of the three with Phys::new(1 << 32) is None.
  • toyos-acpi/src/madt.rs:91-193 — msi_controllers, MsiController, MsiFrame, SpiRange and the Structure split are a second walk of the MADT beside madt_entries, which already answers MadtEntry::Its { id, base } on main (kernel/src/arch/aarch64/boot.rs:293); the body says the frame is kept out of MadtEntry because "that is a choice forced by the fence" — a second path, and a design shaped to spare the fence. By the body's own table G3 only names and refuses a frame, and the hypervisor-GIC boot is already refused by Route::Translated { its: None } and by the MADT holding no ITS; the SPI select bit, count and base are decoded for nobody. Delete all of it from this stage (about 95 production lines and the three frame tests); the stage that owns the kernel's two exhaustive matches adds a MadtEntry variant if it needs the frame at all. The plan's goal for this stage, "the MADT's ITS entry", is on main already.
  • Evidence — the body: "No guest test is added or changed and none is reached: nothing that boots depends on the three crates' new code." The diff rewrites MadtEntries::next into structure() and Structure::decode (toyos-acpi/src/madt.rs:91-129), which kernel/src/drivers/acpi.rs and kernel/src/arch/aarch64/boot.rs run on every boot. No guest run is in the body at this head, and the pull request's host, guest and toolchain checks read skipping. Closed by deleting the rewrite with the item above, or by the guest suite green at the head with its command, exit code and log.
  • Siblings, none removed and none recorded in issues/ with an owner, evidence and an exit:
    • toyos-smmu/src/table.rs:16-43, :102-104 — the VMSAv8-64 stage 1 descriptor bits, the level index and the MAIR attribute bytes 0x04, 0xFF are declared a third time; toyos-bootmap/src/aarch64.rs:11-41 and kernel/src/arch/aarch64/paging.rs:37-55, :107 hold them. The body does not mention it.
    • toyos-smmu/src/lib.rs:27-45 and toyos-its/src/lib.rs:26-44 — Phys<const ALIGN>, the same twelve lines twice in one diff, with its tests twice.
    • toyos-its/src/lpi.rs:12-28 — GICR_CTLR and GICR_TYPER offsets are kernel/src/arch/aarch64/irqchip.rs:67-68's, and GICR_TYPER's fields are kernel/gicv3/src/lib.rs:14-16's subject: the redistributor has two homes for one register.

NOTE

  • toyos-smmu/src/config.rs:10, :42 — Ste(pub [u64; 8]) and Cd(pub [u64; 8]): the kernel can spell Ste([0b100 << 1 | 1, ..]), a bypass, so the body's "Unit is then the only way to ... a translating Ste" and "no bypass is spelled anywhere" hold of this crate's text and not of its type. Private field, one accessor.
  • toyos-smmu/src/config.rs:34, :70, toyos-smmu/src/table.rs:88 — S1ContextPtr, TTB0 and a leaf's output address are not held to output_bits either; these fail closed (C_BAD_STE, C_BAD_CD, F_ADDR_SIZE), so they ride the OAS item's fix and are not its BLOCKER.
  • toyos-smmu/src/queue.rs:48-50 — after is documented as what an index's owner writes back; for SMMU_EVENTQ_CONS that value clears OVACKFLG [31], which re-opens or forges an overflow (§7.4). The acknowledgement has no spelling here beside EVENTQ_OVERFLOW.
  • toyos-its/src/lib.rs:255-263 — is_empty and is_full take raw u32s where toyos-smmu's queue takes the registers and masks them: GITS_CREADR with Stalled [0] set compares unequal to every offset. One form for both, or an offset type offset alone makes.
  • toyos-its/src/command.rs:19-24, toyos-its/src/lpi.rs:72 — MapEvent, Reconfigure and Discard take any u32 event beside a device whose EventBits bounded it; collection: u16 is held to neither HCC nor a collection table; Space::new is held to nothing a GICD_TYPER or GICR_PROPBASER said. Shifts, not types.
  • toyos-smmu/src/queue.rs:60-61 — Signal::Sev is valid only where IDR0.SEV is set, and Unit carries no SEV: unusable as typed. Delete it; with it go what the gate as planned never issues or reads (Smmuv3::pri, Its::collections_held).
  • toyos-smmu/src/unit.rs:135, toyos-acpi/src/iort.rs:100 — coherence is carried twice (Unit::coherent, Smmuv3::coherent) and consumed by nothing, while CR1_WRITE_BACK and STE_CD_CACHED assume it. DEN 0049 Table 14 has the IORT's value win; one answer, and a non-coherent unit refused by name.
  • toyos-acpi/src/iort.rs:79-84 — two shapes the specification allows are refused whole: a root complex single mapping (E.g Table 6 names the root complex among the nodes the flag is valid in), and an ITS group of more than one under a table of revision 5 or 6 (legal until E.g). Both are refusals by name and neither is on virt; the reasons in the body ("which no table read here carries") should say so.
  • toyos-acpi/src/iort.rs:294-303 — an output reference is resolved by a fresh node walk per mapping, in check and again in route: nodes × mappings, about 8 × 10^8 header reads for a 1 MiB table built to cost that. It ends; no_corruption_of_a_published_iort_panics_or_hangs_the_decoder holds only the 172- and 236-byte tables.
  • Body, false of the record — "I could not read an older issue": the documentation service lists every issue from B to E.g. Read here: table revision 3 in E.b, 5 in E.d, 6 in E.e and E.f, 7 in E.g; SMMUv3 node 4 through E.d and 5 from E.e; root complex node 3 in E.b (reserved bytes 33..36, fields end at 36) and 4 from E.c (PASID capabilities at 33, flags at 36, fields end at 40); ITS node 1 throughout. The gate at iort.rs:25, :318, :326, :330, :352-353 matches all of it.

Checked and found right

  • IORT against DEN 0049 E.g Tables 3, 4, 5, 6, 13, 14, 16, 18 and both fixtures' bytes: header fields at 36/40/44; node header; mapping words with the count as number minus one; SMMUv3 base 16, flags 24, GSIVs event 44, PRI 48, GERR 52, sync 56 — the node's order is as the body says, and virt publishes 106, 107, 109, 108; root complex segment at 28; ITS identifier at 20. route: an unmapped RID is Unmapped; a RID sent past the SMMU is Untranslated, so the claim refusal the plan asks for is expressible; an output reference to no node, mid-node or to a wrong type refuses the table at open; overlap refuses the asked ID.
  • SMMUv3 against IHI 0070 H.a field headings and figures: STE V [0], Config [3:1] with 0b000 "report abort to device, no event recorded" and 0b101 stage 1 alone, S1ContextPtr [55:6], S1CDMax [63:59], S1CIR/S1COR/S1CSH, S1STALLD [91] (ILLEGAL to set where STALL_MODEL is not 0b00, and required for PCIe clients where it is), EATS, STRW; CD T0SZ, TG0, EPD1 [30], V [31], IPS, AA64 [41], S/R/A [44]/[45]/[46], ASET [47], ASID, TTB0 [119:68], MAIR0 [223:192]; GBPA.ABORT [20] and Update [31]; CR0, CR1, CR2, IRQ_CTRL, GERROR, CMDQ_CONS.ERR; IDR0, IDR1, IDR5; STRTAB_BASE(_CFG), CMDQ_BASE, EVENTQ_BASE; the six commands' opcodes and fields; F_TRANSLATION's RnW [99], StreamID [63:32], InputAddr [191:128]. A PCIe transaction without a PASID is "Data, unprivileged" (§3.2, pointing at §13.7), and AP[1] set with CD.PAN clear admits it either way. Abort is encoded as abort in both places.
  • The linear stream table: IDR0.ST_LEVEL 0b00 and 0b01 both include it, LOG2SIZE below IDR1.SIDSIZE is legal, and a StreamID at or past 2^LOG2SIZE "is terminated with abort and a C_BAD_STREAMID event is recorded if permitted by SMMU_CR2.RECINVSID" (§6.3.25). Nothing in the left-out list is needed by the gate as planned. What virt's unit reports is G2's first reading.
  • ITS against IHI 0069 H.b: MAPD, MAPC, MAPTI, INV, SYNC, DISCARD numbers and fields (Figures 5-12, 5-13, 5-15, 5-18); GITS_BASER, GITS_CBASER, GITS_CWRITER, GITS_CREADR, GITS_TYPER, GITS_CTLR; Table 5-1; GICR_PROPBASER, GICR_PENDBASER, GICR_TYPER. ITT and device table entries are IMPLEMENTATION DEFINED (§5.3.10), so encoding none is correct.
  • Mutations: 21 of the 162 read, patch and test log, across the three crates (ste-bypass, ste-abort-not-valid, gbpa-abort-bit, cd-raz-wi, cd-stalls, leaf-global, doorbell-as-block, event-rnw-polarity, queue-after-overflows, strtab-past-streams, cr2-broadcast, iort-gsiv-order, iort-single-routes, iort-output-mid-node, iort-overlap-smmu, madt-halt-swallowed, its-mapti-fields-swapped, its-target-form-swapped, its-baser-not-valid, lpi-pending-from-8192, its-queue-after-overflows): each red at a test that names its claim. The encoding tests restate the specification as (msb, lsb, value) triples written by the hand that wrote the constants: they hold a constant against drift, and the oracle for the encodings is the reading above until G2 and G3 put them to QEMU's units.
  • NOTICE, by the rule's words ("every committed third-party file with its hash, upstream and licence"): the six files are this project's capture of what a firmware table generator put in guest memory — addresses, counts and the OEM strings — with no upstream that distributes them and no licence of their own. Not third-party files; no entry owed, as for the nine q35 tables on main. The six src/licence.rs rows are what src/sourcegate.rs requires of a committed binary and are accepted past the fence.
  • Step logs at 286536fb8, tree clean before and after: suites 0 (100, 22, 15), mutations 0 (162 red), clippy 0, cargo test --lib 0, cargo run -- --ci host 0 ("Host: 78 step(s), all green", the three crates' test binaries in it), --build-only 0.

What the landing head must show

host green and guest / suite green at the head that lands, neither skipped: the pull request is a draft and all three checks read skipping at 286536fb8. host's log must hold toyos-acpi's iort tests and both encodings suites. No metal row is owed: no kernel file and no claim path changes. The orchestrator may not land on reading them at this head; after a round that closes the items above, he may land on those two checks alone, provided the MADT walk the kernel boots through is by then untouched or guest / suite is green over it.

SEND BACK

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Three corrections to the round-1 review above, from its author's own report to the orchestrator; none moves the verdict:

  • INV and DISCARD were not checked against their figures. The review lists them among the ITS commands verified; only the figures for MAPC, MAPD, MAPTI and SYNC (5-12, 5-13, 5-15, 5-18) were read. INV (0x0C) and DISCARD (0x0F) are unverified against IHI 0069 H.b.
  • The 8 × 10^8 header reads is an estimate, from the 16-byte node and 20-byte mapping floors under a 1 MiB table, not a run.
  • Register offsets and most section numbers were not read from the specifications. The "checked and found right" entries rest on field headings and the figures named; the SMMU and ITS register offsets in unit.rs, lib.rs and lpi.rs, and section numbers other than §3.2, §6.3.24, §6.3.25, §6.3.26, §7.4 and §13.7, were not looked up. The next round owes them.

Japabu and others added 2 commits October 9, 2026 13:42
…795), into the IORT decode and the SMMUv3 and ITS encodings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…ut size bounds what it is given, and the MADT is main's again

Round 2 of the IORT decode and the SMMUv3 and ITS encodings, answering the
review of 286536f.

toyos-acpi. An SMMUv3 node's own DeviceID mapping is the one its DeviceID
mapping index (offset 64) names: where Table 14's bit 4 says the index is
valid from node revision 5, and before it wherever one of the four control
interrupts has no GSIV, as DEN 0049 E.d words it. That entry's input base
and length are not read, and it routes no stream. A single-mapping flag on
any other entry refuses the table by name. An output reference is followed
by the route that needs it and no longer by the open, which walked the nodes
once per mapping: opening reads each node and mapping once, a route walks
the nodes three times at most. The MADT's MSI controllers are gone, and
madt.rs is main's byte for byte. The node's PRI GSIV is not answered, and
its flag is named for what it is, an override.

toyos-phys, new: the one Phys<ALIGN> toyos-smmu and toyos-its each declared.

toyos-smmu. Every address the unit is given is held to IDR5.OAS: the stream
table and both queues by their whole extent, S1ContextPtr, TTB0, a next
table and a leaf's output. Ste and Cd hold their words privately. The
descriptors, the MAIR and PXN are toyos-bootmap's. The queues are two types
made by the unit: Commands, and Events, whose `after` writes OVACKFLG as
OVFLG was read. probe takes the IORT's override and refuses a unit that is
not coherent. Signal::Sev and CMD_CFGI_CD are gone.

toyos-its. An event, a collection and an LPI are types: an event of its
device's EventBits, a collection of what GITS_TYPER (HCC, CCT, CIL, CIDbits)
and the collection table hold, an LPI of a layout GICD_TYPER bounds in a
redistributor whose GICR_PROPBASER read it back. The command queue's
emptiness and fullness take the registers and mask them. A table register
is a Backing that sizes its own pages. GITS_CTLR.Quiescent, HCC as a public
field, the pending bit and the two redistributor offsets the kernel already
declares are gone.

What still has a second declaration in kernel/ is recorded in
issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2, answering the review of 286536fb8 finding by finding. Head f8022b7e2, which merges origin/main at 55e4e1dd2 (#795). The body is rewritten to this head; the gates, the red-first control and the mutations are in it and in the six comments below.

BLOCKER

  1. The SMMUv3 node's own DeviceID mapping — fixed. Iort::own reads DeviceID mapping index (offset 64) where the node says it holds: Table 14's bit 4 from node revision 5, and in a revision 4 node wherever one of the four GSIVs is zero (E.d: "If all the SMMU control interrupts are GSIV based, this field is ignored"). That entry is skipped by the open's range check and by hits, whatever its flags. The single-mapping flag no longer means anything by itself: on any entry that is not the indexed one it is SingleMapping. The two tests were written first and run at 286536fb8 with nothing else changed: both red there (Some(Range { at: 72, base: 4294967295, last: 4294967295 }) where the table must open), exit 101; green at this head. Mutations: iort-own-routes, iort-own-judged, iort-index-flag-bit, iort-index-offset, iort-rev4-ignores-index, iort-rev4-all-unwired, iort-rev4-three-gsivs, iort-rev4-by-flag, iort-rev5-by-gsiv, iort-own-on-any-node, iort-single-accepted, all red.
  2. output_bits enforced — fixed. Unit::reaches(address, bytes) is the one check, and output_bits is private. stream_table, command_queue and event_queue refuse a table or queue any byte of which is at or past 1 << OAS; the test as stated (OAS 0b000, Phys::new(1 << 32), each of the three None) was red at 286536fb8 (Some((4611686022722355200, 0))), exit 101, and is green here. A table larger than the whole output size at address zero is refused too (output-base-alone).
  3. The second MADT walk — deleted. git diff origin/main -- toyos-acpi/src/madt.rs prints nothing; msi_controllers, MsiController, MsiFrame, SpiRange, the Structure split and the three frame tests are gone. Two tests read MadtEntry::Its out of virt's MADTs through main's madt_entries.
  4. Evidence: the rewritten MadtEntries::next — closed by 3. No line a boot runs differs from main: toyos-acpi gains a module the kernel does not call and toyos-bootmap's PXN gains pub. No guest run is claimed.
  5. Siblings.
    • The descriptor bits and MAIR bytes — toyos-smmu now builds its leaves from toyos_bootmap::aarch64::{block, page, table} and takes MAIR and PXN from it (PXN made pub, the only change to that crate). CD.MAIR0 is therefore 0x0044ff04, the loader's attributes 0 to 3, where round 1 wrote 0xff04. What toyos-bootmap does not declare (AP[1], AP[2], nG, the two decode bits, the level index) is still declared in toyos-smmu and in kernel/src/arch/aarch64/paging.rs: recorded.
    • Phys<const ALIGN> — one home, toyos-phys, a third crate. Neither of the two may depend on the other, toyos-bootmap may change only by a re-export and toyos-abi is outside the fence.
    • GICR_CTLR / GICR_TYPER — the two offsets are deleted from toyos-its: irqchip.rs has them and G3 edits it. The three fields toyos-its reads (EnableLPIs, PLPIS, Processor_Number) beside kernel/gicv3's: recorded.
    • The record is issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md, with G2 and G3 as owners and two rg commands as its exit.

NOTE

  • Ste(pub …), Cd(pub …) — private fields, words() to read. Ste::ABORT and Ste::stage1 are the only constructors.
  • S1ContextPtr, TTB0, a leaf's output — Ste::stage1, Cd::new, table::next and table::plan take the unit and answer None past its output size (ste-context-past-output, cd-root-past-output, next-past-output, plan-past-output).
  • after and OVACKFLG — Queue is gone. Unit::command_queue answers a Commands and Unit::event_queue an Events; Events::after(cons, prod) writes OVACKFLG as OVFLG was read in prod, and Events::overflowed(prod, cons) is the condition. Commands::after takes no event index (events-after-clears-the-ack, events-after-never-acks, events-overflow-bit, events-overflowed-by-prod-alone).
  • ITS is_empty / is_full — they and after take the two registers as read and mask them to Offset (its-queue-empty-reads-the-registers, its-queue-full-reads-the-register, its-queue-after-reads-the-register).
  • Shifts, not types — Event from its device's EventBits; Collection from Its::collections, which reads HCC, CCT, CIL and CIDbits and the collection table's entries; Lpi from a Space, which exists only for a Layout the distributor's GICD_TYPER bounds (LPIS, IDbits, num_LPIs) in a redistributor whose GICR_PROPBASER read its IDbits back. The 24-bit cap is gone.
  • Signal::Sev, Smmuv3::pri, Its::collections_held — deleted, and with them Command::ForgetContext, Unit::coherent, the public Unit::output_bits, CTLR_QUIESCENT, Lpi::pending_bit, and GICR_CTLR's and GICR_TYPER's offsets.
  • Coherence — once: probe takes Smmuv3::coherent_override, and a unit neither IDR0.COHACC nor the override calls coherent is Lacks::CoherentAccess.
  • The two refused-but-legal IORT shapes — said so in IortRefused's own comments and in the body. A single mapping on an SMMUv3 entry its index does not name is a third instance of the first shape, and is refused the same way.
  • nodes × mappings — the open no longer follows a reference; the route that needs one does. Opening the test's 417,236-byte table of 500 nodes and 19,960 mappings reads 824,594 bytes of it and a route 14,812; with the reference followed at open again (iort-open-follows-references) the open reads 120,744,274 and the test is red. Neither of the two ways the brief names is taken: both need storage for node offsets, which a crate without allocation would have to cap. The price is that a reference nothing routes through is not judged.
  • "I could not read an older issue" — false, corrected. E.b, E.c, E.d, E.e, E.f and E.g were fetched and each one's table and node revisions are tabulated in the body. E.c, which E.d deprecates, is table revision 4 with the root complex node already at 4.

What the review could not verify

  • INV and DISCARD — verified against Figures 5-9 and 5-7. On each figure's first row DeviceID sits at x=231 between the ruler's "63" (x=159) and "32 31" (x=328), RES0 at 396 and the number at 488; on the second, RES0 at x=237. pypdf reports no position for the EventID label, so its text matrix was read from the page's content stream: x=414.6 on the second row of both figures, right of the 32/31 mark. inv_and_discard_hold_a_device_and_an_event_and_nothing_else states the layout.
  • Register offsets — every SMMU offset against §6.2.1's and §6.2.2's rows and against each register's own "Accessible at offset" line; every ITS offset against Tables 12-33 and 12-34 and each register's own "Component Offset Instance" line; GICR_PROPBASER and GICR_PENDBASER against Table 12-27 and their own lines. Three tests state the tables. No offset was wrong.
  • Section numbers — each one the three crates cite was matched to its heading in the extracted text, in both specifications. None was wrong; GICR_CTLR.EnableLPIs is now cited to its own section, §12.11.2, where round 1 cited §12.11.
  • The descriptor bits, now that they are toyos-bootmap's: read against DDI 0487 M.d Tables D8-52 and D8-63, and the two leaves' tests compare toyos-bootmap's output to that layout (ten bootmap-* mutations, run under -p toyos-smmu, all red).

The body's table says, position by position, which of a field heading, a table row, a figure's label coordinates or a second place in prose each reading rests on.

Two things the round found

  • MAIR0 changed value (above), which a kernel written against round 1's 0xff04 would not have noticed: attribute 2, Normal non-cacheable, is now present and unused.
  • A revision 4 SMMUv3 node with no PRI interrupt has, by E.d's own words, an index that holds, and so loses its first mapping's streams to the unit's "own" entry. virt wires all four. It fails closed, and it is in the body under what I am unsure of.

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

The negative control for the two findings whose tests the review named, round 2: the patch below, which adds tests and nothing else, applied to the reviewed head 286536fb8, and the two suites run there. toyos-acpi's iort tests: 2 failed of 29, exit 101. toyos-smmu: 1 failed of 23, exit 101. The same tests are in the tree at f8022b7e2 and green there (r2-suites.log, exit 0). The worktree's path is written <worktree>.

The patch (tests only)
diff --git a/toyos-acpi/tests/iort.rs b/toyos-acpi/tests/iort.rs
index da1d0a5cd..3632eec77 100644
--- a/toyos-acpi/tests/iort.rs
+++ b/toyos-acpi/tests/iort.rs
@@ -448,6 +448,74 @@ fn a_single_mapping_is_the_smmus_own_message_and_never_a_root_complexs() {
     );
 }
 
+/// An SMMUv3 node of `revision` at `0x0905_0000` with Table 13's flags (24),
+/// its event, PRI, GERR and sync GSIVs (44..60) and its DeviceID mapping
+/// index (64).
+fn smmu_with(revision: u8, flags: u32, gsivs: [u32; 4], index: u32, mappings: &[[u32; 5]]) -> Vec<u8> {
+    let mut fields = vec![0u8; 52];
+    fields[..8].copy_from_slice(&0x0905_0000u64.to_le_bytes());
+    fields[8..12].copy_from_slice(&flags.to_le_bytes());
+    for (i, gsiv) in gsivs.iter().enumerate() {
+        fields[28 + 4 * i..32 + 4 * i].copy_from_slice(&gsiv.to_le_bytes());
+    }
+    fields[48..52].copy_from_slice(&index.to_le_bytes());
+    node(4, revision, &fields, mappings)
+}
+
+/// `virt`'s four GSIVs: every control interrupt wired.
+const WIRED: [u32; 4] = [106, 107, 109, 108];
+/// Table 14, bit 4: `DeviceID mapping index valid`.
+const INDEX_VALID: u32 = 1 << 4;
+
+/// An ITS of id 7, `smmu`, and a root complex sending requesters
+/// `0x00..=0xff` to it as the same streams.
+fn behind(smmu: Vec<u8>) -> Vec<u8> {
+    table(5, &[its(7), smmu, root_complex(0, &[[0, 0xff, 0, SMMU_AT, 0]])])
+}
+
+/// Where the SMMUv3's own node sends stream 8.
+fn onward(table: &[u8]) -> Option<ItsDevice> {
+    match route(table, 0, 8) {
+        Ok(Route::Translated { stream: 8, its, .. }) => its,
+        other => panic!("requester 8 is stream 8 of the SMMUv3: {other:?}"),
+    }
+}
+
+#[test]
+fn the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream() {
+    // An input base and length no range holds: they are not the table's to judge.
+    let wide = behind(smmu_with(5, INDEX_VALID, WIRED, 0, &[[0xffff_ffff, 0xffff_ffff, 0x30, ITS_AT, 0]]));
+    assert_eq!(refusal(&wide), None);
+    assert_eq!(onward(&wide), None);
+    // And ones that would hold the stream: the entry is the unit's DeviceID and no stream's.
+    let plausible = behind(smmu_with(5, INDEX_VALID, WIRED, 0, &[[0, 0xffff, 0x30, ITS_AT, 0]]));
+    assert_eq!(onward(&plausible), None);
+    // The index picks the entry, and the other one still routes.
+    let second = smmu_with(5, INDEX_VALID, WIRED, 1, &[[0, 0xff, 0x100, ITS_AT, 0], [0, 0xffff, 0x30, ITS_AT, 0]]);
+    assert_eq!(onward(&behind(second)), Some(ItsDevice { its: 7, device: 0x108 }));
+    // Flag clear, the index is ignored whatever the GSIVs say.
+    let ignored = behind(smmu_with(5, 0, [0; 4], 0, &[[0, 0xff, 0x100, ITS_AT, 0]]));
+    assert_eq!(onward(&ignored), Some(ItsDevice { its: 7, device: 0x108 }));
+}
+
+#[test]
+fn a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired() {
+    for unwired in 0..4 {
+        let mut gsivs = WIRED;
+        gsivs[unwired] = 0;
+        let own = behind(smmu_with(4, 0, gsivs, 0, &[[0xffff_ffff, 0xffff_ffff, 0x30, ITS_AT, 0]]));
+        assert_eq!(refusal(&own), None, "GSIV {unwired} zero");
+        assert_eq!(onward(&own), None, "GSIV {unwired} zero");
+        let plausible = behind(smmu_with(4, 0, gsivs, 0, &[[0, 0xffff, 0x30, ITS_AT, 0]]));
+        assert_eq!(onward(&plausible), None, "GSIV {unwired} zero");
+    }
+    // Every one wired, as on `virt`: the field is ignored, and bit 4 of the flags is reserved here.
+    for flags in [0, INDEX_VALID] {
+        let wired = behind(smmu_with(4, flags, WIRED, 0, &[[0, 0xff, 0x100, ITS_AT, 0]]));
+        assert_eq!(onward(&wired), Some(ItsDevice { its: 7, device: 0x108 }));
+    }
+}
+
 #[test]
 fn an_its_group_of_other_than_one_its_is_refused() {
     for count in [0u32, 2] {
diff --git a/toyos-smmu/tests/encodings.rs b/toyos-smmu/tests/encodings.rs
index 2ede94eac..512313193 100644
--- a/toyos-smmu/tests/encodings.rs
+++ b/toyos-smmu/tests/encodings.rs
@@ -278,6 +278,18 @@ fn a_queue_is_inside_the_units_size_and_aligned_to_its_own() {
     assert_eq!((small.command_queue(low, 5), small.event_queue(low, 5)), (None, None));
 }
 
+/// §6.3.24: the bits of `ADDR` above `IDR5.OAS` are RES0, and a unit need not
+/// store them, so it would read its table or queue at the address without
+/// them.
+#[test]
+fn a_table_or_queue_past_the_units_output_size_is_refused() {
+    let unit = idr(&[], &[(20, 16, 19)], &[(2, 0, 0b000)]).expect("a unit of 32 output bits");
+    let past = 1u64 << 32;
+    assert_eq!(unit.stream_table(Phys::new(past).unwrap(), 0), None);
+    assert_eq!(unit.command_queue(Phys::new(past).unwrap(), 0), None);
+    assert_eq!(unit.event_queue(Phys::new(past).unwrap(), 0), None);
+}
+
 // --- §3.5.1: the queues' indexes --------------------------------------------
 
 /// Against a count kept beside it: empty exactly at zero entries, full
The log at 286536f
head 286536fb8606852bce23dcf10b0120b60691797c, with r2-red-first.patch (tests only) applied
   Compiling toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Finished `test` profile [optimized + debuginfo] target(s) in 1.68s
     Running tests/iort.rs (target/debug/deps/iort-41214c1b48b4fa19)

running 29 tests
test a_frame_names_its_spis_only_where_its_flag_says_the_structure_does ... ok
test a_mapping_carries_an_id_by_its_offset_from_the_input_base_and_ends_at_its_last_id ... ok
test a_node_array_inside_the_header_is_refused ... ok
test a_mapping_whose_range_leaves_the_id_space_is_refused ... ok
test a_node_array_past_the_table_is_refused_as_its_first_node ... ok
test a_node_count_that_overruns_the_table_is_refused_where_the_bytes_end ... ok
test a_node_longer_than_the_table_or_shorter_than_its_type_is_refused ... ok
test a_frame_too_short_for_its_fields_is_no_controller_and_a_halt_ends_the_walk ... ok
test a_node_type_not_decoded_is_answered_by_its_type_and_refuses_nothing ... ok
test a_node_revision_whose_layout_was_not_read_is_refused ... ok
test a_revision_whose_layout_was_not_read_is_refused ... ok
test a_single_mapping_is_the_smmus_own_message_and_never_a_root_complexs ... ok
test a_stream_the_smmu_maps_nowhere_has_no_its ... ok
test a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired ... FAILED
test a_table_too_short_for_its_own_header_fields_is_refused ... ok
test an_id_array_lying_over_its_nodes_own_fields_is_refused ... ok
test an_id_array_running_past_its_node_is_refused ... ok
test an_id_two_mappings_claim_is_refused_when_it_is_asked_for ... ok
test an_its_group_of_other_than_one_its_is_refused ... ok
test an_its_node_declaring_an_id_array_is_refused ... ok
test an_output_reference_that_names_no_node_is_refused ... ok
test an_output_reference_to_a_node_its_source_may_not_reach_is_refused ... ok
test the_controllers_come_in_the_tables_order_past_every_other_structure ... ok
test the_hardware_gic_boot_names_a_v2m_frame_and_no_its ... ok
test the_hardware_gic_boot_routes_bus_zero_through_the_smmu_and_nothing_to_an_its ... ok
test the_its_boot_names_the_its_the_iort_routes_to ... ok
test the_its_boot_routes_bus_zero_through_the_smmu_to_the_its_and_the_rest_past_it ... ok
test the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream ... FAILED
test no_corruption_of_a_published_iort_panics_or_hangs_the_decoder ... ok

failures:

---- a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired stdout ----

thread 'a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired' (213038774) panicked at toyos-acpi/tests/iort.rs:507:9:
assertion `left == right` failed: GSIV 0 zero
  left: Some(Range { at: 72, base: 4294967295, last: 4294967295 })
 right: None
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream stdout ----

thread 'the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream' (213038792) panicked at toyos-acpi/tests/iort.rs:488:5:
assertion `left == right` failed
  left: Some(Range { at: 72, base: 4294967295, last: 4294967295 })
 right: None


failures:
    a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired
    the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream

test result: FAILED. 27 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s

error: test failed, to rerun pass `-p toyos-acpi --test iort`
error: 1 target failed:
    `-p toyos-acpi --test iort`
EXIT=101

--- toyos-smmu, same tree: 286536fb8606852bce23dcf10b0120b60691797c with r2-red-first.patch
   Compiling toyos-smmu v0.1.0 (<worktree>/toyos-smmu)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.52s
     Running unittests src/lib.rs (target/debug/deps/toyos_smmu-99c7673da8a8af84)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/encodings.rs (target/debug/deps/encodings-012b7e1502d00a41)

running 23 tests
test a_descriptor_read_back_is_a_table_only_above_level_3 ... ok
test a_command_error_is_the_cons_registers_err_field_alone ... ok
test a_context_descriptor_walks_ttb0_alone_under_its_own_asid ... ok
test a_queue_is_inside_the_units_size_and_aligned_to_its_own ... ok
test a_context_descriptors_ips_is_the_units_output_size_and_no_more_than_48_bits ... ok
test a_queues_indexes_say_empty_full_and_slot_as_a_count_would ... ok
test a_stream_table_is_linear_inside_the_units_streams_and_aligned_to_its_size ... ok
test a_reserved_size_is_refused_and_the_largest_defined_one_is_not ... ok
test a_translation_fault_names_its_stream_its_address_and_whether_it_wrote ... ok
test a_translating_entry_is_stage_1_alone_through_one_context_descriptor ... ok
test a_unit_that_lacks_what_the_configuration_needs_is_refused_by_name ... ok
test a_units_sizes_are_read_from_their_own_fields ... ok
test an_aborting_entry_is_valid_and_configured_to_pass_nothing ... ok
test a_table_or_queue_past_the_units_output_size_is_refused ... FAILED
test an_address_off_its_alignment_or_past_48_bits_is_no_address ... ok
test an_address_past_the_input_or_off_the_leafs_alignment_has_no_plan ... ok
test an_asid_is_no_wider_than_the_units ... ok
test an_error_is_active_while_the_two_registers_differ_in_its_bit ... ok
test each_command_holds_its_opcode_and_parameters_where_its_diagram_has_them ... ok
test memory_is_a_level_2_block_only_its_own_context_and_an_unprivileged_device_reach ... ok
test the_control_values_hold_each_field_where_its_register_has_it ... ok
test the_doorbell_is_one_level_3_page_of_device_memory ... ok
test every_event_number_is_its_own_code_and_only_a_fault_carries_an_attempt ... ok

failures:

---- a_table_or_queue_past_the_units_output_size_is_refused stdout ----

thread 'a_table_or_queue_past_the_units_output_size_is_refused' (213041989) panicked at toyos-smmu/tests/encodings.rs:288:5:
assertion `left == right` failed
  left: Some((4611686022722355200, 0))
 right: None
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    a_table_or_queue_past_the_units_output_size_is_refused

test result: FAILED. 22 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `-p toyos-smmu --test encodings`
   Doc-tests toyos_smmu

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: 1 target failed:
    `-p toyos-smmu --test encodings`
EXIT(toyos-smmu)=101

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations at f8022b7, round 2, of toyos-acpi/src/iort.rs (run with cargo test -p toyos-acpi): each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p <crate> --no-run), run (cargo test -p <crate>) and reverted in the same script, the tree clean before and after each. 39 mutations, 39 red.

iort-table-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-array-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-past-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-too-short: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-node-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-its-revision: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-array-over-fields: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-rev4-fields: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-array-past-node: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-its-with-array: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-range-wraps: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-smmu-onward-any-kind: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-root-output-any-kind: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-output-mid-node: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-single-accepted: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-own-routes: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-own-judged: build EXIT=0 test EXIT=101 RED (3 tests failed)
iort-index-flag-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-index-offset: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-rev4-ignores-index: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-rev4-all-unwired: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-rev4-three-gsivs: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-rev4-by-flag: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-rev5-by-gsiv: build EXIT=0 test EXIT=101 RED (7 tests failed)
iort-own-on-any-node: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-open-follows-references: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-its-group: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-overlap-root: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-overlap-smmu: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-span-exclusive: build EXIT=0 test EXIT=101 RED (5 tests failed)
iort-output-not-offset: build EXIT=0 test EXIT=101 RED (4 tests failed)
iort-any-segment: build EXIT=0 test EXIT=101 RED (4 tests failed)
iort-gsiv-order: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-gerror-is-pri: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-cohacc-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-segment-offset: build EXIT=0 test EXIT=101 RED (2 tests failed)
iort-smmu-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
iort-untranslated-id: build EXIT=0 test EXIT=101 RED (2 tests failed)
The patches
# iort-table-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -188,7 +188,7 @@
     let table = find_table(phys, rsdp_addr, b"IORT", IORT_NEEDED)?;
     let short = TableError::Length { declared: table.len() as u32, needed: IORT_NEEDED };
     let revision = table.byte(SDT_REVISION).ok_or(short)?;
-    if !REVISIONS.contains(&revision) {
+    if false {
         return Err(IortRefused::Revision(revision));
     }
     let nodes = table.u32_at(NODE_COUNT).ok_or(short)?;
# iort-node-array-floor
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -193,7 +193,7 @@
     }
     let nodes = table.u32_at(NODE_COUNT).ok_or(short)?;
     let offset = table.u32_at(NODE_ARRAY).ok_or(short)?;
-    if (offset as usize) < IORT_NEEDED {
+    if false {
         return Err(IortRefused::NodeArray { offset });
     }
     let iort = Iort { table, nodes, array: offset as usize };
# iort-node-past-table
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -311,7 +311,7 @@
             SMMUV3 => SMMU_LEN,
             _ => NODE_HEADER,
         };
-        if len < floor || at + len > t.len() {
+        if len < floor {
             return Err(IortRefused::Node { at, declared: len });
         }
         Ok(Raw { at, kind, len, revision, mappings, array })
# iort-node-too-short
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -311,7 +311,7 @@
             SMMUV3 => SMMU_LEN,
             _ => NODE_HEADER,
         };
-        if len < floor || at + len > t.len() {
+        if at + len > t.len() {
             return Err(IortRefused::Node { at, declared: len });
         }
         Ok(Raw { at, kind, len, revision, mappings, array })
# iort-node-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -367,7 +367,7 @@
                 Node::RootComplex { segment: t.u32_at(at + RC_SEGMENT).ok_or(short)? }
             }
             SMMUV3 => {
-                revised(4..=5)?;
+                revised(0..=255)?;
                 // Event, PRI, GERR, sync: the PRI queue is not used, and its GSIV not answered.
                 let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * i).map(NonZeroU32::new).ok_or(short);
                 Node::Smmuv3(Smmuv3 {
# iort-root-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -363,7 +363,7 @@
                 Node::Its { id: t.u32_at(at + NODE_HEADER + 4).ok_or(short)? }
             }
             ROOT_COMPLEX => {
-                revised(3..=4)?;
+                revised(0..=255)?;
                 Node::RootComplex { segment: t.u32_at(at + RC_SEGMENT).ok_or(short)? }
             }
             SMMUV3 => {
# iort-its-revision
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -355,7 +355,7 @@
         };
         Ok(match kind {
             ITS => {
-                revised(1..=1)?;
+                revised(0..=255)?;
                 let count = t.u32_at(at + NODE_HEADER).ok_or(short)?;
                 if count != 1 {
                     return Err(IortRefused::ItsGroup { at, count });
# iort-array-over-fields
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -395,7 +395,7 @@
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-        let misplaced = (offset as usize) < fields || end > raw.len as u64;
+        let misplaced = end > raw.len as u64;
         if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
# iort-root-rev4-fields
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -390,7 +390,7 @@
             (Node::Other(_), _) => return Ok(()),
             (Node::Its { .. }, _) => ITS_LEN,
             (Node::RootComplex { .. }, 3) => 36,
-            (Node::RootComplex { .. }, _) => 40,
+            (Node::RootComplex { .. }, _) => 36,
             (Node::Smmuv3(_), _) => SMMU_LEN,
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
# iort-array-past-node
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -395,7 +395,7 @@
         };
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-        let misplaced = (offset as usize) < fields || end > raw.len as u64;
+        let misplaced = (offset as usize) < fields;
         if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
# iort-its-with-array
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -396,7 +396,7 @@
         let Raw { at, mappings: count, array: offset, .. } = raw;
         let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
         let misplaced = (offset as usize) < fields || end > raw.len as u64;
-        if count != 0 && (misplaced || matches!(node, Node::Its { .. })) {
+        if count != 0 && (misplaced) {
             return Err(IortRefused::Mappings { at, count, offset });
         }
         let own = self.own(raw);
# iort-range-wraps
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -406,7 +406,7 @@
                 return Err(IortRefused::SingleMapping { at });
             }
             for base in [mapping.input, mapping.output] {
-                if base.checked_add(mapping.span).is_none() {
+                if false {
                     return Err(IortRefused::Range { at, base, last: mapping.span });
                 }
             }
# iort-smmu-onward-any-kind
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -241,7 +241,7 @@
                     None => None,
                     Some((mapping, device)) => match self.output(unit, mapping)? {
                         (_, Node::Its { id: its }) => Some(ItsDevice { its, device }),
-                        _ => return Err(IortRefused::Output { at: unit.at, reference: mapping.reference }),
+                        _ => None,
                     },
                 };
                 Ok(Route::Translated { smmu, stream: id, its })
# iort-root-output-any-kind
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -246,7 +246,7 @@
                 };
                 Ok(Route::Translated { smmu, stream: id, its })
             }
-            _ => Err(IortRefused::Output { at: root.at, reference: mapping.reference }),
+            _ => Ok(Route::Unmapped),
         }
     }
 
# iort-output-mid-node
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -335,7 +335,7 @@
         let refused = IortRefused::Output { at: from.at, reference: mapping.reference };
         for raw in self.raw_nodes() {
             let raw = raw?;
-            if raw.at == mapping.reference as usize {
+            if raw.at <= mapping.reference as usize && raw.at + raw.len > mapping.reference as usize {
                 return Ok((raw, self.decode(raw)?));
             }
         }
# iort-single-accepted
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -402,7 +402,7 @@
         let own = self.own(raw);
         for index in (0..count).filter(|index| Some(*index) != own) {
             let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
-            if mapping.single {
+            if false {
                 return Err(IortRefused::SingleMapping { at });
             }
             for base in [mapping.input, mapping.output] {
# iort-own-routes
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -274,7 +274,7 @@
     /// The mappings of `raw` that hold `id`, each with the ID it puts out.
     fn hits(&self, raw: Raw, id: u32) -> impl Iterator<Item = (Mapping, u32)> + '_ {
         let own = self.own(raw);
-        (0..raw.mappings).filter(move |index| Some(*index) != own).filter_map(move |index| {
+        (0..raw.mappings).filter(move |index| Some(*index) != own || true).filter_map(move |index| {
             let mapping = self.mapping(raw, index)?;
             Some((mapping, mapping.maps(id)?))
         })
# iort-own-judged
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -400,7 +400,7 @@
             return Err(IortRefused::Mappings { at, count, offset });
         }
         let own = self.own(raw);
-        for index in (0..count).filter(|index| Some(*index) != own) {
+        for index in (0..count).filter(|index| Some(*index) != own || true) {
             let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
             if mapping.single {
                 return Err(IortRefused::SingleMapping { at });
# iort-index-flag-bit
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -59,7 +59,7 @@
 /// Table 14, bit 0: `COHACC override`.
 const SMMU_COHACC: u32 = 1 << 0;
 /// Table 14, bit 4, from node revision 5: `DeviceID mapping index valid`.
-const SMMU_INDEXED: u32 = 1 << 4;
+const SMMU_INDEXED: u32 = 1 << 3;
 
 /// Why an IORT cannot be used. `at` is a node's offset in the table.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# iort-index-offset
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -54,7 +54,7 @@
 const SMMU_BASE: usize = 16;
 const SMMU_FLAGS: usize = 24;
 const SMMU_GSIVS: usize = 44;
-const SMMU_INDEX: usize = 64;
+const SMMU_INDEX: usize = 60;
 const SMMU_LEN: usize = 68;
 /// Table 14, bit 0: `COHACC override`.
 const SMMU_COHACC: u32 = 1 << 0;
# iort-rev4-ignores-index
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -262,7 +262,7 @@
         let indexed = if raw.revision >= 5 {
             t.u32_at(raw.at + SMMU_FLAGS)? & SMMU_INDEXED != 0
         } else {
-            (0..4).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
+            false
         };
         if indexed {
             t.u32_at(raw.at + SMMU_INDEX)
# iort-rev4-all-unwired
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -262,7 +262,7 @@
         let indexed = if raw.revision >= 5 {
             t.u32_at(raw.at + SMMU_FLAGS)? & SMMU_INDEXED != 0
         } else {
-            (0..4).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
+            (0..4).all(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
         };
         if indexed {
             t.u32_at(raw.at + SMMU_INDEX)
# iort-rev4-three-gsivs
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -262,7 +262,7 @@
         let indexed = if raw.revision >= 5 {
             t.u32_at(raw.at + SMMU_FLAGS)? & SMMU_INDEXED != 0
         } else {
-            (0..4).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
+            (0..3).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
         };
         if indexed {
             t.u32_at(raw.at + SMMU_INDEX)
# iort-rev4-by-flag
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -259,7 +259,7 @@
         if raw.kind != SMMUV3 {
             return None;
         }
-        let indexed = if raw.revision >= 5 {
+        let indexed = if raw.revision >= 4 {
             t.u32_at(raw.at + SMMU_FLAGS)? & SMMU_INDEXED != 0
         } else {
             (0..4).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
# iort-rev5-by-gsiv
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -259,7 +259,7 @@
         if raw.kind != SMMUV3 {
             return None;
         }
-        let indexed = if raw.revision >= 5 {
+        let indexed = if raw.revision >= 6 {
             t.u32_at(raw.at + SMMU_FLAGS)? & SMMU_INDEXED != 0
         } else {
             (0..4).any(|i| t.u32_at(raw.at + SMMU_GSIVS + 4 * i) == Some(0))
# iort-own-on-any-node
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -256,7 +256,7 @@
     /// The entry's input base and length are not read.
     fn own(&self, raw: Raw) -> Option<u32> {
         let t = &self.table;
-        if raw.kind != SMMUV3 {
+        if false {
             return None;
         }
         let indexed = if raw.revision >= 5 {
# iort-open-follows-references
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -405,6 +405,7 @@
             if mapping.single {
                 return Err(IortRefused::SingleMapping { at });
             }
+            self.output(raw, mapping)?;
             for base in [mapping.input, mapping.output] {
                 if base.checked_add(mapping.span).is_none() {
                     return Err(IortRefused::Range { at, base, last: mapping.span });
# iort-its-group
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -357,7 +357,7 @@
             ITS => {
                 revised(1..=1)?;
                 let count = t.u32_at(at + NODE_HEADER).ok_or(short)?;
-                if count != 1 {
+                if false {
                     return Err(IortRefused::ItsGroup { at, count });
                 }
                 Node::Its { id: t.u32_at(at + NODE_HEADER + 4).ok_or(short)? }
# iort-overlap-root
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -220,7 +220,7 @@
                 continue;
             }
             for found in self.hits(raw, rid) {
-                if hit.replace((raw, found)).is_some() {
+                if hit.replace((raw, found)).is_some() && false {
                     return Err(IortRefused::Overlap { id: rid });
                 }
             }
# iort-overlap-smmu
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -233,7 +233,7 @@
             (unit, Node::Smmuv3(smmu)) => {
                 let mut onward = None;
                 for found in self.hits(unit, id) {
-                    if onward.replace(found).is_some() {
+                    if onward.replace(found).is_some() && false {
                         return Err(IortRefused::Overlap { id });
                     }
                 }
# iort-span-exclusive
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -171,7 +171,7 @@
 impl Mapping {
     fn maps(&self, id: u32) -> Option<u32> {
         // `output + span` was checked not to wrap when the table was opened.
-        (id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
+        (id >= self.input && id - self.input < self.span).then(|| self.output + (id - self.input))
     }
 }
 
# iort-output-not-offset
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -171,7 +171,7 @@
 impl Mapping {
     fn maps(&self, id: u32) -> Option<u32> {
         // `output + span` was checked not to wrap when the table was opened.
-        (id >= self.input && id - self.input <= self.span).then(|| self.output + (id - self.input))
+        (id >= self.input && id - self.input <= self.span).then(|| self.output.wrapping_add(id))
     }
 }
 
# iort-any-segment
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -216,7 +216,7 @@
         let mut hit = None;
         for raw in self.raw_nodes() {
             let raw = raw?;
-            if self.decode(raw)? != (Node::RootComplex { segment }) {
+            if !matches!(self.decode(raw)?, Node::RootComplex { .. }) {
                 continue;
             }
             for found in self.hits(raw, rid) {
# iort-gsiv-order
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -369,7 +369,7 @@
             SMMUV3 => {
                 revised(4..=5)?;
                 // Event, PRI, GERR, sync: the PRI queue is not used, and its GSIV not answered.
-                let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * i).map(NonZeroU32::new).ok_or(short);
+                let gsiv = |i: usize| t.u32_at(at + SMMU_GSIVS + 4 * (3 - i)).map(NonZeroU32::new).ok_or(short);
                 Node::Smmuv3(Smmuv3 {
                     base: t.u64_at(at + SMMU_BASE).ok_or(short)?,
                     coherent_override: t.u32_at(at + SMMU_FLAGS).ok_or(short)? & SMMU_COHACC != 0,
# iort-gerror-is-pri
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -374,7 +374,7 @@
                     base: t.u64_at(at + SMMU_BASE).ok_or(short)?,
                     coherent_override: t.u32_at(at + SMMU_FLAGS).ok_or(short)? & SMMU_COHACC != 0,
                     event: gsiv(0)?,
-                    gerror: gsiv(2)?,
+                    gerror: gsiv(1)?,
                     sync: gsiv(3)?,
                 })
             }
# iort-cohacc-bit
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -57,7 +57,7 @@
 const SMMU_INDEX: usize = 64;
 const SMMU_LEN: usize = 68;
 /// Table 14, bit 0: `COHACC override`.
-const SMMU_COHACC: u32 = 1 << 0;
+const SMMU_COHACC: u32 = 1 << 1;
 /// Table 14, bit 4, from node revision 5: `DeviceID mapping index valid`.
 const SMMU_INDEXED: u32 = 1 << 4;
 
# iort-segment-offset
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -47,7 +47,7 @@
 /// Table 18: memory access properties (16..24), ATS attribute (24..28), PCI
 /// segment number (28..32), then the address size limit and, from node
 /// revision 4, the PASID capabilities and flags.
-const RC_SEGMENT: usize = 28;
+const RC_SEGMENT: usize = 24;
 /// Table 13: base address (16..24), flags (24..28), a reserved word, VATOS
 /// (32..40), model (40..44), the event, PRI, GERR and sync GSIVs (44..60),
 /// proximity domain (60..64), DeviceID mapping index (64..68).
# iort-smmu-floor
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -55,7 +55,7 @@
 const SMMU_FLAGS: usize = 24;
 const SMMU_GSIVS: usize = 44;
 const SMMU_INDEX: usize = 64;
-const SMMU_LEN: usize = 68;
+const SMMU_LEN: usize = 64;
 /// Table 14, bit 0: `COHACC override`.
 const SMMU_COHACC: u32 = 1 << 0;
 /// Table 14, bit 4, from node revision 5: `DeviceID mapping index valid`.
# iort-untranslated-id
--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -229,7 +229,7 @@
             return Ok(Route::Unmapped);
         };
         match self.output(root, mapping)? {
-            (_, Node::Its { id: its }) => Ok(Route::Untranslated(ItsDevice { its, device: id })),
+            (_, Node::Its { id: its }) => Ok(Route::Untranslated(ItsDevice { its, device: rid })),
             (unit, Node::Smmuv3(smmu)) => {
                 let mut onward = None;
                 for found in self.hits(unit, id) {

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations at f8022b7, round 2, of toyos-bootmap/src/aarch64.rs, toyos-smmu/src/config.rs, toyos-smmu/src/table.rs (run with cargo test -p toyos-smmu): each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p <crate> --no-run), run (cargo test -p <crate>) and reverted in the same script, the tree clean before and after each. 51 mutations, 51 red.

ste-abort-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-bypass: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stage2: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-always: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-stalld-never: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-cd-attributes: build EXIT=0 test EXIT=101 RED (1 tests failed)
ste-context-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-root-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-ttb1-walked: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-shared-asid-set: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-raz-wi: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-unrecorded: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-stalls: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-asid-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
cd-t0sz: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-aarch32: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-ips-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
cd-ttb0-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
cd-mair-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-global: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-privileged-only: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-executable-at-el1: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-ap1-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-ng-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
leaf-read-is-writable: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-write-is-read-only: build EXIT=0 test EXIT=101 RED (1 tests failed)
leaf-memory-as-device: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-as-block: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-as-memory: build EXIT=0 test EXIT=101 RED (1 tests failed)
doorbell-at-level-2: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-unaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-past-input: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
plan-index-shift: build EXIT=0 test EXIT=101 RED (2 tests failed)
plan-index-width: build EXIT=0 test EXIT=101 RED (2 tests failed)
entry-page-as-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
entry-invalid-as-table: build EXIT=0 test EXIT=101 RED (1 tests failed)
next-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
mair0-shifted: build EXIT=0 test EXIT=101 RED (2 tests failed)
bootmap-mair-order: build EXIT=0 test EXIT=101 RED (2 tests failed)
bootmap-table-bits: build EXIT=0 test EXIT=101 RED (1 tests failed)
bootmap-page-bits: build EXIT=0 test EXIT=101 RED (1 tests failed)
bootmap-block-bits: build EXIT=0 test EXIT=101 RED (2 tests failed)
bootmap-no-access-flag: build EXIT=0 test EXIT=101 RED (2 tests failed)
bootmap-memory-shareability: build EXIT=0 test EXIT=101 RED (1 tests failed)
bootmap-memory-attribute: build EXIT=0 test EXIT=101 RED (1 tests failed)
bootmap-device-executable: build EXIT=0 test EXIT=101 RED (1 tests failed)
bootmap-pxn-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
bootmap-uxn-bit: build EXIT=0 test EXIT=101 RED (2 tests failed)
The patches
# ste-abort-not-valid
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -26,7 +26,7 @@
     /// `V` set and `Config` `0b000`: every transaction of the stream is
     /// aborted, and no event is recorded for it. An entry of all zeroes
     /// aborts too, and records `C_BAD_STE` each time.
-    pub const ABORT: Self = Self([STE_V, 0, 0, 0, 0, 0, 0, 0]);
+    pub const ABORT: Self = Self([0, 0, 0, 0, 0, 0, 0, 0]);
 
     /// The stream translated by stage 1 through the one context descriptor
     /// at `context`: `S1ContextPtr` [55:6], `S1CDMax` [63:59] zero so a
# ste-bypass
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -14,7 +14,7 @@
 /// `V`, bit [0].
 const STE_V: u64 = 1 << 0;
 /// `Config`, bits [3:1]: `0b101` is stage 1 translate, stage 2 bypass.
-const STE_STAGE1: u64 = 0b101 << 1;
+const STE_STAGE1: u64 = 0b100 << 1;
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
# ste-stage2
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -14,7 +14,7 @@
 /// `V`, bit [0].
 const STE_V: u64 = 1 << 0;
 /// `Config`, bits [3:1]: `0b101` is stage 1 translate, stage 2 bypass.
-const STE_STAGE1: u64 = 0b101 << 1;
+const STE_STAGE1: u64 = 0b110 << 1;
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
# ste-stalld-bit
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -20,7 +20,7 @@
 /// reads the context descriptor.
 const STE_CD_CACHED: u64 = 0b01 << 2 | 0b01 << 4 | 0b11 << 6;
 /// `S1STALLD`, bit [91].
-const STE_S1STALLD: u64 = 1 << 27;
+const STE_S1STALLD: u64 = 1 << 26;
 
 impl Ste {
     /// `V` set and `Config` `0b000`: every transaction of the stream is
# ste-stalld-always
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -38,7 +38,7 @@
         if !unit.reaches(context.get(), 64) {
             return None;
         }
-        let stall = if unit.stall_disable() { STE_S1STALLD } else { 0 };
+        let stall = STE_S1STALLD;
         Some(Self([STE_V | STE_STAGE1 | context.get(), STE_CD_CACHED | stall, 0, 0, 0, 0, 0, 0]))
     }
 
# ste-stalld-never
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -38,7 +38,7 @@
         if !unit.reaches(context.get(), 64) {
             return None;
         }
-        let stall = if unit.stall_disable() { STE_S1STALLD } else { 0 };
+        let stall = 0;
         Some(Self([STE_V | STE_STAGE1 | context.get(), STE_CD_CACHED | stall, 0, 0, 0, 0, 0, 0]))
     }
 
# ste-cd-attributes
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -18,7 +18,7 @@
 /// Doubleword 1: `S1CIR` [67:66] and `S1COR` [69:68] `0b01`, write-back
 /// read-allocate, and `S1CSH` [71:70] `0b11`, inner shareable — how the unit
 /// reads the context descriptor.
-const STE_CD_CACHED: u64 = 0b01 << 2 | 0b01 << 4 | 0b11 << 6;
+const STE_CD_CACHED: u64 = 0b01 << 0 | 0b01 << 2 | 0b11 << 4;
 /// `S1STALLD`, bit [91].
 const STE_S1STALLD: u64 = 1 << 27;
 
# ste-context-past-output
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -35,7 +35,7 @@
     /// no stage 2 field. `None` where the descriptor is past the unit's
     /// output size.
     pub const fn stage1(context: Phys<6>, unit: &Unit) -> Option<Self> {
-        if !unit.reaches(context.get(), 64) {
+        if false {
             return None;
         }
         let stall = if unit.stall_disable() { STE_S1STALLD } else { 0 };
# cd-root-past-output
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -80,7 +80,7 @@
     /// leaf written sets its access flag. `None` where the root table is
     /// past the unit's output size.
     pub const fn new(root: Phys<12>, asid: Asid, unit: &Unit) -> Option<Self> {
-        if !unit.reaches(root.get(), 4096) {
+        if false {
             return None;
         }
         let word0 = CD_T0SZ
# cd-ttb1-walked
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -85,7 +85,6 @@
         }
         let word0 = CD_T0SZ
             | CD_WALK
-            | CD_EPD1
             | CD_V
             | unit.ips() << 32
             | CD_AA64
# cd-not-valid
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -86,7 +86,6 @@
         let word0 = CD_T0SZ
             | CD_WALK
             | CD_EPD1
-            | CD_V
             | unit.ips() << 32
             | CD_AA64
             | CD_FAULTS
# cd-shared-asid-set
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -90,7 +90,6 @@
             | unit.ips() << 32
             | CD_AA64
             | CD_FAULTS
-            | CD_ASET
             | (asid.get() as u64) << 48;
         Some(Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0]))
     }
# cd-raz-wi
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -68,7 +68,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 45;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-unrecorded
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -68,7 +68,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 46;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-stalls
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -68,7 +68,7 @@
 /// `R` [45] records a translation-related fault and `A` [46] aborts its
 /// transaction rather than completing it as read-as-zero, write-ignored;
 /// `S` [44] stays clear, so none stalls (§5.5).
-const CD_FAULTS: u64 = 1 << 45 | 1 << 46;
+const CD_FAULTS: u64 = 1 << 44 | 1 << 45 | 1 << 46;
 /// `ASET`, bit [47]: the ASID is the unit's own and no CPU process's.
 const CD_ASET: u64 = 1 << 47;
 
# cd-asid-field
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -91,7 +91,7 @@
             | CD_AA64
             | CD_FAULTS
             | CD_ASET
-            | (asid.get() as u64) << 48;
+            | (asid.get() as u64) << 32;
         Some(Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0]))
     }
 
# cd-t0sz
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -53,7 +53,7 @@
 pub struct Cd([u64; 8]);
 
 /// `T0SZ`, bits [5:0]: a 48-bit input, walked from level 0.
-const CD_T0SZ: u64 = 64 - crate::table::INPUT_BITS as u64;
+const CD_T0SZ: u64 = crate::table::INPUT_BITS as u64;
 /// `TG0` [7:6] zero is the 4 KiB granule; `IR0` [9:8] and `OR0` [11:10]
 /// `0b01`, write-back write-allocate, and `SH0` [13:12] `0b11`, inner
 /// shareable — how the unit walks the tables; `EPD0` [14] and `ENDI` [15]
# cd-aarch32
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -88,7 +88,6 @@
             | CD_EPD1
             | CD_V
             | unit.ips() << 32
-            | CD_AA64
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
# cd-ips-field
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -87,7 +87,7 @@
             | CD_WALK
             | CD_EPD1
             | CD_V
-            | unit.ips() << 32
+            | unit.ips() << 33
             | CD_AA64
             | CD_FAULTS
             | CD_ASET
# cd-ttb0-word
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -92,7 +92,7 @@
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
-        Some(Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0]))
+        Some(Self([word0, 0, root.get(), crate::table::MAIR0 as u64, 0, 0, 0, 0]))
     }
 
     /// The descriptor's eight doublewords.
# cd-mair-word
--- a/toyos-smmu/src/config.rs
+++ b/toyos-smmu/src/config.rs
@@ -92,7 +92,7 @@
             | CD_FAULTS
             | CD_ASET
             | (asid.get() as u64) << 48;
-        Some(Self([word0, root.get(), 0, crate::table::MAIR0 as u64, 0, 0, 0, 0]))
+        Some(Self([word0, root.get(), crate::table::MAIR0 as u64, 0, 0, 0, 0, 0]))
     }
 
     /// The descriptor's eight doublewords.
# leaf-global
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -42,7 +42,7 @@
 /// What a device's leaf adds to the loader's: unprivileged, its context's
 /// alone, and executable at no level — the loader's memory is executable at
 /// EL1, which no device's access is.
-const DEVICES: u64 = AP_UNPRIVILEGED | NOT_GLOBAL | PXN;
+const DEVICES: u64 = AP_UNPRIVILEGED | PXN;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-privileged-only
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -42,7 +42,7 @@
 /// What a device's leaf adds to the loader's: unprivileged, its context's
 /// alone, and executable at no level — the loader's memory is executable at
 /// EL1, which no device's access is.
-const DEVICES: u64 = AP_UNPRIVILEGED | NOT_GLOBAL | PXN;
+const DEVICES: u64 = NOT_GLOBAL | PXN;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-executable-at-el1
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -42,7 +42,7 @@
 /// What a device's leaf adds to the loader's: unprivileged, its context's
 /// alone, and executable at no level — the loader's memory is executable at
 /// EL1, which no device's access is.
-const DEVICES: u64 = AP_UNPRIVILEGED | NOT_GLOBAL | PXN;
+const DEVICES: u64 = AP_UNPRIVILEGED | NOT_GLOBAL;
 
 /// What a device may do to memory mapped for it.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
# leaf-ap1-bit
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -31,7 +31,7 @@
 /// `AP[1]`, bit [6]: an unprivileged access is permitted. A PCIe transaction
 /// without a PASID prefix is unprivileged (IHI 0070 §13.7), so a leaf without
 /// it is one no device reaches.
-const AP_UNPRIVILEGED: u64 = 1 << 6;
+const AP_UNPRIVILEGED: u64 = 1 << 5;
 /// `AP[2]`, bit [7]: read-only.
 const AP_READ_ONLY: u64 = 1 << 7;
 /// `nG`, bit [11]: the translation is its context's alone. A leaf without it
# leaf-ng-bit
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -37,7 +37,7 @@
 /// `nG`, bit [11]: the translation is its context's alone. A leaf without it
 /// is global, cached for every ASID of the regime and so for every domain,
 /// and no invalidation by ASID removes it.
-const NOT_GLOBAL: u64 = 1 << 11;
+const NOT_GLOBAL: u64 = 1 << 12;
 
 /// What a device's leaf adds to the loader's: unprivileged, its context's
 /// alone, and executable at no level — the loader's memory is executable at
# leaf-read-is-writable
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -97,7 +97,7 @@
     let (tables, size, output, descriptor) = match leaf {
         Leaf::Memory(memory, access) => {
             let access = match access {
-                Access::Read => AP_READ_ONLY,
+                Access::Read => 0,
                 Access::ReadWrite => 0,
             };
             (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
# leaf-write-is-read-only
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -98,7 +98,7 @@
         Leaf::Memory(memory, access) => {
             let access = match access {
                 Access::Read => AP_READ_ONLY,
-                Access::ReadWrite => 0,
+                Access::ReadWrite => AP_READ_ONLY,
             };
             (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
         }
# leaf-memory-as-device
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -100,7 +100,7 @@
                 Access::Read => AP_READ_ONLY,
                 Access::ReadWrite => 0,
             };
-            (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
+            (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Device) | DEVICES | access)
         }
         Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
     };
# doorbell-as-block
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -102,7 +102,7 @@
             };
             (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
         }
-        Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
+        Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), block(register.get(), Cache::Device) | DEVICES),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
         return None;
# doorbell-as-memory
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -102,7 +102,7 @@
             };
             (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
         }
-        Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
+        Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Memory) | DEVICES),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
         return None;
# doorbell-at-level-2
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -102,7 +102,7 @@
             };
             (3, 1u64 << 21, memory.get(), block(memory.get(), Cache::Memory) | DEVICES | access)
         }
-        Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
+        Leaf::Doorbell(register) => (3, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
     };
     if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
         return None;
# plan-unaligned
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -104,7 +104,7 @@
         }
         Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
     };
-    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
+    if at >> INPUT_BITS != 0 || !unit.reaches(output, size) {
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
# plan-past-input
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -104,7 +104,7 @@
         }
         Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
     };
-    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
+    if at & (size - 1) != 0 || !unit.reaches(output, size) {
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
# plan-past-output
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -104,7 +104,7 @@
         }
         Leaf::Doorbell(register) => (4, 1u64 << 12, register.get(), page(register.get(), Cache::Device) | DEVICES),
     };
-    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 || !unit.reaches(output, size) {
+    if at >> INPUT_BITS != 0 || at & (size - 1) != 0 {
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
# plan-index-shift
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -108,7 +108,7 @@
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
-        (at >> (39 - 9 * level) & 0x1FF) as usize
+        (at >> (38 - 9 * level) & 0x1FF) as usize
     }
     Some(Path { indices: [index(at, 0), index(at, 1), index(at, 2), index(at, 3)], tables, descriptor })
 }
# plan-index-width
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -108,7 +108,7 @@
         return None;
     }
     const fn index(at: u64, level: u32) -> usize {
-        (at >> (39 - 9 * level) & 0x1FF) as usize
+        (at >> (39 - 9 * level) & 0xFF) as usize
     }
     Some(Path { indices: [index(at, 0), index(at, 1), index(at, 2), index(at, 3)], tables, descriptor })
 }
# entry-page-as-table
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -129,7 +129,7 @@
 pub const fn entry(descriptor: u64, level: usize) -> Entry {
     if descriptor & VALID == 0 {
         Entry::Invalid
-    } else if level < LEVELS - 1 && descriptor & TABLE_OR_PAGE != 0 {
+    } else if descriptor & TABLE_OR_PAGE != 0 {
         Entry::Table(Phys::of(descriptor))
     } else {
         Entry::Mapped
# entry-invalid-as-table
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -127,7 +127,7 @@
 /// `descriptor` as the walk reads it at `level`: bit [1] names a table above
 /// level 3 and a page at it, and a table's address is bits [47:12].
 pub const fn entry(descriptor: u64, level: usize) -> Entry {
-    if descriptor & VALID == 0 {
+    if descriptor == 0 {
         Entry::Invalid
     } else if level < LEVELS - 1 && descriptor & TABLE_OR_PAGE != 0 {
         Entry::Table(Phys::of(descriptor))
# next-past-output
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -64,7 +64,7 @@
 /// hierarchical fields stay clear: the leaf alone decides the access. `None`
 /// where the table is past the unit's output size.
 pub const fn next(at: Phys<12>, unit: &Unit) -> Option<u64> {
-    if unit.reaches(at.get(), 4096) {
+    if true {
         Some(table(at.get()))
     } else {
         None
# mair0-shifted
--- a/toyos-smmu/src/table.rs
+++ b/toyos-smmu/src/table.rs
@@ -22,7 +22,7 @@
 /// `CD.MAIR0`, attributes 0 to 3 of the `MAIR` the descriptors' `AttrIndx`
 /// names: a [`Cache::Device`] leaf's is Device-nGnRE and a [`Cache::Memory`]
 /// leaf's Normal write-back.
-pub const MAIR0: u32 = MAIR as u32;
+pub const MAIR0: u32 = (MAIR >> 8) as u32;
 
 /// Table D8-50 and Table D8-52: bit [0] valid; bit [1] set for a table
 /// descriptor and a level 3 page, clear for a block.
# bootmap-mair-order
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -18,7 +18,7 @@
 pub const ATTR_NORMAL_NC: u64 = 2;
 /// Each index's attribute byte (D24.2.110), in index order: the value
 /// `PAR_EL1.ATTR` also reports a translation's type in.
-pub const ATTRS: [u8; 3] = [0x04, 0xFF, 0x44];
+pub const ATTRS: [u8; 3] = [0xFF, 0x04, 0x44];
 /// `MAIR_EL1` whole.
 pub const MAIR: u64 = ATTRS[0] as u64 | (ATTRS[1] as u64) << 8 | (ATTRS[2] as u64) << 16;
 
# bootmap-table-bits
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -23,7 +23,7 @@
 pub const MAIR: u64 = ATTRS[0] as u64 | (ATTRS[1] as u64) << 8 | (ATTRS[2] as u64) << 16;
 
 /// A table descriptor (bits 1:0 = 0b11) naming the next table down.
-const TABLE: u64 = 0b11;
+const TABLE: u64 = 0b01;
 /// A block descriptor (bits 1:0 = 0b01) at level 1 or 2.
 const BLOCK: u64 = 0b01;
 /// `SH` = inner shareable, bits 9:8.
# bootmap-page-bits
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -38,7 +38,7 @@
 const UXN: u64 = 1 << 54;
 
 /// A page descriptor (bits 1:0 = 0b11) at level 3.
-const PAGE: u64 = 0b11;
+const PAGE: u64 = 0b01;
 
 /// A descriptor naming the next table down, at `phys`.
 pub const fn table(phys: u64) -> u64 {
# bootmap-block-bits
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -25,7 +25,7 @@
 /// A table descriptor (bits 1:0 = 0b11) naming the next table down.
 const TABLE: u64 = 0b11;
 /// A block descriptor (bits 1:0 = 0b01) at level 1 or 2.
-const BLOCK: u64 = 0b01;
+const BLOCK: u64 = 0b11;
 /// `SH` = inner shareable, bits 9:8.
 const INNER_SHAREABLE: u64 = 0b11 << 8;
 /// `SH` = outer shareable.
# bootmap-no-access-flag
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -31,7 +31,7 @@
 /// `SH` = outer shareable.
 const OUTER_SHAREABLE: u64 = 0b10 << 8;
 /// The access flag, bit 10: set, so the first access does not fault.
-const AF: u64 = 1 << 10;
+const AF: u64 = 0;
 /// Privileged execute-never, bit 53.
 pub const PXN: u64 = 1 << 53;
 /// Unprivileged execute-never, bit 54.
# bootmap-memory-shareability
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -27,7 +27,7 @@
 /// A block descriptor (bits 1:0 = 0b01) at level 1 or 2.
 const BLOCK: u64 = 0b01;
 /// `SH` = inner shareable, bits 9:8.
-const INNER_SHAREABLE: u64 = 0b11 << 8;
+const INNER_SHAREABLE: u64 = 0b10 << 8;
 /// `SH` = outer shareable.
 const OUTER_SHAREABLE: u64 = 0b10 << 8;
 /// The access flag, bit 10: set, so the first access does not fault.
# bootmap-memory-attribute
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -55,7 +55,7 @@
 
 const fn attributes(cache: Cache) -> u64 {
     match cache {
-        Cache::Memory => ATTR_NORMAL << 2 | INNER_SHAREABLE | UXN,
+        Cache::Memory => ATTR_NORMAL_NC << 2 | INNER_SHAREABLE | UXN,
         Cache::Device => ATTR_DEVICE << 2 | PXN | UXN,
         Cache::Scanout => ATTR_NORMAL_NC << 2 | OUTER_SHAREABLE | PXN | UXN,
         Cache::Firmware => panic!("AArch64 has no range registers to type memory: its plan types by the map"),
# bootmap-device-executable
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -56,7 +56,7 @@
 const fn attributes(cache: Cache) -> u64 {
     match cache {
         Cache::Memory => ATTR_NORMAL << 2 | INNER_SHAREABLE | UXN,
-        Cache::Device => ATTR_DEVICE << 2 | PXN | UXN,
+        Cache::Device => ATTR_DEVICE << 2 | PXN,
         Cache::Scanout => ATTR_NORMAL_NC << 2 | OUTER_SHAREABLE | PXN | UXN,
         Cache::Firmware => panic!("AArch64 has no range registers to type memory: its plan types by the map"),
     }
# bootmap-pxn-bit
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -33,7 +33,7 @@
 /// The access flag, bit 10: set, so the first access does not fault.
 const AF: u64 = 1 << 10;
 /// Privileged execute-never, bit 53.
-pub const PXN: u64 = 1 << 53;
+pub const PXN: u64 = 1 << 52;
 /// Unprivileged execute-never, bit 54.
 const UXN: u64 = 1 << 54;
 
# bootmap-uxn-bit
--- a/toyos-bootmap/src/aarch64.rs
+++ b/toyos-bootmap/src/aarch64.rs
@@ -35,7 +35,7 @@
 /// Privileged execute-never, bit 53.
 pub const PXN: u64 = 1 << 53;
 /// Unprivileged execute-never, bit 54.
-const UXN: u64 = 1 << 54;
+const UXN: u64 = 1 << 55;
 
 /// A page descriptor (bits 1:0 = 0b11) at level 3.
 const PAGE: u64 = 0b11;

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations at f8022b7, round 2, of toyos-phys/src/lib.rs, toyos-smmu/src/queue.rs, toyos-smmu/src/unit.rs (run with cargo test -p toyos-phys, -p toyos-smmu): each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p <crate> --no-run), run (cargo test -p <crate>) and reverted in the same script, the tree clean before and after each. 60 mutations, 60 red.

unit-ips-52: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-full-is-empty: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-no-wrap-flag: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-after-overflows: build EXIT=0 test EXIT=101 RED (1 tests failed)
queue-empty-ignores-wrap: build EXIT=0 test EXIT=101 RED (1 tests failed)
events-after-clears-the-ack: build EXIT=0 test EXIT=101 RED (1 tests failed)
events-after-never-acks: build EXIT=0 test EXIT=101 RED (1 tests failed)
events-overflow-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
events-overflowed-by-prod-alone: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-sync-signal-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-sync-irq-value: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-stream-nonleaf: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-stream-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-forget-all-range: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-asid-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-invalidate-all-opcode: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-rnw-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-rnw-polarity: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-address-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
event-stream-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
event-permission-unnamed: build EXIT=0 test EXIT=101 RED (2 tests failed)
event-attempt-on-any: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-no-stage1: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-ttf-bit: build EXIT=0 test EXIT=101 RED (20 tests failed)
probe-big-endian: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-forced-stall: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-no-4k: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-preset: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-stream-bits-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-queue-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-eventq-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
probe-cohacc-bit: build EXIT=0 test EXIT=101 RED (20 tests failed)
probe-incoherent-accepted: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-override-ignored: build EXIT=0 test EXIT=101 RED (1 tests failed)
probe-oas-table: build EXIT=0 test EXIT=101 RED (6 tests failed)
asid-wider-than-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
output-unbounded: build EXIT=0 test EXIT=101 RED (4 tests failed)
output-end-exclusive: build EXIT=0 test EXIT=101 RED (8 tests failed)
output-base-alone: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-misaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-past-streams: build EXIT=0 test EXIT=101 RED (1 tests failed)
strtab-past-output: build EXIT=0 test EXIT=101 RED (3 tests failed)
strtab-no-read-allocate: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmdq-past-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmdq-misaligned: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmdq-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
eventq-entry-size: build EXIT=0 test EXIT=101 RED (1 tests failed)
eventq-past-unit: build EXIT=0 test EXIT=101 RED (1 tests failed)
eventq-past-output: build EXIT=0 test EXIT=101 RED (1 tests failed)
errors-not-toggled: build EXIT=0 test EXIT=101 RED (1 tests failed)
cmd-error-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
cr1-table-shareability: build EXIT=0 test EXIT=101 RED (1 tests failed)
cr2-broadcast: build EXIT=0 test EXIT=101 RED (1 tests failed)
gbpa-abort-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
smmu-offset-gerrorn: build EXIT=0 test EXIT=101 RED (1 tests failed)
smmu-offset-strtab-cfg: build EXIT=0 test EXIT=101 RED (1 tests failed)
smmu-eventq-cons-in-page-0: build EXIT=0 test EXIT=101 RED (1 tests failed)
phys-past-48: build EXIT=0 test EXIT=101 RED (2 tests failed)
phys-unaligned: build EXIT=0 test EXIT=101 RED (2 tests failed)
phys-of-keeps-the-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# unit-ips-52
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -217,7 +217,7 @@
             40 => 0b010,
             42 => 0b011,
             44 => 0b100,
-            _ => 0b101,
+            _ => 0b110,
         }
     }
 
# queue-full-is-empty
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -49,7 +49,7 @@
     /// No command can be written: the indexes are equal and the wrap flags
     /// differ.
     pub const fn is_full(self, prod: u32, cons: u32) -> bool {
-        self.0.position(prod) ^ self.0.position(cons) == 1 << self.0.log2size
+        self.0.position(prod) == self.0.position(cons)
     }
 
     /// The unit has consumed every command written.
# queue-no-wrap-flag
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -30,7 +30,7 @@
     /// The position after `register`'s, wrap flag toggled where it passes
     /// the top.
     const fn after(self, register: u32) -> u32 {
-        self.position(self.position(register) + 1)
+        (self.position(register) + 1) & ((1 << self.log2size) - 1)
     }
 }
 
# queue-after-overflows
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -30,7 +30,7 @@
     /// The position after `register`'s, wrap flag toggled where it passes
     /// the top.
     const fn after(self, register: u32) -> u32 {
-        self.position(self.position(register) + 1)
+        self.position(register + 1)
     }
 }
 
# queue-empty-ignores-wrap
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -24,7 +24,7 @@
 
     /// The indexes and their wrap flags are equal.
     const fn is_empty(self, prod: u32, cons: u32) -> bool {
-        self.position(prod) == self.position(cons)
+        self.slot(prod) == self.slot(cons)
     }
 
     /// The position after `register`'s, wrap flag toggled where it passes
# events-after-clears-the-ack
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -95,7 +95,7 @@
     /// `prod` was read with. That acknowledges an overflow seen in `prod`,
     /// and no value this answers raises one the unit did not report.
     pub const fn after(self, cons: u32, prod: u32) -> u32 {
-        self.0.after(cons) | prod & OVERFLOW
+        self.0.after(cons)
     }
 }
 
# events-after-never-acks
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -95,7 +95,7 @@
     /// `prod` was read with. That acknowledges an overflow seen in `prod`,
     /// and no value this answers raises one the unit did not report.
     pub const fn after(self, cons: u32, prod: u32) -> u32 {
-        self.0.after(cons) | prod & OVERFLOW
+        self.0.after(cons) | cons & OVERFLOW
     }
 }
 
# events-overflow-bit
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -65,7 +65,7 @@
 }
 
 /// `SMMU_EVENTQ_PROD.OVFLG` and `SMMU_EVENTQ_CONS.OVACKFLG`, bit [31] of each.
-const OVERFLOW: u32 = 1 << 31;
+const OVERFLOW: u32 = 1 << 30;
 
 /// The event queue's indexes, as the driver that consumes from it reads
 /// `SMMU_EVENTQ_PROD` and `SMMU_EVENTQ_CONS`: made by
# events-overflowed-by-prod-alone
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -87,7 +87,7 @@
     /// Records were dropped on a full queue and that is not yet
     /// acknowledged: `OVFLG` differs from `OVACKFLG` (§7.4).
     pub const fn overflowed(self, prod: u32, cons: u32) -> bool {
-        (prod ^ cons) & OVERFLOW != 0
+        prod & OVERFLOW != 0
     }
 
     /// What `SMMU_EVENTQ_CONS` is written with once the record at `cons` is
# cmd-sync-signal-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -143,7 +143,7 @@
                     Signal::None => 0b00,
                     Signal::Irq => 0b01,
                 };
-                [0x46 | cs << 12, 0]
+                [0x46 | cs << 11, 0]
             }
         }
     }
# cmd-sync-irq-value
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -141,7 +141,7 @@
             Self::Sync(signal) => {
                 let cs = match signal {
                     Signal::None => 0b00,
-                    Signal::Irq => 0b01,
+                    Signal::Irq => 0b10,
                 };
                 [0x46 | cs << 12, 0]
             }
# cmd-forget-stream-nonleaf
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -131,7 +131,7 @@
     pub const fn words(self) -> [u64; 2] {
         match self {
             // StreamID [63:32]; Leaf [64].
-            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 1],
+            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 0],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
# cmd-forget-stream-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -131,7 +131,7 @@
     pub const fn words(self) -> [u64; 2] {
         match self {
             // StreamID [63:32]; Leaf [64].
-            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 1],
+            Self::ForgetStream(stream) => [0x03 | (stream as u64) << 12, 1],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
# cmd-forget-all-range
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -133,7 +133,7 @@
             // StreamID [63:32]; Leaf [64].
             Self::ForgetStream(stream) => [0x03 | (stream as u64) << 32, 1],
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
-            Self::ForgetAll => [0x04, 31],
+            Self::ForgetAll => [0x04, 0],
             // ASID [63:48], VMID [47:32].
             Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
             Self::InvalidateAll => [0x30, 0],
# cmd-asid-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -135,7 +135,7 @@
             // `CMD_CFGI_STE_RANGE` with Range [68:64] of 31.
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
-            Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
+            Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 32, 0],
             Self::InvalidateAll => [0x30, 0],
             // CS [13:12].
             Self::Sync(signal) => {
# cmd-invalidate-all-opcode
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -136,7 +136,7 @@
             Self::ForgetAll => [0x04, 31],
             // ASID [63:48], VMID [47:32].
             Self::InvalidateAsid(asid) => [0x11 | (asid.get() as u64) << 48, 0],
-            Self::InvalidateAll => [0x30, 0],
+            Self::InvalidateAll => [0x10, 0],
             // CS [13:12].
             Self::Sync(signal) => {
                 let cs = match signal {
# event-rnw-bit
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -238,7 +238,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[2], write: record[1] & 1 << 34 == 0 })
         }
         _ => None,
     };
# event-rnw-polarity
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -238,7 +238,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[2], write: record[1] & 1 << 35 != 0 })
         }
         _ => None,
     };
# event-address-word
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -238,7 +238,7 @@
     };
     let attempt = match code {
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
-            Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
+            Some(Attempt { address: record[3], write: record[1] & 1 << 35 == 0 })
         }
         _ => None,
     };
# event-stream-field
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -242,5 +242,5 @@
         }
         _ => None,
     };
-    Event { stream: (record[0] >> 32) as u32, code, attempt }
+    Event { stream: (record[0] >> 12) as u32, code, attempt }
 }
# event-permission-unnamed
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -229,7 +229,6 @@
         0x10 => Code::Translation,
         0x11 => Code::AddressSize,
         0x12 => Code::AccessFlag,
-        0x13 => Code::Permission,
         0x20 => Code::TlbConflict,
         0x21 => Code::ConfigurationConflict,
         0x24 => Code::PageRequest,
# event-attempt-on-any
--- a/toyos-smmu/src/queue.rs
+++ b/toyos-smmu/src/queue.rs
@@ -240,7 +240,7 @@
         Code::Translation | Code::AddressSize | Code::AccessFlag | Code::Permission => {
             Some(Attempt { address: record[2], write: record[1] & 1 << 35 == 0 })
         }
-        _ => None,
+        _ => Some(Attempt { address: record[2], write: true }),
     };
     Event { stream: (record[0] >> 32) as u32, code, attempt }
 }
# probe-no-stage1
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -147,7 +147,7 @@
     if idr0 & 1 << 4 == 0 && !coherent_override {
         return Err(Lacks::CoherentAccess);
     }
-    if idr0 & 1 << 1 == 0 {
+    if false {
         return Err(Lacks::Stage1);
     }
     if idr0 & 1 << 3 == 0 {
# probe-ttf-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -150,7 +150,7 @@
     if idr0 & 1 << 1 == 0 {
         return Err(Lacks::Stage1);
     }
-    if idr0 & 1 << 3 == 0 {
+    if idr0 & 1 << 2 == 0 {
         return Err(Lacks::Aarch64Tables);
     }
     // `TTENDIAN` [22:21]: `0b00` mixed and `0b10` little-endian can be set to little-endian.
# probe-big-endian
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -154,7 +154,7 @@
         return Err(Lacks::Aarch64Tables);
     }
     // `TTENDIAN` [22:21]: `0b00` mixed and `0b10` little-endian can be set to little-endian.
-    if idr0 >> 21 & 0b11 == 0b11 {
+    if false {
         return Err(Lacks::LittleEndianTables);
     }
     // `STALL_MODEL` [25:24]: `0b00` stall and terminate, `0b01` terminate only.
# probe-forced-stall
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -160,7 +160,7 @@
     // `STALL_MODEL` [25:24]: `0b00` stall and terminate, `0b01` terminate only.
     let stall = match idr0 >> 24 & 0b11 {
         0b00 => true,
-        0b01 => false,
+        0b01 | 0b10 => false,
         other => return Err(Lacks::Termination(other as u8)),
     };
     if idr5 & 1 << 4 == 0 {
# probe-no-4k
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -163,7 +163,7 @@
         0b01 => false,
         other => return Err(Lacks::Termination(other as u8)),
     };
-    if idr5 & 1 << 4 == 0 {
+    if false {
         return Err(Lacks::Granule4K);
     }
     if idr1 & (1 << 30 | 1 << 29) != 0 {
# probe-preset
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -166,7 +166,7 @@
     if idr5 & 1 << 4 == 0 {
         return Err(Lacks::Granule4K);
     }
-    if idr1 & (1 << 30 | 1 << 29) != 0 {
+    if idr1 & (1 << 30) != 0 {
         return Err(Lacks::FixedStructures);
     }
     let stream_bits = (idr1 & 0x3F) as u8;
# probe-stream-bits-bound
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -172,7 +172,7 @@
     let stream_bits = (idr1 & 0x3F) as u8;
     let command_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
     let event_queue_log2 = (idr1 >> 16 & 0x1F) as u8;
-    if stream_bits > 32 {
+    if stream_bits > 63 {
         return Err(Lacks::Size(stream_bits));
     }
     if command_queue_log2 > 19 {
# probe-queue-bound
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -175,7 +175,7 @@
     if stream_bits > 32 {
         return Err(Lacks::Size(stream_bits));
     }
-    if command_queue_log2 > 19 {
+    if command_queue_log2 > 31 {
         return Err(Lacks::Size(command_queue_log2));
     }
     if event_queue_log2 > 19 {
# probe-eventq-field
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -171,7 +171,7 @@
     }
     let stream_bits = (idr1 & 0x3F) as u8;
     let command_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
-    let event_queue_log2 = (idr1 >> 16 & 0x1F) as u8;
+    let event_queue_log2 = (idr1 >> 21 & 0x1F) as u8;
     if stream_bits > 32 {
         return Err(Lacks::Size(stream_bits));
     }
# probe-cohacc-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -144,7 +144,7 @@
 /// with the IORT's `COHACC override` (DEN 0049 Table 14), which stands for
 /// `IDR0.COHACC` [4] where it is set.
 pub const fn probe(idr0: u32, idr1: u32, idr5: u32, coherent_override: bool) -> Result<Unit, Lacks> {
-    if idr0 & 1 << 4 == 0 && !coherent_override {
+    if idr0 & 1 << 5 == 0 && !coherent_override {
         return Err(Lacks::CoherentAccess);
     }
     if idr0 & 1 << 1 == 0 {
# probe-incoherent-accepted
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -144,7 +144,7 @@
 /// with the IORT's `COHACC override` (DEN 0049 Table 14), which stands for
 /// `IDR0.COHACC` [4] where it is set.
 pub const fn probe(idr0: u32, idr1: u32, idr5: u32, coherent_override: bool) -> Result<Unit, Lacks> {
-    if idr0 & 1 << 4 == 0 && !coherent_override {
+    if false {
         return Err(Lacks::CoherentAccess);
     }
     if idr0 & 1 << 1 == 0 {
# probe-override-ignored
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -144,7 +144,7 @@
 /// with the IORT's `COHACC override` (DEN 0049 Table 14), which stands for
 /// `IDR0.COHACC` [4] where it is set.
 pub const fn probe(idr0: u32, idr1: u32, idr5: u32, coherent_override: bool) -> Result<Unit, Lacks> {
-    if idr0 & 1 << 4 == 0 && !coherent_override {
+    if idr0 & 1 << 4 == 0 {
         return Err(Lacks::CoherentAccess);
     }
     if idr0 & 1 << 1 == 0 {
# probe-oas-table
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -185,7 +185,7 @@
         stream_bits,
         command_queue_log2,
         event_queue_log2,
-        output_bits: [32, 36, 40, 42, 44, 48, 52, 56][(idr5 & 0b111) as usize],
+        output_bits: [32, 36, 40, 44, 42, 48, 52, 56][(idr5 & 0b111) as usize],
         asid16: idr0 & 1 << 12 != 0,
         stall,
     })
# asid-wider-than-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -195,7 +195,7 @@
     /// `tag` as an ASID of this unit: eight bits where `IDR0.ASID16` is
     /// clear, under which a wider one makes its context descriptor ILLEGAL.
     pub const fn asid(&self, tag: u16) -> Option<Asid> {
-        if self.asid16 || tag <= 0xFF {
+        if true {
             Some(Asid(tag))
         } else {
             None
# output-unbounded
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -224,7 +224,7 @@
     /// Whether the unit reads all of `bytes` at `address`: none of them at
     /// or past `1 << IDR5.OAS`.
     pub(crate) const fn reaches(&self, address: u64, bytes: u64) -> bool {
-        address + bytes <= 1 << self.output_bits
+        address + bytes <= 1 << 56
     }
 
     /// `bytes` at `address`, aligned to their own size, where the unit reads
# output-end-exclusive
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -224,7 +224,7 @@
     /// Whether the unit reads all of `bytes` at `address`: none of them at
     /// or past `1 << IDR5.OAS`.
     pub(crate) const fn reaches(&self, address: u64, bytes: u64) -> bool {
-        address + bytes <= 1 << self.output_bits
+        address + bytes < 1 << self.output_bits
     }
 
     /// `bytes` at `address`, aligned to their own size, where the unit reads
# output-base-alone
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -224,7 +224,7 @@
     /// Whether the unit reads all of `bytes` at `address`: none of them at
     /// or past `1 << IDR5.OAS`.
     pub(crate) const fn reaches(&self, address: u64, bytes: u64) -> bool {
-        address + bytes <= 1 << self.output_bits
+        address < 1 << self.output_bits
     }
 
     /// `bytes` at `address`, aligned to their own size, where the unit reads
# strtab-misaligned
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -240,7 +240,7 @@
     /// its own size as the unit reads its address, or it ends past the
     /// unit's output size.
     pub const fn stream_table(&self, table: Phys<6>, log2size: u8) -> Option<(u64, u32)> {
-        if log2size > self.stream_bits || !self.holds(table.get(), 64 << log2size) {
+        if log2size > self.stream_bits || !self.reaches(table.get(), 64 << log2size) {
             return None;
         }
         Some((1 << 62 | table.get(), log2size as u32))
# strtab-past-streams
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -240,7 +240,7 @@
     /// its own size as the unit reads its address, or it ends past the
     /// unit's output size.
     pub const fn stream_table(&self, table: Phys<6>, log2size: u8) -> Option<(u64, u32)> {
-        if log2size > self.stream_bits || !self.holds(table.get(), 64 << log2size) {
+        if !self.holds(table.get(), 64 << log2size) {
             return None;
         }
         Some((1 << 62 | table.get(), log2size as u32))
# strtab-past-output
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -240,7 +240,7 @@
     /// its own size as the unit reads its address, or it ends past the
     /// unit's output size.
     pub const fn stream_table(&self, table: Phys<6>, log2size: u8) -> Option<(u64, u32)> {
-        if log2size > self.stream_bits || !self.holds(table.get(), 64 << log2size) {
+        if log2size > self.stream_bits || table.get() & ((64 << log2size) - 1) != 0 {
             return None;
         }
         Some((1 << 62 | table.get(), log2size as u32))
# strtab-no-read-allocate
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -243,7 +243,7 @@
         if log2size > self.stream_bits || !self.holds(table.get(), 64 << log2size) {
             return None;
         }
-        Some((1 << 62 | table.get(), log2size as u32))
+        Some((table.get(), log2size as u32))
     }
 
     /// `SMMU_CMDQ_BASE` (§6.3.26) for a queue of `1 << log2size` 16-byte
# cmdq-past-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -252,7 +252,7 @@
     /// or where the queue is not aligned to the larger of its size and 32
     /// bytes.
     pub const fn command_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Commands)> {
-        if log2size > self.command_queue_log2 || !self.holds(queue.get(), 16 << log2size) {
+        if !self.holds(queue.get(), 16 << log2size) {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Commands(Indexes { log2size })))
# cmdq-misaligned
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -252,7 +252,7 @@
     /// or where the queue is not aligned to the larger of its size and 32
     /// bytes.
     pub const fn command_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Commands)> {
-        if log2size > self.command_queue_log2 || !self.holds(queue.get(), 16 << log2size) {
+        if log2size > self.command_queue_log2 || !self.reaches(queue.get(), 16 << log2size) {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Commands(Indexes { log2size })))
# cmdq-past-output
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -252,7 +252,7 @@
     /// or where the queue is not aligned to the larger of its size and 32
     /// bytes.
     pub const fn command_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Commands)> {
-        if log2size > self.command_queue_log2 || !self.holds(queue.get(), 16 << log2size) {
+        if log2size > self.command_queue_log2 || queue.get() & ((16 << log2size) - 1) != 0 {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Commands(Indexes { log2size })))
# eventq-entry-size
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -261,7 +261,7 @@
     /// `SMMU_EVENTQ_BASE` (§6.3.29) for a queue of `1 << log2size` 32-byte
     /// records, and its indexes: `WA` [62], the address, `LOG2SIZE` [4:0].
     pub const fn event_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Events)> {
-        if log2size > self.event_queue_log2 || !self.holds(queue.get(), 32 << log2size) {
+        if log2size > self.event_queue_log2 || !self.holds(queue.get(), 16 << log2size) {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Events(Indexes { log2size })))
# eventq-past-unit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -261,7 +261,7 @@
     /// `SMMU_EVENTQ_BASE` (§6.3.29) for a queue of `1 << log2size` 32-byte
     /// records, and its indexes: `WA` [62], the address, `LOG2SIZE` [4:0].
     pub const fn event_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Events)> {
-        if log2size > self.event_queue_log2 || !self.holds(queue.get(), 32 << log2size) {
+        if !self.holds(queue.get(), 32 << log2size) {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Events(Indexes { log2size })))
# eventq-past-output
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -261,7 +261,7 @@
     /// `SMMU_EVENTQ_BASE` (§6.3.29) for a queue of `1 << log2size` 32-byte
     /// records, and its indexes: `WA` [62], the address, `LOG2SIZE` [4:0].
     pub const fn event_queue(&self, queue: Phys<5>, log2size: u8) -> Option<(u64, Events)> {
-        if log2size > self.event_queue_log2 || !self.holds(queue.get(), 32 << log2size) {
+        if log2size > self.event_queue_log2 || queue.get() & ((32 << log2size) - 1) != 0 {
             return None;
         }
         Some((1 << 62 | queue.get() | log2size as u64, Events(Indexes { log2size })))
# errors-not-toggled
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -62,7 +62,7 @@
 /// `SMMU_GERROR` (§6.3.19): an error is active while its bit differs from
 /// `SMMU_GERRORN`'s, and writing `GERRORN` equal acknowledges it (§7.5).
 pub const fn active_errors(gerror: u32, gerrorn: u32) -> u32 {
-    gerror ^ gerrorn
+    gerror & !gerrorn
 }
 
 /// `SMMU_GERROR.CMDQ_ERR`: the command queue stopped on [`CommandError`].
# cmd-error-field
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -88,7 +88,7 @@
 }
 
 pub const fn command_error(cons: u32) -> CommandError {
-    match (cons >> 24 & 0x7F) as u8 {
+    match (cons >> 24) as u8 {
         0 => CommandError::None,
         1 => CommandError::Illegal,
         2 => CommandError::Abort,
# cr1-table-shareability
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -41,7 +41,7 @@
 /// inner-shareable write-back memory — `TABLE_SH` [11:10], `TABLE_OC` [9:8],
 /// `TABLE_IC` [7:6], `QUEUE_SH` [5:4], `QUEUE_OC` [3:2], `QUEUE_IC` [1:0],
 /// each shareability `0b11` and each cacheability `0b01`.
-pub const CR1_WRITE_BACK: u32 = 0b11 << 10 | 0b01 << 8 | 0b01 << 6 | 0b11 << 4 | 0b01 << 2 | 0b01;
+pub const CR1_WRITE_BACK: u32 = 0b11 << 12 | 0b01 << 8 | 0b01 << 6 | 0b11 << 4 | 0b01 << 2 | 0b01;
 
 /// `SMMU_CR2` (§6.3.12): `RECINVSID` [1], so a transaction under a StreamID
 /// past the stream table is recorded as well as aborted, and `PTM` [2], so
# cr2-broadcast
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -47,7 +47,7 @@
 /// past the stream table is recorded as well as aborted, and `PTM` [2], so
 /// no CPU's broadcast TLB invalidation reaches the unit's entries: its ASIDs
 /// are its own. `E2H` [0] stays clear.
-pub const CR2_RECORD_PRIVATE: u32 = 1 << 1 | 1 << 2;
+pub const CR2_RECORD_PRIVATE: u32 = 1 << 1;
 
 /// `SMMU_GBPA` (§6.3.15): what happens to every transaction while `SMMUEN`
 /// is clear. `ABORT` [20] aborts them; a write takes effect by carrying
# gbpa-abort-bit
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -52,7 +52,7 @@
 /// `SMMU_GBPA` (§6.3.15): what happens to every transaction while `SMMUEN`
 /// is clear. `ABORT` [20] aborts them; a write takes effect by carrying
 /// `Update` [31], which reads back set until the unit has taken it (§6.3.15.1).
-pub const GBPA_ABORT: u32 = 1 << 20;
+pub const GBPA_ABORT: u32 = 1 << 19;
 pub const GBPA_UPDATE: u32 = 1 << 31;
 
 /// `SMMU_IRQ_CTRL` (§6.3.17), and `SMMU_IRQ_CTRLACK` as `SMMU_CR0ACK`.
# smmu-offset-gerrorn
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -19,7 +19,7 @@
 pub const IRQ_CTRL: usize = 0x050;
 pub const IRQ_CTRLACK: usize = 0x054;
 pub const GERROR: usize = 0x060;
-pub const GERRORN: usize = 0x064;
+pub const GERRORN: usize = 0x068;
 pub const STRTAB_BASE: usize = 0x080;
 pub const STRTAB_BASE_CFG: usize = 0x088;
 pub const CMDQ_BASE: usize = 0x090;
# smmu-offset-strtab-cfg
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -21,7 +21,7 @@
 pub const GERROR: usize = 0x060;
 pub const GERRORN: usize = 0x064;
 pub const STRTAB_BASE: usize = 0x080;
-pub const STRTAB_BASE_CFG: usize = 0x088;
+pub const STRTAB_BASE_CFG: usize = 0x084;
 pub const CMDQ_BASE: usize = 0x090;
 pub const CMDQ_PROD: usize = 0x098;
 pub const CMDQ_CONS: usize = 0x09C;
# smmu-eventq-cons-in-page-0
--- a/toyos-smmu/src/unit.rs
+++ b/toyos-smmu/src/unit.rs
@@ -29,7 +29,7 @@
 /// The event queue's indexes are in register page 1, 64 KiB above page 0
 /// (§6.1, §6.2.2): `SMMU_EVENTQ_PROD` and `SMMU_EVENTQ_CONS` at 0xA8 and 0xAC of it.
 pub const EVENTQ_PROD: usize = 0x1_00A8;
-pub const EVENTQ_CONS: usize = 0x1_00AC;
+pub const EVENTQ_CONS: usize = 0x00AC;
 
 /// `SMMU_CR0` (§6.3.9), and `SMMU_CR0ACK`, which reads each bit back once
 /// the unit has acted on it.
# phys-past-48
--- a/toyos-phys/src/lib.rs
+++ b/toyos-phys/src/lib.rs
@@ -11,7 +11,7 @@
 pub struct Phys<const ALIGN: u32>(u64);
 
 impl<const ALIGN: u32> Phys<ALIGN> {
-    const FIELD: u64 = (1 << 48) - (1 << ALIGN);
+    const FIELD: u64 = 0u64.wrapping_sub(1 << ALIGN);
 
     pub const fn new(address: u64) -> Option<Self> {
         if address & !Self::FIELD == 0 {
# phys-unaligned
--- a/toyos-phys/src/lib.rs
+++ b/toyos-phys/src/lib.rs
@@ -11,7 +11,7 @@
 pub struct Phys<const ALIGN: u32>(u64);
 
 impl<const ALIGN: u32> Phys<ALIGN> {
-    const FIELD: u64 = (1 << 48) - (1 << ALIGN);
+    const FIELD: u64 = (1 << 48) - 1;
 
     pub const fn new(address: u64) -> Option<Self> {
         if address & !Self::FIELD == 0 {
# phys-of-keeps-the-word
--- a/toyos-phys/src/lib.rs
+++ b/toyos-phys/src/lib.rs
@@ -24,7 +24,7 @@
     /// The address a word holds in its bits `[47:ALIGN]`, whatever its other
     /// bits are.
     pub const fn of(word: u64) -> Self {
-        Self(word & Self::FIELD)
+        Self(word)
     }
 
     pub const fn get(self) -> u64 {

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations at f8022b7, round 2, of toyos-its/src/command.rs, toyos-its/src/lib.rs (run with cargo test -p toyos-its): each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p <crate> --no-run), run (cargo test -p <crate>) and reverted in the same script, the tree clean before and after each. 56 mutations, 56 red.

its-mapd-size: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-mapd-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-unmapd-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapc-rdbase-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapc-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapti-fields-swapped: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-mapti-collection-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-device-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-sync-rdbase-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-inv-is-discard: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-discard-is-inv: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-inv-event-high-half: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-discard-event-word: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-target-form-swapped: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-events-unbounded: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-events-zero: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-event-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-event-one-bit-wide: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-itt-bytes: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-no-physical-lpis: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-minus-one: build EXIT=0 test EXIT=101 RED (4 tests failed)
its-typer-devbits-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-itt-entry-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-typer-pta-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-hcc-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-cct-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-cil-bit: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-typer-cidbits-minus-one: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collection-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collection-inclusive: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collections-always-held: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collections-held-without-a-table-alone: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collections-held-under-cct-alone: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-collections-past-the-icid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-type-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-collections: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-entry-size: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-table-entries: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-table-pages-rounded-down: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-table-pages-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-pages-bound: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-size-not-minus-one: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-page-size-64k: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-not-valid: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-cache-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-full-is-empty: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-full-reads-the-register: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-empty-reads-the-registers: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-no-wrap: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-queue-after-reads-the-register: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-offset-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
its-queue-offset-unbounded: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-cbaser-size: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-offset-cwriter: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-baser-stride: build EXIT=0 test EXIT=101 RED (1 tests failed)
its-translation-frame: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# its-mapd-size
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -41,7 +41,7 @@
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
             Self::MapDevice { device: id, table, events } => {
-                [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
+                [device(0x08, id), events.0 as u64, V | table.get(), 0]
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
# its-mapd-not-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -41,7 +41,7 @@
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
             Self::MapDevice { device: id, table, events } => {
-                [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
+                [device(0x08, id), (events.0 - 1) as u64, table.get(), 0]
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
# its-unmapd-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -43,7 +43,7 @@
             Self::MapDevice { device: id, table, events } => {
                 [device(0x08, id), (events.0 - 1) as u64, V | table.get(), 0]
             }
-            Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
+            Self::UnmapDevice { device: id } => [device(0x08, id), 0, V, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection.0 as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
# its-mapc-rdbase-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -45,7 +45,7 @@
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
-            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection.0 as u64, 0],
+            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 32 | collection.0 as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
# its-mapc-not-valid
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -45,7 +45,7 @@
             }
             Self::UnmapDevice { device: id } => [device(0x08, id), 0, 0, 0],
             // RDbase [51:16] and ICID [15:0] of doubleword 2.
-            Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection.0 as u64, 0],
+            Self::MapCollection { collection, target } => [0x09, 0, target.0 << 16 | collection.0 as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
# its-mapti-fields-swapped
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -48,7 +48,7 @@
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection.0 as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
-                [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
+                [device(0x0A, id), (event.0 as u64) << 32 | lpi.intid() as u64, collection.0 as u64, 0]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
# its-mapti-collection-word
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -48,7 +48,7 @@
             Self::MapCollection { collection, target } => [0x09, 0, V | target.0 << 16 | collection.0 as u64, 0],
             // pINTID [63:32] of doubleword 1; ICID [15:0] of doubleword 2.
             Self::MapEvent { device: id, event, lpi, collection } => {
-                [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
+                [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, 0, collection.0 as u64]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
# its-device-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -36,7 +36,7 @@
     pub const fn words(self) -> [u64; 4] {
         // DeviceID is bits [63:32] of doubleword 0, and EventID bits [31:0] of doubleword 1.
         const fn device(number: u64, device: u32) -> u64 {
-            number | (device as u64) << 32
+            number | (device as u64) << 8
         }
         match self {
             // Size [4:0] of doubleword 1 is the EventID bits minus one; ITT_addr [51:8] of doubleword 2.
# its-sync-rdbase-field
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -52,7 +52,7 @@
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
-            Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
+            Self::Sync(target) => [0x05, 0, target.0, 0],
         }
     }
 }
# its-inv-is-discard
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -50,7 +50,7 @@
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
             }
-            Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
+            Self::Reconfigure { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
             Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
         }
# its-discard-is-inv
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -51,7 +51,7 @@
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
-            Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
+            Self::Discard { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
             Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
         }
     }
# its-inv-event-high-half
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -50,7 +50,7 @@
             Self::MapEvent { device: id, event, lpi, collection } => {
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
             }
-            Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
+            Self::Reconfigure { device: id, event } => [device(0x0C, id), (event.0 as u64) << 32, 0, 0],
             Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
             Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
         }
# its-discard-event-word
--- a/toyos-its/src/command.rs
+++ b/toyos-its/src/command.rs
@@ -51,7 +51,7 @@
                 [device(0x0A, id), (lpi.intid() as u64) << 32 | event.0 as u64, collection.0 as u64, 0]
             }
             Self::Reconfigure { device: id, event } => [device(0x0C, id), event.0 as u64, 0, 0],
-            Self::Discard { device: id, event } => [device(0x0F, id), event.0 as u64, 0, 0],
+            Self::Discard { device: id, event } => [device(0x0F, id), 0, event.0 as u64, 0],
             Self::Sync(target) => [0x05, 0, target.0 << 16, 0],
         }
     }
# its-target-form-swapped
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -172,7 +172,7 @@
     /// The redistributor at `frame` whose `GICR_TYPER.Processor_Number` is
     /// `number`, as this ITS names it.
     pub const fn target(&self, number: u16, frame: Phys<16>) -> Target {
-        Target(if self.physical_targets { frame.get() >> 16 } else { number as u64 })
+        Target(if !self.physical_targets { frame.get() >> 16 } else { number as u64 })
     }
 }
 
# its-events-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -145,7 +145,7 @@
     /// A translation table of `1 << bits` events. `None` for no bit, or more
     /// than the ITS's EventIDs have.
     pub const fn events(&self, bits: u8) -> Option<EventBits> {
-        if bits >= 1 && bits <= self.event_bits {
+        if bits >= 1 {
             Some(EventBits(bits))
         } else {
             None
# its-events-zero
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -145,7 +145,7 @@
     /// A translation table of `1 << bits` events. `None` for no bit, or more
     /// than the ITS's EventIDs have.
     pub const fn events(&self, bits: u8) -> Option<EventBits> {
-        if bits >= 1 && bits <= self.event_bits {
+        if bits <= self.event_bits {
             Some(EventBits(bits))
         } else {
             None
# its-event-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -110,7 +110,7 @@
 impl EventBits {
     /// `id` as an event of a device whose table holds this many.
     pub const fn event(self, id: u32) -> Option<Event> {
-        if (id as u64) >> self.0 == 0 {
+        if true {
             Some(Event(id))
         } else {
             None
# its-event-one-bit-wide
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -110,7 +110,7 @@
 impl EventBits {
     /// `id` as an event of a device whose table holds this many.
     pub const fn event(self, id: u32) -> Option<Event> {
-        if (id as u64) >> self.0 == 0 {
+        if (id as u64) >> (self.0 + 1) == 0 {
             Some(Event(id))
         } else {
             None
# its-itt-bytes
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -155,7 +155,7 @@
     /// The zeroed bytes `MAPD` is given for that table, at a 256-byte
     /// aligned address.
     pub const fn itt_bytes(&self, events: EventBits) -> u64 {
-        (self.itt_entry_bytes as u64) << events.0
+        (self.itt_entry_bytes as u64) << (events.0 - 1)
     }
 
     /// The collections this ITS has once its collection table holds
# its-no-physical-lpis
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -83,7 +83,7 @@
 }
 
 pub const fn probe(typer: u64) -> Result<Its, Lacks> {
-    if typer & 1 == 0 {
+    if false {
         return Err(Lacks::PhysicalLpis);
     }
     Ok(Its {
# its-typer-minus-one
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -88,7 +88,7 @@
     }
     Ok(Its {
         device_bits: (typer >> 13 & 0x1F) as u8 + 1,
-        event_bits: (typer >> 8 & 0x1F) as u8 + 1,
+        event_bits: (typer >> 8 & 0x1F) as u8,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         held: (typer >> 24) as u8,
         cumulative: typer & 1 << 2 != 0,
# its-typer-devbits-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -87,7 +87,7 @@
         return Err(Lacks::PhysicalLpis);
     }
     Ok(Its {
-        device_bits: (typer >> 13 & 0x1F) as u8 + 1,
+        device_bits: (typer >> 12 & 0x1F) as u8 + 1,
         event_bits: (typer >> 8 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         held: (typer >> 24) as u8,
# its-typer-itt-entry-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -89,7 +89,7 @@
     Ok(Its {
         device_bits: (typer >> 13 & 0x1F) as u8 + 1,
         event_bits: (typer >> 8 & 0x1F) as u8 + 1,
-        itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
+        itt_entry_bytes: (typer >> 4 & 0xF) as u8,
         held: (typer >> 24) as u8,
         cumulative: typer & 1 << 2 != 0,
         collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
# its-typer-pta-bit
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -93,7 +93,7 @@
         held: (typer >> 24) as u8,
         cumulative: typer & 1 << 2 != 0,
         collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
-        physical_targets: typer & 1 << 19 != 0,
+        physical_targets: typer & 1 << 18 != 0,
     })
 }
 
# its-typer-hcc-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -90,7 +90,7 @@
         device_bits: (typer >> 13 & 0x1F) as u8 + 1,
         event_bits: (typer >> 8 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
-        held: (typer >> 24) as u8,
+        held: (typer >> 16) as u8,
         cumulative: typer & 1 << 2 != 0,
         collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
         physical_targets: typer & 1 << 19 != 0,
# its-typer-cct-bit
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -91,7 +91,7 @@
         event_bits: (typer >> 8 & 0x1F) as u8 + 1,
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         held: (typer >> 24) as u8,
-        cumulative: typer & 1 << 2 != 0,
+        cumulative: typer & 1 << 3 != 0,
         collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
         physical_targets: typer & 1 << 19 != 0,
     })
# its-typer-cil-bit
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -92,7 +92,7 @@
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         held: (typer >> 24) as u8,
         cumulative: typer & 1 << 2 != 0,
-        collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
+        collection_bits: if typer & 1 << 37 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
         physical_targets: typer & 1 << 19 != 0,
     })
 }
# its-typer-cidbits-minus-one
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -92,7 +92,7 @@
         itt_entry_bytes: (typer >> 4 & 0xF) as u8 + 1,
         held: (typer >> 24) as u8,
         cumulative: typer & 1 << 2 != 0,
-        collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 + 1 },
+        collection_bits: if typer & 1 << 36 == 0 { 16 } else { (typer >> 32 & 0xF) as u8 },
         physical_targets: typer & 1 << 19 != 0,
     })
 }
# its-collection-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -128,7 +128,7 @@
 
 impl Collections {
     pub const fn collection(self, id: u16) -> Option<Collection> {
-        if (id as u32) < self.0 {
+        if true {
             Some(Collection(id))
         } else {
             None
# its-collection-inclusive
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -128,7 +128,7 @@
 
 impl Collections {
     pub const fn collection(self, id: u16) -> Option<Collection> {
-        if (id as u32) < self.0 {
+        if (id as u32) <= self.0 {
             Some(Collection(id))
         } else {
             None
# its-collections-always-held
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -163,7 +163,7 @@
     /// §5.3.1): the table's, those the ITS holds itself where it has no
     /// table or counts them beside it, and no more than an ICID numbers.
     pub const fn collections(&self, in_table: u64) -> Collections {
-        let held = if in_table == 0 || self.cumulative { self.held as u64 } else { 0 };
+        let held = self.held as u64;
         let numbered = 1u64 << self.collection_bits;
         let total = held.saturating_add(in_table);
         Collections(if total < numbered { total } else { numbered } as u32)
# its-collections-held-without-a-table-alone
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -163,7 +163,7 @@
     /// §5.3.1): the table's, those the ITS holds itself where it has no
     /// table or counts them beside it, and no more than an ICID numbers.
     pub const fn collections(&self, in_table: u64) -> Collections {
-        let held = if in_table == 0 || self.cumulative { self.held as u64 } else { 0 };
+        let held = if in_table == 0 { self.held as u64 } else { 0 };
         let numbered = 1u64 << self.collection_bits;
         let total = held.saturating_add(in_table);
         Collections(if total < numbered { total } else { numbered } as u32)
# its-collections-held-under-cct-alone
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -163,7 +163,7 @@
     /// §5.3.1): the table's, those the ITS holds itself where it has no
     /// table or counts them beside it, and no more than an ICID numbers.
     pub const fn collections(&self, in_table: u64) -> Collections {
-        let held = if in_table == 0 || self.cumulative { self.held as u64 } else { 0 };
+        let held = if self.cumulative { self.held as u64 } else { 0 };
         let numbered = 1u64 << self.collection_bits;
         let total = held.saturating_add(in_table);
         Collections(if total < numbered { total } else { numbered } as u32)
# its-collections-past-the-icid
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -166,7 +166,7 @@
         let held = if in_table == 0 || self.cumulative { self.held as u64 } else { 0 };
         let numbered = 1u64 << self.collection_bits;
         let total = held.saturating_add(in_table);
-        Collections(if total < numbered { total } else { numbered } as u32)
+        Collections(if total < u32::MAX as u64 { total } else { u32::MAX as u64 } as u32)
     }
 
     /// The redistributor at `frame` whose `GICR_TYPER.Processor_Number` is
# its-table-type-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -219,7 +219,7 @@
 }
 
 pub const fn table(baser: u64) -> Backing {
-    let table = match baser >> 56 & 0b111 {
+    let table = match baser >> 59 & 0b111 {
         0b000 => Table::Unimplemented,
         0b001 => Table::Devices,
         0b100 => Table::Collections,
# its-table-collections
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -222,7 +222,7 @@
     let table = match baser >> 56 & 0b111 {
         0b000 => Table::Unimplemented,
         0b001 => Table::Devices,
-        0b100 => Table::Collections,
+        0b010 => Table::Collections,
         other => Table::Other(other as u8),
     };
     let page = match baser >> 8 & 0b11 {
# its-table-entry-size
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -231,7 +231,7 @@
         // `0b11` is reserved and treated as `0b10`.
         _ => PageSize::K64,
     };
-    Backing { table, page, entry_bytes: (baser >> 48 & 0x1F) as u8 + 1 }
+    Backing { table, page, entry_bytes: (baser >> 48 & 0x1F) as u8 }
 }
 
 /// `InnerCache` `0b111`, read-allocate write-allocate write-back, with
# its-table-entries
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -243,7 +243,7 @@
 impl Backing {
     /// The entries a flat table of `pages` of its pages holds.
     pub const fn entries(self, pages: u16) -> u64 {
-        pages as u64 * self.page.bytes() / self.entry_bytes as u64
+        pages as u64 * self.page.bytes()
     }
 
     /// The fewest pages that hold `entries`. `None` for none, or more than
# its-table-pages-rounded-down
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -252,7 +252,7 @@
         let Some(bytes) = entries.checked_mul(self.entry_bytes as u64) else {
             return None;
         };
-        let pages = bytes.div_ceil(self.page.bytes());
+        let pages = bytes / self.page.bytes();
         if pages >= 1 && pages <= 256 {
             Some(pages as u16)
         } else {
# its-table-pages-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -253,7 +253,7 @@
             return None;
         };
         let pages = bytes.div_ceil(self.page.bytes());
-        if pages >= 1 && pages <= 256 {
+        if pages >= 1 {
             Some(pages as u16)
         } else {
             None
# its-baser-pages-bound
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -265,7 +265,7 @@
     /// `Page_Size` [9:8], `Size` [7:0] the pages minus one. `None` for no
     /// page or more than the 256 the field counts.
     pub const fn baser(self, at: Phys<16>, pages: u16) -> Option<u64> {
-        if pages == 0 || pages > 256 {
+        if pages == 0 {
             return None;
         }
         let size = match self.page {
# its-baser-size-not-minus-one
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -273,7 +273,7 @@
             PageSize::K16 => 0b01,
             PageSize::K64 => 0b10,
         };
-        Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
+        Some(VALID | CACHED | at.get() | size << 8 | (pages as u64 & 0xFF))
     }
 }
 
# its-baser-page-size-64k
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -271,7 +271,7 @@
         let size = match self.page {
             PageSize::K4 => 0b00,
             PageSize::K16 => 0b01,
-            PageSize::K64 => 0b10,
+            PageSize::K64 => 0b11,
         };
         Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
     }
# its-baser-not-valid
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -273,7 +273,7 @@
             PageSize::K16 => 0b01,
             PageSize::K64 => 0b10,
         };
-        Some(VALID | CACHED | at.get() | size << 8 | (pages - 1) as u64)
+        Some(CACHED | at.get() | size << 8 | (pages - 1) as u64)
     }
 }
 
# its-cache-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -237,7 +237,7 @@
 /// `InnerCache` `0b111`, read-allocate write-allocate write-back, with
 /// `OuterCache` zero, as the inner; `Shareability` [11:10] `0b01`, inner
 /// shareable. `GITS_BASER<n>` and `GITS_CBASER` hold them at the same bits.
-const CACHED: u64 = 0b111 << 59 | 0b01 << 10;
+const CACHED: u64 = 0b111 << 56 | 0b01 << 10;
 const VALID: u64 = 1 << 63;
 
 impl Backing {
# its-queue-full-is-empty
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -325,6 +325,6 @@
     /// One more command would make the writer's offset the reader's, which
     /// reads as empty: the queue holds one command fewer than its bytes do.
     pub const fn is_full(self, cwriter: u64, creadr: u64) -> bool {
-        self.after(cwriter) == self.offset(creadr) as u64
+        self.offset(cwriter) == self.offset(creadr) && false
     }
 }
# its-queue-full-reads-the-register
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -325,6 +325,6 @@
     /// One more command would make the writer's offset the reader's, which
     /// reads as empty: the queue holds one command fewer than its bytes do.
     pub const fn is_full(self, cwriter: u64, creadr: u64) -> bool {
-        self.after(cwriter) == self.offset(creadr) as u64
+        self.after(cwriter) == creadr
     }
 }
# its-queue-empty-reads-the-registers
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -319,7 +319,7 @@
 
     /// The ITS has read every command: the two registers' offsets are equal.
     pub const fn is_empty(self, cwriter: u64, creadr: u64) -> bool {
-        self.offset(cwriter) == self.offset(creadr)
+        cwriter == creadr
     }
 
     /// One more command would make the writer's offset the reader's, which
# its-queue-no-wrap
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -314,7 +314,7 @@
     /// What `GITS_CWRITER` is written with once the command at its offset is
     /// in memory: the offset after, wrapping at the queue's end.
     pub const fn after(self, cwriter: u64) -> u64 {
-        ((self.offset(cwriter) + 32) % self.bytes) as u64
+        (self.offset(cwriter) + 32) as u64
     }
 
     /// The ITS has read every command: the two registers' offsets are equal.
# its-queue-after-reads-the-register
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -314,7 +314,7 @@
     /// What `GITS_CWRITER` is written with once the command at its offset is
     /// in memory: the offset after, wrapping at the queue's end.
     pub const fn after(self, cwriter: u64) -> u64 {
-        ((self.offset(cwriter) + 32) % self.bytes) as u64
+        ((cwriter as u32).wrapping_add(32) % self.bytes) as u64
     }
 
     /// The ITS has read every command: the two registers' offsets are equal.
# its-queue-offset-field
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -308,7 +308,7 @@
     /// The byte offset an offset register names: its `Offset` field alone,
     /// and inside the queue whatever the ITS wrote there.
     pub const fn offset(self, register: u64) -> u32 {
-        (register & 0xF_FFE0) as u32 % self.bytes
+        (register & 0xF_FFFF) as u32 % self.bytes
     }
 
     /// What `GITS_CWRITER` is written with once the command at its offset is
# its-queue-offset-unbounded
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -308,7 +308,7 @@
     /// The byte offset an offset register names: its `Offset` field alone,
     /// and inside the queue whatever the ITS wrote there.
     pub const fn offset(self, register: u64) -> u32 {
-        (register & 0xF_FFE0) as u32 % self.bytes
+        (register & 0xF_FFE0) as u32
     }
 
     /// What `GITS_CWRITER` is written with once the command at its offset is
# its-cbaser-size
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -302,7 +302,7 @@
     /// `Physical_Address` [51:12] with its low four bits zero, `Size` [7:0]
     /// the pages minus one.
     pub const fn cbaser(self, at: Phys<16>) -> u64 {
-        VALID | CACHED | at.get() | (self.bytes / 4096 - 1) as u64
+        VALID | CACHED | at.get() | (self.bytes / 4096) as u64 & 0xFF
     }
 
     /// The byte offset an offset register names: its `Offset` field alone,
# its-offset-cwriter
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -30,7 +30,7 @@
 pub const GITS_CTLR: usize = 0x0000;
 pub const GITS_TYPER: usize = 0x0008;
 pub const GITS_CBASER: usize = 0x0080;
-pub const GITS_CWRITER: usize = 0x0088;
+pub const GITS_CWRITER: usize = 0x0084;
 pub const GITS_CREADR: usize = 0x0090;
 
 /// `GITS_BASER<n>`, n = 0 to 7 (§12.19.1): one per table the ITS may ask
# its-baser-stride
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -37,7 +37,7 @@
 /// memory for.
 pub const fn gits_baser(n: usize) -> Option<usize> {
     if n < 8 {
-        Some(0x0100 + 8 * n)
+        Some(0x0100 + 4 * n)
     } else {
         None
     }
# its-translation-frame
--- a/toyos-its/src/lib.rs
+++ b/toyos-its/src/lib.rs
@@ -47,7 +47,7 @@
 /// `GITS_TRANSLATER` is at 0x0040 of it (Table 12-34): a function's message
 /// is one 32-bit write of its EventID there, and the bus says which device
 /// wrote it.
-pub const TRANSLATION_FRAME: u64 = 0x1_0000;
+pub const TRANSLATION_FRAME: u64 = 0x2_0000;
 pub const GITS_TRANSLATER: u64 = 0x0040;
 
 /// `GITS_CTLR.Enabled` [0] (§12.19.4).

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutations at f8022b7, round 2, of toyos-its/src/lpi.rs (run with cargo test -p toyos-its): each a checked patch (git apply --check) applied to the committed tree, built (cargo test -p <crate> --no-run), run (cargo test -p <crate>) and reverted in the same script, the tree clean before and after each. 24 mutations, 24 red.

lpi-first: build EXIT=0 test EXIT=101 RED (2 tests failed)
lpi-past-space: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-res1: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-priority-bits: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-config-index: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-pending-bytes: build EXIT=0 test EXIT=101 RED (2 tests failed)
lpi-config-bytes: build EXIT=0 test EXIT=101 RED (2 tests failed)
lpi-layout-floor: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-layout-past-the-distributor: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-layout-without-lpis: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-lpis-bit: build EXIT=0 test EXIT=101 RED (6 tests failed)
lpi-idbits-field: build EXIT=0 test EXIT=101 RED (2 tests failed)
lpi-idbits-minus-one: build EXIT=0 test EXIT=101 RED (6 tests failed)
lpi-num-lpis-ignored: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-num-lpis-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-num-lpis-count: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-count-past-the-tables: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-space-of-any-idbits: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-space-idbits-mask: build EXIT=0 test EXIT=101 RED (4 tests failed)
lpi-propbaser-idbits: build EXIT=0 test EXIT=101 RED (5 tests failed)
lpi-pendbaser-not-zeroed: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-cache-field: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-processor-number: build EXIT=0 test EXIT=101 RED (1 tests failed)
lpi-offset-propbaser: build EXIT=0 test EXIT=101 RED (1 tests failed)
The patches
# lpi-first
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -6,7 +6,7 @@
 
 /// The first LPI's INTID (§2.2): everything below is an SGI, a PPI, an SPI
 /// or special.
-pub const FIRST: u32 = 8192;
+pub const FIRST: u32 = 8191;
 
 /// Offsets in a redistributor's `RD_base` frame (§12.10, Table 12-27).
 pub const GICR_PROPBASER: usize = 0x0070;
# lpi-past-space
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -117,7 +117,7 @@
 impl Space {
     /// `intid` as an LPI of this space.
     pub const fn lpi(self, intid: u32) -> Option<Lpi> {
-        if intid >= FIRST && intid - FIRST < self.lpis {
+        if intid >= FIRST {
             Some(Lpi(intid))
         } else {
             None
# lpi-config-res1
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -45,7 +45,7 @@
 /// One LPI's byte in the configuration table (Table 5-1): the priority's
 /// upper six bits in [7:2], bit [1] RES1, and the enable in bit [0].
 pub const fn configuration(priority: u8, enabled: bool) -> u8 {
-    priority & 0xFC | 0b10 | enabled as u8
+    priority & 0xFC | enabled as u8
 }
 
 /// The two tables laid out for INTIDs of `id_bits` bits, which the
# lpi-config-priority-bits
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -45,7 +45,7 @@
 /// One LPI's byte in the configuration table (Table 5-1): the priority's
 /// upper six bits in [7:2], bit [1] RES1, and the enable in bit [0].
 pub const fn configuration(priority: u8, enabled: bool) -> u8 {
-    priority & 0xFC | 0b10 | enabled as u8
+    priority | 0b10 | enabled as u8
 }
 
 /// The two tables laid out for INTIDs of `id_bits` bits, which the
# lpi-config-index
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -38,7 +38,7 @@
     /// Where its byte is in the configuration table: the table's first byte
     /// is INTID 8192's (§5.1.1).
     pub const fn configuration_index(self) -> usize {
-        (self.0 - FIRST) as usize
+        self.0 as usize
     }
 }
 
# lpi-pending-bytes
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -86,7 +86,7 @@
 
     /// One bit for every INTID, the first 8192 included.
     pub const fn pending_bytes(self) -> u64 {
-        (1 << self.id_bits) / 8
+        ((1 << self.id_bits) - FIRST as u64) / 8
     }
 
     /// `GICR_PROPBASER` (§12.11.33) for the configuration table at `table`:
# lpi-config-bytes
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -81,7 +81,7 @@
 
     /// One byte for every INTID from 8192 that `id_bits` bits number.
     pub const fn configuration_bytes(self) -> u64 {
-        (1 << self.id_bits) - FIRST as u64
+        1 << self.id_bits
     }
 
     /// One bit for every INTID, the first 8192 included.
# lpi-layout-floor
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -68,7 +68,7 @@
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
         let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
-        if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
+        if gicd_typer & 1 << 17 == 0 || id_bits > widest {
             return None;
         }
         let numbered = (1u64 << id_bits) - FIRST as u64;
# lpi-layout-past-the-distributor
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -68,7 +68,7 @@
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
         let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
-        if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
+        if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > 32 {
             return None;
         }
         let numbered = (1u64 << id_bits) - FIRST as u64;
# lpi-layout-without-lpis
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -68,7 +68,7 @@
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
         let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
-        if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
+        if id_bits < 14 || id_bits > widest {
             return None;
         }
         let numbered = (1u64 << id_bits) - FIRST as u64;
# lpi-lpis-bit
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -68,7 +68,7 @@
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
         let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
-        if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
+        if gicd_typer & 1 << 16 == 0 || id_bits < 14 || id_bits > widest {
             return None;
         }
         let numbered = (1u64 << id_bits) - FIRST as u64;
# lpi-idbits-field
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -67,7 +67,7 @@
     /// LPI, or above its INTIDs' (`IDbits` [23:19] plus one). Its `num_LPIs`
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
-        let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
+        let widest = (gicd_typer >> 18 & 0x1F) as u8 + 1;
         if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
             return None;
         }
# lpi-idbits-minus-one
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -67,7 +67,7 @@
     /// LPI, or above its INTIDs' (`IDbits` [23:19] plus one). Its `num_LPIs`
     /// [15:11], where not zero, counts the LPIs there are from 8192 on.
     pub const fn new(gicd_typer: u32, id_bits: u8) -> Option<Self> {
-        let widest = (gicd_typer >> 19 & 0x1F) as u8 + 1;
+        let widest = (gicd_typer >> 19 & 0x1F) as u8;
         if gicd_typer & 1 << 17 == 0 || id_bits < 14 || id_bits > widest {
             return None;
         }
# lpi-num-lpis-ignored
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -74,7 +74,7 @@
         let numbered = (1u64 << id_bits) - FIRST as u64;
         let counted = match gicd_typer >> 11 & 0x1F {
             0 => numbered,
-            count => 2u64 << count,
+            _ => numbered,
         };
         Some(Self { id_bits, lpis: if counted < numbered { counted } else { numbered } as u32 })
     }
# lpi-num-lpis-field
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -72,7 +72,7 @@
             return None;
         }
         let numbered = (1u64 << id_bits) - FIRST as u64;
-        let counted = match gicd_typer >> 11 & 0x1F {
+        let counted = match gicd_typer >> 10 & 0x1F {
             0 => numbered,
             count => 2u64 << count,
         };
# lpi-num-lpis-count
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -74,7 +74,7 @@
         let numbered = (1u64 << id_bits) - FIRST as u64;
         let counted = match gicd_typer >> 11 & 0x1F {
             0 => numbered,
-            count => 2u64 << count,
+            count => 1u64 << count,
         };
         Some(Self { id_bits, lpis: if counted < numbered { counted } else { numbered } as u32 })
     }
# lpi-count-past-the-tables
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -76,7 +76,7 @@
             0 => numbered,
             count => 2u64 << count,
         };
-        Some(Self { id_bits, lpis: if counted < numbered { counted } else { numbered } as u32 })
+        Some(Self { id_bits, lpis: counted as u32 })
     }
 
     /// One byte for every INTID from 8192 that `id_bits` bits number.
# lpi-space-of-any-idbits
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -99,7 +99,7 @@
     /// its `IDbits` is not what [`Layout::propbaser`] wrote, which a
     /// redistributor that holds the register as its own answers.
     pub const fn space(self, propbaser: u64) -> Option<Space> {
-        if (propbaser & 0x1F) as u8 == self.id_bits - 1 {
+        if true {
             Some(Space { lpis: self.lpis })
         } else {
             None
# lpi-space-idbits-mask
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -99,7 +99,7 @@
     /// its `IDbits` is not what [`Layout::propbaser`] wrote, which a
     /// redistributor that holds the register as its own answers.
     pub const fn space(self, propbaser: u64) -> Option<Space> {
-        if (propbaser & 0x1F) as u8 == self.id_bits - 1 {
+        if (propbaser & 0xFF) as u8 == self.id_bits - 1 {
             Some(Space { lpis: self.lpis })
         } else {
             None
# lpi-propbaser-idbits
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -92,7 +92,7 @@
     /// `GICR_PROPBASER` (§12.11.33) for the configuration table at `table`:
     /// `Physical_Address` [51:12] and `IDbits` [4:0], the bits minus one.
     pub const fn propbaser(self, table: Phys<12>) -> u64 {
-        CACHED | table.get() | (self.id_bits - 1) as u64
+        CACHED | table.get() | self.id_bits as u64
     }
 
     /// The LPIs of a redistributor that reads `propbaser` back: `None` where
# lpi-pendbaser-not-zeroed
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -129,5 +129,5 @@
 /// all zeroes: `Physical_Address` [51:16], and `PTZ` [62], which says so and
 /// lets the redistributor not read it.
 pub const fn pendbaser(table: Phys<16>) -> u64 {
-    1 << 62 | CACHED | table.get()
+    CACHED | table.get()
 }
# lpi-cache-field
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -59,7 +59,7 @@
 /// `InnerCache` [9:7] `0b111`, read-allocate write-allocate write-back, with
 /// `OuterCache` [58:56] zero, as the inner; `Shareability` [11:10] `0b01`,
 /// inner shareable. Both base registers hold them at the same bits.
-const CACHED: u64 = 0b111 << 7 | 0b01 << 10;
+const CACHED: u64 = 0b111 << 8 | 0b01 << 10;
 
 impl Layout {
     /// `GICD_TYPER` (§12.9.38) judged: `None` where the distributor takes no
# lpi-processor-number
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -22,7 +22,7 @@
 /// `GICR_TYPER.Processor_Number` [23:8]: how an ITS whose `GITS_TYPER.PTA`
 /// is clear names this redistributor.
 pub const fn processor_number(typer: u64) -> u16 {
-    (typer >> 8) as u16
+    (typer >> 16) as u16
 }
 
 /// An LPI's INTID, one the distributor and its redistributor both take:
# lpi-offset-propbaser
--- a/toyos-its/src/lpi.rs
+++ b/toyos-its/src/lpi.rs
@@ -9,7 +9,7 @@
 pub const FIRST: u32 = 8192;
 
 /// Offsets in a redistributor's `RD_base` frame (§12.10, Table 12-27).
-pub const GICR_PROPBASER: usize = 0x0070;
+pub const GICR_PROPBASER: usize = 0x0078;
 pub const GICR_PENDBASER: usize = 0x0078;
 
 /// `GICR_CTLR.EnableLPIs` [0] (§12.11.2): from here the redistributor reads

@Japabu Japabu changed the title toyos-acpi decodes the IORT and the MADT's MSI controllers; toyos-smmu and toyos-its hold the SMMUv3's and the ITS's encodings toyos-acpi decodes the IORT; toyos-smmu and toyos-its hold the SMMUv3's and the ITS's encodings, over toyos-phys's one address type Oct 9, 2026
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #798 at f8022b7e2, round 2, against .claude/agents/reviewer.md. Read: git diff 286536fb8 f8022b7e2 less the merge of #795, every changed file whole at the head, the body, the answer and its six evidence comments, the r2-* step logs, 18 mutation patches with their test logs. No build, test or QEMU was run.

Oracles fetched for this round from Arm's documentation service (User-Agent: toyos-build (https://github.com/ToyOSOrg/ToyOS)): IHI 0070 H.a, IHI 0069 H.b, DEN 0049 E.b, E.d, E.e, E.g. Text extracted with pypdf page by page; figure labels read with their page coordinates. No other kernel's source was read.

Growth. git diff --shortstat origin/main...f8022b7e2: 30 files, +3706 −2. Production Rust +1808 −2, tests +1746, manifests, lockfile, SOURCE and the issue +152. No consumer before G2 and G3, as in round 1; accepted as the stage's scope.

Earlier BLOCKERs

  1. CLOSED — the SMMUv3's own DeviceID mapping. iort.rs:257-272 reads offset 64 where E.g Table 14 bit 4 is set (node revision 5) and, in revision 4, where any of the four GSIVs is zero: E.d's row reads "If all the SMMU control interrupts are GSIV based, this field is ignored", E.g's "If the DeviceID mapping index valid flag is set to 0, this field must be ignored" and "Regardless of the flags, the OSPM must ignore the input base and length fields" — both read here. The indexed entry is skipped by check (:403) and by hits (:277), so its base and length are never judged and it routes no stream. Red first: r2-red-first.log, head 286536fb8 plus the tests-only patch, toyos-acpi --test iort 2 of 29 failed (Some(Range { at: 72, base: 4294967295, last: 4294967295 })), exit 101; both green in r2-suites.log at this head. Mutations iort-own-routes, iort-own-judged, iort-rev4-three-gsivs, iort-rev5-by-gsiv, iort-index-offset read: each red.
  2. CLOSED — IDR5.OAS. Every address handed to the unit passes Unit::reaches (unit.rs:226): stream table (:243), command queue (:255), event queue (:264), S1ContextPtr (config.rs:38), TTB0 (config.rs:83), a next-level table (table.rs:67), a leaf's output (table.rs:107). Nothing else carries an address to the unit: CMD_SYNC's MSIAddress is zero and no MSI configuration is written. The encodings (unit.rs:188) match IHI 0070 H.a §6.3.6's table row for row: 0b000 32, 0b001 36, 0b010 40, 0b011 42, 0b100 44, 0b101 48, 0b110 52, 0b111 56 ("In SMMUv3.3, this value is Reserved" in H.a's text) — all eight are defined in H.a, so there is no unknown encoding to refuse; 52 and 56 are past every 48-bit Phys and CD.IPS is capped at 0b101. address + bytes cannot overflow (address < 2^48, bytes ≤ 2^38). Red first: toyos-smmu 1 of 23 failed (Some((4611686022722355200, 0))), exit 101. Mutations ste-context-past-output, cd-root-past-output, next-past-output, plan-past-output, output-base-alone read: each red at the test that names the bound.
  3. CLOSED — git diff origin/main f8022b7e2 -- toyos-acpi/src/madt.rs is empty here too. What the kernel and loader link that differs from main: toyos-acpi/src/lib.rs (a mod iort, a pub use, a doc line) and toyos-bootmap/src/aarch64.rs (pub on PXN, a visibility change, same value). Nothing under kernel/, bootloader/ or userland/ names iort, Iort or any new crate (searched). No line a boot runs changed; no guest run is owed by this branch.
  4. CLOSED with 3.
  5. Siblings — CLOSED. The stage 1 leaves are now toyos_bootmap::aarch64::{block, page, table} with MAIR/PXN from there; the remaining bits and the redistributor fields are recorded in issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md with an exit a command can read. toyos-phys: no other physical-address newtype is in the tree (kernel/src/mm/pmm.rs's PhysPage is an ownership handle over a raw u64; toyos_acpi::Phys is a memory reader). Neither existing pure crate is its subject (toyos-bootmap is the loader's tables, toyos-abi the ABI), so a crate of one type both encoders share is accepted. toyos-virtio: the virtio 1.2 PCI transport and split virtqueue as one pure crate, with netstack's virtio-net its first client; toyos-device-memory: the one boundary toyos-i219 and toyos-virtio are written against #796, now on main, adds toyos-device-memory, whose device_addr is what a device is told — an IOVA, never a physical address by its own contract — so no overlap in purpose; git merge-tree of origin/main (bd5d0ad82) and this head is clean.

Earlier NOTEs

All closed: Ste/Cd hold private words, ABORT and stage1 the only constructors; Events::after(cons, prod) writes OVACKFLG as prod's OVFLG and cannot forge one, Commands::after takes no event index (events-after-clears-the-ack, events-overflowed-by-prod-alone red); CommandQueue masks both registers to Offset (its-queue-full-reads-the-register red); Event, Collection, Lpi are types bounded by EventBits, Its::collections and Layout/Space (lpi-space-of-any-idbits red); coherence carried once, refused as Lacks::CoherentAccess (probe-incoherent-accepted red); the two refused-but-legal shapes are said so at IortRefused::SingleMapping and ItsGroup. Deletions: a claim's release stays expressible — IHI 0070 §4.3.1 "This command invalidates all Context Descriptors (including L1CD) that were cached using the given StreamID", so ForgetStream (CMD_CFGI_STE) covers CMD_CFGI_CD with one CD per stream, and InvalidateAsid (CMD_TLBI_NH_ASID) stays; ITS release keeps DISCARD and UnmapDevice. Nothing deleted is needed by the gate as planned.

Checked this round

  • CD.MAIR0 0x0044ff04: byte 0 0x04 Device-nGnRE, byte 1 0xFF Normal inner/outer write-back non-transient read/write-allocate, byte 2 0x44 Normal non-cacheable, byte 3 zero; [223:192] holds attributes 0..3. The memory leaf's AttrIndx is 1 (Normal WB, SH inner) and the doorbell's 0 (Device-nGnRE) — each right for its use. Attribute 2 is present and unused, harmless. mair0-shifted red.
  • INV, DISCARD (IHI 0069 H.b PDF pages 106, 108): the ruler reads 63@x159, 32 31 8 7 0 DW@x328; DW0's row DeviceID@231, RES0@396, 0x0F/0x0C@488; DW1's row RES0@237; pypdf places the EventID label at (0,0) on both pages, as the implementer says, and the page's text order puts it after DW1's RES0 ("RES0 EventID"), so EventID is [31:0] of DW1. The prose gives "DISCARD DeviceID, EventID" / "INV DeviceID, EventID". command.rs:53-54 match. its-discard-is-inv red.
  • Register offsets, each against its summary row in the extracted text: SMMU page 0 (§6.2.1) 0x0020 SMMU_CR0, 0x0024 SMMU_CR0ACK, 0x0044 SMMU_GBPA, 0x0080 SMMU_STRTAB_BASE, 0x0088 SMMU_STRTAB_BASE_CFG, 0x0090 SMMU_CMDQ_BASE, 0x0098 SMMU_CMDQ_PROD, 0x009C SMMU_CMDQ_CONS, 0x00A0 SMMU_EVENTQ_BASE; page 1 (§6.2.2, "Offsets are relative to the base of Page 1,0x10000") 0x00A8 SMMU_EVENTQ_PROD, 0x00AC SMMU_EVENTQ_CONS; IDR5 "Accessible at offset0x0014". ITS Table 12-33 0x0000 GITS_CTLR, 0x0008 GITS_TYPER, 0x0080 GITS_CBASER, 0x0088 GITS_CWRITER, 0x0090 GITS_CREADR, 0x0100-0x0138 GITS_BASER<n>; Table 12-34 0x0040 GITS_TRANSLATER in the frame at "ITS_base + 0x010000"; Table 12-27 0x0070 GICR_PROPBASER, 0x0078 GICR_PENDBASER. All match unit.rs:11-32, toyos-its/src/lib.rs:30-51, lpi.rs:12-13.
  • DEN 0049 E.g Table 13: Event 44, PRI 48, GERR 52, Sync 56, index 64; Table 14 bit 4 "DeviceID mapping index valid". Matches iort.rs:54-62.
  • The deferred reference judgement — sound, accepted. A route's answer rests on its own hit alone: overlap is judged on input ranges (hits), which needs no reference, so a malformed reference in another mapping cannot change which mapping a requester takes; the one reference a route follows is judged and refused there. A caller never acts on a route that a whole-table refusal would have changed. Cost per route: one node walk for the segment's root complexes plus their mappings, two output walks and the SMMU's mappings — linear; measured 14,812 bytes on the 417,236-byte table, against 824,594 to open (opening_a_table_and_routing_a_requester_each_read_it_a_bounded_number_of_times; iort-open-follows-references red). The kernel routes once per claim. The body says a reference nothing routes through is unjudged.
  • Mutations: 18 read (the 14 named above plus iort-rev5-by-gsiv, iort-open-follows-references, events-overflowed-by-prod-alone, lpi-space-of-any-idbits): each a real change, each red in its test log; r2-mutations.log ends EXIT=0 with 230 red.
  • Evidence at f8022b7e2 (r2-gates.txt: head and porcelain 0 before and after): suites 0 (101/27/21/2), mutations 0, clippy 0 ("24 invocations clean"), cargo test --lib 0 (339 passed), cargo run -- --ci host 0 ("Host: 78 step(s), all green", the iort, both encodings and phys binaries in it; its 47 FAILED lines are the harness's own negative controls, each "verdict(s) reached"), --build-only 0.

BLOCKER

None.

NOTE

  • toyos-acpi/src/iort.rs:262-271 — a revision 5+ SMMUv3 node with the index-valid flag set and an index at or past its mapping count is accepted and treated as having no own entry (a_single_mapping_that_is_not_its_smmus_own_is_refused asserts it routes). E.g says the index is "an index into the array of ID mapping"; one past the array is malformed firmware, and the rule is refusal by name, not a reading that happens to fail open. Refuse it at open (Mappings or a named variant) and turn that assertion into the refusal.
  • toyos-acpi/src/iort.rs:214-251 — Route::Translated { stream } cannot say that another requester shares the StreamID: overlap is judged on input IDs within the asked segment, so two RID ranges of one root complex, or two segments, mapped to the same output streams of one SMMU both answer Translated with the same stream. G2 attaches a claim's domain at that stream, which would hand the other function the claim's DMA. Not a defect of the decoder, which reports the table truthfully, but the gate's isolation rests on it: either route refuses a stream another mapping into the same SMMU also reaches, or the issue records it with stage 6's SMMUv3 unit as owner and an exit (a claim on a shared stream is refused, and a test reds without it).
  • toyos-acpi/src/lib.rs:38 — IORT_NEEDED is exported and read by nothing outside iort.rs; drop it from the pub use.
  • issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md — the owner is "the ARM desktop gate's next two stages", which no record in the tree names; the owner that exists is issues/toyos-runs-on-arm64.md's stage 6 (SMMUv3 from IORT, and the ITS moved there).

What the landing head must show

The branch must merge main first (#796 has landed; the merge is clean by git merge-tree). At the head that lands, host and guest / suite green, neither skipped — the pull request is a draft and its checks read skipping; host's log holding the iort tests, both encodings suites and phys. No metal row is owed: no kernel, loader or claim path changes. Once the three non-prose NOTEs above are made, the orchestrator may land on reading those two checks alone, without another review round, provided the diff to toyos-acpi/src/madt.rs is still empty.

LAND AFTER NAMED CHANGES

Japabu and others added 2 commits October 9, 2026 14:46
…mapping index past its array is refused by name

DEN 0049 E.g Table 13 makes the DeviceID mapping index "an index into
the array of ID mapping". Where the node says the field is valid (the
flag from node revision 5, an unwired control interrupt before it) an
index at or past the array's count named no entry and read as a node
with no mapping of its own, which routed: malformed firmware failing
open. `iort` now refuses it as `IortRefused::OwnMapping`.

`Route::Translated` named a stream without saying whether anyone else's
transactions arrive under it. Overlap was judged on input IDs within
the asked segment, so two RID ranges of one root complex, two segments,
or a named component mapped onto the same streams of one SMMUv3 each
answered `Translated` with the same stream, and a claim attaching its
domain there would hand another function its DMA. The IORT holds every
mapping that reaches the unit, so `route` now walks them once more and
refuses a stream any other mapping puts out to the same unit as
`IortRefused::SharedStream`, whatever the other node's type. A node type
`check` does not decode has its ID array held to Table 4's header and
its own length before that walk reads it.

`IORT_NEEDED` leaves the crate's exports; nothing outside `iort.rs`
reads it. The duplicated-declarations issue names its owner as
`issues/toyos-runs-on-arm64.md`'s stage 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…d the installed NVMe image (#797), into the IORT decode and the SMMUv3 and ITS encodings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round-3 mutation patches, each applied to 6193b7376 by the round's mutation script (git apply --check, apply, cargo test -p toyos-acpi --no-run, cargo test -p toyos-acpi, git apply -R, the tree checked clean before and after). r3-mutations.log, which ends EXIT=0:

iort-own-past-accepted: build EXIT=0 test EXIT=101 RED (2 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-own-past-by-one: build EXIT=0 test EXIT=101 RED (2 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-unchecked: build EXIT=0 test EXIT=101 RED (2 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-any-unit: build EXIT=0 test EXIT=101 RED (3 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-counts-itself: build EXIT=0 test EXIT=101 RED (13 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-by-index-alone: build EXIT=0 test EXIT=101 RED (1 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-single-spans: build EXIT=0 test EXIT=101 RED (1 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-span-exclusive: build EXIT=0 test EXIT=101 RED (1 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-array-floor: build EXIT=0 test EXIT=101 RED (1 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
iort-shared-array-end: build EXIT=0 test EXIT=101 RED (1 tests failed) [toyos-acpi toyos-acpi/src/iort.rs]
EXIT=0

iort-own-past-accepted, test exit 101, failed: a_single_mapping_that_is_not_its_smmus_own_is_refused an_smmus_own_mapping_index_at_or_past_its_id_array_is_refused

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -434,7 +434,7 @@
             return Err(IortRefused::Mappings { at, count, offset });
         }
         let own = self.own(raw);
-        if let Some(index) = own.filter(|index| *index >= count) {
+        if let Some(index) = own.filter(|_| false) {
             return Err(IortRefused::OwnMapping { at, index, count });
         }
         for index in (0..count).filter(|index| Some(*index) != own) {

iort-own-past-by-one, test exit 101, failed: a_single_mapping_that_is_not_its_smmus_own_is_refused an_smmus_own_mapping_index_at_or_past_its_id_array_is_refused

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -434,7 +434,7 @@
             return Err(IortRefused::Mappings { at, count, offset });
         }
         let own = self.own(raw);
-        if let Some(index) = own.filter(|index| *index >= count) {
+        if let Some(index) = own.filter(|index| *index > count) {
             return Err(IortRefused::OwnMapping { at, index, count });
         }
         for index in (0..count).filter(|index| Some(*index) != own) {

iort-shared-unchecked, test exit 101, failed: a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused a_stream_is_not_judged_alone_past_an_id_array_out_of_its_node

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -251,7 +251,6 @@
                         _ => return Err(IortRefused::Output { at: unit.at, reference: mapping.reference }),
                     },
                 };
-                self.alone(unit, (root.at, index), id)?;
                 Ok(Route::Translated { smmu, stream: id, its })
             }
             _ => Err(IortRefused::Output { at: root.at, reference: mapping.reference }),

iort-shared-any-unit, test exit 101, failed: a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused an_id_two_mappings_claim_is_refused_when_it_is_asked_for the_its_boot_routes_bus_zero_through_the_smmu_to_the_its_and_the_rest_past_it

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -306,7 +306,7 @@
                 let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
                 let last = if mapping.single { 0 } else { mapping.span };
                 let reaches = stream >= mapping.output && stream - mapping.output <= last;
-                if reaches && mapping.reference as usize == unit.at {
+                if reaches {
                     return Err(IortRefused::SharedStream { stream, at });
                 }
             }

iort-shared-counts-itself, test exit 101, failed: a_mapping_carries_an_id_by_its_offset_from_the_input_base_and_ends_at_its_last_id a_revision_4_smmu_indexes_its_own_mapping_whenever_a_control_interrupt_is_not_wired a_route_through_an_output_reference_that_names_no_node_is_refused a_single_mapping_that_is_not_its_smmus_own_is_refused a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused a_stream_is_not_judged_alone_past_an_id_array_out_of_its_node a_stream_the_smmu_maps_nowhere_has_no_its an_id_two_mappings_claim_is_refused_when_it_is_asked_for an_smmus_own_mapping_index_at_or_past_its_id_array_is_refused the_its_boot_routes_bus_zero_through_the_smmu_to_the_its_and_the_rest_past_it the_hardware_gic_boot_routes_bus_zero_through_the_smmu_and_nothing_to_an_its the_mapping_a_revision_5_smmu_indexes_is_the_units_own_and_routes_no_stream opening_a_table_and_routing_a_requester_each_read_it_a_bounded_number_of_times

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -302,7 +302,7 @@
             if count != 0 && ((offset as usize) < NODE_HEADER || end > raw.len as u64) {
                 return Err(IortRefused::Mappings { at, count, offset });
             }
-            for index in (0..count).filter(|index| (at, *index) != by) {
+            for index in (0..count).filter(|_| true) {
                 let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
                 let last = if mapping.single { 0 } else { mapping.span };
                 let reaches = stream >= mapping.output && stream - mapping.output <= last;

iort-shared-by-index-alone, test exit 101, failed: a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -302,7 +302,7 @@
             if count != 0 && ((offset as usize) < NODE_HEADER || end > raw.len as u64) {
                 return Err(IortRefused::Mappings { at, count, offset });
             }
-            for index in (0..count).filter(|index| (at, *index) != by) {
+            for index in (0..count).filter(|index| *index != by.1) {
                 let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
                 let last = if mapping.single { 0 } else { mapping.span };
                 let reaches = stream >= mapping.output && stream - mapping.output <= last;

iort-shared-single-spans, test exit 101, failed: a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -304,7 +304,7 @@
             }
             for index in (0..count).filter(|index| (at, *index) != by) {
                 let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
-                let last = if mapping.single { 0 } else { mapping.span };
+                let last = mapping.span;
                 let reaches = stream >= mapping.output && stream - mapping.output <= last;
                 if reaches && mapping.reference as usize == unit.at {
                     return Err(IortRefused::SharedStream { stream, at });

iort-shared-span-exclusive, test exit 101, failed: a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -305,7 +305,7 @@
             for index in (0..count).filter(|index| (at, *index) != by) {
                 let mapping = self.mapping(raw, index).ok_or(IortRefused::Mappings { at, count, offset })?;
                 let last = if mapping.single { 0 } else { mapping.span };
-                let reaches = stream >= mapping.output && stream - mapping.output <= last;
+                let reaches = stream >= mapping.output && stream - mapping.output < last;
                 if reaches && mapping.reference as usize == unit.at {
                     return Err(IortRefused::SharedStream { stream, at });
                 }

iort-shared-array-floor, test exit 101, failed: a_stream_is_not_judged_alone_past_an_id_array_out_of_its_node

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -299,7 +299,7 @@
             // [`Iort::check`] placed the arrays of the types it decodes; a
             // type it does not is held to Table 4's header and its own length.
             let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-            if count != 0 && ((offset as usize) < NODE_HEADER || end > raw.len as u64) {
+            if count != 0 && end > raw.len as u64 {
                 return Err(IortRefused::Mappings { at, count, offset });
             }
             for index in (0..count).filter(|index| (at, *index) != by) {

iort-shared-array-end, test exit 101, failed: a_stream_is_not_judged_alone_past_an_id_array_out_of_its_node

--- a/toyos-acpi/src/iort.rs
+++ b/toyos-acpi/src/iort.rs
@@ -299,7 +299,7 @@
             // [`Iort::check`] placed the arrays of the types it decodes; a
             // type it does not is held to Table 4's header and its own length.
             let end = u64::from(offset) + u64::from(count) * MAPPING as u64;
-            if count != 0 && ((offset as usize) < NODE_HEADER || end > raw.len as u64) {
+            if count != 0 && (offset as usize) < NODE_HEADER {
                 return Err(IortRefused::Mappings { at, count, offset });
             }
             for index in (0..count).filter(|index| (at, *index) != by) {

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 3, answering #798 (comment). Head 6193b7376: bf56c79ef (the four NOTEs) with origin/main at 558283168 merged in, clean. git diff origin/main HEAD -- toyos-acpi/src/madt.rs prints nothing.

NOTE 1, an own-mapping index past the array — fixed. check refuses an SMMUv3 whose index holds (the flag from node revision 5, an unwired GSIV in revision 4) and is at or past its mapping count, as IortRefused::OwnMapping { at, index, count }. The assertion in a_single_mapping_that_is_not_its_smmus_own_is_refused that had it route is now that refusal, and an_smmus_own_mapping_index_at_or_past_its_id_array_is_refused covers indices 1, 2 and u32::MAX of one mapping, index 0 of none, revision 4 by an unwired GSIV, and the three shapes in which the field is ignored and no value of it is refused. Mutations iort-own-past-accepted and iort-own-past-by-one are red (2 tests each).

NOTE 2, a stream another requester shares — fixed by refusing. The IORT holds every mapping into a unit, so no record was needed. route judges a Translated stream last: one more walk of every node and its mappings, and IortRefused::SharedStream { stream, at } where any mapping but the requester's own puts that stream out to the same SMMUv3, whatever the other node's type (a single mapping counts its output base alone). A node type check does not decode has its ID array held to Table 4's header and its own length before the walk reads it. a_stream_another_mapping_puts_out_to_the_same_smmu_is_refused has two RID ranges of one root complex mapped onto the same streams (refused at both ends of the overlap, routed on each side of it), two segments onto one unit, the same stream numbers of a second unit (routed), and a named component's single mapping onto the unit (refused) and onto an ITS (routed). a_stream_is_not_judged_alone_past_an_id_array_out_of_its_node covers the placement check, and the cost test now holds a translated route through 500 nodes to twice the table's length. Eight mutations (iort-shared-*) are red. Patches and results: #798 (comment).

NOTE 3, IORT_NEEDED — fixed. It is gone from toyos-acpi/src/lib.rs's pub use and is a private const in iort.rs.

NOTE 4, the issue's owner — fixed. issues/the-smmuv3-and-its-crates-declare-what-the-aarch64-kernel-also-declares.md names issues/toyos-runs-on-arm64.md's stage 6 (its SMMUv3 unit and its ITS) as owner; the body's sibling paragraph says the same.

Gates at 6193b7376, each the command's own exit, one script, the tree clean before and after: suites 0 (toyos-acpi 104, toyos-smmu 27, toyos-its 21, toyos-phys 2); the ten new mutations 0, ten red; cargo run -- --clippy 0; cargo test --lib 0 (340 passed); cargo run -- --ci host 0 ("Host: 78 step(s), all green"); cargo run -- --build-only 0. Round 2's 230 mutations were not run again. Not run: --metal in any form, and any guest test, since no line a boot runs changed.

Two things this round changes beyond the NOTEs' words, both fail-closed and both in the body's "unsure" list. A revision 4 SMMUv3 with an unwired GSIV and no ID mapping now refuses the whole table, because its index holds and names nothing. And a reserved memory range node mapping a stream counts as a sharer of it.

@Japabu
Japabu marked this pull request as ready for review October 9, 2026 13:06
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

CI at 6193b7376, read from each job's own log (the orchestrator). host: [ci] Host: 79 step(s), all green, running toyos-acpi's tests/iort.rs, both tests/encodings.rs suites and tests/phys.rs. guest / suite: test result: ok. 38 passed, 38 total, [ci] Guest: 5 step(s), all green, no FAIL line. git diff origin/main 6193b7376 -- toyos-acpi/src/madt.rs is empty, so nothing a booting kernel runs differs from main. git merge-tree against main at 558283168 exits 0 with no conflict.

@Japabu
Japabu added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit dcfeecf Oct 9, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-arm-g1 branch October 9, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant