Skip to content

pcidev: a BAR behind a bridge is placed only where every bridge on its path forwards - #928

Merged
Japabu merged 6 commits into
mainfrom
wt/toyos-bridge-window
Oct 14, 2026
Merged

Japabu merged 6 commits into
mainfrom
wt/toyos-bridge-window

Conversation

@Japabu

@Japabu Japabu commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Status: draft. Head 317b1bb52 (main e136c77fd merged). Review round 1 is answered. Both bench rows passed at 5321afa0c, before this round, and still stand (see "Metal"). This is the interim fix under issues/a-bar-behind-a-bridge-with-no-free-2-mib-of-its-windows-is-not-handed-over.md (renamed from pcidev-moves-a-bar-behind-a-bridge-out-of-the-range-the-bridge-forwards.md, which this change refutes); its exit stays with P1 of issues/there-is-no-wifi.md.

Problem

Before a hand-over, a claim moves each BAR it maps onto a 2 MiB window of its own. A 2 MiB page is the only page this kernel maps into a process, and at firmware's address that page also holds other functions' registers.

The candidates come from the root's free runs. Those runs exclude every range a bridge forwards (publish pushes forwarded() into what is taken). So a function behind a bridge is only ever offered addresses its bridge does not forward. It answers 0xffffffff at all eight, and the claim is refused.

  • On the AMD bench (03:00.4 behind 00:08.1, which forwards 0xd0000000..0xd0500000 and, prefetchable, 0xfc90000000..0xfca0200000): candidates 0x450200000..0x451000000, none answering (#921's log).
  • On q35, with the shipping image's virtio NIC behind a pcie-root-port (https://github.com/ToyOSOrg/evidence/blob/3f22d59a59e483946e0bfaf4f21d46a9ef5aa63c/r1/manual-boot-control-serial.log): main's kernel tries eight candidates from 0xc020400000, all above the port's 0xc000000000..0xc020000000, and refuses the NIC.

Not #916's regression.

What changes

The path. bridge::path walks from the function's bus up to its root bus (§3.2.5.3, §3.2.5.4).

  • Each step is the one bridge whose secondary bus is the bus reached, sitting on a lower bus, with the function's bus in its subordinate range.
  • A bridge firmware never configured (both bus numbers 0), or one claiming every bus, is not a step. So no bus-0 function leaves the root runs because of what such a bridge answers.
  • Two bridges naming one secondary bus refuse that bus's functions alone, as PathAmbiguous.
  • Each step lowers the bus, so the walk always ends.

Placement behind a bridge. A BAR whose path is non-empty takes its candidates from placement::behind_bridges. A candidate there:

  • lies in a window of the bridge whose secondary bus it is, of a kind the BAR may use (PCI-to-PCI Bridge Architecture §3.2.5.6 to §3.2.5.8, cited at Bridge::forwards):
    • a non-prefetchable BAR only in the memory window;
    • a prefetchable one in either window, trying the prefetchable window first so it leaves the memory window to BARs that may use nothing else;
    • a 64-bit BAR also below 4 GiB, where the AMD bench's bus-3 window is;
    • a 32-bit BAR never above 4 GiB;
  • lies inside the declared root windows;
  • is free of the firmware memory map (as the root runs are), of every BAR, of every window already cut or being probed, and of the windows of every bridge not on the path;
  • is forwarded by every bridge on the path. One that some bridge does not forward is passed over unread, so an absurd nesting is offered nothing.

The probe that proves a placement is unchanged. A root-bus function takes its candidates from the root runs, as before.

One behaviour change on the root-bus path. A successful placement now gives back every candidate tried before it that the function did not answer at. Before this PR, such a root-run candidate stayed spent once a later one answered. Behind a bridge this keeps trying holding only what is being probed.

  • For root runs the release is the existing placement::release, which returns only the address its run handed out last. So it can return a refused address only when that address came from a different run than the one that answered.
  • It returns an address nothing answered at to the pool, as a refused claim already does.

No routed room. Where nothing is free, the BAR stays where firmware put it and is handed to nobody, logged as keeps BAR n where firmware put it and hands it to nobody — no 2 MiB the bridges above it forward is free. At firmware's address it shares its 2 MiB page, so the reason for moving it still holds and it cannot be mapped. A function with no other mappable BAR is then refused NoMappableBar.

What stays until P1. The move itself stays until P1 (4 KiB user mappings for device windows, stage 3 of issues/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md). On the AMD bench, bus 3's window has no free 2 MiB, so 03:00.4 stays unclaimable under the 2 MiB rule, now with that reason named.

Checks

Evidence: pr-928 at 882142b5f

check revision result
Round 1 cargo run -- --ci host 503d19599 exit 0, 86 steps (log)
Round 1 q35: usbd_drives_the_spare, bar_map_again, lent_device_memory_refused, iommu_virtio_platform, bar_map_behind_a_bridge 503d19599 exit 0, 5 passed (log)
Round 1 mutation: a bridge's own bus not required below its secondary patch exit 101. The climb test fails, and the bus-0 test never ends: an unconfigured 0/0 bridge becomes its own parent and the walk loops (log)
T14 (orchestrator): usbd_drives_the_type_c_controller images 24ed5a756 1 passed (judge, readbacks)
AMD bench (orchestrator): amdvi_translates_the_stick_controller images 24ed5a756 1 passed (judge)
cargo run -- --ci host 88dab83e0 exit 0, 83 steps (log)
q35: usbd_drives_the_spare, bar_map_again, lent_device_memory_refused, iommu_virtio_platform, and the new bar_map_behind_a_bridge 88dab83e0 exit 0, 5 passed (log)
Negative control: every kernel/ change reversed, bar_map_behind_a_bridge 88dab83e0 minus patch exit 1, the NIC NOT HANDED OVER (log)
Mutation: the path's routing check ignored patch exit 101, the hostile-path host test red (log)
Manual q35 boots, shipping image, NIC behind a root port fix / main fix: placed at 0xc000200000 inside the port's window, handed over; main: refused (fix, main)
--build-only x86_64 and aarch64 88dab83e0 0, 0
Host tests behind after 24ed5a756 (the bench's measured port window in the test data only) 24ed5a756 4 passed

Why a guest test. bar_map_behind_a_bridge runs on a new q35 profile, HeadlessNicBehindPort. It is the only check that the kernel reads a real bridge's bus numbers and windows and lands the BAR inside them, and it fails on main's kernel. The T14's claimed functions are on bus 0. The AMD bench's function behind a bridge has no room and holds its MSI-X table in its one BAR.

Metal. Both rows passed at 5321afa0c, and this round does not change what they exercise.

  • The T14's Type-C xHCI is on bus 0, and the AMD row claims nothing behind a bridge.
  • For a bus-0 function, the walk finds no step, because no valid bridge has secondary bus 0 (its own bus must be lower). The dispatch therefore picks the root runs as before.
  • The root runs and their placement code are untouched. The memory-map fence and the window preference apply only behind a bridge.
  • The one change on the root-bus path, a successful placement giving back earlier refused candidates, acts only after a failed probe. Neither bench boot at 5321afa0c has a BAR n left 0x… line in its kernel log, so no probe failed and that path was not reached there.

What judges the fix on metal.

  • The T14 row guards the unchanged root-run path.
  • No row on main claims a function behind a bridge on either bench. The AMD testcases boot shows only that nothing regressed.
  • On this tree 03:00.4's one BAR holds its MSI-X table, which a claim withholds, so it is refused NoMappableBar before placement. AMD-Vi stage 6: interrupt remapping, and claims only where every unit filters SMIs #921 hands that BAR over, so on top of this fix its amdvi_usbd_drives_its_own_entry should show 03:00.4 keeps BAR 0 where firmware put it and hands it to nobody — no 2 MiB the bridges above it forward is free. It stays red until P1, by that name, not by candidates outside the bridge.

Related records, not closed here

  • issues/nothing-reds-when-the-bar-survey-drops-an-assigned-bar.md
  • issues/no-boot-reaches-the-retry-that-undoes-a-placement-nothing-answered.md

Both still rest on P1's placement.

🤖 Generated with Claude Code

https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1

Japabu and others added 3 commits October 14, 2026 18:44
…s path forwards

A claim moves each BAR it maps onto a 2 MiB window of its own, because a
2 MiB page is the only one this kernel maps into a process: at firmware's
address the page holds other functions' registers too. The candidates came
from the root's free runs alone, and those exclude every range a bridge
forwards. So a function behind a bridge was only ever offered addresses its
bridge does not forward. On the AMD bench, 03:00.4 (the xHCI behind the
port that forwards 0xd0000000..0xd0700000) was moved onto eight addresses
from 0x450200000 up and answered 0xffffffff at each. A q35 virtio NIC behind
a pcie-root-port reproduces it on main: eight candidates above its port's
0xc000000000..0xc020000000, none answering, "NOT HANDED OVER".

A function on a bus a bridge leads to now takes its candidates from the
windows of the bridge whose secondary bus it is, of the BAR's kind (a
non-prefetchable BAR only in the memory window, PCI-to-PCI Bridge
Architecture §3.2.5.6 to §3.2.5.8), below 4 GiB for a 32-bit BAR, inside
the root's declaration, and free of every BAR, every window cut or being
tried, and the windows of every bridge not on the path. A candidate some
bridge on the path does not forward is passed over unread. Where nothing is
free the BAR stays where firmware put it and is handed to nobody, named
NoRoutedRun: there it shares its page, so the reason for moving it still
holds.

`bar_map_behind_a_bridge` claims the NIC behind a root port on a new q35
profile and checks the placed address is inside what the port forwards; it
fails on main's kernel. Not caused by #916, which changed no candidate: the
root runs never held a bridge's windows.

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

The host test's AMD bus 3 had the root's window for its port, and lacked
03:00.7's BARs; both are now the bench's own reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
The interim places a BAR behind a bridge inside what its path forwards and
names the case with no room; the move itself and the exit stay with P1.

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

Evidence: pr-928 at 882142b5fea7112a8daa4c781fb871862988cdbc (https://github.com/ToyOSOrg/evidence/blob/882142b5fea7112a8daa4c781fb871862988cdbc/r1/README.md). Head 5321afa0c. The staged images are built at 24ed5a756; the head adds only the issue edit.

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #928 at 5321afa0c (base 2c3f61a3e)

Architecture

The ownership is right. Placement stays in the pure toyos-pci crate (placement::behind_bridges, Bridge::forwards), the kernel reads the Type 1 registers and keeps the probe as proof, and the root-bus path is unchanged. The kind rule is correct against PCI-to-PCI Bridge Architecture and the conservative reading of PCIe §7.5.1.2.1: a non-prefetchable BAR is never offered in the prefetchable window, a 64-bit BAR may use either window, and a 32-bit BAR stays below 4 GiB. Base above limit still means disabled (bridge::forwarded), the 64-bit prefetchable upper dwords are read, and the 1 MiB granularity is unchanged and tested. Every at + span goes through offered's checked_add, so there is no new overflow. No lock is held across a probe. The NoRoutedRun path writes nothing to the BAR: take returns None before the first write, and the only touches are the existing bar_size probe and its restore. A claim refused NoMappableBar takes bring_up's existing undo (MSI/MSI-X disabled, drained, message dropped, binding released).

The weak point is how the path is derived. "Every bridge whose [secondary, subordinate] covers the bus" is not a walk to the root, and a bridge's config-space bus numbers are trusted without validation (finding 1). The behind-bridge candidate set has also dropped the firmware memory-map exclusion that root-run candidates keep (finding 2).

Net size: about +150 production lines (kernel/pci 35+50, pcidev about 120 net) and about +180 test lines (4 host tests, 1 q35 row with its profile). The host tests are proportionate to the change. The q35 row is the one reproduction of the defect, and the orchestrator accepted it.

Findings

BLOCKER 1. kernel/pci/src/bridge.rs:43 (leads_to) and kernel/src/pcidev/mod.rs:1512 (Source::of): a bridge's bus numbers are unvalidated, and one bridge can move every root-bus function onto the behind-bridge path.

  • Problem. Bridge does not record which bus the bridge itself sits on. leads_to accepts any secondary <= bus <= subordinate, and Source::of selects Behind if any bridge on the machine passes that test. The Secondary and Subordinate Bus Number registers reset to 00h, so a bridge that firmware never configured reads 0..=0. A hostile external or hot-plug switch can also answer anything, for example secondary 0, subordinate 0xff.
  • Scenario. Either such bridge makes leads_to(0) true. Every bus-0 function then takes Source::Behind, and behind_bridges picks that bridge as the "parent" (find(secondary == 0)). Its window is disabled, or 0..1 MiB (outside any declaration), so take returns None and each BAR is NoRoutedRun. The function is then refused NoMappableBar: on the T14 that is the NVMe, xHCI and I219 claims. With subordinate 0xff, every function behind a real port also has to be forwarded by the bogus bridge.
  • What holds and what does not. Nothing is misplaced, because the path is an intersection. But one device's config space disables every claim on the machine. That is not containing an untrusted device at its boundary (root CLAUDE.md: external and hot-plug devices are isolated; external input is refused at its boundary).
  • Fix.
    • Record the bridge's own bus in Bridge.
    • Derive the path as an actual walk from the function's bus to the root: the parent is the unique bridge whose secondary == bus, and the next step is that bridge's own bus. Require own bus < secondary <= bus <= subordinate at each step, which also rules out a bus-number loop.
    • Refuse an ambiguous step (two bridges claiming one secondary bus) for that function alone.
    • Move the path derivation into toyos-pci (placement or bridge), so the dispatch Source::of does today becomes host-testable.
  • Tests to add. A host test for the unconfigured or bogus bridge: bus-0 functions still take the root runs (this is also the T14-shaped bus-0 case the host tests now lack). A host test for the duplicate-secondary case.

BLOCKER 2. kernel/src/pcidev/mod.rs:1527-1540 (Source::take): behind-bridge candidates are no longer excluded from the firmware memory map.

  • Problem. publish puts every MemoryMapEntry (RAM, reserved, MMIO) into the taken that root runs are cut from (mod.rs:548). The behind path builds its own taken from decoded, windows, trying and the windows of bridges off the path only, so the map is gone.
  • Consequence. A firmware or own-decode mistake that puts a bridge window over RAM or a reserved MMIO block is no longer fenced. One example would be a future bug in the 64-bit prefetchable decode. The probe then reads that memory as the function's answer, and on a coincidental match a process is handed the page. Root CLAUDE.md keeps exactly this fence ("a check is a bug fence, kept where it is cheap ... such as a write landing in RAM").
  • Brief. Item 3 asked whether the candidate set still excludes everything it excluded before. It does not.
  • Fix. Keep the map extents in Machine (one Vec<Window> set in publish) and chain them into the behind taken. Confirm on q35 that OVMF's map does not cover the port's 0xc000000000..0xc020000000: main's root runs at 0xc020400000 suggest it does not. The existing row then still covers the change.

BLOCKER 3. False and stale comments about trying and cut.

  • kernel/src/pcidev/mod.rs:372-374: "one the function did not answer at stays spent, as a refused run address does" is false. Every refused Candidate::Behind is removed from trying by give_back (mod.rs:1571). Refused run addresses are also released by give_back, not kept spent. Say only what trying is for: it keeps a candidate from a concurrent claim while it is probed.
  • mod.rs:1603-1607 (cut): the doc still says the address "is already out of the runs: placement::reserve took it there under with_runs". That is false for a behind-bridge candidate, which trying held and the new body moves into windows.
  • mod.rs:1372-1373 (place_bar): "gives every address it took back to the run it came out of". A behind candidate came from no run, so "gives back every address it took" is enough.

BLOCKER 4. issues/pcidev-moves-a-bar-behind-a-bridge-out-of-the-range-the-bridge-forwards.md: the tree now refutes the slug and the body's present-tense account.

  • Problem. After this PR, pcidev never moves a BAR behind a bridge out of the range the bridge forwards: it places it inside, or leaves it (NoRoutedRun). The body's second paragraph ("So no candidate ever lies inside the range a function's own bridge forwards ... its claim is refused") is still in the present tense. So is its line on no-boot-reaches-the-retry… ("Under this issue no candidate lies inside a port's window").
  • Rule. issues/README.md § Slugs: "one the tree has refuted is renamed by the work that takes it up, with every citation moved".
  • What remains. A BAR behind a bridge whose window has no 2 MiB of its own (the T14's 00:1c.4 and the AMD bench's 00:02.2 each forward 1 MiB) is not handed over until P1.
  • Fix. Rename the issue to that remaining defect and rewrite the body to state it, with the old mechanism moved to the commit message. Move the one citation (issues/there-is-no-wifi.md, P1's exit).

SUGGESTION A. kernel/pci/src/placement.rs:138-150: a prefetchable BAR takes the lowest address, so it fills the scarce memory window first.

A 64-bit prefetchable BAR is placed below 4 GiB whenever the memory window has room, which can starve a later non-prefetchable BAR behind the same port. Trying the prefetchable window first for a prefetchable BAR would avoid that.

SUGGESTION B. kernel/pci/src/bridge.rs:52: the kind rule in forwards has no test.

The kind rule is implemented twice, in the parent's kinds selection and in forwards for the ancestors. Only the first is tested: mutating forwards to holds(self.memory) || holds(self.prefetchable) leaves all four behind tests green, because the_bars_kind_picks_the_window never reaches forwards for a non-prefetchable BAR. A three-line forwards unit test would hold the "classic mistake" directly.

SUGGESTION C. kernel/src/pcidev/mod.rs:772-776: the NoRoutedRun text repeats the surrounding log line and claims more than the code knows.

place_bars already logs "keeps BAR {index} where firmware put it and hands it to nobody — {why}", so "so it stays where firmware put it" repeats it, and BAR {i} is named twice. "There it shares a 2 MiB page ... with whatever else decodes there" is not checked and is not always true. "no 2 MiB the bridges above it forward is free" is enough.

Evidence checked

  • cargo run -- --ci host at 5321afa0c, run by me in my own worktree: exit 0, "Host: 83 step(s), all green". The four behind host tests pass.
  • q35 rows at 88dab83e0: guests.log shows 5 passed, exit 0, with bar_map_behind_a_bridge placing 01:00.0 BAR 4 at 0xc000200000.
  • Rows still cover the head. 24ed5a756 changes only constants inside placement.rs's mod tests (the port's window becomes 0xd0000000..0xd0500000, plus two BARs), and 5321afa0c touches only the issue. The q35 rows therefore still cover the head's code.
  • Negative control: real. With the kernel changes reversed, the job panics "the virtio NIC's claim: NotSupported" (exit 1).
  • Mutation: real. With routed ignored, an_address_one_bridge_on_the_path_does_not_forward_is_never_offered goes red (exit 101).
  • AMD data: the host test's port 00:08.1 window matches the brief.
  • PCI apertures are a declaration the kernel holds, made from each root's _CRS (design; stage A: the declaration and the hybrid IOMMU rule) #916 claim: the PR's statement that PCI apertures are a declaration the kernel holds, made from each root's _CRS (design; stage A: the declaration and the hybrid IOMMU rule) #916 changed no candidate checks out (git diff 5b48a3d17 cc1a44c9b over placement.rs and bridge.rs is empty).
  • PR body: it does not claim 03:00.4 becomes claimable. It says that function stays unclaimable until P1, refused by name.
  • Not yet run: the T14 and AMD bench rows are staged only.

Fixing blockers 1 and 2 changes kernel placement code, so the q35 rows need a rerun after them, along with the new host tests. Blockers 3 and 4 are comments and records only.

SEND BACK

Japabu and others added 2 commits October 14, 2026 19:38
…it, and the issue names what remains

- A bridge records the bus it sits on, and `bridge::path` walks from a
  function's bus to its root bus: each step is the one bridge whose
  secondary bus is the one reached, sitting on a lower bus, with the
  function's bus in its subordinate range. A bridge firmware never
  configured (both bus numbers 0) or one claiming every bus is no step, so
  no bus-0 function leaves the root runs on its word; two bridges naming
  one secondary bus refuse that bus's functions alone (`PathAmbiguous`).
  Host tests: the T14's bus-0 shape with such bridges, a two-level climb,
  the duplicate, and `Bridge::forwards`'s kind rule.
- Candidates behind a bridge are cut from the firmware memory map too, as
  the root runs are.
- A prefetchable BAR tries its port's prefetchable window first.
- `trying` holds a candidate while it is probed and no longer; `cut` and
  `place_bar` say what they do for a candidate from either source;
  `NoRoutedRun` says only what is known.
- The issue the interim refuted is renamed to the defect left, a BAR
  behind a bridge with no free 2 MiB of its windows, and `there-is-no-wifi`
  cites it by its new name. Before this branch every candidate behind a
  bridge lay outside it, since the root runs exclude every forwarded range.

Merges origin/main e136c77 (#926), keeping both new q35 profiles.

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

Review round 1 answered at 503d1959945d867ea6b4dad6f6c5fefb01503012 (main e136c77fd merged).

  • B1: bridge::path walks from the function's bus to its root bus. Each step must be a bridge on a lower bus that names the reached bus as its secondary one and whose subordinate range holds the function's bus. Two bridges naming one secondary bus refuse that bus alone (PathAmbiguous). Host tests cover the T14's bus-0 shape with an unconfigured (0/0) bridge and one claiming every bus, a two-level climb, and the duplicate. With the lower-bus check removed, the climb test fails and the bus-0 walk loops.
  • B2: the firmware memory map is chained into the candidates' taken behind a bridge. bar_map_behind_a_bridge still places at 0xc000200000, so OVMF's map does not cover the port's window.
  • B3: the comments on trying, cut and place_bar are corrected. trying now holds a candidate only while it is probed: a refused one is given back on success too.
  • B4: the issue is renamed to the defect that remains, a BAR behind a bridge with no free 2 MiB of its windows, and there-is-no-wifi cites the new name.
  • Suggestions: taken A (a prefetchable BAR tries the prefetchable window first), B (a Bridge::forwards kind test) and C (NoRoutedRun says only what is known).
  • Checks: --ci host (86 steps) and the five q35 rows are green at the head.
  • Bench: the readbacks from 5321afa0c are published. Both rows exercise the root-bus path, and this round leaves that path's placement untouched, so they stand.

Evidence: pr-928 at 3f22d59a59e483946e0bfaf4f21d46a9ef5aa63c.

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Re-review of #928, round 2, at 503d19599 (fixes in merge a88fcc71b, main e136c77fd)

This round checks only the fixes. I compared e136c77fd..503d19599 over the branch's files. cargo run -- --ci host at 503d19599 in my own worktree exits 0 with 86 steps green, including the 12 bridge:: and behind:: tests.

Resolved

  • B1, the path walk (kernel/pci/src/bridge.rs:61-80).
    • The walk. A step is a bridge with secondary == reached, bus < secondary, and the function's bus in secondary..=subordinate. Bridge::bus is the enumerated location from PciDevice::bus, not a register, so a device cannot lie about where it sits.
    • Termination. Each step sets at to parent.bus, which is strictly less than reached, so the walk ends within 256 steps. The doc states this ("Each step lowers the bus, so the walk ends"). An ambiguous step yields one Err and stops (at = None).
    • Bad bridges. A 0/0 bridge and a 0/0xff bridge both fail bus < secondary, so they are never steps. A hostile bridge further down the tree can at most make one bus ambiguous (PathAmbiguous, which skips that BAR with its name logged); it can no longer pull a root-bus function off the root runs.
    • Wiring. Source::of takes the path or the refusal, and taken excludes the windows of every bridge off the path.
    • Tests. They cover the T14 bus-0 shape with both bad bridges, the two-level climb plus a bridge on a higher bus and a reversed range, and a duplicate that refuses one bus and not another.
    • Mutation (r2/mutation-trusted-bus). With the lower-bus check removed, the climb test fails and the bus-0 test hangs. That shows the check is load-bearing.
  • B2, the memory map (kernel/src/pcidev/mod.rs:654, :1518). Machine::map is set in publish and is first in the behind-bridge taken. The q35 row still places at 0xc000200000 (r2/guests.log), which confirms OVMF's map leaves the port's window free.
  • B3, the three comments.
    • trying (mod.rs:381-384) is now true. The success path now calls give_back (mod.rs:1438), so refused candidates no longer stay in trying after a win. My round-1 finding missed that leak.
    • cut (mod.rs:1595) and place_bar's doc are now true for both sources.
  • B4, the issue. It is renamed to a-bar-behind-a-bridge-with-no-free-2-mib-of-its-windows-is-not-handed-over.md, with status: open and owner P1. The one citation (there-is-no-wifi.md:151) has moved, and no other reference to the old slug remains.
  • Suggestions taken.
    • Prefetchable-first (placement.rs:139): the window order is tried in turn, falling back to the memory window, and this is tested.
    • The forwards kind and extent unit test has been added.
    • The NoRoutedRun text now says only what is known.

Findings

BLOCKER. issues/a-bar-behind-a-bridge-with-no-free-2-mib-of-its-windows-is-not-handed-over.md:16-17, 22-24: the new issue says the AMD bench's spare xHCI 03:00.4 is refused by NoRoutedRun. On this tree it is not.

Note for the orchestrator (no change to the branch). The bench rows from 5321afa0c still cover this head, but "this round leaves that path's placement untouched" is not exact.

  • What changed on the root runs. The new success-path give_back (mod.rs:1438) also changes them. A run candidate the function did not answer at, taken before a later candidate it did answer at, is now returned to its run (placement::release restores it only if it was that run's last hand-out), where it used to stay spent.
  • Why the rows still stand. main already returns every refused candidate when a walk fails, so the re-offer is not a new kind of state. It can only matter on a bench claim that logged BAR n left 0x… before a placed at. The published metal-5321afa0c readbacks carry only the judge exits (both EXIT=0), not the serial.
  • Cheapest confirmation. Grep the two benches' serial logs for left 0x on a claim that then placed. If there is none, the rows cover the head as claimed. The PR text should name this change rather than say the path is untouched.

Housekeeping

While setting up this review I found that my round-1 cleanup had left this worktree's rust and rust/library/backtrace fork registrations behind. That made my first host run fail at fork checkout. I removed both registrations by path (git worktree remove --force on my own paths only); nothing of anyone else's was touched.

SEND BACK. The one blocker is a single clause in the issue. Once it is fixed, with the orchestrator's serial check above, I see nothing else that blocks LAND.

The AMD bench's 03:00.4 and the T14's AX210 hold their MSI-X table in their
one BAR, which a claim withholds, so each is refused NoMappableBar before
placement; NoRoutedRun is what a claim handing that BAR over would meet.

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

Re-review answered at 317b1bb5298d04bf18776d3587af3c77c70ff0da, comments and records only, no rerun.

  • The issue: 03:00.4 and the T14's AX210 hold their MSI-X table in their one BAR, which a claim withholds, so on this tree each is refused NoMappableBar before placement. NoRoutedRun is what AMD-Vi stage 6: interrupt remapping, and claims only where every unit filters SMIs #921's hand-over of the table's BAR would meet.
  • The PR body now names the behaviour change: a successful placement gives back the candidates tried before it that the function did not answer at, including a root-run one that used to stay spent.
  • The bench rows stand: neither boot at 5321afa0c logs a BAR n left 0x… line, so no probe failed there and that path was not reached.

@Japabu
Japabu marked this pull request as ready for review October 14, 2026 18:28
@Japabu
Japabu added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 335927b Oct 14, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-bridge-window branch October 14, 2026 18:53
Japabu added a commit that referenced this pull request Oct 14, 2026
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
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