Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions build/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,12 @@ pub(crate) const CONTROLS: &[Control] = &[
Fails("pcid::tests::two_live_address_spaces_never_share_a_pcid"),
Passes("pcid::oracle::tests::the_counting_allocator_produces_a_cross_space_read"),
]),
// A grant past its holder's limit is issued, truncated, as another grant.
red(KERNEL, "iova-ignores-limit", None, &[
Fails("iova::tests::a_function_that_drops_its_upper_bits_reaches_only_its_own_grant"),
Fails("iova::tests::every_grant_is_the_lowest_allowed_run_below_its_limit"),
Fails("iova::tests::the_amd_benchs_layout"),
]),
red(KERNEL, "mutate-spawn-skips-the-insert-recheck", None, &[
Fails("proclife::interleave::tests::a_published_exit_leaves_no_unretired_thread"),
Fails("proclife::interleave::tests::a_kill_racing_a_spawn_leaves_no_unretired_thread"),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
status: open
kind: finding
opened: 2026-10-21
---

# A narrow function's device addresses are spent once per boot

A domain hands each device address out once, for the boot
(`kernel/pure/iova.rs`), and a function keeps its domain across claims. So
the addresses below a function's width are a per-boot budget. On the AMD
bench a 32-bit function has 1,704 leaves of 2 MiB below 4 GiB, the lend
window's 128 among them. A claim may hold up to 16 leaves of grants, and
retired grant addresses are never reused. Past roughly a hundred claims of
one narrow function in a boot, its next grant is refused (`ResourceExhausted`).
That refusal is clean, but nothing else announces the limit.

Exit: either the budget is shown unreachable for every narrow function a
shipped driver claims, or an address whose function's reset has completed is
handed out again, which the residue rules would have to allow.

This file was deleted.

26 changes: 26 additions & 0 deletions issues/i219-msi-passes-only-when-the-network-sends-it-a-frame.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
status: open
kind: defect
opened: 2026-10-21
---

# `i219_msi` passes only when the network sends it a frame

`i219_messages_on_its_msi_vector` fails unless a frame arrives within
`LISTEN_NANOS` after the job's own DHCP discover
(`tests/guest/rust-tests/src/bin/i219_msi.rs`, "no frame arrived on a message
under traffic"). The frame has to come from a peer on the T14's wire: the
row's receive claim is about whatever traffic the network happens to carry,
and the job controls none of it. Its passing runs counted one or two frames.

On 2026-10-21 the T14's wire was quiet. #974 at `94968aca2` and main
`b580e0078` (with the same test-only output line, `bf581bdeb`) were booted
back to back, and each failed alike. The MAC's own counters read "saw 0
frame(s), took 0 good, missed 0, 0 with CRC errors", while 65 frames were
sent and written back and the link came up after 2.7 s:
https://github.com/ToyOSOrg/evidence/tree/e240b6db1696f7e5c805f1146578d0451e88673a/t14-i219

Exit: the job makes the traffic it receives, by MAC or PHY loopback or a
frame addressed to itself, so its receive claim no longer depends on the
network sending anything. Its verdict is then the same on a quiet wire and a
busy one.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
status: open
kind: defect
opened: 2026-10-21
---

# The HDMI audio function 1002:1637 is driven at no address width

The AMD bench's HDMI audio function `03:00.1` (`1002:1637`) sets GCAP's 64OK
(`0x4001`, HDA 1.0a §3.3.2) and keeps all 32 bits of its upper base registers.
But it issues only bits 0 to 39 of an address, in both its command engine and
its stream engine. Bit 40 and every bit above it are dropped, and bit 39 is
issued as programmed.

So its 64OK is contradicted, and `hda` refuses the function by name
(`address_bits` in `system/hda/driver/src/controller.rs`) rather than state a
width for it. The 40 bits are this machine's measurement, not a property of
every `1002:1637`: neither a vendor document nor an AMD engineer's code has
been read for it.

Evidence: the probe boot at `c8acfbc07` on the AMD bench (LENOVO 82A2, BIOS
DMCN38WW), the link and stream ladders and the bit patterns:
https://github.com/ToyOSOrg/evidence/tree/30bf1f4f70c2ce4771f0e7393fcc9a64d1c08cd1/probe
(`amd-round1/kernel.log` lines 396–501; the probe itself is
`c8acfbc07-probe.patch`).

Exit: a width for `1002:1637` that an independent source supports. That is
AMD's documentation, or AMD's own code read clean-room with the fact's
provenance in evidence. `hda` then drives the function at that width, and the
AMD bench's `testcases` boot walks `03:00.1`'s codec with no `DMA FAULT`.
6 changes: 3 additions & 3 deletions issues/the-t14-answers-only-through-a-usb-stick.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ Constraints a reader would otherwise pay to re-derive:
- **QEMU's `virtio-net-pci-non-transitional` on `q35` advertises no PCIe
function-level reset** — measured, not assumed: `pcidev`'s refusal on that
ground reddened every netd registration at once. So a re-claim is made safe by
ordering instead: bus mastering starts on the claim's **first grant**, never at
hand-over, so a function still holding its last holder's queue addresses can
act on none of them. `release` asks for a reset where the function advertises
ordering instead: bus mastering starts when the claim's holder **starts** it
(`device_dma_start`), never at hand-over, so a function still holding its
last holder's queue addresses can act on none of them before it is stopped. `release` asks for a reset where the function advertises
one; the I219 does, so on the T14 both hold.
- **The machine served its own log, and the Mac found it by name.** `logd`
served the boot from its first line on a TCP port, and netd answers multicast
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ swapped-in netd's first grant started it mastering while it still ran the old
netd's rings, its write faulted at the unit, and the fault handler cleared its
bus mastering with receive still enabled. It stayed that way to the reset. The
swap's fix (the kernel resets a released function, netd stops the part before
its first grant) removes that state; the next swap run says whether the hang
it starts it mastering) removes that state; the next swap run says whether the hang
goes with it. If it does not, the reboot path is owed a quiesce of every
function a process holds, as it already does for its own xHCI controllers.
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ already whole.

A transitional device offers no `VIRTIO_F_ACCESS_PLATFORM` on either: QEMU
refuses `iommu_platform=on` on a transitional device, and VirtualBox's virtio
devices offer the bit nowhere. So behind a unit such a NIC is one no
claim may take
(`issues/a-virtio-function-that-offers-no-access-platform-is-handed-over-as-translated.md`);
devices offer the bit nowhere. So behind a unit such a NIC is one the
supervisor refuses every holder (`translated` in `system/supervisor/src/main.rs`);
the matches reach a card only on the path
`issues/a-machine-without-an-iommu-refuses-every-claim.md` rules for a machine
with no unit, and the kernel's console on every machine.
Expand Down
1 change: 0 additions & 1 deletion issues/toyos-runs-in-any-open-source-hypervisor.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ signature. What they do depend on, each filed:

- `issues/diskserver-claims-qemus-nvme-by-its-pci-id.md`
- `issues/the-virtio-drivers-match-only-a-non-transitional-device-id.md`
- `issues/a-virtio-function-that-offers-no-access-platform-is-handed-over-as-translated.md`
- `issues/the-x86-clock-calibrates-against-an-hpet-or-the-boot-panics.md`
- `issues/a-hardware-reduced-x86-machine-has-no-power-off-and-no-power-button.md`
- `issues/no-claim-gets-an-address-space-under-virtio-iommu.md`
Expand Down
4 changes: 4 additions & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ protocol-port = []
# counter that wrapped at 4095 and reissued a live tag, and the isolation tests
# red under it. Never a default and reached by no kernel build.
counting-allocator = []
# `pure/iova.rs`'s negative control: a grant is handed out whatever the
# holder's address limit, and the truncation test reds under it. Never a
# default and reached by no kernel build.
iova-ignores-limit = []
# The process lifecycle's controls (`pure/proclife/`), declared for the
# library's own tests and never enabled by any kernel build: each reverts one
# decision to the form this kernel really had, so the host model that refuses
Expand Down
15 changes: 15 additions & 0 deletions kernel/pci/src/af.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub const fn resets(af_capabilities: u8) -> bool {
af_capabilities & (FLR_CAPABLE | TP_CAPABLE) == FLR_CAPABLE | TP_CAPABLE
}

/// Whether the function implements AF Status' Transactions Pending, from AF
/// Capabilities.
pub const fn reports_pending(af_capabilities: u8) -> bool {
af_capabilities & TP_CAPABLE != 0
}

/// Whether the function still has transactions outstanding.
pub const fn pending(af_status: u8) -> bool {
af_status & TRANSACTIONS_PENDING != 0
Expand All @@ -53,6 +59,15 @@ mod tests {
}
}

#[test]
fn transactions_pending_is_reported_by_bit_zero_alone() {
assert!(reports_pending(0b01));
assert!(reports_pending(0xff));
for without in [0b00, 0b10, 0xfe] {
assert!(!reports_pending(without), "{without:#x}");
}
}

#[test]
fn pending_is_bit_zero_of_status() {
assert!(pending(1));
Expand Down
32 changes: 31 additions & 1 deletion kernel/pci/src/express.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
//! attached. The only reset that means the same thing on every function is the
//! one the function itself implements — and QEMU's virtio functions implement
//! none, so `pcidev` does not rest on this: what makes a re-claim safe there is
//! that bus mastering starts on the first grant. This is the belt, taken where
//! that bus mastering starts only when its next holder starts it, once that
//! holder has stopped it. This is the belt, taken where
//! a function offers it.

/// This capability's id in a function's capability list.
Expand All @@ -19,6 +20,7 @@ pub const CAP_ID: u8 = 0x10;
pub const CAPABILITIES: u64 = 0x02;
pub const DEVICE_CAPABILITIES: u64 = 0x04;
pub const DEVICE_CONTROL: u64 = 0x08;
pub const DEVICE_STATUS: u64 = 0x0A;
/// Device Control 2 (§7.5.3.16), which only a version 2 structure carries.
pub const DEVICE_CONTROL_2: u64 = 0x28;

Expand All @@ -32,6 +34,22 @@ const FLR_CAPABLE: u32 = 1 << 28;
/// and self-clearing: it always reads zero.
const INITIATE_FLR: u16 = 1 << 15;

/// Device Status bit 5, "Transactions Pending" (§7.5.3.5): a Non-Posted
/// Request the function issued has not yet been completed or timed out.
const TRANSACTIONS_PENDING: u16 = 1 << 5;

/// How long a function that stopped mastering is given for its last
/// Non-Posted Requests to complete or time out: twice the default range's top,
/// 50 ms (§2.8, Device Control 2 §7.5.3.16). One whose request outlasts this
/// is refused.
pub const QUIESCE_NANOS: u64 = 100_000_000;

/// Whether the function has a Non-Posted Request outstanding, from its Device
/// Status.
pub const fn pending(device_status: u16) -> bool {
device_status & TRANSACTIONS_PENDING != 0
}

/// §6.6.2: after initiating the reset, software waits at least 100 ms before
/// it may access the function again.
pub const SETTLE_NANOS: u64 = 100_000_000;
Expand Down Expand Up @@ -67,6 +85,18 @@ pub const fn restored(device_control: u16) -> u16 {
mod tests {
use super::*;

/// Bit 5 and nothing else: its neighbours are AUX Power Detected (4) and
/// Emergency Power Reduction Detected (6).
#[test]
fn only_bit_five_says_a_transaction_is_pending() {
assert!(pending(1 << 5));
assert!(!pending(0));
assert!(!pending(!(1u16 << 5)));
for bit in (0..16).filter(|bit| *bit != 5) {
assert!(!pending(1 << bit), "bit {bit}");
}
}

/// Bit 28 and nothing else. The neighbours are Role-Based Error Reporting
/// (bit 15) and the Captured Slot Power values (25:18), so an off-by-one
/// here reads a power limit as a reset capability.
Expand Down
Loading