Skip to content

AMD-Vi stage 6: interrupt remapping, and claims only where every unit filters SMIs - #921

Merged
Japabu merged 17 commits into
mainfrom
wt/toyos-amdvi-intremap
Oct 14, 2026
Merged

Japabu merged 17 commits into
mainfrom
wt/toyos-amdvi-intremap

Conversation

@Japabu

@Japabu Japabu commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Stage 6 of the AMD-Vi track: interrupt remapping and the SMI filter, the second PR of the design accepted on #909. Draft, for the landing round. It closes issues/an-amd-vi-machine-isolates-no-device.md: its translation and remapping rows passed on the AMD bench, and its claimed-function clause, a claim's message delivered through its own remapping entry with a foreign one refused, did not pass but moved to P1 of issues/there-is-no-wifi.md (the 03:00.4 bullet, with its reason). It also closes issues/a-controller-whose-msix-table-is-in-bar-0-cannot-be-driven-from-userland.md into P1 (ruled): the table's BAR is no longer withheld, and what remains is P1's placement.

Source: AMD 48882 rev 3.11, SHA-256 f7c375a15db5ed63de760356867211063d164a2ed59f2d38613daec95894ce22, cited at each site.

What this PR does

  • Remapping is decided once per machine. It is on when every programmed unit has GASup (and XTSup on an x2APIC machine) and the IVRS names every I/O APIC the MADT has. Otherwise every interrupt half stays invalid and messages pass as written, said by name. iommu-no-remap stands in for a unit that cannot remap.
  • Where it remaps, CONTROL gains GAEn (and XTEn on x2APIC). Every device table entry's interrupt half aborts fixed and arbitrated messages until its requester has a 256-entry table of its own, indexed by vector. Kernel drivers, claims and I/O APIC pins each write the entry at their vector and invalidate it. A message on a vector the requester's table does not hold is aborted and recorded.
  • The SMI filter. At init each unit with SmiFSup has its filter planned: what firmware left valid, plus the ids the IVRS names the chipset's I/O APIC and HPET by, every register locked (§2.1.5.3). iommu::drivers_settled() runs after the kernel's drivers have taken their controllers from firmware and before userland starts. It writes the registers and sets SmiFEn | SmiFLogEn, logging SMI filter on control=… passing …. A unit with too few free registers filters nothing, said.
  • The reset path. Every unit's filter is turned off, without a lock, before the reset register or SLP_EN is written.
  • A refused message is an IO_PAGE_FAULT at an interrupt-range address (Table 3; bits 63:40 all zero, or all ones as the unit writes the range's base). toyos_amdvi::queue::disposition says whose it is:
    • an IO_PAGE_FAULT with I [51] set (Table 57) is a refused message too, whatever address it names;
    • from a requester id the IVRS gives the platform (I/O APIC, HPET, or an id firmware's filter passes), it is the platform's, whatever its type: logged with id, type and address, bounded, and the machine goes on (the orchestrator's ruling, 6179195333);
    • from a function a claim holds, it is the holder's fault;
    • an SMI (Table 4) from anyone else is logged and the machine goes on;
    • any other is a fault on its requester.
  • One gate for claims. iommu::remapping() grants a claim only where every message its function sends reaches only its own interrupt, whatever its holder writes into its MSI-X table or aims a DMA at. On VT-d and the ITS that means remapping. On AMD-Vi it also needs every unit filtering SMIs, since a write to the interrupt range is an interrupt (§2.1.4.2) and an SMI no remapped message (Table 10). Where a claim is granted, the MSI-X table's BAR is placed like any other. Refusals say which reason applies: its interrupts would not be remapped… or an IOMMU unit on this machine filters no SMI….
  • Claims never take platform ids. A requester an IVHD special entry names (I/O APIC, HPET), or one firmware's filter passes, is refused at the claim. A claimed function's entry passes nothing firmware declared and gives no SysMgt, for the rest of the boot.
  • QEMU's AMD unit has no SMI filter, so QEMU grants no claim behind it. usbd_refused_without_smi_filter reads that refusal.

The round-3 panic, and its fix

At 4f1d6fc3a the testcases image hung the AMD bench, which never reset itself. The owner's photo of the panel (photo, metadata stripped; transcribed in comment 6173269406) shows its end:

iommu: DMA FAULT owner=kernel unit0 stream=00:14.0 addr=0xfffffffdf8000020 access=none reason=0x02 ... IO_PAGE_FAULT
panic: rebooting in 60 s, timed by the calibrated clock

That tree turned the filter on in program, before the kernel's xHCI handoff, with no register valid. Firmware leaves the filter off on this bench, with 2 registers. The unit then refused a message from 00:14.0, the id the IVRS gives the chipset's I/O APIC and HPET, and recorded it as an IO_PAGE_FAULT. The decoder read that record as a DMA fault on a kernel-owned stream, and the kernel's bug fence panicked. The panic's reset writes SMI_CMD (0xb0 <- 0xfb on this bench). The SMI the chipset raises for it would reach the filter under the same refused id, which explains, as an inference, why the machine never reset. The stick held only the loader's log.

Round 4 changed three things:

  • the filter goes on only after the handoff;
  • it passes the chipset's ids (00:14.0 and 00:00.1);
  • it is turned off before any reset.

At c648c1439 the bench read iommu: amdvi unit0 SMI filter on control=0x000000000142140d, passing 00:14.0 00:00.1, logged no refused message, and reset itself.

The record's type. Its address 0xfffffffdf8000020 has bits 63:40 all ones, the interrupt range as §2.2.5 writes its base, and its MT reads 000b, a fixed interrupt, not Table 4's SMI 010b. Whether the filter or 00:14.0's remapping table refused it is not settled by one reading. Under the orchestrator's ruling (6179195333) that no longer matters: since 1ceab43bc, a refused message from a platform requester id is the platform's whatever its type, logged and survived, never a kernel DMA fault. No kernel driver drives those ids, and a claim on one is refused.

P1 dependency

amdvi_usbd_drives_its_own_entry cannot pass on the AMD bench before P1 of issues/there-is-no-wifi.md. The row claims 03:00.4, whose only BAR holds its MSI-X table. That BAR sits behind port 00:08.1, which forwards only 0xd0000000..0xd0500000, and bus 3's BARs fill that window. The claim needs a BAR placed where the bridge forwards it, and a 2 MiB slot of its own does not exist there. At c648c1439, before #928, the claim probed addresses the bridge does not forward and was refused with its BAR was moved onto 8 64-bit address(es) … and the function answered at none of them. With #928 merged (335927b83), this branch places the table's BAR like any other, so 03:00.4's one BAR meets NoRoutedRun (keeps BAR 0 where firmware put it and hands it to nobody — no 2 MiB the bridges above it forward is free) and the claim is refused NoMappableBar. The issue #928 opened now says so. The AMD-Vi filter is not involved.

The row therefore leaves the registered AMD rows and goes into P1's exit. Its four jobs stay (usbd_spare, usbd_spare_fault, end_returns_claims, usbd_spare_vector), and its judge amdvi_usbd comes back with P1 from c648c1439. The T14's usbd_drives_the_type_c_controller row is unchanged. The xhci-leave image is no longer staged: no registered row judges that boot, and the named refusal is #928's to read, not this PR's.

#928 landed and is merged here.

AMD bench at 639028982 (orchestrator's run, the landing images)

3 of 3 rows pass in 2 boots (judge):

row boot result
amdvi_translates_the_stick_controller, amdvi_remaps_the_stick_controller testcases pass: SMI filter on control=0x000000000142140d, passing 00:14.0 00:00.1; no event record; the machine reset itself
amdvi_claim_refused_without_remapping testcases-amd-no-remap pass

Readbacks: testcases kernel log, no-remap kernel log, manifest, runner log. The testcases boot's ACPI_ENABLE write to SMI_CMD at 7.411 s, after the filter went on at 7.368 s, set SCI_EN: the platform's runtime SMI passes with the filter on. The commits since (1ceab43bc, and this round's I bit) change only the event decoder, which acts only on a record; these boots wrote none.

AMD bench at c648c1439 (orchestrator's run)

row boot result
amdvi_translates_the_stick_controller, amdvi_remaps_the_stick_controller testcases, alone first pass: filter on, bits 22 and 24 set, passing 00:14.0 00:00.1; the machine reset itself
amdvi_claim_refused_without_remapping testcases-amd-no-remap pass
amdvi_usbd_drives_its_own_entry testcases-amd-xhci-leave red: the placement refusal above; moved to P1

Readbacks: judge, testcases alone, judge, all three, testcases kernel log, no-remap kernel log, xhci-leave kernel log, manifests r4a and r4b. Machine, stick and user identifiers are masked.

Checks at 4b052d558 (the landing re-review's records, I read as an interrupt)

command target exit log
cargo run -- --ci host host, 86 steps; toyos-amdvi 22 tests, a_refused_message_is_told_from_a_dma_fault and a_page_fault_is_its_requesters_with_its_access now holding an I-set record as Event::Interrupt 0 r8-ci-host
cargo test --test toyos-build -- amdvi_selftest amdvi_firmware_left usbd_refused_without_smi_filter usbd_refused_without_amdvi_remapping QEMU AMD-Vi 0 (4 passed); QEMU's I-set selftest record still reads owner=kernel … access=none … IO_PAGE_FAULT r8-g1

This round also: device_bar_map's contract no longer names a withheld BAR (B1). The two issues that still described the withheld table BAR are corrected (B2). The MSI-X-table issue is closed into P1 (B3, ruled). The platform ids are published once each (S2). Two comments are reworded (S3). The usbd_spare vector and fault jobs stay, because P1's 03:00.4 bullet names them; their judge amdvi_usbd comes back from c648c1439 with P1 (S4).

Checks at 1ceab43bc (the platform-message ruling, on 639028982)

command target exit log
cargo run -- --ci host host, 86 steps; toyos-amdvi 22 tests, with a_refused_message_is_told_from_a_dma_fault and a_platform_message_is_the_platforms_whatever_its_type (the round-3 record, MT 000b from 00:14.0: the platform's; from anyone else: a fault) 0 r7-ci-host
cargo test --test toyos-build -- amdvi_selftest amdvi_firmware_left QEMU AMD-Vi 0 (2 passed) r7-g1
Negative control: disposition calling a platform id's message the platform's only when it is an SMI (patch); cargo test -p toyos-amdvi host 101, a_platform_message_is_the_platforms_whatever_its_type red log

No third bench boot for this change. It changes only the fault decoder and the drain, which act only when the unit writes an event record. The bench's boots at c648c1439 wrote none, and the two staged boots at 639028982 judge the same rows. The amdvi_remaps judge's change is print-only: it prints the drain's new … said so far lines instead of the old SMI filter refused ones, and passes or fails on the same lines as before. The kernel's wiring, a lock-free table of the platform's ids published before any unit is armed, has no QEMU oracle: QEMU's unit writes no such record.

Checks at 639028982 (main 335927b83 merged, clean tree)

command target exit log
cargo run -- --ci host host, 86 steps 0 r6-ci-host
cargo run -- --build-only / --arch aarch64 both 0 / 0 x86, in the stage, arm
cargo test --test toyos-build -- usbd_drives_the_spare usbd_refused_without_smi_filter usbd_refused_without_amdvi_remapping amdvi_selftest amdvi_firmware_left iommu_virtio_platform virt_claim_lpi QEMU AMD-Vi (intremap on and off), VT-d, virt/ITS 0 (7 passed) r6-g1
--metal --metal-readback … amdvi_claim_refused_without_remapping amdvi_remaps_the_stick_controller amdvi_translates_the_stick_controller stages the AMD bench, 2 boots, no machine 2 (staged) stage, request
Negative control at a8791b689 (the #928 merge after it changed neither the gate nor its row's path, which refuses before placement): the AMD-Vi SMI gate made false && (patch); … usbd_refused_without_smi_filter QEMU AMD-Vi 1, red as wanted: 00:10.0 handed over, the job panics on the grant log
Negative control: smi taking only Table 4's zero bits 63:40 (patch); cargo test -p toyos-amdvi host 101, an_smi_the_filter_refused_is_told_from_a_dma_fault red log

Round 4's host controls on the filter plan still hold, because reg.rs is unchanged since: registers left unlocked, and a missing source skipped instead of refused, each red (nolock, noroom).

Earlier rounds

  • Remapping round (b85bbb0ca): QEMU intremap=on,xtsup=on claim through its own entry, and the entry-off-by-one negative control (s6). On the bench, amdvi_remaps_the_stick_controller passed and the no-remapping control went red (bench).
  • Ruled round (c22745327): the MSI-X table's BAR handed over where the machine remaps; xhci-leave= by address; the metal claim rows (s6r2).
  • Build-review round (4f1d6fc3a): the first SMI filter, which caused the panic above. Its g8.log and ci-host5.log ran at e81f97f53 with the fix uncommitted, not at 4f1d6fc3a; the reviewer reran both clean at 4f1d6fc3a (s6r3).
  • Round 4 (c648c1439): R1(a) one gate, R2 decoder, the filter after the handoff and off before reset, R3 to R6, S1 to S4 (s6r4).

Risks

  • The SMI sources are an inference, now partly measured. The bench passed 00:14.0 and 00:00.1, reset itself through SMI_CMD, and logged no refused SMI in its three boots. A platform SMI raised under another id would still be refused, and logged, while the machine runs.
  • A claimed controller's handoff SMI. If a claimed controller raises its legacy SMI under its own id when usbd asks firmware for it, the refusal is the holder's fault. P1's AMD row will read this.
  • A function once claimed keeps Setting::CLAIMED for the rest of the boot. This is stricter than firmware's byte.
  • The unit's own interrupt stays a compatibility message (Capability Offset 10h); under XTEn it targets APIC 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1

Japabu and others added 3 commits October 14, 2026 02:56
…invalidation

Stage 6 of issues/an-amd-vi-machine-isolates-no-device.md encodes, against
AMD 48882 rev 3.11's own field positions:

- a device table entry's interrupt half (Table 7, bits 191:128): IV, a
  256-entry table (IntTabLen 1000b) at its root, IntCtl 00b (aborted) or
  10b (remapped), and the pass bits where Table 7 puts Table 106's;
  `Setting::CLAIMED`, which passes nothing and gives no SysMgt (review B2
  of the stage 5 design);
- the 128-bit remapping entry, GuestMode 0 (Figure 17, Table 22; Figure 19
  with XTEn): RemapEn, fixed, physical, the vector at 71:64, an 8-bit or a
  32-bit destination;
- INVALIDATE_INTERRUPT_TABLE (Figure 48), CONTROL's GAEn and XTEn, and EFR's
  GASup and XTSup.

Host tests hold each against the specification's bit positions, and the
bench's EFR as having GASup and XTSup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Stage 6 takes a claim's vector from #918's allocator (`idt::user_dev`,
`kernel::owners`) rather than beside it, as the orchestrator directed.
No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
…8-bit entries, claims through their own

Stage 6 of issues/an-amd-vi-machine-isolates-no-device.md, the second PR of
the design accepted on #909 (AMD 48882 rev 3.11).

- Where every programmed unit has GASup (and XTSup where the CPUs run in
  x2APIC) and the IVRS names every I/O APIC the MADT has, the kernel
  remaps: CONTROL gains GAEn (and XTEn on x2APIC), every device table
  entry's interrupt half is IV with IntCtl 00b (each requester's fixed
  messages aborted) before IommuEn, and a requester's first entry makes it
  a 256-entry table of its own (IntTabLen 1000b), named by one 64-bit
  write and an INVALIDATE_DEVTAB_ENTRY (§2.2.2.2). Otherwise every
  interrupt half stays invalid and messages pass as written, and the
  remapping decision says which requirement is missing.
- The table is indexed by MSI data bits 10:0 (Figure 14), so with fixed
  delivery the entry is the message's own vector: a function keeps the
  compatibility message it would carry with no unit, and reaches only the
  vectors its own table holds present. Source verification is the table's.
- Kernel drivers' messages, claims' and I/O APIC pins' each write the
  entry at their vector; a pin keeps its redirection entry's ordinary
  format, so a level pin's EOI names the vector the CPU took. A claim takes
  its vector from #918's allocator; its release writes the entry absent and
  invalidates before the vector is given back.
- A claim marks its function claimed: from then on its entry passes
  nothing firmware declared (no NMI, INIT, ExtInt, LINT) and gives it no
  SysMgt, whichever domain it names (stage 5 review B2).
- A claim on a function no programmed unit serves, or one an IVMD names,
  is refused at the claim (`Refusal::NotIsolated`), before a domain or a
  vector is spent (stage 5 review S2).
- `iommu_unit::interrupt` dispatches to the backend that remaps; the
  message, pin and refusal types it shares move there from VT-d, whose
  module keeps only its entries.
- Harness: `Unit::AmdVi { intremap }` (`intremap=on,xtsup=on` and the
  split irqchip), `Profile::HeadlessUsbSpareAmdVi`, and
  `usbd_drives_the_spare_amdvi`, which also reads the claim's entry
  present at its vector. The AMD bench's row
  `amdvi_remaps_the_stick_controller` reads GAEn, every I/O APIC named,
  and each kernel-driven xHCI armed through its own entry.

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

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator ruling: on a machine where every unit remaps, pcidev hands over a BAR that holds the function's MSI-X table.

The refusal in issues/a-controller-whose-msix-table-is-in-bar-0-cannot-be-driven-from-userland.md guarded against a holder aiming its device's message at an address the LAPIC decodes. With remapping on every unit, a message a holder writes reaches only a vector present in its own table and any other is aborted and recorded (48882, the per-requester table; VT-d with source verification gives the same), so the guard is the IOMMU's, not the kernel's refusal. Under the trust model that refusal withheld a feature (both AMD bench xHCI controllers) for a fence the hardware already keeps. Where any unit does not remap, the refusal stands.

This PR takes up that issue: it rewrites the exit to the remapping condition and keeps the non-remapping arm recorded. xhci-leave= takes a function address, since the bench's two controllers share an id. Then the three claim rows run on the bench: usbd drives 03:00.4 through its own entry with the fault arm, a claimed function writing a vector not its own is refused and recorded, and claim_refused_without_remapping gets a bench arm on the function the bench has. The two staged boots (head, no-remapping control) are running now and stand as they are.

Japabu and others added 3 commits October 14, 2026 08:15
…-leave takes an address; the AMD bench's claim rows

The orchestrator's ruling on #921: on a machine where every unit remaps,
the BAR holding a function's MSI-X table is handed over, because the
unit's per-requester table is the guard: a message the holder writes
there reaches only the claim's own interrupt, and anything else is
refused and recorded. A claim exists only where the machine remaps
(`Refusal::NotRemapped` first), so `place_bars` takes the `Remapping`
witness and no longer keeps the table's BAR back; `msix_bar` goes. The
release masks the entry where the table is now, since its BAR may have
moved since it was armed.

Takes up issues/a-controller-whose-msix-table-is-in-bar-0-cannot-be-driven-from-userland.md,
its exit rewritten to that condition.

- `xhci-leave=` names a function by `bus:device.function`: the AMD
  bench's two xHCI controllers share one id. The T14 row names `00:0d.0`;
  QEMU's spare is pinned at `00:10.0`.
- `usbd_spare`'s arms move to `tests/guest/rust-tests/src/usbd_spare.rs`,
  with a binary each for the fault, foreign-vector and unremapped-refusal
  arms, because a metal job takes no argument. The new `vector` arm
  rewrites the claim's own MSI-X entry, through the BAR it now holds, to a
  vector not the claim's, and reads that usbd is never told of its No-Op.
- QEMU: `Profile::HeadlessUsbSpareAmdVi`'s spare is armed on MSI-X with
  its table in its register BAR; `usbd_drives_the_spare_amdvi` adds the
  foreign-vector arm; `usbd_refused_without_amdvi_remapping` boots the AMD
  unit with `intremap=off` and reads the claim refused before anything on
  the function changes. QEMU records no fault for the refused message,
  filed as issues/qemus-amd-iommu-records-no-fault-for-a-message-its-table-refuses.md.
- AMD bench rows: `amdvi_usbd_drives_its_own_entry` (`03:00.4` handed over
  with its table's BAR, its No-Op answered through its own entry, aimed
  past its grant and recorded at that address, its holder's foreign vector
  refused and recorded and absent from the stop's unclaimed census), and
  `amdvi_claim_refused_without_remapping` (`iommu-no-remap`); the AMD unit
  now says when that actuator stands in for a unit that cannot remap.

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

#918 moved since 9b39b7c: review round 1 and two merges of main (#895,
#917 and more). No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
#918 landed; main's copy now stands where the pre-merged branch did. No
conflict.

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

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Build review: #921 at c22745327 (main 5b48a3d17 merged). Judged against reviewer.md, root CLAUDE.md, #909's design review and acceptance (6152887692, 6152903557), and the orchestrator's ruling (6158860882). I checked every encoding against my own PDFKit extraction of 48882 rev 3.11 (SHA-256 f7c375a1…ce22). Evidence read: g5 and ci-host4 (both stamped c22745327, clean), mut-table-bar-withheld (stamped b85bbb0ca dirty), metal2-stage, and the bench kernel log at dd9fb9aac.

Assessment

The stage-6 architecture is the one accepted on #909, and it is built well:

  • one machine-wide decision;
  • a 256-entry, 128-bit table per requester, indexed by vector, created on the requester's first entry;
  • every interrupt half blocked (IV, IntCtl 00b) before IommuEn;
  • one dispatch in iommu_unit::interrupt, with VT-d's shared types moved there rather than copied.

The encodings match the specification:

  • Table 7: IV 128, IntTabLen 132:129 = 1000b, root 179:134, IntCtl 189:188, and the Table 106 → 184/185/186/190/191 pass-bit mapping.
  • Figures 17/19 and Table 22: RemapEn 0, destination 15:8 or 31:8 plus 127:120, vector 71:64, IntType 000b, DM 0.
  • Figure 48: code 05h, DeviceID 15:0.
  • GAEn 17, XTEn 50, XTSup 2, GASup 7.

The index = vector rule holds because msi_message arms data = vector, which is Fixed. An arbitrated or other non-Fixed message from a holder indexes past entry 255, and Table 44 says an IRTE beyond the end of the table is target-aborted and logged. So the Fixed-delivery limit is safe.

The release order is right:

  1. tear_down turns off bus mastering and masks the entry.
  2. Remapped::drop writes the entry absent and sends INVALIDATE_INTERRUPT_TABLE, with a completion wait.
  3. user_dev::give_back returns the vector.

S2's refusal comes before function_space and claim_msi, so no domain or vector is spent. The Remapping witness on place_bars means the BAR is handed over only where remapping() holds, and on AMD that is the all-unit decision. The code is direct, and the net cost (production +599/−160, tests +701/−243) is justified by the work.

Three things fall short of what the ruling and the PR claim:

  • the claim that the IOMMU is the whole guard for the handed-over table (B1);
  • B2 on a shared requester id (B2);
  • the release path's handling of external input (B3).

Two test and judge defects would make the bench row red regardless of the hardware, or make a QEMU arm unable to fail (B4, B5).

Findings

B1 BLOCKER: on AMD-Vi, a holder of a handed-over MSI-X table can raise SMIs, so the ruling's premise does not hold. Locations:

  • kernel/src/pcidev/mod.rs:52-57 (the header's contract) and :944-947 (place_bars);
  • kernel/src/arch/x86_64/amdvi/mod.rs:491-497.

Problem:

  • Table 10 says: "SMI interrupts are passed through unmapped. There is no pass field to control SMI requests."
  • §2.1.5 says that with SmiFEn = 0, "SMI interrupts from any source are delivered to the processor(s)".
  • Table 19 routes message type 2 (SMI) to the SMI filter, not to the IRTE.
  • program writes CONTROL whole, so SmiFEn (bit 22) is always 0. On the bench, firmware also handed over control=0x0000220000000400 (filter off), even though the EFR has SmiFSup = 01b.

Consequence: the holder of 03:00.4 writes delivery mode 010b into its own MSI-X entry, and every CPU enters SMM, as often as it likes. That is a new authority for a process; before this PR the table was withheld. It also falsifies the ruling's "anything else is refused and recorded" for AMD-Vi. VT-d is unaffected: compatibility format is blocked, and a remappable message's delivery mode comes from the IRTE.

Fix: this changes the basis of an orchestrator ruling, so it goes back to the orchestrator. The options I see:

  • (a) where SmiFSup is set, enable the filter (SmiFEn, with SmiFLogEn so a refused SMI is recorded), keep any filter registers firmware locked, and hand over the table BAR only on units that filter;
  • (b) keep withholding the table BAR on AMD-Vi until (a) exists.

Either way, the pcidev header should state the actual condition.

B2 BLOCKER: B2 and per-requester isolation fail for a function that shares its requester id with an IVHD special entry. Locations: kernel/src/arch/x86_64/amdvi/interrupt.rs:69-81 and :141-144; kernel/src/arch/x86_64/amdvi/domain.rs:82-93.

Problem:

  • Remap::write writes the interrupt half (the setting) only when it creates a requester's table.
  • domain::claimed changes only settings.
  • On every AMD FCH, the I/O APIC speaks as 00:14.0, which is also a PCI function. The bench log shows special variety=1 handle=33 as 00:14.0 data=0xd7 and PCI 00:14.0 … 1022:790b.
  • The pin's table already exists at boot, with firmware's 0xd7 (INIT, ExtInt, NMI, LINT0, LINT1).
  • isolates refuses only unserved or IVMD-named ids.

Consequence: a claim on such a function writes its vector into the I/O APIC's table. Its entry keeps every pass bit, and it reaches every pin vector. On the bench 00:14.0 has no BAR, so the claim is refused later, but only after claim_msi has written into the I/O APIC's table and permanently set that requester's setting to CLAIMED.

Fix:

  • Refuse in isolates (S2's refusal) any requester id that an IVHD special entry uses (I/O APIC, HPET), recorded at start.
  • Have interrupt::claim assert that no table exists yet for that requester, so the invariant fails loudly.

B3 BLOCKER: release asserts on device-supplied config space. Locations: kernel/src/pcidev/mod.rs:1509 and kernel/src/drivers/pci.rs:504-510.

Problem: mask_claimed_msix re-reads the capability chain, the BIR and the BAR register from the device at teardown, then assert!s that it succeeded. The device is outside the trusted base. One that is surprise-removed, wedged, or returns all-ones (some devices do this transiently after a reset triggered from MMIO) makes msix_entry return Err, and the kernel panics. A device that answers a different BAR value gets kernel RAM remapped UC at 2 MiB grain and written to. Before this PR, the release used the Mmio mapped at arm time and read no config.

Fix: compute the entry's address from the kernel's own record:

  • the BIR and offset captured at arm;
  • plus bound.bar_at[bir], which place_bars chose.

No config re-read and no assert on external input. Alternatively, arm after place_bars and keep the mapping in Armed::Msix.

B4 BLOCKER: the bench judge reads the stop's census from a log that never carries it. Location: tests/toyos.rs:4031, kernel.must_say("irq: unclaimed vectors").

Problem: Boot::kernel() ends where logkeeper was made whole (tests/common/metal.rs:246-250). The census is logged inside the stop, after that point. irq_census_conservation (tests/toyos.rs:499-505) says so ("no file carries it") and reads after_the_reset().

Consequence: amdvi_usbd_drives_its_own_entry is red by construction, whatever the silicon does.

Fix: read the census from b[0].after_the_reset()?. The running boots can be re-judged from their readback without another boot.

B5 BLOCKER: the QEMU foreign-vector arm cannot fail, and three texts claim it shows a refusal. Locations:

  • tests/toyos.rs:4045-4061 (foreign_vector's doc says "the unit refuses that message and records it", while the code asserts that no record exists);
  • issues/qemus-amd-iommu-records-no-fault-for-a-message-its-table-refuses.md:11-19 ("drops such a message"; "that test holds … that the message reached nobody");
  • issues/a-controller-whose-msix-table-is-in-bar-0-cannot-be-driven-from-userland.md:19-20 ("reaches nobody").

Problem: the arm asserts only that usbd was never told of its No-Op. A unit that forwarded the message unmapped to 0x43 passes it too, because 0x43 is an unheld claim vector that unclaimed absorbs. Nothing in g5 shows the message reached nobody. "QEMU drops it" is an inference stated as a measurement.

Fix (cheapest): have the QEMU arm read the census after a clean stop and assert that 0x43 is absent. That gives QEMU an oracle, and the issue's wording then becomes true. Otherwise drop the arm from QEMU and correct all three texts to say the bench row holds the refusal.

B6 BLOCKER: stale or false records and contracts.

  • sdk/abi/src/syscall.rs:2428-2431: device_bar_map still documents InvalidArgument for the BAR holding the MSI-X table, and why. That is false at this head; the PR changed sdk/abi/src/pci.rs but not this.
  • issues/an-amd-vi-machine-isolates-no-device.md:19-22: "What remains" says pcidev refuses the bench's controllers and that xhci-leave= takes an id. Both were fixed by f0462d2.
  • Same issue, :38-41: the exit names a guest boot on Profile::MetalAmdVi, which is intremap=off, so a claimed function's remapped message cannot happen there.
  • When the bench rows are green, this PR meets both issues' exits, so it should close them (or rewrite them for what remains) before landing.
  • tests/toyos.rs:441-444: the bench's controllers "cannot yet be claimed to show". This PR's own row claims one.
  • kernel/src/actuator.rs:140-141: iommu-no-remap is documented as reading the DMAR only, and judged only by claim_refused_without_remapping. It now also stands in on AMD-Vi (amdvi/mod.rs:282).
  • tests/guest/rust-tests/src/usbd_spare.rs:45-47: "QEMU's … and the T14's … the other is absent" over a three-entry SPARES.

S1 SUGGESTION: amdvi/mod.rs:383 re-derives interrupt_half(id)'s address. Use one helper.

S2 SUGGESTION: amdvi::interrupt::arm returns Result but cannot fail. xhci/wait/boot.rs:120 hand-builds a requester id.

S3 SUGGESTION: usbd_drives_the_spare now asserts "PCI {at}: msi", which matches msix too. Assert the exact kind per profile (tests/toyos.rs:3915).

S4 SUGGESTION: usbd_spare.rs:31-35 defines cap() in the middle of the use block.

S5 SUGGESTION: the handover also applies on AArch64 ITS machines through the same Remapping witness. One clause in the pcidev header on why the ITS, with its hardware DeviceID, keeps the guarantee would make the scope explicit. msix-exclusive-bar=on in tests/common/qemu.rs:2305 and build/qemu.rs:203 is now a workaround on remapping profiles; record it or remove it.

Evidence

  • Host CI at c22745327 passed 77 steps, exit 0.
  • g5 at c22745327 (clean) passed 7 tests, including both new QEMU tests. usbd_drives_the_spare_amdvi shows control=0x000400000002140d, then p=1 delivers=0x42 and p=0 released each round.
  • The table-BAR-withheld negative control is red with NoMappableBar. Its build is b85bbb0ca dirty, i.e. the ruled-round tree before it was committed; the PR should say so.
  • The earlier bench pair at b85bbb0ca (head green, no-remapping control red) stands for the stick rows; nothing since changes the remapping arm.
  • B2 still has only its host encoding test, which the PR states honestly.

What the three bench boots must show (re-judged after B4's fix):

  • amdvi_usbd_drives_its_own_entry:
    • 03:00.4 handed over;
    • msix address= and irte source=03:00.4 vector=<v> p=1;
    • usbd's No-Op answered;
    • a DMA FAULT owner=process … stream=03:00.4 at the aimed address (access=read);
    • an access=none … IO_PAGE_FAULT record for the rewritten vector;
    • that vector absent from the stop's irq: unclaimed vectors in the after-reset pass;
    • no kernel-owned fault.
  • amdvi_claim_refused_without_remapping: the iommu-no-remap stands in line, 03:00.4 NOT HANDED OVER — … would not be remapped, and no msix, irte or BAR line for it.
  • amdvi_remaps_the_stick_controller and amdvi_translates_the_stick_controller: as at b85bbb0ca.

A green amdvi_usbd_drives_its_own_entry does not answer B1: an SMI-mode message is outside what any row sends.

SEND BACK

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator on the build review (6163050204): send back, with this ruling on blocker 1.

B1, ruled: the unit's SMI filter is enabled, logged, on every AMD-Vi unit the kernel programs (48882's SMI filter registers), and a BAR holding a function's MSI-X table is handed over on AMD-Vi only where every programmed unit filters SMIs; where a unit cannot (no SmiFSup), the refusal stands and is logged by name. The earlier ruling's premise, that the IOMMU is the fence for a holder's written message, holds only with the filter on; VT-d is unaffected. The bench supports the filter, so the rows stay runnable.

  • B2: a claim on any requester id an IVRS special entry uses (the I/O APICs, the HPET) is refused by name, and the claim asserts no interrupt table exists for the function yet.
  • B3: the release computes the MSI-X entry's address from the BIR and offset captured at arm and the BAR address the kernel itself placed; a device's answer is never an input to a kernel address, and a removed or all-ones device is a recorded refusal, never a panic.
  • B4: the row reads the census from after_the_reset(); the boots that ran are re-judged from their readbacks.
  • B5: the QEMU foreign-vector arm asserts 0x43 absent from the census.
  • B6: the six records, and both issues close when the bench rows pass.
  • Also: the xhci-leave=<address> and iommu-no-remap parameters need their FLASHABLE rows in toyos-metal with the survival ruling; the flash tool refused both images for lack of them, which is why only the testcases boot ran (it passed both stick rows again at this head). The negative control's build revision is stated.

Japabu and others added 2 commits October 14, 2026 11:24
No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
…e messages are fenced, and the build review's fixes

Build review of #921 (6163050204) and the orchestrator's rulings (6163086612).

- B1, ruled: every AMD-Vi unit with an SMI filter (EFR `SmiFSup` 01b) has
  it on and logging (CONTROL `SmiFEn`, `SmiFLogEn`; 48882 §2.1.5), its
  registers left as firmware set them and each said at boot. An SMI is no
  remapped message (Table 10, Table 19), so a holder's own MSI-X table
  could otherwise send one to every CPU. `Remapping::fences_tables` says
  whether a message written into a claim's table is fenced: VT-d and the
  ITS always, AMD-Vi only where every programmed unit filters. Where not,
  `pcidev` keeps the table's BAR back and says so by name. A refused SMI
  from a requester no claim holds is said and the machine goes on; a
  claimed function's is its holder's fault.
- B2: a claim on a requester id an IVHD special entry uses (I/O APIC,
  HPET), or one the SMI filter passes, is refused at the claim
  (`iommu::isolates`, `unclaimable`); a claim asserts no other source wrote
  its requester's remapping table first.
- B3: the release masks the MSI-X entry at an address from the BIR and
  offset captured at arming and the BAR address the kernel placed, never
  the device's answer; nothing there can panic on the device.
- B4: the bench row reads the stop's census after the reset.
- B5: QEMU's unit has no SMI filter, so under the ruling it hands no table
  over and the foreign-vector arm leaves QEMU; its spare is armed on MSI
  again. `usbd_table_kept_without_smi_filter` reads, on an MSI-X spare,
  the table's BAR kept back by name and the claim refused. The two issues'
  texts say what was measured.
- B6: `device_bar_map`'s contract, the track issue's remaining work and
  exit, the guest-test registration, `iommu-no-remap`'s contract and the
  spare list's doc are true of the tree.
- `FLASHABLE` rows for `xhci-leave=00:0d.0` and `xhci-leave=03:00.4`, the
  old id form gone; `iommu-no-remap`'s row covers AMD-Vi.
- `usbd_spare`'s refusal arm is `unhanded`, for any refusal whose reason
  the kernel's line gives. One helper for an entry's interrupt half; the
  xHCI driver names the left controller by `pcidev::requester`.

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

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Focused re-review: #921 at 4f1d6fc3a (main f0a987bc6 merged). Read against the build review (6163050204), the rulings (6163086612, 6158860882), root CLAUDE.md and reviewer.md. I read the fix commit (e81f97f53..4f1d6fc3a, 25 files, +451/−162) in full along with its callers, and checked every 48882 citation against rev 3.11 (§2.1.4.2, §2.1.5–2.1.5.4, Table 3, Table 10, Table 19, MMIO 0018h/0030h/0060h).

Assessment

Most of the round is right, and B2–B6 are fixed as ruled.

  • SMI filter registers: they are where 48882 puts them. SmiFEn [22] and SmiFLogEn [24] are in CONTROL; SmiFSup [17:16] = 01b and SmiFRC [20:18] are in the EFR; the filter registers are at 0060h with SmiDID/SmiDV/SmiFLock.
  • What the kernel must write: it writes SmiFEn | SmiFLogEn on every unit with SmiFSup. Both bits reset to 0, and §2.1.5.4 says "when the IOMMU is reset, software must program the registers to make the SMI filter active". So writing them is what puts the filter in effect. Firmware's own state would not have been enough: the bench handed over control=0x0000220000000400, with bit 22 clear.
  • Filter registers left as firmware set them: this is sufficient. With SmiFEn = 1 only registers with SmiDV = 1 pass, and every id they name is refused at the claim (SMI_SOURCE).
  • B2: the special-entry refusal sits at the claim boundary (iommu::isolates → unit::domain::unclaimable), before function_space and claim_msi.
  • B3: release reads nothing from the device.
  • B4: the census is read from after_the_reset().
  • Handover rule: one decision point, Remapping::fences_tables, gates the BAR for VT-d, the ITS and AMD-Vi, and the kept-back BAR is named to the claimant's log.

One finding goes back to the orchestrator because it changes where the ruled fence belongs. On AMD-Vi, the MSI-X table is not the only way a holder can make its function send an SMI: any DMA write the holder points at the interrupt range is one too. So withholding the table's BAR does not fence SMIs where a unit has no filter. The fence belongs at the claim, and putting it there would also delete most of this round's withholding machinery.

Findings

R1 BLOCKER (orchestrator decision): on AMD-Vi without an SMI filter, a granted claim still lets its holder raise SMIs; keeping the table's BAR back does not stop it. Locations: kernel/src/pcidev/mod.rs:52-59 and :903-917, kernel/src/arch/x86_64/iommu_unit.rs:288-298, kernel/src/iommu/mod.rs:299-330.

Why:

  • 48882 §2.1.4.2 says that accesses to the interrupt address range (Table 3) "go through the interrupt remapping portion of the IOMMU and not through address translation processing". §2.2.9 says the same of "DMA requests … in the 0xFEEx_xxxx address range … treated as MSI interrupts".
  • An MSI is an ordinary posted write. The IOMMU cannot tell one sent from an MSI-X entry apart from one sent by a descriptor the holder aimed at 0xFEE0_0000, for example an RX buffer or an event ring.
  • With MT = SMI, that write goes to the SMI filter (Table 19). The DTE has no pass bit for it (Table 10), and SysMgt governs only the FD_F910… range.

Consequence: wherever smi_filtered_everywhere() is false, the holder can still send SMIs to every CPU, table BAR or not. That includes QEMU's amd-iommu (EFR 0x29d3, SmiFSup 0) and any AMD-Vi part without SmiFSup. usbd_drives_the_spare_amdvi grants exactly such a claim at this head. This is the authority B1 was about, and this PR introduces it, because before stage 6 every AMD-Vi claim was refused NotRemapped. The ruling's premise ("the IOMMU is the fence for a holder's written message, holds only with the filter on") is about messages, not tables. The pcidev header and the fences_tables docs say the BAR rule closes this gap; it does not.

Options for the orchestrator:

  • (a) Put the condition at the claim. On AMD-Vi, remapping() (or isolates) refuses unless every programmed unit filters SMIs. Then a claim's table is always fenced. fences_tables (three backends) can be deleted, along with withheld, MsixAt::base, HeadlessUsbSpareAmdViMsix, usbd_table_kept_without_smi_filter and the unhanded rename. The cost is that QEMU can no longer grant an AMD-Vi claim, so usbd_drives_the_spare_amdvi becomes a refusal check, and live claim-through-entry coverage on AMD moves to the bench row only.
  • (b) Keep claims on filterless units. In that case, record the gap in issues/ with an owner and an exit, and correct the header and docs to say what the BAR rule actually covers.

R2 BLOCKER: smi() treats 48882's reserved bits 63:48 as must-be-zero, against its own doc comment. Location: kernel/amdvi/src/queue.rs:73-79.

The mask 0xFFFF_FFFF_FF00_00C3 requires bits 63:40 to be zero, but the comment, Figure 3 and Table 4 give only 47:40 as MBZ, and 63:48 are reserved. Table 19's text also builds interrupt addresses on the sign-extended base FFFF_FFFD_F800_0000h.

Consequence: if the unit writes those reserved bits as ones, a refused SMI from a function this kernel drives is not recognised. It falls through to policy::report as owner=kernel, and the drain halts the machine. This is not far-fetched on the bench. Both xHCI controllers read USBLEGCTLSTS 0xc0002000 before the kernel's handoff (bench kernel log at dd9fb9aac, lines 194/216 and the line after each), so SMI-on-OS-ownership is armed at the moment the kernel takes them, and this round turns the filter on before that handoff.

Fix: mask 0x0000_FFFF_FF00_00C3, and add one encoding-test case with bits 63:48 set.

R3 BLOCKER: the ITS doc comment is misplaced. Location: kernel/src/arch/aarch64/irqchip/its.rs:375-382.

fences_tables was inserted between is_armed's doc comment and is_armed. As a result, fences_tables now carries two docs, the first of which ("Whether an ITS is armed…") is false for it, and is_armed has none. Fix: move the line back to is_armed.

R4 BLOCKER: the doc of a profile this PR relies on is now false. Location: tests/common/qemu.rs:688-692 (HeadlessUsbSpare).

It still says the spare is "the controller xhci-leave=1b36:000d leaves" (the id form this PR removed), and that it is armed on MSI "because a claim never maps the BAR that holds an MSI-X table". At this head a claim does map that BAR wherever the machine fences messages, VT-d included. This is the same class of stale record as B6. Fix: name the address 00:10.0 and drop the reason, or state the real one.

R5 BLOCKER: the 03:00.4 FLASHABLE row ties survival to a power cycle. Location: tests/metal/src/lib.rs:820-825.

The measured facts in the row are right:

  • USBLEGSUP 0x00010401 on 03:00.4;
  • the Intel BT module 8087:0029 is on it;
  • the SanDisk stick is on 03:00.3.

But the survival argument is "firmware … takes it back at the next power-on". The gate's criterion (:718-731) is state that "a reboot does not undo". The argument should be the one that actually holds: the reset that ends every boot resets the function and re-runs POST, which takes the controller back. That covers what both images can leave behind:

  • 03:00.4 halted with bus mastering off after the fault arm;
  • in the no-remap image, a controller left firmware-owned with USBLEGCTLSTS SMI-on-ownership still armed.

The T14 row (:811-818) carries the same "next power-on" wording and should be corrected the same way. The iommu-no-remap row's AMD-Vi clause is sound: interrupt halves live in memory, and CONTROL resets.

R6 BLOCKER: this round's QEMU and host evidence is stamped on an uncommitted tree, and the PR table presents it as head. Location: PR body, "Build-review round" table.

g8.log (evidence 599d36d29) shows supervisor: build e81f97f53… dirty in every boot. ci-host5.log line 1 also reads e81f97f53. The table header says "head 4f1d6fc3a". This is the same provenance defect the build review raised for the negative control.

I re-ran both at the clean head, so this needs only a body correction:

  • cargo run -- --ci host: 77 steps, exit 0;
  • the eight QEMU tests: see Evidence below.

S1 SUGGESTION: amdvi/fault.rs:83 calls pcidev::note_fault inside a match guard. For a claimed function it posts the fault twice: once in the guard and again in policy::report (iommu/fault.rs:203). This is harmless today because fault() sets a flag, but a side effect in a guard is easy to misread. Bind held = smi(address) && … before the match, or test held-ness without posting.

S2 SUGGESTION: amdvi/mod.rs:221 indexes PLATFORM by variety == 2, so a reserved variety (Table 109) is labelled "I/O APIC". A three-arm match says what it means.

S3 SUGGESTION: the comment at amdvi/interrupt.rs:154-156 attributes the kernel-driven refusal to iommu::isolates. It actually happens in pcidev::claim (toyos_pci::claimed). Separately, the "an SMI is no remapped message, so a holder's table could send one" rationale is written six times (pcidev header, Remapping::fences_tables, remapping(), iommu_unit, SMI_FILTERED, program). Whichever way R1 goes, keep it once at the contract and delete the rest; the three added lines on remapping() (iommu/mod.rs:321-322) are pure restatement.

S4 SUGGESTION: consider locking the filter registers (SmiFLock, §2.1.5.4 "to freeze a particular configuration") once they have been read. That makes the claim refusals derived from them a fact for the boot rather than an assumption. Also, tests/toyos.rs:4073-4074 has a double blank line left where foreign_vector was removed.

B1–B6 against the rulings

  • B1: the filter is enabled and logging on every programmed unit with SmiFSup. Each register is logged, the handover is conditioned on all units filtering, and the kept-back BAR is named.
    • Missing: amdvi_remaps (tests/toyos.rs:4109-4121) checks GAEn in the read-back control= but not bits 22/24. The bench's read-back is the only runtime evidence that the write took, and the judge can be re-run on the readbacks without a boot. I ask the orchestrator to read bits 22 and 24 from each unit's translating control= line in this run, and suggest adding the check to the row.
    • R1 covers what the ruling's letter misses.
  • B2: fixed. I/O APIC and HPET requester ids, and ids the filter passes, are refused at isolates. interrupt::claim asserts that no table exists, unless the table is a previous claim's (CLAIMED); that exception is correct for a re-claim.
  • B3: fixed. The address comes from the BIR and offset captured at arm, plus bar_at[bir] where the kernel placed the BAR, otherwise the arm-time base. That base was already the address the arm itself wrote through, so no new device answer is trusted, and release reads nothing and cannot panic.
  • B4: fixed, as stated.
  • B5: resolved by the B1 ruling, which removed the arm from QEMU. The QEMU issue now says that drop versus delivery was not measured, which is true.
  • B6:
    • all six records are corrected;
    • both issues' exits now name the bench rows;
    • neither issue is closed yet. The PR must close both (per issues/README.md) in the same landing once the rows pass.

Records

  • The two issues state only what was measured or ruled. The negative-control note (the "b85bbb0ca dirty" tree that f0462d211 committed) matches the build review's reading.
  • The PR body's top sections still describe the b85bbb0ca change. "What changes" omits the table-BAR rule, the SMI filter and xhci-leave=<address>. The diff size (+823/−211 against pcidev: a claim takes an interrupt and gives it back; the four-slot limit goes #918) and "see the open decision" are stale. These corrections belong to the orchestrator.

Evidence (this review)

  • cargo run -- --ci host at 4f1d6fc3a (clean worktree): 77 steps, all green, exit 0.
  • cargo test --test toyos-build -- usbd_drives_the_spare usbd_refused_without_amdvi_remapping usbd_table_kept_without_smi_filter amdvi_selftest amdvi_firmware_left iommu_virtio_platform virt_claim_lpi from the same clean worktree (git status empty, HEAD 4f1d6fc3a): 8 passed, exit 0. This settles R6's provenance for QEMU; I ran it only for that reason.
  • Bench kernel log s6/bench/head/testcases/kernel.log at evidence dd9fb9aac:
    • 03:00.4 carries 8087:0029 and the stick is on 03:00.3;
    • both controllers read USBLEGSUP 0x00010401 and USBLEGCTLSTS 0xc0002000;
    • firmware's control=0x0000220000000400.
  • Staging metal3-stage.log and request.txt are at clean 4f1d6fc3a.

For the running bench rows, as inference: this round enables the filter before the kernel's xHCI handoff on a platform whose controllers have SMI-on-ownership armed. A boot that shows iommu: amdvi unit0 SMI filter refused an SMI from 03:00.x, or firmware still owns the controller, where b85bbb0ca showed firmware released the controller in ~1 ms, would mean the filter is blocking a firmware SMI path this platform uses.

SEND BACK

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator rulings on review 6166411911 (round 3).

  • R1: (a). On AMD-Vi a holder's DMA write into the interrupt address range is an interrupt (48882 §2.1.4.2, §2.2.9), so withholding the MSI-X table's BAR closes nothing where no unit filters SMIs. A function is handed to userland on AMD-Vi only where every programmed unit has the SMI filter on; elsewhere every claim is refused by name (SMM is in the trusted base; a holder must not reach it). The BAR-withholding path and its "kept back by name" state go; the table's BAR is handed over wherever a claim is granted at all (VT-d and the ITS under remapping, AMD-Vi under the filter). QEMU's AMD unit has no filter: usbd_drives_the_spare_amdvi becomes the refusal row, and metal keeps its claims, which is the rule ("QEMU must never reduce what metal gets"). Record the QEMU gap in the issue that closes with this PR, as a line, not a new issue.
  • R2–R6: required as written. R2 reads the event entry by the spec's defined fields only.
  • The bench hang (my reading of the review's inference + R2): the testcases image at 4f1d6fc hung the AMD bench before the watchdog could reboot it. With the filter now on before the kernel takes the xHCI from firmware, the controller's SMI on ownership change is refused by the filter; a refused SMI from a function userland does not hold is then handled as a kernel DMA fault, and the machine halts. The fix is the implementer's: the filter must admit what firmware's own devices raise during handover (or the filter is turned on only after the kernel owns every function it drives), and a refused SMI from a function no claim holds is logged and the machine keeps running, as the fix round already said. The stick's log for the hung boot is read before any reflash, and the readings must show the filter bits (CR bits 22 and 24) taken on each unit.
  • Both issues close in this landing, once the rows pass; the PR body's top sections describe the final change.

Focused re-review on the fixes after the bench rows.

Japabu and others added 3 commits October 14, 2026 12:54
…and every claim needs it

Round-3 review and the AMD bench's hang at 4f1d6fc.

The hang: 4f1d6fc turned every unit's SMI filter on in `program`,
before the kernel took its xHCI controllers from firmware, with no
register valid on the bench (firmware names no source there). This bench
resets through `SMI_CMD` (its FADT's reset register is port 0xB0), and an
SMI the chipset raises upstream is what the filter refuses (48882
§2.1.5.1); with every source refused, neither the boot deadline's reset
nor a panic's could end the boot. That is an inference from the earlier
boot's log and the specification; the hung boot's own log is unread.

- The filter is planned at `init` and turned on by `iommu::drivers_settled`,
  after every kernel driver took its functions and before userland runs.
  It passes what firmware left valid and every id the IVRS names the
  chipset's I/O APIC and HPET by, every register locked (§2.1.5.3); a unit
  with too few free registers filters nothing, said. The chipset raising
  the platform's SMIs under those ids is an inference the bench reads.
- Every unit's filter is off before the reset register or `SLP_EN` is
  written, lock-free from the fault handler's unit table.
- R1 (ruling a): a write to the interrupt range is an interrupt (§2.1.4.2),
  so withholding the table's BAR closed nothing. `iommu::remapping` is now
  the one gate: a claim exists only where every message reaches only its
  own interrupt, which on AMD-Vi needs every unit filtering SMIs; the
  refusal names which. The withheld-BAR path, `fences_tables`,
  `HeadlessUsbSpareAmdViMsix` and `usbd_table_kept_without_smi_filter`
  are gone; `usbd_drives_the_spare_amdvi` is `usbd_refused_without_smi_filter`.
- R2: an SMI's address is read by Table 4's fields only, bits 63:48 not
  read; `toyos_amdvi::queue::event` decodes `RefusedSmi` itself. A claimed
  function's refused SMI is its holder's fault; any other's is logged,
  bounded, and the machine goes on. Whether a claim holds the function is
  read without a side effect (S1).
- R3, R4, R5: `fences_claims` and `is_armed` documented apart; the spare's
  profile names 00:10.0; the FLASHABLE rows argue from the reset that ends
  every boot.
- S2: the special entry's reason is a match on its variety. S3: the
  claim's comment names `pcidev::claim` for a kernel driver's function,
  and the SMI rationale lives at `fences_claims`. S4: the filter's
  registers are locked once written.
- `amdvi_remaps` reads `SmiFEn` and `SmiFLogEn` on every unit.

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

pcidev: main's root-bridge declaration (`windows` to `bring_up` and
`place_bars`, the `Undeclared` and `AperturesRefused` refusals) beside
this branch's `NotRemapped` reasons and `NotIsolated`; the table's BAR
is placed like any other, so `table_bar` goes. amdvi: `domain::start`
takes main's optional reserved ranges and this branch's platform ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title AMD-Vi remaps interrupts: a table per requester indexed by vector, claims through their own entries AMD-Vi stage 6: interrupt remapping, and claims only where every unit filters SMIs Oct 14, 2026
@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Round 4 on the AMD bench at c648c1439: 3 of 4 rows pass; the hang is explained and gone; the one red is a regression from #916, not this PR's.

  • testcases (booted alone first): came back and reset itself. iommu: amdvi unit0 SMI filter on control=0x000000000142140d, passing 00:14.0 00:00.1 (bits 22 and 24 set); amdvi_remaps_the_stick_controller and amdvi_translates_the_stick_controller PASS.
  • testcases-amd-no-remap: amdvi_claim_refused_without_remapping PASS.
  • testcases-amd-xhci-leave: amdvi_usbd_drives_its_own_entry FAIL — test_rs_usbd_spare 101: pcidev: PCI 03:00.4 NOT HANDED OVER — its BAR was moved onto 8 64-bit address(es) inside the windows firmware declared and the function answered at none of them (candidates 0x450200000..0x451000000, every one answering 0xffffffff, while the function still answers 0x01100020 at its firmware BAR 0xd0100000). No SMI filter refused line: the filter is not involved.

Cause (my reading of the log): 03:00.4 sits behind a bridge whose windows are 0xd0000000..0xd0700000 (non-prefetchable, where firmware placed every bus-3 BAR) and 0xfc90000000..0xfca0200000 (prefetchable, filled by the GPU). Since #916 landed, a claim's BAR is placed inside the root's declared windows without regard to the bridge windows on the path, so an address the bridge never forwards is chosen and the function cannot answer there. The same row passed at c22745327 before #916. The T14's xHCI is on bus 0, so #926's T14 row did not see it. This is a defect on main for every claimable function behind a bridge; it goes to the #916 implementer as a fix PR (placement respects every bridge window on the path; where no routed free range exists the firmware assignment stays). #921 re-stages only the xhci-leave image after that fix lands; the other three rows stand.

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

The round-3 hang, witnessed on the panel. The owner re-ran the 4f1d6fc3a testcases image (sha 472f8677…) and photographed the AMD bench's screen (page 4/4 of the kernel console). The last lines, transcribed:

[ 7.379 cpu0 kernel] xHCI: found at PCI 03:00.3 1022:1639
[ 7.379 cpu0 kernel] xHCI: BAR0=0xd0000000
[ 7.379 cpu0 kernel] iommu: amdvi irte source=03:00.3 vector=0x21 p=1 dst=0x0 delivers=0x21
[ 7.379 cpu0 kernel] PCI 03:00.3: msix address=0xfee00000 data=0x00000021
[ 7.379 cpu0 kernel] xHCI: MSI-X enabled (vector 0x21)
[ 7.379 cpu0 kernel] xHCI: max_slots=64 max_ports=6 ctx_size=64 pagesize=0x1
[ 7.379 cpu0 kernel] xHCI: dma 2048 KiB: scratchpad=2 device blocks=64 of 12288 B (max_slots=64)
[ 7.379 cpu0 kernel] iommu: DMA FAULT owner=kernel unit0 stream=00:14.0 addr=0xfffffffdf8000020 access=none reason=0x02 domain=unrecorded bme=cleared unitfaults=1 streamfaults=1 first=y IO_PAGE_FAULT
[ 7.379 cpu0 kernel alert] panic: rebooting in 60 s, timed by the calibrated clock

So it was a panic, not a silent halt: with the filter on and no source admitted, the unit logged the chipset's refused SMI (stream 00:14.0, the I/O APIC's requester id, interrupt-address shape 0xfffffffdf8…) as an IO_PAGE_FAULT from a kernel-owned stream; the kernel's bug fence panicked; the promised reboot never came because the reset register on this bench is SMI_CMD, blocked by the same filter. Round 4's three changes (R2's decoder, the admitted chipset sources, the filter dropped before a reset) each address one of these; the round-4 testcases boot came back by itself. The photo is kept in the orchestrator's evidence for pr-921 (the implementer publishes it with s6r4/).

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Correction to my round-4 reading above (6173159858). The red amdvi_usbd_drives_its_own_entry is not a #916 regression: #916 changed no placement code, the row never ran at c22745327 (only the testcases boot did), and on main 03:00.4 is refused earlier as NoMappableBar. The defect is the one already recorded in issues/pcidev-moves-a-bar-behind-a-bridge-out-of-the-range-the-bridge-forwards.md: a claim moves a BAR onto its own 2 MiB slot because 2 MiB pages are the only ones the kernel maps into a process, and the root's free runs never include what a bridge forwards. #928 fixes the candidate choice (the port's windows, routed by every bridge on the path), but the AMD bench's port 00:08.1 forwards only 0xd0000000..0xd0500000, which bus 3 fills, so 03:00.4 stays where firmware put it and is refused by name until P1 (issues/there-is-no-wifi.md: a BAR stays in place, in 4 KiB pages). Consequence for this PR: the row cannot pass on this bench before P1; at landing it moves to P1's exit with that reason, the other three rows stand, and the issue it would have closed stays open with P1 as its dependency.

Japabu and others added 4 commits October 14, 2026 20:24
…w moves to P1

Conflicts: `usbd_spare` claims by arrival as #926 has every job do, in
the shared `src/usbd_spare.rs` this branch moved its arms into: the one
function of a spare's card the kernel does not drive, or, where it
drives every one, the only one (the AMD bench's `03:00.3` and `03:00.4`
share an id). `tests/toyos.rs` keeps #926's `drives` row beside this
branch's refusal rows.

`amdvi_usbd_drives_its_own_entry` leaves the registered AMD rows for P1's
exit in `issues/there-is-no-wifi.md`: on the AMD bench `03:00.4`'s only
BAR sits behind port `00:08.1`, whose window `0xd0000000..0xd0500000`
bus 3 fills, so it cannot move to a 2 MiB slot of its own and stays where
firmware put it, which only P1's 4 KiB pages hand over. Its jobs stay;
its judge `amdvi_usbd` goes and comes back with P1 (at c648c14).

`issues/an-amd-vi-machine-isolates-no-device.md` closes: its rows passed
on the AMD bench at c648c14 (`amdvi_translates_the_stick_controller`,
`amdvi_remaps_the_stick_controller` with `SmiFEn` and `SmiFLogEn` set,
`amdvi_claim_refused_without_remapping`), its remaining claim reading is
P1's. Every citation of it is rewritten. The MSI-X issue stays open,
depending on P1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
The round-3 panic on the AMD bench, photographed on its panel, shows the
unit's IO_PAGE_FAULT for a message it refused from 00:14.0 at
0xfffffffdf8000020: bits 63:40 all ones, the interrupt range as 48882
§2.2.5 writes its base (FFFF_FFFD_F800_0000h), where Table 4 gives an
SMI's 47:40 as zero. `smi` now takes 63:40 all zero or all ones; any
other value there is no SMI's. The host test holds both forms and a
partial extension.

That record's MT reads 000b, not Table 4's SMI 010b, so this decoder
still reads it as a requester's fault; whether the filter or the
remapping table refused it is not settled by one reading (the PR says so).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
The merge's claim by arrival bound one arm's `Pci` by reference twice
and the other once; the guest job did not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
pcidev: #928's bridge windows (`Bridge`, `msix`) beside this branch's
table BAR placed like any other. On the AMD bench `03:00.4`'s one BAR,
which holds its MSI-X table, now meets `NoRoutedRun` behind `00:08.1`
and the claim is refused `NoMappableBar`; the issue #928 opened says so.

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

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator ruling on the open risk (a platform message the filter does not pass).

A record in the interrupt address range (48882 §2.2.5's form or Table 4's) from a requester id the IVRS gives to the platform (I/O APIC, HPET, any special entry) is the platform's message, whatever its type field says: it is logged with its id, type and address and the machine keeps running. It is never a kernel DMA fault, because no kernel driver owns those ids and no claim can hold them (B2 refuses them at the claim). The bug fence stays for every other stream. A second boot with the filter passing only 00:00.1 is not needed to land: the rule removes the panic path for the whole class, and the record it logs is the measurement if the case ever recurs. Add the host test (an interrupt-range record of type 000b from 00:14.0 is logged, not fatal) and ship it in this landing; the two boots now running on the bench judge the rows, and this change needs no third boot unless it touches the rows' path (it is the fault decoder only — say so).

…'s, whatever its type

The orchestrator's ruling on #921 (6179195333). The round-3 panic's
record was an IO_PAGE_FAULT from 00:14.0, the id the IVRS gives the
chipset's I/O APIC and HPET, at 0xfffffffdf8000020: the interrupt range,
`MT` 000b. Read as a DMA fault on a kernel-owned stream, it panicked the
kernel. No kernel driver drives a platform id and a claim on one is
refused (`domain::unclaimable`), so nothing of this kernel's went wrong.

`toyos_amdvi::queue::event` decodes any IO_PAGE_FAULT at an
interrupt-range address (Table 3, either address form) as
`Event::Interrupt`, and `disposition` says whose it is: a platform id's
whatever its type, a held function's its holder's fault, an SMI from
anyone else said, any other a fault. The drain reads the platform ids
from a lock-free table `init` publishes before any unit is armed, and
says a platform message or an SMI with its id, type and address, bounded
to powers of two.

Host tests: the decoding of both address forms, and the disposition of
the round-3 record from 00:14.0 (platform) and from anyone else (fault).

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

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

AMD bench readbacks at 639028982 (round 6, the orchestrator's run): 3 of 3 rows pass in 2 boots. Evidence commit dee06f4e779993af35363fb005aaf32a5db55f7d, s6r6/metal/bench/, with machine, stick and user identifiers masked.

  • amdvi_translates_the_stick_controller and amdvi_remaps_the_stick_controller passed on the testcases boot.
  • amdvi_claim_refused_without_remapping passed on the testcases-amd-no-remap boot.

Readbacks:

1ceab43bc adds only the platform-message decoding, which acts only when the unit writes an event record. These boots wrote none, so the readings stand for it.

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Focused landing re-review: #921 at 1ceab43bc (main 335927b83 merged). I read the round-3 review (6166411911), the rulings (6166446249, 6179195333), the full diff against origin/main (44 files; production +1053/−193, tests +709/−259, issues ±89), the 639028982..1ceab43bc delta, the callers of every changed function and the evidence: s6r6 and s6r7 logs, the bench readbacks at dee06f4e7 s6r6/metal/bench/, and the r7 mutation. I ran nothing.

Assessment

The kernel side is right and matches the rulings.

  • SMI filter (48882 §2.1.5).
    • amdvi::init plans each unit's registers. The plan keeps what firmware left valid, adds every IVRS special id, and locks every register (reg::smi_filter). With too few free registers the unit is None, which is logged.
    • settle() (amdvi/mod.rs:594) runs from iommu::drivers_settled() (main.rs:550). That is after devices ready and before userland, so after the kernel's xHCI handoff. It writes only unlocked registers, then SmiFEn|SmiFLogEn. It publishes SMI_FILTERED only when every programmed unit is filtering.
    • pass_every_smi() takes no lock and runs before the reset outb (power.rs:163) and before both SLP_TYP/SLP_EN writes (power.rs:218). Every reset funnels through arch::power::reset: power::reset_now from panic_reboot.rs:146, deadline.rs:253 and hardlockup/mod.rs:287, and power::reboot. So the panic path is covered too.
    • The bench at 639028982 read SMI filter on control=0x142140d, passing 00:14.0 00:00.1. Its ACPI_ENABLE write to SMI_CMD at 7.411 s, after the filter went on at 7.368 s, set SCI_EN. So the platform's runtime SMI passes with the filter on. Both boots reset themselves, at 46 s and 43 s.
  • R1 (a).
    • fences_claims (iommu_unit.rs) returns Unremapped, or on AMD-Vi SmiUnfiltered unless smi_filtered_everywhere(). Each reason has its own refusal text.
    • isolates refuses platform ids (NotIsolated).
    • msix_bar, table_bar, fences_tables and the withheld state are gone from code, and the table's BAR goes through place_bars like any other.
    • usbd_refused_without_smi_filter is the QEMU row; it passed in r6-g1. The R5 negative control (false && on the gate) went red.
  • Platform-message ruling.
    • queue::disposition checks, in order: a platform id (any type), then a held function (the holder's fault), then a stranger's SMI (logged), and otherwise a fault. That is the ruling.
    • Both address forms are decoded: bits 63:40 all zero or all ones, with 0xFD_F8 at 39:24.
    • fault::platform is called exactly once, in init (amdvi/mod.rs:319), before any fault::arm. Its Relaxed stores happen-before arm's Release store of regs, which service Acquire-loads before it reads PLATFORM. Nothing writes the table afterwards, so the lock-free read is sound.
    • Logging is bounded to powers of two by said.
    • The host test pins the round-3 record. The r7 mutation (platform only when the record is an SMI) reds it with exit 101.
  • Merges.
  • The no-third-boot argument holds. 639028982..1ceab43bc touches queue.rs, encodings.rs, fault.rs, one line in amdvi/mod.rs (the PLATFORM publication) and a judge print. The decode only reclassifies interrupt-range records, which were faults before. The judge's must_not_say("iommu: DMA FAULT") can only pass more often, and neither bench boot wrote a record.
  • --ci host at the head: the r7-ci-host.log stamp is 1ceab43bc with 0 dirty files; 86 steps, all green, EXIT 0.

What remains are records and one ABI doc that still describe the deleted BAR-withholding.

Findings

B1 BLOCKER — sdk/abi/src/syscall.rs:2344-2346: the device_bar_map contract still promises BAR-withholding.

  • It says InvalidArgument is returned "for the BAR holding this function's MSI-X table where the machine would not fence a message written there".
  • No such case exists any more: on such a machine the claim is refused whole. bar_object (pcidev/mod.rs:1745) refuses only an index past BARS or one whose bar_bytes is 0.
  • This is R1(a) residue at the kernel/program boundary, and a userland driver author reading it would code for a refusal that never happens.
  • Fix: delete the clause.

B2 BLOCKER — two open issues still describe and require the mechanism this PR deletes.

  • issues/a-claims-own-refusals-are-read-by-nothing.md:25-35 names place_bars's Some(index) == table_bar and msix_bar as an unread boundary. Its exit (:45-47) requires "for the withheld table BAR, one that holds a function publishing a table and is told that BAR's size is zero".
    • That behaviour is now the opposite of the design.
    • An agent meeting this exit would rebuild the withholding R1(a) deleted.
  • issues/every-driver-is-still-in-the-kernel.md:71-75 cites pcidev::msix_bar. It also says nec-usb-xhci's claim is refused because its table is in BAR 0. Under VT-d remapping that is no longer the reason.
  • These are not unrelated stale issues: this PR is what made them false.
  • Fix: drop that bullet and that exit clause from the first file, and correct the second sentence. One line each.

B3 BLOCKER — the MSI-X-table records contradict each other.

  • P1 in issues/there-is-no-wifi.md:155-157 still owes "the exit of a-controller-whose-msix-table-is-in-bar-0… rewritten, from a refused write to the table's page to the NVMe driven through a table in BAR 0".
  • This PR already rewrote that exit, to P1's 03:00.4 bench reading. So the P1 bullet describes an exit that no longer exists.
  • The issue itself no longer tracks a defect of its own:
    • its title's cause (the table's BAR is never mapped) is removed here;
    • what it waits for is the placement that a-bar-behind-a-bridge-with-no-free-2-mib-of-its-windows-is-not-handed-over.md owns;
    • its exit repeats P1's 03:00.4 bullet word for word.
  • Keeping it open was the orchestrator's landing call, so I do not overrule that. But at minimum the P1 bullet must go or match. Since the slug is now refuted by the tree, the simpler record is to close the issue into P1. P1 already carries both readings: msix-exclusive-bar=on gone and 03:00.4 claimed. The QEMU-has-no-filter line already lives in no-claim-gets-an-address-space-under-virtio-iommu.md and toyos-runs-in-any-open-source-hypervisor.md.

S1 SUGGESTION — kernel/amdvi/src/queue.rs:65: also treat an IO_PAGE_FAULT with I [51] set as Event::Interrupt, not only one whose address is in the FD_F8 range.

  • The round-3 record printed access=none, so its I was set.
  • I is the field Table 57 defines for "this was an interrupt request". Keying on it as well covers a refused platform message logged in any other address form, which today would still reach the kernel's bug fence.
  • The ruling scoped the two address forms, so this is optional.

S2 SUGGESTION — kernel/src/arch/x86_64/amdvi/fault.rs:46-57: deduplicate the ids before filling PLATFORM.

  • On the bench, 00:14.0 arrives twice (I/O APIC and HPET), plus once per SMI-filter id.
  • Past 32 entries a platform message falls back to the bug fence. Deduplicating keeps the 32 slots for distinct ids.

S3 SUGGESTION — wording.

  • iommu/mod.rs:301-303: "what the units guard against a holder may now be guarded against firmware's handover too" is hard to parse. Suggested replacement: "Once, after every kernel driver has taken its functions from firmware and before userland runs: AMD-Vi turns its SMI filters on."
  • amdvi/interrupt.rs:152-155: "one that already has a table is one of those" leaves out the case the assert also admits, a re-claim whose setting is already CLAIMED.

S4 SUGGESTION — the vector/fault jobs and their judge. usbd_spare_vector, usbd_spare_fault and usbd_spare::vector/msix_table now have no registered runner, but amdvi_usbd was deleted "to come back with P1". Bringing the jobs back from history alongside the judge, or keeping both, would be consistent; keeping only half is not.

Records and PR body (for the orchestrator)

  • Bench evidence at 639028982 is not cited. The body cites only c648c1439, while the landing bench run is published at evidence dee06f4e7 s6r6/metal/bench/:
    • judge-s9.log: 3 passed, 2 boots;
    • both kernel logs build 639028982 clean.
    • Its testcases boot also shows ACPI_ENABLE through SMI_CMD succeeding with the filter on, which is worth stating under Risks.
  • "It closes an-amd-vi-machine-isolates-no-device.md, whose rows passed" overstates. That exit's "claimed function's message delivered through its own remapping entry" did not pass; it moved to P1, as the closing merge a4f3e6c26 correctly says. The body should say the same.
  • Item 5:
    • The FLASHABLE rows' reasoning holds after R5, since the reset that ends a boot reruns POST.
    • P1 carries the moved row with its reason.
    • The MSI-X issue is assigned on P1 (but see B3).

SEND BACK

…issue closed into P1, `I` read as an interrupt

B1: `device_bar_map`'s contract names no withheld BAR; on a machine that
would not fence a message the claim is refused whole.

B2: `issues/a-claims-own-refusals-are-read-by-nothing.md` loses the
withheld-table bullet and exit clause; its subject, a claim's refusals
read by nothing, stands. `every-driver-is-still-in-the-kernel.md` and
`the-kernel-is-small-interrupts-post-and-threads-wait.md` no longer cite
`pcidev::msix_bar` or a claim refused for its table's BAR.

B3, ruled: `issues/a-controller-whose-msix-table-is-in-bar-0-cannot-be-driven-from-userland.md`
closes into P1 of `issues/there-is-no-wifi.md`, whose `03:00.4` bullet
carries the dependency and its reason; P1's bullet rewriting that
issue's exit goes, and so does the hypervisor issue's citation of it.
The rule it leaves: a claim exists only where every message its
function sends reaches only its own interrupt, and there the table's
BAR is placed like any other (`kernel/src/pcidev/mod.rs`'s header).

S1: an `IO_PAGE_FAULT` with `I` [51] set is `Event::Interrupt` whatever
address it names (Table 57), so a refused platform message in another
address form is the platform's too. S2: the platform ids are published
once each. S3: `drivers_settled`'s doc and the claim's re-claim case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
@Japabu
Japabu marked this pull request as ready for review October 14, 2026 22:49
@Japabu
Japabu added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit acfb091 Oct 14, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-amdvi-intremap branch October 14, 2026 23:19
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