Repository navigation
iommu: lowest-first device addresses below each claim's stated limit; a claim masters only when its holder starts it - #974
Conversation
…ted limit, and a claim masters the bus only when its holder starts it A domain's addresses used to start a quarter of the way up the unit's range, above all memory. The AMD bench's HDMI audio function 1002:1637 sets 64OK yet issues only 40 address bits, so every grant it got was out of its reach. Worse, a grant above a function's real width is issued truncated, and can then land on another grant of the same domain. Allocation (owner decision K1): `kernel/pure/iova.rs`, host-tested. - A domain's allowed addresses are the unit's range less the first leaf, the root bridges' windows, firmware's IVMD and RMRR regions, the x86 interrupt range (VT-d 5.0 §3.15, 48882 §2.2.9), AMD-Vi's special range FD_0000_0000..2^40 (48882 Table 3) and the SMMU's ITS doorbell page. - Addresses are handed out lowest first and each exactly once. - No grant ends above the limit its holder states. The stated limit is a new syscall, `device_dma_limit`, made once per claim before any grant or lend. - The above-RAM rule, the quarter floor, the CPU-width branch and `physical_address_bits` are deleted. - The control `iova-ignores-limit` reds the truncation, model and AMD-layout tests. Hand-over (owner decision K2): - A claimed function's Bus Master Enable is cleared and drained before its domain is attached. Firmware's stale DMA no longer faults against the holder. - A function now masters only once its holder calls the new syscall `device_dma_start`, not at its first grant. - Each driver states its width and starts the function after its own stop or reset: - virtio, in `negotiate`: 64 bits; - nvme and i219: 64 bits; - usbd: 64 bits, since it requires AC64; - hda: 64OK gives 64 bits, otherwise 32; - the kernel's xHCI: AC64 decides, 64 or 32 bits. Unavailable while investigating (K3): hda refuses 1002:1637 by name. Its 64OK is contradicted and no independent source gives its width; see issues/the-hdmi-audio-function-1002-1637-is-driven-at-no-address-width.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed to every holder Virtio 1.2 §6.1: a device that does not offer VIRTIO_F_ACCESS_PLATFORM uses physical addresses whatever translates the bus. QEMU keeps such a function on the machine's own address space, the unit bypassed. Since domains now hand out their lowest addresses first, an honest holder's device addresses are also physical addresses of memory, and such a function would write into memory nothing granted it. The supervisor mints every claim and is trusted, so it reads the function's offered features through the claim before the claim moves to its holder. It reads them without resetting the device: `offered_features` writes only the feature select. A virtio function that offers no ACCESS_PLATFORM, or whose offer cannot be read, is refused by name, and its holder starts without it. Neither `kernel/src/pcidev` nor `kernel/src/iommu` names virtio. The QEMU profile HeadlessNicUntranslated creates virtio-net without iommu_platform=on. The row a_virtio_function_without_access_platform_is_refused reads the refusal and `netstack: no NIC on this machine`. With the check deleted, netstack negotiated the NIC with access_platform=n and the row stalled red. Closes the issue a-virtio-function-that-offers-no-access-platform-is- handed-over-as-translated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The traffic line says how many frames the driver received, but not whether the wire carried any. A quiet network and a receive the driver never got both read as no frame received. This adds the MAC's own counts (TPR, GPRC, MPC, CRCERRS), which it already reads, to the output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recorded from the T14 comparison of #974 against main, where both runs failed with the MAC counting no frame seen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1: #974 at
|
- The kernel's virtio console. A console that offers no VIRTIO_F_ACCESS_PLATFORM uses physical addresses whatever translates the bus (virtio 1.2 §6.1). With lowest-first domains its pool sat at about 2 MiB of somebody else's memory. Such a console is now told physical addresses: an untranslated space, kept on the identity domain. The QEMU row the_kernel_tells_a_bypassing_console_physical_addresses (on q35 with VT-d and the console created without iommu_platform=on) boots over it. With the fix reverted, the boot never reaches its ready marker. - The hand-over drain. A config read flushes the function's posted writes (PCIe Base §2.4.1) but orders none of its DMA reads. After clearing Bus Master Enable, the kernel now waits for Device Status' Transactions Pending (§7.5.3.5) to clear before attaching the domain. The wait is bounded at twice the top of the Completion Timeout's default range (§2.8). A function still transacting past that is refused by name. A function with no Express capability has only the posted-write flush. - bring_up's doc comment no longer argues the order in which the old code started mastering. - DmaPool::alloc_in is fallible. A device whose width its pages or its domain cannot meet refuses that device by name (the kernel's xHCI, the console) instead of panicking the kernel. Every other mapping refusal is still a kernel bug. Suggestions taken: the two syscall wrappers now sit beside the other `device_dma_*` wrappers, and the per-boot address budget of a narrow function is recorded in issues/. Declined: claiming as PciDev at the supervisor's mint sites, which would special-case the one generic path every claim kind shares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r2: #974 at
|
…nd refuses a truncated capability list Review r2. - A conventional function that publishes Advanced Features with Transactions Pending (PCI Advanced Capabilities ECN; the PCH's integrated functions do) now has that bit polled before the domain attaches. It uses the same deadline and the same `StillTransacting` refusal as the Express bit. Conventional PCI has no Completion Timeout, so the bound is a hang ceiling there. `af::reports_pending` is new, with a host test. - A function with neither capability has no pending signal. Its posted writes are flushed, and a read still in flight may fault in its new domain against its holder; isolation holds. The comment at the site now says so. - A truncated capability list refuses the hand-over (`CapsTruncated`) rather than skipping the wait. - Optional points taken: the console declines the function when its pool is refused, and QUIESCE_NANOS cites Device Control 2 §7.5.3.16 and says only an outstanding request outlasting it is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Metal, T14 (ThinkPad T14 Gen 2), head The kernel log has no The AMD result at |
r3: #974 at
|
#974 changed the kernel xHCI driver and the old usbd binary (system/usbd/src/hc.rs), both of which this branch deletes. They stay deleted, and their new limit and start calls are carried into the new usbd in the next commit. dispatch.rs takes #974's two DMA syscalls, and SYS_PARTITION_* stays deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…troller firmware keeps On #974's hand-over contract. A claim's function masters nothing until its holder starts it (SYS_DEVICE_DMA_START), and every grant and lend is placed below the limit the holder states (SYS_DEVICE_DMA_LIMIT). - After the firmware handoff and the controller's halt and reset, usbd states its limit. That is 64 bits, or 32 where HCCPARAMS1.AC64 is clear (xHCI 1.2 §5.3.6). It then takes its grant and starts mastering before it writes any address to the controller. A controller without AC64 is no longer refused: #974 places its grants below 4 GiB. - A controller firmware keeps (HC BIOS Owned still set after HANDOFF_NS, §4.22.1) is now taken, by name, with its SMIs off. Until usbd starts it, its bus mastering is off, so the halt and reset cut no transfer of firmware's that could reach memory (PCIe Base 6.0 §7.5.1.1.3). That is the AMD bench's 03:00.3. The previous commit's refusal stood in for this contract until it landed. - A refused limit or start ends usbd by name, as every other refusal does. - Test: a_controller_firmware_keeps_is_taken_by_name_with_its_smis_off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Us offer The row demanded x2APIC of every bench, which is the T14's alone. The kernel takes x2APIC exactly where CPUID.01H:ECX[21] offers it, and the AMD bench's Ryzen 7 4800U does not: every CPU its scout read reports leaf 1 ECX = 0x76d8320b, bit 21 clear, against the T14's 0x77fafbbf. So the AMD bench declared xAPIC as it should, and the row failed on the expectation alone; main's own AMD readback for #974 says the same. The row now takes the mode from the bench, told by its signature as the topology and panel rows are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Us offer The row demanded x2APIC of every bench, which is the T14's alone. The kernel takes x2APIC exactly where CPUID.01H:ECX[21] offers it, and the AMD bench's Ryzen 7 4800U does not: every CPU its scout read reports leaf 1 ECX = 0x76d8320b, bit 21 clear, against the T14's 0x77fafbbf. So the AMD bench declared xAPIC as it should, and the row failed on the expectation alone; main's own AMD readback for #974 says the same. The row now takes the mode from the bench, told by its signature as the topology and panel rows are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Us offer The row demanded x2APIC of every bench, which is the T14's alone. The kernel takes x2APIC exactly where CPUID.01H:ECX[21] offers it, and the AMD bench's Ryzen 7 4800U does not: every CPU its scout read reports leaf 1 ECX = 0x76d8320b, bit 21 clear, against the T14's 0x77fafbbf. So the AMD bench declared xAPIC as it should, and the row failed on the expectation alone; main's own AMD readback for #974 says the same. The row now takes the mode from the bench, told by its signature as the topology and panel rows are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
A domain's device addresses started a quarter of the way up the unit's range, above all memory. The AMD bench's HDMI audio function
03:00.1(1002:1637) sets GCAP 64OK, but it issues only address bits 0–39 in both of its DMA engines (probe evidence below). So it faulted on every grant.Two other problems sat under that:
03:00.3faulted against usbd for exactly this reason (usbd drives every xHCI controller; the kernel drives no USB #971).What this does (owner decisions K1–K3, 2026-10-21)
Lowest-first allocation, below a stated limit (K1). The allocator is
kernel/pure/iova.rs, pure and host-tested.FD_0000_0000..2^40(48882 §2.1.2, Table 3), and the SMMU's ITS doorbell page.device_dma_limit(claim, bits)takes 32–64 bits. It is stated once per claim, before any grant or lend, and every grant ends at or below that limit. Running out isResourceExhausted.physical_address_bits) andWindowBelowMemory. Without a declaration of the root-bridge windows there is no domain, on every backend.DMA held off until the holder starts the function (K2).
device_dma_start, after the driver's own stop or reset.Each driver's stated width:
negotiate1002:1637unavailable by name (K3). hda refuses it before any grant, because its 64OK is contradicted and no independent source gives its width. The 40-bit measurement stays as scoped evidence inissues/the-hdmi-audio-function-1002-1637-is-driven-at-no-address-width.md, which replacesa-function-that-truncates-….A device that bypasses translation is refused (
fd95b3af0). Virtio 1.2 §6.1: a virtio function that does not offerVIRTIO_F_ACCESS_PLATFORMuses physical addresses, whatever unit sits on the bus. With low addresses, such a function would write wherever an honest holder's addresses land in RAM.offered_featureswrites only the feature select, so the device is not reset).kernel/src/pcidev,kernel/src/iommu).issues/a-virtio-function-that-offers-no-access-platform-is-handed-over-as-translated.md.Judge.
domain_ends_below_the_host_bridgesbecomesdomains_map_clear_of_the_host_bridges, judged against everymaps … atrecord.Not added: a generic reset control for holders, because nothing calls one yet. The design is FLR, AF FLR or PM D3hot, with mastering off; a secondary bus reset only where the bridge's bus holds that function alone.
Checks
cargo run -- --ci hostexit 0 at the tree committed as6cb27ce9f. The only later edit is a comment inkernel/pci/src/express.rs. 88 steps green, including the controliova-ignores-limit, which redsa_function_that_drops_its_upper_bits_reaches_only_its_own_grant,every_grant_is_the_lowest_allowed_run_below_its_limitandthe_amd_benchs_layout.cargo test --test toyos-build -- …): all pass at the final tree.usbd_drives_the_sparereds unless usbd starts the controller before naming any address to it. QEMU's xHCI reads its segment table when ERSTBA is written. The fix moves the start in usbd, and it passes.amdvi_selftestassumed a domain's addresses are memory the identity domain does not map, which is no longer true. It now aims past the memory top.amdvi_translates_the_stick_controllerhas no fault.Checks after
fd95b3af0cargo run -- --ci host: exit 0.a_virtio_function_without_access_platform_is_refused, on q35 with a virtio-net created withoutiommu_platform=on: it reads the supervisor's refusal andnetstack: no NIC on this machine. QEMU is the only machine that can create such a function.access_platform=n.Metal (run by the coordinator)
6cb27ce9f: 6/6 PASS.6cb27ce9f: 5/6 PASS.i219_messages_on_its_msi_vector. The failure is independent of this change:94968aca2and mainb580e0078(plus the same test-only wire line) were booted back to back in one session, and both failed alike.issues/i219-msi-passes-only-when-the-network-sends-it-a-frame.md.94968aca2adds the wire-count line to the job's output.fd95b3af0(the supervisor's virtio refusal) and94968aca2(an output line). Neither machine has a virtio function, so the refusal only passes through the non-virtio path there; QEMU covers it, with its control.Review r1 (
f8d86c93e)ACCESS_PLATFORMis now given physical addresses (virtio 1.2 §6.1) and stays on the identity domain.the_kernel_tells_a_bypassing_console_physical_addresses, on q35 with VT-d and the console created withoutiommu_platform=on: green.96e929bd1). After BME is cleared and the posted writes are flushed (PCIe Base §2.4.1), the kernel waits for Transactions Pending to clear before attaching the domain.StillTransacting).CapsTruncated).bring_up's doc is corrected.DmaPool::alloc_inis now fallible on a device's width. The kernel's xHCI and the console refuse such a device by name instead of panicking.device_dma_*ones, and the per-boot address budget is recorded inissues/a-narrow-functions-device-addresses-are-spent-once-per-boot.md.PciDevat the mint sites, because the devices path shares one generic claim for every claim kind.cargo run -- --ci host: exit 0.f8d86c93e: AMD 6/6, T14 6/6 PASS.StillTransactingand no hand-over fault on the T14's 00:0d.0, 00:1f.3 and 00:1f.6.Review r2 (
96e929bd1)CapsTruncatedrefusal described above.af::reports_pendinghas a host test.QUIESCE_NANOSwording and citation.cargo run -- --ci host: exit 0.testcasesboot is re-staged, so its PCH functions exercise the Advanced Features path. AMD is not re-run: its claimed functions are Express endpoints, and their path differs only by a read-only walk for the Advanced Features capability and the truncation check, which the MSI-X arming before it already refuses.Risks
Residue left by an earlier holder that stated a wider limit than the function really has. This needs three things together:
Only (3) lets an address at or above that width be handed out at all, because allocation is lowest-first. On the AMD bench (3) is 1,704 leaves below 4 GiB, or 9,129 below 2^40. One claim may hold at most 32 MiB of grants (16 leaves), so it takes over 100 claims of that function in one boot, every one of them under the wrong statement.
If all three hold, a stale transfer truncates onto an address the domain already handed out: a residue placement (fresh pages the new holder was placed at, which is the existing residue rule) or the lend window. New grants are never placed at a residue above the new holder's limit.
The ECAM hole is allocatable. That rests on the IOMMU seeing requests before address decode: reasoning, not a citation.
Probe evidence: https://github.com/ToyOSOrg/evidence/tree/30bf1f4f70c2ce4771f0e7393fcc9a64d1c08cd1/probe
Host and QEMU logs: https://github.com/ToyOSOrg/evidence/tree/47debd4a123d06c83f4381d24929ea76f2e10ae4
🤖 Generated with Claude Code