Repository navigation
PCI is walked only over the buses each MCFG window decodes, each window mapped exactly or refused by name - #840
Conversation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
…nly into buses a bridge forwards The kernel mapped 256 buses from the first MCFG allocation's base and read every one of them. On an AMD laptop whose MCFG decodes buses 0 to 0x3f, the addresses past bus 0x3f are other hardware's (the IOAPIC, the HPET, flash, then DRAM): the walk read thousands of phantom functions out of them, filled its 256-function cap, and remapped what lay there uncached. - toyos-acpi decodes every MCFG allocation structure (PCI Firmware Specification 3.3, Table 4-3) with its segment group and start and end bus, and refuses by name one that is inverted, off a bus boundary, wraps the address space, is cut short by the table's end, sits on a segment group other than the first accepted one's, or decodes a bus an earlier window already does. `ecam_base`, which read the first base and nothing else, is gone. - toyos-pci's `buses` decides which buses of a window a walk enters: the window's first bus, and each bridge's secondary inside what the bus above it is forwarded, each at most once and always above the bus that named it, so the walk ends however the bridges are numbered. - The kernel maps each window over its own buses only, walks it with that, and answers `function_window` only for a bus a window decodes; the ACPI holder's mediated window and the loader's watchdog read the same decode. An unusable MCFG is a boot with no PCI function, said by name, instead of a panic on firmware input. issues/the-mcfg-entrys-start-bus-is-never-read.md is closed: the decode returns the range beside the base and no caller indexes a bus outside it. Its claim that the base is the start bus's was wrong: the base is bus 0's (Linux's `pci_mmcfg` reads it so, and `toyos_userbound::firmware::Ecam` already did). issues/pci-is-walked-from-one-root-bus-per-ecam-window.md records what the walk still does not enter: a root bus other than a window's first, and a segment group other than the first window's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
13d1622 to
1650ff2
Compare
|
|
|
|
|
|
|
|
|
|
|
|
The target laptop's reading (BLOCKER: hardware). It is from the orchestrator's test image v6 on the AMD Zen 2 laptop: the measurement-only branch Every PCI row in the log is on bus 0x00 to 0x03, and none is past bus 0x3f: 28 rows. |
|
Review of #840 at Net lines ( Earlier BLOCKERs
BLOCKER
NOTE
SEND BACK |
|
The laptop's 28 28 rows, all on buses 0x00 to 0x03, none past 0x3f. |
Keeps both sides of #846's VirtIts and virt_claim_lpi beside this branch's VirtLowEcam and virt_low_ecam in tests/common/qemu.rs and tests/toyos.rs. The merge also returns issues/the-loader-does-only-what-must-precede-the-handover.md to main's text: a pull request edits only the issue it takes up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Review of #840 at Net lines ( Earlier BLOCKERs
The laptop reading at
|
#846, #843, #849, #850, #840, #839, #837) into the batch: the icons' and wallpaper's digests hash with toyos-sha2-hw, and the loader's wall clock reads through its own UEFI bindings The batch's merge of main at f72d53d moved #813's wallpaper and #814's icon digest tests onto `toyos_sha2`. #833, already on main, had replaced the root package's `toyos-sha2` dependency with `toyos-sha2-hw`, so CI's merge of the two compiled no `toyos-build` lib test and both the build system's tests and clippy went red with E0432. Both tests now hash through `toyos_sha2_hw`, as every other SHA-256 the build takes does. #842's `bootloader/src/wallclock.rs` was written on the `uefi` crate, which #815 removes; it now calls `efi`'s `RuntimeServices::get_time`, whose `Time` fields are plain and whose error is the `Status` itself. `start_kernel` takes main's `wall_clock` and the batch's `SystemTable`; the batch's `armed_at` goes, as main replaced it with `wallclock::now`. Cargo.lock takes main's `ntapi`; `nonempty` goes with `gix`, which the batch removed and which was its only user (`cargo metadata --offline`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
The kernel mapped 256 buses from the first MCFG allocation's base and read every one of them. On an AMD laptop whose MCFG decodes buses 0–0x3F, the addresses past bus 0x3F belong to other hardware: the IOAPIC, the HPET, flash, then DRAM. The scan read thousands of phantom functions out of them, filled its 256-function cap, and remapped that memory uncached. Now each MCFG window of segment group 0 is mapped exactly over its own start..=end buses, and every bus in that range is scanned. A window the kernel cannot map exactly is refused by name.
What changed, per decision
toyos-acpi/src/mcfg.rs).toyos_acpi::EcamWindowreplaces both the first round'sAllocationandtoyos_userbound::firmware::Ecam, which had public fields.EcamWindow::decode, over one allocation structure's 16 bytes (PCI Firmware Specification 3.3, Table 4-3). It refusesOtherSegment,Inverted(end bus below start bus),Misaligned(base off a 1 MiB bus boundary) andWraps(last byte past the address space).ecam_allocationswalks the table, refusingPartial(the table ends inside a structure),Overlaps(a bus an earlier window already decodes) andSharesBytes(bytes an earlier window already decodes as other buses, where one function would answer under two bus numbers, be mapped twice and be decided by whichever window the policy read first). Every bus is one megabyte on a megabyte boundary, so the walk keeps each claimed bus's address and refuses a window that holds one.offset(bus, device, function → byte offset from the start bus) andlocate(address → register). The kernel's scan,function_windowand theacpiclaim's memory and configuration policy all read these, and none computes an offset of its own.arch/x86/pci/mmconfig_64.c, v6.10, lines 105–110).SEGMENT_GROUP). Before, the first window's group won, so a table listing segment 1 first lost segment 0 in the kernel and in the loader's TCO arm. Every machine has segment group 0. The window carries no segment, since it would always be 0.kernel/src/drivers/pci.rs). The bridge walk (kernel/pci/src/buses.rs) and the bridge branch ofenumerateare deleted. Inside a window the host bridge decodes every bus, and a bus nothing routes ends in Unsupported Request and reads all ones. So the in-window scan was never the defect; reading and mapping past the end bus was. Peer root buses inside a window, such as a second root complex orpxb-pcie, are enumerated again.EcamWindow::mappable, called fromacpi::ecam_windows). Three refusals:OffGrain: the window's bytes start or end off the grainmap_mmiomaps at. x86'smap_mmiomaps whole 2 MiB pages, andmap_2msilently replaces a Normal leaf with Uncacheable, so a window on an odd MiB would remap its cached neighbour uncached. The grain ispaging::MMIO_GRAIN: 2 MiB on x86-64, 4 KiB on AArch64, which maps exactly. This is a compromise on x86-64: a well-formed off-grain window is never enumerated, and if it is the only one the machine has no PCI. It is filed asissues/an-ecam-window-off-the-2-mib-grain-is-not-enumerated-on-x86.md, with its exit: x86-64 maps an MMIO window to the 4 KiB page, and a host test accepts an odd-bus window at x86-64's grain.PastLimit: the window reachestoyos_bootmap::DIRECT_MAP_WINDOW, wheremap_mmio'sPHYS_OFFSET + physwould overflow.OverMemory: firmware's map lists memory there (toyos_bootmap::is_read_as_memory, now public: RAM, ACPI reclaim, ACPI NVS). The kernel already maps that memory cached. On AArch64 the uncached map of it was a kernel panic on firmware input, as the negative control below shows. On x86 it was a silent remap.MAX_TABLE_LEN(1 MiB) would otherwise log up to 65,536 lines at boot.acpiclaim's policy (toyos_userbound::firmware) decides over&[EcamWindow]instead of the first window, anda_later_window_is_held_as_the_first_isholds both of its readers (Memory::decide,config) to a second window. The loader's TCO arm takes the window that decodes bus 0. An unusable MCFG boots with no PCI function, said by name (ACPI: MCFG unusable: …), wheremainused to panic on it.issues/the-mcfg-entrys-start-bus-is-never-read.mdis closed. Its exit is met: the decode returns the range beside the base, and every caller refuses a bus outside it by name (offsetandlocateanswerNone; the policy refusesConfigUnreachable). Its claim that the base is the start bus's was wrong.pci-is-walked-from-one-root-bus-per-ecam-window.mdshrinks to its segment-group half, under the slugissues/pci-is-enumerated-on-segment-group-0-alone.md.issues/an-ecam-window-past-the-cpus-physical-address-width-is-mapped.mdis filed for the boundPastLimitdoes not yet take.issues/an-ecam-window-off-the-2-mib-grain-is-not-enumerated-on-x86.mdis filed forOffGrainon x86-64.issues/the-loader-does-only-what-must-precede-the-handover.mdstill citestoyos_acpi::ecam_base, which this branch replaces withecam_allocations.Checks
This is high-risk code: a firmware trust boundary, device enumeration and an MMIO mapping. The head is
728f483b0, which mergesorigin/mainat26b3684b9; that merge brings #846's ITS claim code intokernel/src/drivers/pci.rsand the AArch64paging.rs, and keeps #846'sVirtIts/virt_claim_lpibeside this branch'sVirtLowEcam/virt_low_ecam. The host suite, the whole guest suite and both images were run at728f483b0. The mutation rows were run at916b5e16cand the rows markedec8fce3d1at that head; the merge changes none oftoyos-acpi,toyos-userbound,kernel/src/drivers/acpi.rsor the x86-64paging.rs(git diff --stat 916b5e16c 728f483b0over them is empty).728f483b0cargo run -- --ci host728f483b0(virt_low_ecamandvirt_claim_lpiboth in it, both PASS)cargo test728f483b0cargo run -- --build-only728f483b0cargo run -- --build-only --arch aarch64916b5e16cfor window in self.ecam→for window in self.ecam.iter().take(1)inMemory::decide, thencargo test -p toyos-userbound --test firmwarea_later_window_is_held_as_the_first_is)916b5e16cecam.iter().any(→ecam.iter().take(1).any(inconfig, same command916b5e16c916b5e16cSharesBytescheck deleted fromAllocations::claim, thencargo test -p toyos-acpi --test mcfga_window_over_another_windows_bytes_is_refused)916b5e16cgit apply --checked, applied, run and reverted withgit apply -R; then both suites on the restored treeec8fce3d1offset'su64::from(bus - self.start_bus) << BUS_SHIFT→u64::from(bus) << BUS_SHIFT, thencargo test -p toyos-acpi --test mcfga_window_starting_at_bus_0x10_counts_its_functions_from_bus_0x10); restored, 0, tree cleanec8fce3d1e3e21e421, test kept,cargo test --test toyos-build -- virt_low_ecamec8fce3d1pci_inventoryandacpi_table_inventory, run by the orchestrator from the staged image (sha25669e449ef…, request)Host tests (
toyos-acpi/tests/mcfg.rs,toyos-userbound/tests/firmware.rs)offset(0x10,0,0)is 0, and its last function is 16 MiB − 4 KiB.locateinvertsoffsetfor every function of every bus at three registers, and names nothing one byte either side of the window.locatesu64::MAX; one bus more is refusedWraps.mappable:OffGrainat 2 MiB and mapped at 4 KiB.PastLimitat the limit's edge.OverMemoryfor usable, boot-services, ACPI reclaim and NVS memory overlapping by one page at either edge. Accepted beside RAM and over reserved or memory-mapped I/O ranges.SharesBytesover all of its bytes, over its last megabyte alone, and over its first megabyte from below; windows ending on the megabyte before it and starting on the megabyte after it are accepted.AsConfigwith its absolute bus 0x42 for a read and for a write; the write is then refusedConfigWriteand the read passes, as the kernel'sacpi_modehandles that verdict.configreaches buses 0x3f, 0x40 and 0x7f, and refuses 0x80ConfigUnreachable.EcamWindow::decode. The one that guarded saturating arithmetic on a base ofu64::MAX − 0xFFFis gone, because the type refuses that base (Misaligned). A top-of-space window thatdecodeaccepts replaces it.corpus.rsmutation of every fixture byte runs the MCFG walk to exhaustion, andfixtures.rsholds QEMU's q35 MCFG to its one 0..=0xff window at 0xb0000000.Why a guest test (
virt_low_ecam, new profileVirtLowEcam). The host tests reach the decode and the arithmetic, but not the kernel mapping a real firmware's window and scanning through it with RAM right past it.map_2msilently replaces a Normal leaf with Uncacheable, so the over-memory map is a silent remap there and no x86 row could see it. AArch64 refuses it loudly, and the only AArch64 machine is QEMU'svirt. q35 cannot make a narrow window either: its MCFG decodes as many buses as thePCIEXBARlength firmware programs, and QEMU 11.1.1 has no machine ormchproperty for it.virtwithhighmem-ecam=offdoes make one:ACPI: ECAM window: segment 0 buses 0x00..=0x0f, bus 0 at 0x3f000000, mapped0x3f000000+0x1000000, with guest RAM from 0x40000000.logkeeper.Negative control. On the merge base, the same machine panics mapping 256 MiB from 0x3f000000 over RAM:
paging: 0x40001000 is mapped Normal (0x40000040001707) and cannot also be Uncacheable. That is the laptop's defect on QEMU.Independent oracles
arch/x86/pci/mmconfig_64.clines 105–110 mapaddress + PCI_MMCFG_BUS_OFFSET(start_bus)and subtract that offset back, so an absolute bus indexes fromaddress.virtboot above.Real hardware
ec8fce3d1: read. The orchestrator booted the staged image (reading): exit 0, "2 passed, 0 failed". From that boot's readback (rows and diff):ACPI: ECAM window: segment 0 buses 0x00..=0x79, bus 0 at 0xc0000000, a narrow window on real hardware;mmio: 0xc0000000+0x7a00000 PAT Uncacheable (MTRR UC), exactly 0x7a buses;PCIrows, diffed against the reference two earlier T14 boots recorded on BDF, class, vendor and device, exit 0, no difference.ec8fce3d1the T14's path gains only the byte-overlap check, which a table of one window passes, and the merge oforigin/main; it has not been booted again.916b5e16c. The orchestrator's test image v6 on the AMD Zen 2 laptop, the measurement-only branchwt/toyos-yoga-tpat100e61b9e, which carries916b5e16c; the round-3 review checked thatgit diff --exit-code 916b5e16c 100e61b9eover the decode, the mapping and the scan's files exits 0. Its recorded failure (phantom functions past bus 0x3f, memory remapped uncached) is this change's independent oracle. From that boot's kernel log (reading, the 28 rows, the whole masked log on The local APIC runs in xAPIC mode where CPUID offers no x2APIC #841: 01 02):ACPI: ECAM window: segment 0 buses 0x00..=0x3f, bus 0 at 0xf8000000;mmio: 0xf8000000+0x4000000 PAT Uncacheable (MTRR UC), exactly 64 buses;pcidev: 28 functions, and the 28PCIrows are all on buses 0x00 to 0x03, none past 0x3f.origin/maininto728f483b0has not been booted on it.Growth
git diff --shortstat origin/main...728f483b0: 23 files, +848 −182.mcfg.rs(241: the decode,offset/locate,mappableand the claim). The rest is the kernel, loader and policy callers, the twoMMIO_GRAINconstants and the lockfile line.Production grows because the decode used to read one field of the first structure. It now reads the range, refuses malformed and unmappable windows by name, and owns the address arithmetic. In exchange:
Ecamand its saturatingholdsare gone;acpi.rs,acpi_mode.rs) are gone;What I am unsure of
issues/an-ecam-window-past-the-cpus-physical-address-width-is-mapped.md. Every MCFG this tree has read is below 4 GiB.OffGrainon x86-64. A well-formed window off the 2 MiB grain is refused, so its functions are never enumerated:issues/an-ecam-window-off-the-2-mib-grain-is-not-enumerated-on-x86.md. No MCFG read so far is off the grain.issues/pci-is-enumerated-on-segment-group-0-alone.md. No machine in reach has another.OverMemoryreads firmware's map, not the direct map's own tables. A window over an address the map does not list as memory is mapped. On x86 the direct map's lowBOOT_MAP_BYTESare mapped whole with Normal 2 MiB pages, holes and registers included (toyos_bootmap::x86_64::direct_map_end). So a grain-aligned window there replaces those leaves uncached, as everymap_mmioof a register there already does. I have not measured whether any CPU in reach holds a stale Normal line for such a hole.🤖 Generated with Claude Code
https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C