Repository navigation
toyos-virtio: the virtio 1.2 PCI transport and split virtqueue as one pure crate, with netstack's virtio-net its first client; toyos-device-memory: the one boundary toyos-i219 and toyos-virtio are written against - #796
Conversation
…and netstack's virtio-net is its first client toyos-virtio holds what every virtio driver in userland decides before its device type begins: which capabilities name the device's structures and whether the BAR holds them, the initialisation virtio 1.2 §3.1.1 orders, the feature negotiation §2.2 bounds, a queue's configuration, the publication order §2.7.13 fixes, and what a used-ring element has to satisfy. It is written against two traits, the register window and the DMA grant, which netstack implements one instruction deep; its host tests drive it with a device written from the specification and with rings no device would write. The order of the initialisation is in the types: only a negotiated device configures a queue, a queue is enabled by the call that gave it its addresses and its vector, and only a live device is notified. What changes for the shipped NIC driver, each from the specification: - Register accesses take the width §4.1.3.1 requires: a byte for device_status, where a 32-bit write also covered config_generation and queue_select, and two 32-bit halves for a queue's addresses. - A used element's length is bounded by its chain's device-writable bytes (§2.7.8), which for a transmitted frame is none. - The available index is the driver's own count and is never read back from memory the device reaches. - A used index past the chains made available is refused, and netstack ends by name on it: nothing in the ring can be told from a stale entry. - A notify offset the notification structure does not hold, and a device field past the device structure, are refusals where they were window assertions. - A device without VIRTIO_F_VERSION_1 is refused; a refusal after the acknowledgement sets FAILED. The frames, the room and wake contract and the outward interface of `VirtioNet` are as they were. The kernel's own transport is not shared: it is written against the kernel's `Mmio` and `Dma`, waits on the kernel's clock, and loses two of its three users to the same plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Mutation patches and their verdictsAt head m01-head-bound.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -345,7 +345,7 @@
let len = Untrusted::new(self.mem.read32(at + USED_ELEM_LEN));
self.last_used = self.last_used.wrapping_add(1);
- let first = id.index(self.slots.len()).map_err(UsedRefusal::Head)?;
+ let first = id.index(usize::MAX).map_err(UsedRefusal::Head)?;
// Exact: `index` proved it below the table's length, a `u16`.
let head = first as u16;
let chain = self.slots[first];m02-written-bound.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -353,7 +353,7 @@
return Err(UsedRefusal::NoChain { head });
}
let written = len
- .at_most(chain.writable as u64)
+ .at_most(u64::MAX)
.map_err(|len| UsedRefusal::Written { head, len })?;
for slot in &mut self.slots[first..first + chain.descs as usize] {
*slot = Slot::default();m03-jump.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -334,9 +334,6 @@
// §2.7.8: an element "matches an entry placed in the available ring by
// the guest earlier", so the device's count never passes the driver's.
let available = self.next_avail.wrapping_sub(self.last_used);
- if pending > available {
- return Err(UsedRefusal::Jumped { used, taken: self.last_used, available: self.next_avail });
- }
self.mem.observe();
let at = self.parts.used
+ RING_ENTRIESm04-no-chain.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -349,9 +349,6 @@
// Exact: `index` proved it below the table's length, a `u16`.
let head = first as u16;
let chain = self.slots[first];
- if chain.descs == 0 {
- return Err(UsedRefusal::NoChain { head });
- }
let written = len
.at_most(chain.writable as u64)
.map_err(|len| UsedRefusal::Written { head, len })?;m05-writable-is-whole-chain.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -309,7 +309,7 @@
self.slots[first + nth].held = true;
}
self.slots[first].descs = chain.len() as u16;
- self.slots[first].writable = writable;
+ self.slots[first].writable = total;
let entry = (self.next_avail % size) as usize;
self.mem.write16(self.parts.avail + RING_ENTRIES + entry * AVAIL_ENTRY_BYTES, head);m06-publish-barrier.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:55
@@ -313,7 +313,6 @@
let entry = (self.next_avail % size) as usize;
self.mem.write16(self.parts.avail + RING_ENTRIES + entry * AVAIL_ENTRY_BYTES, head);
- self.mem.publish();
self.next_avail = self.next_avail.wrapping_add(1);
self.mem.write16(self.parts.avail + RING_IDX, self.next_avail);
self.mem.publish();m07-observe-barrier.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:56
@@ -337,7 +337,6 @@
if pending > available {
return Err(UsedRefusal::Jumped { used, taken: self.last_used, available: self.next_avail });
}
- self.mem.observe();
let at = self.parts.used
+ RING_ENTRIES
+ (self.last_used % self.size) as usize * USED_ELEM_BYTES;m08-outside-bar.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -226,9 +226,6 @@
align: u32,
) -> Result<Self, Refusal> {
// Sixty-four bits, so two device-chosen dwords cannot wrap.
- if cap.offset as u64 + cap.length as u64 > window as u64 {
- return Err(Refusal::OutsideBar(what));
- }
if (cap.length as usize) < need {
return Err(Refusal::TooShort(what));
}m09-outside-bar-wraps.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -226,7 +226,7 @@
align: u32,
) -> Result<Self, Refusal> {
// Sixty-four bits, so two device-chosen dwords cannot wrap.
- if cap.offset as u64 + cap.length as u64 > window as u64 {
+ if cap.offset.wrapping_add(cap.length) as u64 > window as u64 {
return Err(Refusal::OutsideBar(what));
}
if (cap.length as usize) < need {m10-doorbell-bound.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -401,9 +401,6 @@
// product fits sixty-four bits.
let notify_off = self.wires.regs.read16(at + common::QUEUE_NOTIFY_OFF);
let into = notify_off as u64 * self.wires.notify_off_multiplier as u64;
- if into + 2 > self.wires.notify.bytes as u64 || !into.is_multiple_of(2) {
- return Err(self.wires.refuse(Refusal::Doorbell { queue: index, notify_off }));
- }
let doorbell = self.wires.notify.at + into as usize;
self.bind(common::QUEUE_MSIX_VECTOR, entry, Source::Queue(index))?;m11-accepts-unoffered.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -323,7 +323,7 @@
if self.offered & VIRTIO_F_VERSION_1 == 0 {
return Err(self.wires.refuse(Refusal::NotVersion1 { offered: self.offered }));
}
- let features = self.offered & (wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM);
+ let features = wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM;
let wires = &mut self.wires;
let at = wires.common;
wires.regs.write32(at + common::DRIVER_FEATURE_SELECT, 0);m12-device-config-bound.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -415,9 +415,6 @@
/// One byte of the device-specific structure, `at` bytes into it.
pub fn device_read8(&mut self, at: usize) -> Result<u8, Refusal> {
let Region { at: base, bytes } = self.wires.device;
- if at >= bytes {
- return Err(self.wires.refuse(Refusal::PastDeviceConfig { at, bytes }));
- }
Ok(self.wires.regs.read8(base + at))
}
m13-vector-unverified.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -363,9 +363,6 @@
let at = self.wires.common + field;
self.wires.regs.write16(at, entry);
// §4.1.5.1.2.2: "on success, the previously written value is returned".
- if self.wires.regs.read16(at) != entry {
- return Err(self.wires.refuse(Refusal::NoVector(source)));
- }
Ok(())
}
m14-features-ok-unread.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -332,9 +332,6 @@
wires.regs.write32(at + common::DRIVER_FEATURE, (features >> 32) as u32);
wires.set_status(status::FEATURES_OK);
let answered = wires.regs.read8(at + common::DEVICE_STATUS);
- if answered & status::FEATURES_OK == 0 {
- return Err(wires.refuse(Refusal::FeaturesRefused { accepted: features, status: answered }));
- }
Ok(Setup { wires: self.wires, features, doorbells: Vec::new() })
}
}m15-reserved-bar.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -189,7 +189,7 @@
pub fn of(caps: &[VendorCap]) -> Result<Self, Refusal> {
let first = |cfg_type: u8, what: &'static str| {
caps.iter()
- .find(|cap| cap.cfg_type == cfg_type && cap.bar <= LAST_BAR)
+ .find(|cap| cap.cfg_type == cfg_type)
.copied()
.ok_or(Refusal::MissingCap(what))
};m16-shallow-queue.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -382,9 +382,6 @@
self.wires.regs.write16(at + common::QUEUE_SELECT, index);
// The most the device takes, and 0 for a queue it does not have.
let offered = self.wires.regs.read16(at + common::QUEUE_SIZE);
- if offered < wanted {
- return Err(self.wires.refuse(Refusal::QueueTooShallow { queue: index, offered, wanted }));
- }
self.wires.regs.write16(at + common::QUEUE_SIZE, wanted);
for (field, addr) in [
(common::QUEUE_DESC, queue.desc_addr()),m17-reset-unread.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -293,9 +293,6 @@
let at = wires.common;
wires.regs.write8(at + common::DEVICE_STATUS, 0);
- if wires.regs.read8(at + common::DEVICE_STATUS) != 0 {
- return Err(Refusal::ResetUnanswered);
- }
wires.set_status(status::ACKNOWLEDGE);
wires.set_status(status::DRIVER);
m18-version-1.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -320,9 +320,6 @@
/// wherever it is offered. Nothing the device did not offer is written
/// (§2.2.1).
pub fn accept(mut self, wanted: u64) -> Result<Setup<R>, Refusal> {
- if self.offered & VIRTIO_F_VERSION_1 == 0 {
- return Err(self.wires.refuse(Refusal::NotVersion1 { offered: self.offered }));
- }
let features = self.offered & (wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM);
let wires = &mut self.wires;
let at = wires.common;m19-too-short.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -229,9 +229,6 @@
if cap.offset as u64 + cap.length as u64 > window as u64 {
return Err(Refusal::OutsideBar(what));
}
- if (cap.length as usize) < need {
- return Err(Refusal::TooShort(what));
- }
if !cap.offset.is_multiple_of(align) {
return Err(Refusal::Misaligned(what));
}m20-misaligned.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -232,9 +232,6 @@
if (cap.length as usize) < need {
return Err(Refusal::TooShort(what));
}
- if !cap.offset.is_multiple_of(align) {
- return Err(Refusal::Misaligned(what));
- }
Ok(Self { at: cap.offset as usize, bytes: cap.length as usize })
}
}m21-split-bars.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -197,9 +197,6 @@
let notify = first(CFG_NOTIFY, "NOTIFY_CFG")?;
let isr = first(CFG_ISR, "ISR_CFG")?;
let device = first(CFG_DEVICE, "DEVICE_CFG")?;
- if [notify, isr, device].iter().any(|cap| cap.bar != common.bar) {
- return Err(Refusal::SplitAcrossBars);
- }
Ok(Self { common, notify, device })
}
m22-status-32bit.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:56
@@ -253,7 +253,7 @@
impl<R: Registers> Wires<R> {
fn set_status(&mut self, bit: u8) {
self.status |= bit;
- self.regs.write8(self.common + common::DEVICE_STATUS, self.status);
+ self.regs.write32(self.common + common::DEVICE_STATUS, self.status as u32);
}
/// Give the device up (§3.1.1), and hand the reason back.m23-enable-before-vector.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 12:30:57
+++ b/toyos-virtio/src/pci.rs 2026-10-09 12:30:57
@@ -406,8 +406,8 @@
}
let doorbell = self.wires.notify.at + into as usize;
- self.bind(common::QUEUE_MSIX_VECTOR, entry, Source::Queue(index))?;
self.wires.regs.write16(at + common::QUEUE_ENABLE, 1);
+ self.bind(common::QUEUE_MSIX_VECTOR, entry, Source::Queue(index))?;
self.doorbells.push((index, doorbell));
Ok(())
}m24-in-flight-overlap.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 12:30:57
+++ b/toyos-virtio/src/queue.rs 2026-10-09 12:30:57
@@ -280,11 +280,6 @@
let mut writable = 0u32;
for (nth, buffer) in chain.iter().enumerate() {
assert!(
- !self.slots[first + nth].held,
- "virtqueue {queue}: a chain at head {head} takes descriptor {}, which is in flight",
- first + nth
- );
- assert!(
buffer.writable || writable == 0,
"virtqueue {queue}: the chain at head {head} has a device-readable element after \
a device-writable one"m25-netstack-jump-not-fatal.patch--- a/userland/netstack/src/virtio_net.rs 2026-10-09 12:30:57
+++ b/userland/netstack/src/virtio_net.rs 2026-10-09 12:30:57
@@ -224,9 +224,7 @@
Err(
UsedRefusal::Head(_) | UsedRefusal::NoChain { .. } | UsedRefusal::Written { .. },
) => *refused = refused.saturating_add(1),
- Err(why @ UsedRefusal::Jumped { .. }) => {
- panic!("netstack: this NIC cannot be driven on — {why}")
- }
+ Err(UsedRefusal::Jumped { .. }) => return None,
}
}
} |
Review, round 1, at
|
…ng refusal ends netstack's driver; a refused Setup is gone The owner's ruling, 2026-10-09. Asked "Userland drivers reach their device through a small interface (read/write registers, DMA buffers, two memory barriers). The Intel NIC crate has its own copy and the new virtio crate added a second, identical one. An issue in the tree reserves this call for you: one shared interface, or one per driver crate?", he answered: "One shared interface (Recommended)". toyos-device-memory declares it: `Registers`, `DmaBuffers` and the order of the two barriers, stated once. The widths are in the method names, each one an access a driver in the tree makes. toyos-i219 and toyos-virtio are written against it and their own declarations are gone; the Intel crate's change is its accesses renamed to the width they always had (32-bit registers, 64-bit descriptor halves), and its model now reds on any other. netstack implements the boundary once, in device.rs, over `toyos::volatile::Window`, so the two fences are one body; a driver's holder keeps the mappings, in the order the Intel wrapper dropped them before. `Clock` and `Interrupts` stay toyos-i219's: one driver needs them. issues/a-userland-device-driver-has-no-shared-boundary-to-be-written-against.md is closed by that: its exit was the ruling and a second driver written against the answer. netstack ends on each of the four `UsedRefusal`s, by the refusal's own words. The branch's queue made a counted drop a deferred failure: the used index may not pass the available one and a refused element is taken, so each one spent an element a chain in flight was owed, and a `Written` on a transmit chain kept its head for the boot. `rx_refused`, `TxQueue::refused`, `reported` and `VirtioNet::report` go with it. What ending costs is issues/netstack-ends-for-the-boot-on-a-nic-it-cannot-drive-on.md. `Setup`'s steps take it by value and a refusal does not give it back, so `DRIVER_OK` cannot be written over `FAILED`. `NO_VECTOR` is the stub's. Filed: the capability walk's `unwrap_or(0)` and unmasked links, owned by the second client, which moves the walk into the crate; stage 1 of issues/every-driver-is-still-in-the-kernel.md says so, and that QEMU's `len` on a device-readable chain is measured before a client ends on it there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Mutation patches and their verdicts, round 2At head m01 to m24 are round 1's, written again against this head because both of their files changed. m25 to m28 are the review's: each of the four m01-head-bound.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
@@ -349,7 +349,7 @@
let len = Untrusted::new(self.mem.read32(at + USED_ELEM_LEN));
self.last_used = self.last_used.wrapping_add(1);
- let first = id.index(self.slots.len()).map_err(UsedRefusal::Head)?;
+ let first = id.index(usize::MAX).map_err(UsedRefusal::Head)?;
// Exact: `index` proved it below the table's length, a `u16`.
let head = first as u16;
let chain = self.slots[first];m02-written-bound.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
@@ -357,7 +357,7 @@
return Err(UsedRefusal::NoChain { head });
}
let written = len
- .at_most(chain.writable as u64)
+ .at_most(u64::MAX)
.map_err(|len| UsedRefusal::Written { head, len })?;
for slot in &mut self.slots[first..first + chain.descs as usize] {
*slot = Slot::default();m03-jump.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
@@ -338,9 +338,6 @@
// §2.7.8: an element "matches an entry placed in the available ring by
// the guest earlier", so the device's count never passes the driver's.
let available = self.next_avail.wrapping_sub(self.last_used);
- if pending > available {
- return Err(UsedRefusal::Jumped { used, taken: self.last_used, available: self.next_avail });
- }
self.mem.observe();
let at = self.parts.used
+ RING_ENTRIESm04-no-chain.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
@@ -353,9 +353,6 @@
// Exact: `index` proved it below the table's length, a `u16`.
let head = first as u16;
let chain = self.slots[first];
- if chain.descs == 0 {
- return Err(UsedRefusal::NoChain { head });
- }
let written = len
.at_most(chain.writable as u64)
.map_err(|len| UsedRefusal::Written { head, len })?;m05-writable-is-whole-chain.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:29
@@ -313,7 +313,7 @@
self.slots[first + nth].held = true;
}
self.slots[first].descs = chain.len() as u16;
- self.slots[first].writable = writable;
+ self.slots[first].writable = total;
let entry = (self.next_avail % size) as usize;
self.mem.write16(self.parts.avail + RING_ENTRIES + entry * AVAIL_ENTRY_BYTES, head);m06-publish-barrier.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
@@ -317,7 +317,6 @@
let entry = (self.next_avail % size) as usize;
self.mem.write16(self.parts.avail + RING_ENTRIES + entry * AVAIL_ENTRY_BYTES, head);
- self.mem.publish();
self.next_avail = self.next_avail.wrapping_add(1);
self.mem.write16(self.parts.avail + RING_IDX, self.next_avail);
self.mem.publish();m07-observe-barrier.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
@@ -341,7 +341,6 @@
if pending > available {
return Err(UsedRefusal::Jumped { used, taken: self.last_used, available: self.next_avail });
}
- self.mem.observe();
let at = self.parts.used
+ RING_ENTRIES
+ (self.last_used % self.size) as usize * USED_ELEM_BYTES;m08-outside-bar.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -226,9 +226,6 @@
align: u32,
) -> Result<Self, Refusal> {
// Sixty-four bits, so two device-chosen dwords cannot wrap.
- if cap.offset as u64 + cap.length as u64 > window as u64 {
- return Err(Refusal::OutsideBar(what));
- }
if (cap.length as usize) < need {
return Err(Refusal::TooShort(what));
}m09-outside-bar-wraps.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -226,7 +226,7 @@
align: u32,
) -> Result<Self, Refusal> {
// Sixty-four bits, so two device-chosen dwords cannot wrap.
- if cap.offset as u64 + cap.length as u64 > window as u64 {
+ if cap.offset.wrapping_add(cap.length) as u64 > window as u64 {
return Err(Refusal::OutsideBar(what));
}
if (cap.length as usize) < need {m10-doorbell-bound.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -404,9 +404,6 @@
// product fits sixty-four bits.
let notify_off = self.wires.regs.read16(at + common::QUEUE_NOTIFY_OFF);
let into = notify_off as u64 * self.wires.notify_off_multiplier as u64;
- if into + 2 > self.wires.notify.bytes as u64 || !into.is_multiple_of(2) {
- return Err(self.wires.refuse(Refusal::Doorbell { queue: index, notify_off }));
- }
let doorbell = self.wires.notify.at + into as usize;
let mut bound = self.bind(common::QUEUE_MSIX_VECTOR, entry, Source::Queue(index))?;m11-accepts-unoffered.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -323,7 +323,7 @@
if self.offered & VIRTIO_F_VERSION_1 == 0 {
return Err(self.wires.refuse(Refusal::NotVersion1 { offered: self.offered }));
}
- let features = self.offered & (wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM);
+ let features = wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM;
let wires = &mut self.wires;
let at = wires.common;
wires.regs.write32(at + common::DRIVER_FEATURE_SELECT, 0);m12-device-config-bound.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -418,9 +418,6 @@
/// One byte of the device-specific structure, `at` bytes into it.
pub fn device_read8(mut self, at: usize) -> Result<(Self, u8), Refusal> {
let Region { at: base, bytes } = self.wires.device;
- if at >= bytes {
- return Err(self.wires.refuse(Refusal::PastDeviceConfig { at, bytes }));
- }
let byte = self.wires.regs.read8(base + at);
Ok((self, byte))
}m13-vector-unverified.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -366,9 +366,6 @@
let at = self.wires.common + field;
self.wires.regs.write16(at, entry);
// §4.1.5.1.2.2: "on success, the previously written value is returned".
- if self.wires.regs.read16(at) != entry {
- return Err(self.wires.refuse(Refusal::NoVector(source)));
- }
Ok(self)
}
m14-features-ok-unread.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -332,9 +332,6 @@
wires.regs.write32(at + common::DRIVER_FEATURE, (features >> 32) as u32);
wires.set_status(status::FEATURES_OK);
let answered = wires.regs.read8(at + common::DEVICE_STATUS);
- if answered & status::FEATURES_OK == 0 {
- return Err(wires.refuse(Refusal::FeaturesRefused { accepted: features, status: answered }));
- }
Ok(Setup { wires: self.wires, features, doorbells: Vec::new() })
}
}m15-reserved-bar.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -189,7 +189,7 @@
pub fn of(caps: &[VendorCap]) -> Result<Self, Refusal> {
let first = |cfg_type: u8, what: &'static str| {
caps.iter()
- .find(|cap| cap.cfg_type == cfg_type && cap.bar <= LAST_BAR)
+ .find(|cap| cap.cfg_type == cfg_type)
.copied()
.ok_or(Refusal::MissingCap(what))
};m16-shallow-queue.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -385,9 +385,6 @@
self.wires.regs.write16(at + common::QUEUE_SELECT, index);
// The most the device takes, and 0 for a queue it does not have.
let offered = self.wires.regs.read16(at + common::QUEUE_SIZE);
- if offered < wanted {
- return Err(self.wires.refuse(Refusal::QueueTooShallow { queue: index, offered, wanted }));
- }
self.wires.regs.write16(at + common::QUEUE_SIZE, wanted);
for (field, addr) in [
(common::QUEUE_DESC, queue.desc_addr()),m17-reset-unread.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -293,9 +293,6 @@
let at = wires.common;
wires.regs.write8(at + common::DEVICE_STATUS, 0);
- if wires.regs.read8(at + common::DEVICE_STATUS) != 0 {
- return Err(Refusal::ResetUnanswered);
- }
wires.set_status(status::ACKNOWLEDGE);
wires.set_status(status::DRIVER);
m18-version-1.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -320,9 +320,6 @@
/// wherever it is offered. Nothing the device did not offer is written
/// (§2.2.1).
pub fn accept(mut self, wanted: u64) -> Result<Setup<R>, Refusal> {
- if self.offered & VIRTIO_F_VERSION_1 == 0 {
- return Err(self.wires.refuse(Refusal::NotVersion1 { offered: self.offered }));
- }
let features = self.offered & (wanted | VIRTIO_F_VERSION_1 | VIRTIO_F_ACCESS_PLATFORM);
let wires = &mut self.wires;
let at = wires.common;m19-too-short.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -229,9 +229,6 @@
if cap.offset as u64 + cap.length as u64 > window as u64 {
return Err(Refusal::OutsideBar(what));
}
- if (cap.length as usize) < need {
- return Err(Refusal::TooShort(what));
- }
if !cap.offset.is_multiple_of(align) {
return Err(Refusal::Misaligned(what));
}m20-misaligned.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -232,9 +232,6 @@
if (cap.length as usize) < need {
return Err(Refusal::TooShort(what));
}
- if !cap.offset.is_multiple_of(align) {
- return Err(Refusal::Misaligned(what));
- }
Ok(Self { at: cap.offset as usize, bytes: cap.length as usize })
}
}m21-split-bars.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -197,9 +197,6 @@
let notify = first(CFG_NOTIFY, "NOTIFY_CFG")?;
let isr = first(CFG_ISR, "ISR_CFG")?;
let device = first(CFG_DEVICE, "DEVICE_CFG")?;
- if [notify, isr, device].iter().any(|cap| cap.bar != common.bar) {
- return Err(Refusal::SplitAcrossBars);
- }
Ok(Self { common, notify, device })
}
m22-status-32bit.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -253,7 +253,7 @@
impl<R: Registers> Wires<R> {
fn set_status(&mut self, bit: u8) {
self.status |= bit;
- self.regs.write8(self.common + common::DEVICE_STATUS, self.status);
+ self.regs.write32(self.common + common::DEVICE_STATUS, self.status as u32);
}
/// Give the device up (§3.1.1), and hand the reason back.m23-enable-before-vector.patch--- a/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/pci.rs 2026-10-09 13:22:30
@@ -409,8 +409,8 @@
}
let doorbell = self.wires.notify.at + into as usize;
+ self.wires.regs.write16(at + common::QUEUE_ENABLE, 1);
let mut bound = self.bind(common::QUEUE_MSIX_VECTOR, entry, Source::Queue(index))?;
- bound.wires.regs.write16(at + common::QUEUE_ENABLE, 1);
bound.doorbells.push((index, doorbell));
Ok(bound)
}m24-in-flight-overlap.patch--- a/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
+++ b/toyos-virtio/src/queue.rs 2026-10-09 13:22:30
@@ -284,11 +284,6 @@
let mut writable = 0u32;
for (nth, buffer) in chain.iter().enumerate() {
assert!(
- !self.slots[first + nth].held,
- "virtqueue {queue}: a chain at head {head} takes descriptor {}, which is in flight",
- first + nth
- );
- assert!(
buffer.writable || writable == 0,
"virtqueue {queue}: the chain at head {head} has a device-readable element after \
a device-writable one"m25-netstack-head-counted-not-fatal.patch--- a/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
+++ b/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
@@ -130,9 +130,17 @@
/// writable ones, and an index past what was made available. Nothing drives
/// this NIC from there, and this dies where it can be read.
fn finished(rings: &mut Virtqueue<Grant>) -> Option<Used> {
- rings
- .poll_used()
- .unwrap_or_else(|why| panic!("netstack: this NIC cannot be driven on — {why}"))
+ let mut refused = 0u32;
+ loop {
+ match rings.poll_used() {
+ Ok(done) => return done,
+ Err(toyos_virtio::queue::UsedRefusal::Head(_)) => {
+ refused = refused.saturating_add(1);
+ continue;
+ }
+ Err(why) => panic!("netstack: this NIC cannot be driven on — {why}"),
+ }
+ }
}
/// The bound the capability walk rests on, asked once before the walk.m26-netstack-no-chain-counted-not-fatal.patch--- a/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
+++ b/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
@@ -130,9 +130,17 @@
/// writable ones, and an index past what was made available. Nothing drives
/// this NIC from there, and this dies where it can be read.
fn finished(rings: &mut Virtqueue<Grant>) -> Option<Used> {
- rings
- .poll_used()
- .unwrap_or_else(|why| panic!("netstack: this NIC cannot be driven on — {why}"))
+ let mut refused = 0u32;
+ loop {
+ match rings.poll_used() {
+ Ok(done) => return done,
+ Err(toyos_virtio::queue::UsedRefusal::NoChain { .. }) => {
+ refused = refused.saturating_add(1);
+ continue;
+ }
+ Err(why) => panic!("netstack: this NIC cannot be driven on — {why}"),
+ }
+ }
}
/// The bound the capability walk rests on, asked once before the walk.m27-netstack-written-counted-not-fatal.patch--- a/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
+++ b/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
@@ -130,9 +130,17 @@
/// writable ones, and an index past what was made available. Nothing drives
/// this NIC from there, and this dies where it can be read.
fn finished(rings: &mut Virtqueue<Grant>) -> Option<Used> {
- rings
- .poll_used()
- .unwrap_or_else(|why| panic!("netstack: this NIC cannot be driven on — {why}"))
+ let mut refused = 0u32;
+ loop {
+ match rings.poll_used() {
+ Ok(done) => return done,
+ Err(toyos_virtio::queue::UsedRefusal::Written { .. }) => {
+ refused = refused.saturating_add(1);
+ continue;
+ }
+ Err(why) => panic!("netstack: this NIC cannot be driven on — {why}"),
+ }
+ }
}
/// The bound the capability walk rests on, asked once before the walk.m28-netstack-jumped-counted-not-fatal.patch--- a/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
+++ b/userland/netstack/src/virtio_net.rs 2026-10-09 13:22:30
@@ -130,9 +130,17 @@
/// writable ones, and an index past what was made available. Nothing drives
/// this NIC from there, and this dies where it can be read.
fn finished(rings: &mut Virtqueue<Grant>) -> Option<Used> {
- rings
- .poll_used()
- .unwrap_or_else(|why| panic!("netstack: this NIC cannot be driven on — {why}"))
+ let mut refused = 0u32;
+ loop {
+ match rings.poll_used() {
+ Ok(done) => return done,
+ Err(toyos_virtio::queue::UsedRefusal::Jumped { .. }) => {
+ refused = refused.saturating_add(1);
+ return None;
+ }
+ Err(why) => panic!("netstack: this NIC cannot be driven on — {why}"),
+ }
+ }
}
/// The bound the capability walk rests on, asked once before the walk.m29-i219-descriptor-read-as-32-bits.patch--- a/toyos-i219/src/lib.rs 2026-10-09 13:22:30
+++ b/toyos-i219/src/lib.rs 2026-10-09 13:22:30
@@ -1396,7 +1396,7 @@
fn reclaim_tx(&mut self) {
while self.tx_clean != self.tx_next {
let at = OFF_TX_RING + self.tx_clean * tx_desc::BYTES;
- let word = self.dma.read64(at + 8);
+ let word = self.dma.read32(at + 8) as u64;
let status = ((word >> tx_desc::STATUS_SHIFT) & tx_desc::STATUS_MASK) as u8;
if status & tx_desc::STATUS_DD == 0 {
return;m30-i219-register-written-as-16-bits.patch--- a/toyos-i219/src/lib.rs 2026-10-09 13:22:30
+++ b/toyos-i219/src/lib.rs 2026-10-09 13:22:30
@@ -1385,7 +1385,7 @@
// tail pointer", so the descriptor and the frame both have to be
// visible before the tail moves over them.
self.dma.publish();
- self.regs.write32(regs::TDT, self.tx_next as u32);
+ self.regs.write16(regs::TDT, self.tx_next as u16);
}
/// Take back every transmit descriptor the device has written back. |
Answer to the round-1 review, at
|
|
The T14 on the Intel driver written against the shared boundary (the orchestrator's reading). One boot of the outbound image staged from the measurement branch
SAME: every datagram taken, every descriptor sent reached the wire, none stranded, no refusal line, no anomaly line about frames left in the ring. |
Review, round 2, at
|
…e driver to their order The Intel model's `publish` and `observe` were empty, so no test in the crate could fail on the order its header states. Three mutations at 459e1f3, each alone, stayed green at 88 of 88: `publish` deleted before `TDT`, before `RDT`, and `observe` deleted in receive between the two loads of a descriptor. The model now records every store, load and barrier the driver makes on the grant and every register it writes, as `toyos-virtio`'s stub does. Two tests read that trace over frames moved both ways on both parts: a tail register is written only after a `publish` that follows the last store into the grant, and after the load that first finds `DD` in a descriptor nothing is loaded before an `observe`. Test files only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…load of the device's answer The contract stated two orders and was silent on the third a ring driver meets once it reads a suppression word or accepts VIRTIO_F_EVENT_IDX. Neither driver written against the boundary makes that pair, so the contract says who adds the method. A doc comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Round 3: answer to the round-2 review, at
|
| Gate | Exit |
|---|---|
cargo test -p toyos-i219: 90 passed |
0 |
cargo test -p toyos-virtio -p toyos-device-memory: 33 and 0 passed |
0 |
cargo test in userland/netstack: 30 passed |
0 |
cargo run -- --ci host: 78 steps, all green |
0 |
cargo run -- --build-only |
0 |
No guest run at this head: the merge brought no netstack source. Round 2's guest runs are at 459e1f3db and the body says so.
The patches
m31-i219-no-publish-before-tdt.patch
--- a/toyos-i219/src/lib.rs
+++ b/toyos-i219/src/lib.rs
@@ -1384,7 +1384,6 @@
// §7.2.4.1: "The 82574 NEVER fetches descriptors beyond the descriptor
// tail pointer", so the descriptor and the frame both have to be
// visible before the tail moves over them.
- self.dma.publish();
self.regs.write32(regs::TDT, self.tx_next as u32);
}
m32-i219-no-publish-before-rdt.patch
--- a/toyos-i219/src/lib.rs
+++ b/toyos-i219/src/lib.rs
@@ -1303,7 +1303,6 @@
self.rx_tail = tail;
// §7.1.8: the device fetches on the tail write, so the descriptors have
// to be there before the write is.
- self.dma.publish();
self.regs.write32(regs::RDT, tail as u32);
}
m33-i219-no-observe-in-receive.patch
--- a/toyos-i219/src/lib.rs
+++ b/toyos-i219/src/lib.rs
@@ -1232,7 +1232,6 @@
}
// §7.1.7.1: the descriptor was written back in a batch, so its
// other fields are read only after the load that found `DD`.
- self.dma.observe();
let word = self.dma.read64(at + 8);
self.rx_next = (index + 1) % RX_RING;
self.rx_budget -= 1;m34-i219-no-observe-in-reclaim.patch
--- a/toyos-i219/src/lib.rs
+++ b/toyos-i219/src/lib.rs
@@ -1401,7 +1401,6 @@
if status & tx_desc::STATUS_DD == 0 {
return;
}
- self.dma.observe();
self.tx_clean = (self.tx_clean + 1) % TX_RING;
self.counters.sent = self.counters.sent.saturating_add(1);
// Stranded descriptors are the ring's oldest, so each one taken
Review, round 3, at
|
|
CI at
|
…Me (#797), into the one-boot merge No conflict. In tests/toyos.rs main's two hunks are the `nvme_disk_keeps_log_and_home` machine test's row in MACHINE_TESTS and its arm and function after `run_machine_test`, both guest tests and neither in the METAL table or near `shared_metal`. tests/common/qemu.rs moved on main only; this branch does not touch it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…d the installed NVMe image (#797), into the IORT decode and the SMMUv3 and ITS encodings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…796), into the entropy stage No hunk conflicted. The Headless shape main brings (one NVMe disk, no stick) already carries `rng: false`, and no Shape literal it adds lacks the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…nd the two debug timing rows ride shared-debug: 23 boots to 20 (#799) Stage 3b of step B of the one-boot plan for the T14's metal rows: merge boots. Stage 1 was #785, stage 2 #789, stage 3a #794. Head `01fd3aedd`, which holds `main` at `558283168` (#797, with #796 under it) by a merge with no conflict: in `tests/toyos.rs` `main`'s two hunks are the `nvme_disk_keeps_log_and_home` machine test's row in `MACHINE_TESTS` and its arm and function after `run_machine_test`, guest tests both, neither in the `METAL` table nor near `shared_metal`; `tests/common/qemu.rs` moved on `main` alone, and this branch does not touch it. `--metal --list` is unchanged by it. `--metal --list` before (`965e62bb1`): 61 registrations and 232 shared members over **23** boots. After: 61 and 232 over **20**. `shared`, `ccorpus` and `testcases-debug` are gone; no test is. **The T14 read `2c7e1be1a`: 266 passed, 1 failed.** The red was this merge's own: see the first decision. This head is staged and owed a reading: see "The T14". ## What changed, per decision - **`endowment_denied` holds a link against toybox's policy table only where the link's target has a `[programs]` row.** The red at `2c7e1be1a`: `"/system/bin/134_double_to_signed" is a link to something else: a second multicall binary needs its own policy table, not this one`, after every earlier phase passed. The member walks `/system/bin` and asserted every link lands on `/system/bin/toybox`; the merged ROOT carries the C corpus, whose 137 cases are links to the comparator `test_rs_ccheck`, which reads `argv[0]` so the kernel records each run under the case's name. On `shared`'s own ROOT there were none. **The walk was too wide, and the corpus is not a second multicall binary in the sense the claim is about.** What a link carries is its target's row: the supervisor's `declared` follows one link and matches a row by its whole path, and a target no row names is answered `NotDeclared`, which the caller spawns itself with what it holds; the build's `unnamed_program` says the same of every harness binary ("a harness binary has none, holding only what its spawner moved in"), and `test-runner` spawns every job directly in any case. A policy table limits what a row grants; a binary with no row is granted nothing by one. So the walk skips a link whose target the image's manifest names no row for, matched by the whole path as the supervisor matches it (its own parse of the manifest, as before), and a second binary that *does* have a row behind a link reds as before. The applets line counts the links it passed over. The corpus's shape is unchanged: one comparator under one link per case is what lets a stick say which case failed. Nothing of the endowment policy for C programs is decided here: the corpus's cases hold what `test-runner` gives every job, as on `ccorpus`' own boot. - **`shared` and `ccorpus` ride `testcases`.** Its list is the ten jobs its rows name, then the 85 discovered Rust binaries, then the 137 C cases, then the three bounds programs (`LAST_MEMBERS`), then `reboot`: 234 jobs, since `fault_gates` is a row's job and a member and runs once, among the rows'. `c_corpus_metal` gives the corpus's part of that boot and `shared_metal` puts the Rust members around it. - **`testcases-debug` rides `shared-debug`.** `tlb_shootdown_waits` and `trace_record_cost` name that boot, its kernel and its parameters, and their two jobs run before its seven members. - **A member says what it adds to its list's bound** (`metal::Member`), where a shared boot said one number for all of its members: one boot now carries Rust tests at 300 ms and C cases at 100. - **A boot's last job ends its rows' jobs, and the members run behind it.** This reverses what stage 3a built. `acpi_hold` gives up 54 000 ms after boot (`UNTIL_MS`); behind the members it would have started 51.8 to 52.5 s in by the readings before the merge. On the T14 at `2c7e1be1a`, before them, it started 43 095 ms into the kernel's clock with 10.9 s left and exited 0. It also keeps the three bounds programs the last jobs of the boot. **The compromise under it is filed, not fixed**: `issues/acpi-hold-gives-up-at-a-time-counted-from-boot-whatever-its-runner-was-given.md`, owner the harness, exit the hold bounded from its own start or by the bound its runner was given; it now carries the T14's start too. - **Two jobs of one boot the kernel records under one name are refused**, in `batches`, for every boot. The C corpus checked its own cases; the merged list puts Rust members, C cases and rows' jobs under one log, so the check is the boot's now and the corpus's own is deleted. - **The judge prints a shared boot's members' time summed between each member's own markers**, against what they add to the bound, and says over how many: `its members took <ms> ms of the <ms> ms they add to the list's bound, summed over the <n> of <all> with both markers`, and a line `<k> without a start and an end marker, the first <job>` only where there is one. It is a line a reader reads and reds nothing; no host test holds it. On the T14: `9253 ms of the 40100 ms … summed over the 225 of 225`. - **The record rows of `shared`, `ccorpus` and `testcases-debug` are deleted** from the machine's record under `tests/metal/`. The three `testcases-window` rows stay: `issues/the-t14s-record-keeps-three-rows-of-a-boot-nothing-stages.md` owns them and half its exit. - **The allowance issue's exit is three parts**, by the orchestrator's ruling on the round-1 review: (1) each allowance derived from the members' sum between their own markers by one stated factor, the judge redding a boot whose members' sum is past that share; (2) what a boot's rows' jobs are given derived the same way from what they take, the judge redding a boot whose rows' jobs end past their share; (3) `testcases` reading both inside, the pair from the kernel's own lines, the margin to the list bound beside them. None of it is built and the issue stays open. Its present numbers are now the T14's reading of the merged boot, beside the sum of the three boots it was. ## What the merged boots arm `--metal --list` and the staging's own lines at `01fd3aedd`; the wait is `metal::return_secs`' arithmetic. The T14 read the same pairs off the kernel's own lines at `2c7e1be1a`. | boot | jobs | `--bound-ms=` | `boot-deadline=` | hard-lockup bound | `toyos-metal` waits | |---|---|---|---|---|---| | `testcases` | 234 | 100 100 (`main`: 60 000) | 200 200 (`main`: 120 000) | 100 100 (`main`: 60 000) | 501 s (`main`: 420) | | `shared-debug` | 9 (`main`: 7) | 62 100, unchanged | 124 200, unchanged | 62 100, unchanged | 425 s, unchanged | 100 100 is 60 000 for the rows' jobs, 88 members at 300 and 137 at 100. No other boot's list, bound or deadline changes. ## The merged list against its bound, as the T14 measured it `testcases` at `2c7e1be1a`, each part between its own markers, the kernel's clock counted from its `Boot: complete (1135ms)` line; beside it the sum of the three boots it was, three readings each (`testcases` at `9e70cd2e3`, `473efea22`, `accbd79dd`; `shared` and `ccorpus` at `eff8b20ee`, `d6d008e88`, `49e12f23b`). | part | merged, `2c7e1be1a` | its parts apart | its share of the bound | |---|---|---|---| | the boot, to its first job | 1 191 ms | 1 190 to 1 196 ms | | | the rows' ten jobs | 41 924 ms, ending 43 115 ms in | 41 913 to 41 983 ms | 60 000 ms | | the 225 members | 9 253 ms | 8 735 to 9 298 ms | 40 100 ms | | the list's last record | 52 352 ms | 51 838 to 52 477 ms | 100 100 ms | Read per part, as `issues/a-shared-members-allowance-sets-the-kernels-deadline-at-several-times-the-work.md`'s exit reads: the members at about 4.3 times their work, the rows' jobs at about 1.4 times theirs (`counters_metal` alone 32.7 s), the last record 47.7 s inside its bound, read by nothing. No part of that exit is met and none is built: the issue stays open. Behind 42 s of rows' jobs the members took 9 253 ms, inside what they took alone. The loader at `2c7e1be1a`: ROOT read 9 301 ms, loader 13 069 ms, as the measured rate predicted (30.5 ms/MiB to read, 7.1 to hash, a ROOT partition of 305 MiB), under the firmware's 60 s. `shared-debug`: 176 ms of members over 7 of 7, its last record 1 754 ms of 62 100 ms. ## Why each row can share its boot Rows' jobs run first, so every row's own jobs see the machine `testcases` gave them at stage 1: the same config, the same prefix, one job at a time. What changes is what stands in the log, the page and the census behind them. By what each judge reads: | rows | what the judge reads | why 225 programs behind it cannot move it | |---|---|---| | `blackbox_unclaimed_page`, `control_regs`, `ioapic_topology`, `klogd_hosted`, `smp_roster_and_tsc_trail`, `pmm_accounting`, `acpi_table_inventory`, `timer_calibration`, `pci_inventory`, `loader_watchdog_arms`' control arm | the loader's file and kernel records written before the first job | a kernel record is the kernel's (`Readback::kernel` drops every program's line), and these are said at boot | | `wake_storm_cost`, `syscall_cost` | the job's exit record, and `syscall_cost`'s own two lines | the first and fifth jobs of the list; `exit_code` takes the lowest pid of a name, and a row's job is spawned before any member | | `audio_idle_suspend`, `hda_tone`, `hda_client_stall`, `shipped_client_departures` | `job_window`: the log from the job's spawn to its sessions' end, or to the next spawn | each window closes before the hold. Two reads are boot-wide: no null sink, said at soundserver's start, and no `repeated completion for free buffer`. The second gains no stream: no member opens one on soundserver. `cpal_drop_unreleased` is its own stream server (its header: "No sound is played and soundserver is not involved"), and in the three `shared` readbacks soundserver says nothing after its start | | `counters` | `counters_metal`'s own lines by their `counters_metal <phase>:` head; the idle second by stamp; no `acpi: legacy mode again` in the boot | no member prints that head (none in six readings of the member boots); the idle second is inside the job, which ends before the first member starts; legacy mode returns only when the `acpi` claim's holder goes, and no member holds or kills it (none in six readings) | | `acpi_server_events`, `acpi_tables_loaded` | lines whose tag is `acpiserver`, and boot records | a member's line carries the runner's tag. The last count line is read with `rfind`: a boot that passes the server's next interval prints a later one, also a count | | `claim_reuses_its_remapping_entry` | every hand-over and release of the I219, and every `iommu: irte` record whose source is it, counted: two and two | no member claims a PCI function: `pci_reclaim` is on `RUST_SKIP`, and six readings of the member boots carry no `handed over on slot` | | `domain_ends_below_the_host_bridges` | every `iommu: domain` record of the boot | a domain a member's boot made is held to the same windows; judged over the member boots' readbacks below | | `crash_report_reads_no_kernel_memory` | three refusals `fault_gates` stages, and no report line anywhere in the boot that carries a kernel address's contents | members fault on purpose, and their reports are now read too: a leak in any of them is the defect. Judged over `shared`'s readback below | | `irq_census_conservation` | the stop's census, off the page: every device delivery on cpu0, every shootdown IPI counted by its issuer | the census is the whole boot's, members included, and the rule is conservation, not a count. Judged over the member boots' readbacks below | | `tlb_shootdown_waits` (to `shared-debug`) | its exit | every wait it asserts is a floor (`elapsed >= FLOOR_NANOS`), which no load shortens. The eleven self-tests run at init and on the first syscall of the boot (`task_probes`, once), before any job | | `trace_record_cost` (to `shared-debug`) | its exit and one printed line | a million records written by one syscall with interrupts closed; the number is printed and not recorded. On that boot each syscall pays one atomic swap in `task_probes`; the flood is one syscall | **The boot-wide judges, run over the member boots a machine has already answered.** The `shared` and `ccorpus` readbacks of #794's third reading, relabelled `testcases` and judged at `3055ab0d1` (`--metal --metal-readback <dir>` with thirteen row names): | readback | exit | | |---|---|---| | `shared`'s 88 members | 0 | 13 passed, `irq_census_conservation` (5 196 shootdowns, every delivery accounted for), `domain_ends_below_the_host_bridges`, `acpi_tables_loaded` and `crash_report_reads_no_kernel_memory` among them | | `ccorpus`' 137 cases | 1 | 12 passed; `crash_report_reads_no_kernel_memory` red for its premise, since `fault_gates` did not run on that boot | And the old readbacks under the new profile (`boot:testcases boot:shared-debug` over stage 1's `testcases` and #794's `shared-debug`): exit 1, every `testcases` row and every self-test row PASS, 224 members and the two moved rows red as never run, which is what those logs hold. `shared-debug` reads `its members took 176 ms of the 2100 ms they add`, the sum taken by hand above. **The numbers each boot measures** are the boot's own (`boot.testcases.complete_ms`, `panel_us`, `panel_max_us`): the kernel's time to `Boot: complete`, before any job, and the panel's census, which painted 10 times on `testcases`, `shared` and `ccorpus` alike. No row riding these two boots records a number of its own. **No row kept a boot for sharing's sake.** `mkdir_cap` and `readdir_bound` keep theirs until stage 3c. ## The members that read machine-wide state The red was a member reading something the merged ROOT changed. Each other member whose source reads state another program moves, by `rg` over `tests/toyos-rust-tests/src/bin` for directory walks, roster reads (`SYS_SYSINFO` entries), the memory header and the log: | member | what it reads | why the merged ROOT or population cannot move its verdict | |---|---|---| | `std_fs` | `/system/bin`'s listing | asserts it non-empty and its own binary in it: more entries cannot red it | | `hierarchy_paths` | `/`'s listing against `ROOT_ENTRIES` | `/` is the mount table, not ROOT's content; the corpus's `expect/` and binaries land under `/system` | | `toybox_file_tools` | its scratch directory for `.part` names; `/home` for one name | each keyed on names it wrote itself ("other tests write there in the same boot") | | `empty_dir_stat` | `/tmp/empty_dir_stat_empty` | its own directory | | `readdir_bound`, `mkdir_cap` | `/tmp`'s exact count; the VFS directory cap, left full | the reason both ride boots of their own (`testcases-readdir`, `testcases-mkdir`), unchanged here | | `endowment_denied` | the roster in a 256-entry buffer, its own pid in it; `ps`'s row count above zero | members run one at a time, so the live set is the services and one member: `ps` counted 26 processes at `2c7e1be1a`. Past 256 threads it would red "does not contain this process", loud and misnamed; nothing near that | | `kill_ends_every_wait` | the roster in 256 entries, one pid's state | refuses past 256 by name | | `process_tree`, `abuse_thread_name` | the roster in 1 024 entries, filtered to names they spawned | keyed on their own names and pids | | `audio_idle_suspend` | soundserver's threads in a 128-entry roster | a rows' job: it runs before any member | | `allocator_stress` | the memory header: used above zero and below total | a sum of the whole machine against itself | | `abuse_connect_flood` | the memory header before and after its own 32 connects, the growth under 32 MiB | the window holds its own syscalls; ROOT is read into memory by the loader before the kernel starts, not in the window. It is the first member, as on `main`'s `shared` | | `shm_release_reclaims`, `handle_lifetime` | per-kind object counts | their own headers: "per kind, and not the machine's free memory" | | `inbox_log_post` | the log, until its own child's exit record | keyed on its child's pid | | `file_mtime` | its own file on `/log` | its own file | No other member walks a directory it did not make, counts processes or files, or reads a log line count. This is evidence for this list; a member a later landing adds is held to the same question by its own review. ## The log The T14's `testcases` log at `2c7e1be1a`: 8 123 829 bytes, whole (no part missing), where the sum of the boots it was predicted 8 095 744 to 8 130 458. Eight parts at logkeeper's 1 MiB a part; a fresh volume keeps sixteen before `retire` deletes one of this boot's own continuations; a part that is missing reds the whole boot by name (`bootlog::lost_parts`, #785), unchanged. ## The T14 **At `2c7e1be1a`** (the orchestrator's reading; worktree clean before and after, each image's sha256 checked in the command that flashed it): three boots, each `toyos-metal` exit 0; judged with `--metal --metal-readback <dir> boot:testcases boot:shared-debug`: exit 1, **266 passed, 1 failed**, `test_rs_endowment_denied` exit 101 as above. Everything else as the request asked: the armed pairs 200 200 / 100 100 and 124 200 / 62 100 from the kernel's own lines; `acpi_hold` at the end of the rows' jobs, exit 0; the bounds programs last; no bound fired; each loader pass after the reset `DONE`; the members' line over 225 of 225. That reading is this change's negative control: the walk as it stood, on a ROOT with the corpus's links, red. **At `01fd3aedd`**: staged with `cargo test --test toyos-build -- --metal --metal-readback <dir> boot:testcases boot:shared-debug`, exit 2, which is "staged"; no machine touched. The images of `2c7e1be1a` were gone already (the runner deletes each after its boot). | boot | sha256 | bytes | |---|---|---| | `testcases` | `d99c1440e52def650a5f9600cd75fe4283322db709d40f95fb12f181358003cd` | 429 916 160 | | `shared-debug` | `82c1d8aee120d02b9acee3af3245ec2f12d4d33abd81b664c1467cdd5299e43c` | 136 314 880 | | `testcases-watchdog` | `c296fb0c0b9b37e2617a1b371c442a78598c011436a320510cd5b818783f1ed4` | 119 537 664 | The hashes stand in `request.txt` as `shasum -a 256` lines, and `shasum -a 256 -c` over them answers OK for the three. The request names round 2's fourteen with this head's expected numbers, then: `PASS test_rs_endowment_denied` with its line `applets: 15 links behind /system/bin/toybox, 14 declared over-grants and no undeclared one; 137 links to a binary no row names` and the lines after it; and 267 passed. Sent back by any FAIL or a count other than 267, an applets line with other numbers, a bound or a `WEDGED`, a missing part, the hold at or past 54 000 ms, another order, or a members' line over fewer than 225. **A mutation boot, optional, staged beside it**: `m4-a-second-binary-with-a-row-behind-a-link` (the patch is in the round-3 comment) adds `bin/zz_second_multicall -> /system/bin/symbolize` to `tests/testcases/system.toml`, `symbolize` having a row. Staged from a never-pushed commit of it on `01fd3aedd` with `--metal --metal-readback <dir> endowment_denied 134_double_to_signed`, exit 2, the tree restored to `01fd3aedd` and clean after; one image of 134 217 728 bytes, sha256 `b6787c435359a87d68264f014fef286583667a9a73d8e93c8f91752b6db74d71`, its config carrying both the C case's link to `test_rs_ccheck` and the mutation's. Expected: exit 1, `test_rs_endowment_denied` red naming `/system/bin/zz_second_multicall`: a second binary with a row behind a link still reds. **No machine has run it**, and only a machine can: see below. ## Where the tree differs from the design's text - The design counts 24 boots to 21. `main` is at 23 (#794 was 25 to 23), so this stage is 23 to 20. - The design puts a boot's registered jobs first "in today's order" and names no last job. Stage 3a built the last job behind the members; this stage puts it back among the rows', on the measurement above. - The design's merged boot arms "about 198 s"; the tree's formula gives 200 200 ms. - Nothing of stage 4b is built or prepared. ## Gates, at `01fd3aedd` Logs are kept beside the orchestrator's scratch as `r3-*`. | command | exit | |---|---| | `cargo run -- --clippy` | 0, 24 invocations clean | | `cargo test --test toyos-checks` | 0, 39 passed | | `cargo test -p toyos-build --lib` | 0 | | `cargo test --test toyos-build -- --metal --list` | 0, 61 registrations and 232 members over 20 boots | | `cargo run -- --ci host` | 0, 78 steps green | | `cargo run -- --build-only` | 0 | | `cargo test --test toyos-build -- test_rs_endowment_denied` | 1, `No test matches filter "test_rs_endowment_denied"` | | `cargo test --test toyos-build -- --metal --metal-readback <dir> boot:testcases boot:shared-debug` | 2, "staged": three images | | the same with `endowment_denied 134_double_to_signed`, under `m4` | 2, "staged": one image | Host load when the gates started: 18.65 / 29.42 / 32.46. **No guest runs `endowment_denied`, so the T14 is its only oracle.** The guest suite's names are `MACHINE_TESTS` and `SCREEN_TESTS`; the discovered Rust binaries and the C corpus are shared members, run only by `--metal` and the interactive `--debug`, and the filter above is refused for that reason. No QEMU boot carries a ROOT with the corpus's links either: the guest stages C cases as `test_c_<case>` and no comparator. The rest of the change is reached only through `--metal` and `toyos-checks`; `guest / suite` is a required check and runs at the landing head. ## High-risk checks The harness's batching decides what the kernel's deadline is armed with and which program's exit a verdict is read from; `endowment_denied` is a security claim about endowments. - **Negative controls**: round 2's three mutations of `tests/common/metal.rs`, each red (101) in `metal_rows_run_before_members_under_a_bound_the_members_widen` at `tests/checks/metal.rs` 479, 480 and 499 (the round-2 comment); `metal.rs` has not changed since. For `endowment_denied`: the T14's reading of `2c7e1be1a`, the walk before the change on a ROOT with the corpus's links, red; and `m4`, staged, owed a machine. - **Independent oracle**: the T14, owed at this head. The supervisor's `declared` and the build's `unnamed_program` are the rule the narrowed walk follows, read, not run. ## The host check, and what it sees that reading cannot `rows_run_before_members_under_a_bound_the_members_widen` is changed, not added: the last job's place in a list rows and members share, a bound summed over members of two allowances, and the refusal of two jobs under one recorded name. No guest test is added or cut; `endowment_denied`'s walk is narrowed, its claim kept. No dependency is added. No program is changed. ## Size `git diff --shortstat origin/main...01fd3ae`: 9 files, +266 −151. `issues/`: 4 files, +97 −25, one new. `tests/checks/`: +29 −18. The harness and the record: +128 −106 as before. `endowment_denied.rs`: +12 −2. ## Unsure of - Whether the 266 that passed at `2c7e1be1a` pass again: one reading of a boot of its kind. - The order `read_dir` lists `/system/bin` in. On `m4`'s boot the walk reds on the mutation's link whichever it meets first; that it passes over the C case's link there holds only if it meets that one first. The main boot's 137 is what shows the pass-over. - `endowment_denied`'s roster read takes 256 entries and asserts its own pid among them without first refusing a larger roster by name, as `kill_ends_every_wait` does. Not this change's, and far from reached (26 processes). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
toyos-virtiois the virtio 1.2 PCI transport and split virtqueue as one pure,no_std,forbid(unsafe_code)crate, and netstack's virtio-net driver is its first client.toyos-device-memoryis the shared device boundary, and two crates are written against it,toyos-i219andtoyos-virtio;diskserver's NVMe driver andsoundserver's virtio still carry their own barriers andissues/a-driver-is-tested-on-the-host-and-its-real-implementation-is-one-instruction-deep.mdowns moving them.The tree's stages this serves:
issues/every-driver-is-still-in-the-kernel.md(the owner's target,virtionowhere in the kernel; its stage 1 names the next clients) and stages 6 and 7 ofissues/toyos-runs-on-arm64.md.Head
90e9f6451ec73e037393202cd0c07448cecd3137, withorigin/mainat55e4e1dd2(#795) merged in: the merge brought twoissues/files,issues/toyos-runs-on-arm64.mdandsrc/build.rs, none of them this branch's, and no conflict. #793 had not landed when this was measured.What changed, per decision
One shared device boundary: the owner's ruling, 2026-10-09. Asked "Userland drivers reach their device through a small interface (read/write registers, DMA buffers, two memory barriers). The Intel NIC crate has its own copy and the new virtio crate added a second, identical one. An issue in the tree reserves this call for you: one shared interface, or one per driver crate?", he answered: "One shared interface (Recommended)".
toyos-device-memorydeclaresRegisters,DmaBuffersand the order of the two barriers, stated once. It is named for what it declares, the two kinds of memory a driver and its device share. Nottoyos-driver, which would invite everything a driver needs; nottoyos-dma, which is the kernel's and is half of it. It has no dependency and no test: it is two traits. The contract states two orders and says of the third, a store followed by a load of what the device wrote in answer (a suppression word,VIRTIO_F_EVENT_IDX), that neither barrier gives it and the first driver that needs it adds a method. Neither driver here makes that pair.toyos-i219loses its own two declarations. Its change is its accesses renamed to the width they always had:read32/write32for a register,read64/write64for a descriptor half.ClockandInterruptsstay its own: one driver needs them. Its 88 tests are 90. What changed instub.rsandtests.rsis the trait implementations, which panic on an access of any other width, and the two order tests below.device.rs, overtoyos::volatile::Window. Both drivers use it, so the twofencecalls exist in one body. A driver's holder keeps the mappings; the Intel wrapper drops the driver, the BAR mapping, the grant and the claim in the order it did onmain.publishorobserveindevice.rspasses every host test and every x86 guest, where TSO hides it. Nothing runs these drivers on AArch64 before stage 7 of the ARM track. What is measured is the order of the calls, in each crate's host suite: m06 and m07 intoyos-virtio, m31 to m34 intoyos-i219.publishandobservewere empty, and three mutations at459e1f3dbstayed green, 88 of 88 (the control, below).stub.rsnow records every store, load and barrier the driver makes on the grant and every register it writes, astoyos-virtio's stub does, and two tests read that trace over frames moved both ways on both parts, eight seeds each:a_tail_register_is_written_only_after_a_publish_that_follows_the_last_descriptor_store: no store into the grant stands between the lastpublishand a write ofTDTorRDT. It also counts thatTDTmoved over a stored descriptor once per frame sent, so it is not vacuous.a_descriptors_fields_are_read_only_after_an_observe_that_follows_the_load_that_found_dd: after the load that first findsDDin a descriptor, nothing is loaded from the grant before anobserve. It counts one such load per frame received and per descriptor sent.git diff 459e1f3db HEAD -- toyos-i219/src/lib.rs toyos-i219/src/pch.rs toyos-i219/src/phy.rs userland/netstack/src/device.rs userland/netstack/src/i219.rs userland/netstack/src/card.rsis empty, exit 0 (r3-driver-source-diff.log).toyos-device-memory/src/lib.rsdiffers from459e1f3dbin five comment lines: with every comment-only line stripped from both,diffexits 0, 26 lines each side (r3-device-memory-code-diff.log).issues/toyos-runs-on-arm64.mdis not given the I219: what its list owes NVMe and xHCI is a test of the order, and for the I219 these two tests are that test on the host tier. What stays unmeasured is the instruction in netstack'sdevice.rs, whichissues/assembly-outside-an-arch-module-in-userland-and-guest-probes.mdalready owns.issues/a-userland-device-driver-has-no-shared-boundary-to-be-written-against.mdis deleted: its exit was the ruling and a second driver written against the answer. Nothing else cited it. Its one durable line is in the new crate's header: what one driver alone needs of its substrate is that driver crate's own trait.Every used-ring refusal ends the driver.
finishedinvirtio_net.rspanics with "this NIC cannot be driven on" and the refusal's own words for each ofHead,NoChain,WrittenandJumped.rx_refused,TxQueue::refused,reportedandVirtioNet::reportare gone, andCard::reportdoes nothing for virtio.main.rsis untouched.Writtenon a transmit chain kept its head for the boot.system.tomlgivesrestart = trueto diskserver and fileserver only. netstack ends by name and the machine has no network until it boots again. Filed asissues/netstack-ends-for-the-boot-on-a-nic-it-cannot-drive-on.md; whether netstack restarts is not decided here.The transport.
Layout::of(&[VendorCap])takes the capability walk's result;Offer::acknowledge→Offer::accept→Setup→Setup::driver_ok→Live.Setupstep now takes theSetupby value and a refusal does not give it back.DRIVER_OKcannot be written overFAILED(§3.1.1): there is no value left to calldriver_okon.NO_VECTORis the stub's constant; the crate compares against the entry it wrote.stub.rsis the device written from it and asserts what the specification requires of the driver. No other system's driver source was read.The device is untrusted. Every device-written word is bounded before it is an offset, index or length:
barMissingCapoffset,lengthOutsideBar,TooShort,Misaligneddevice_statusafter resetResetUnansweredVERSION_1required;FEATURES_OKread backNotVersion1,FeaturesRefusedqueue_sizeQueueTooShallowqueue_notify_off× multiplierDoorbellNoVectorPastDeviceConfigidUsedRefusal::HeadidUsedRefusal::NoChainlenUsedRefusal::WrittenidxUsedRefusal::JumpedWhat changes in the shipped virtio NIC driver, each from the specification:
mainwrotedevice_statusas 32 bits and a queue address as one 64-bit store.lenis bounded by the chain's writable bytes, which for a transmit chain is 0.mainthree were counted and the fourth did not exist.Windowassertions a device could trip; both are refusals.VIRTIO_F_VERSION_1is refused. A refusal after the acknowledgement setsFAILED.FEATURES_OKis read back; its text is the same.Left for the client that needs it, and not built:
DRIVER_OK.issues/every-driver-is-still-in-the-kernel.mdnow says the second client moves it into the crate, and that it measures what QEMU reports aslenon a device-readable chain before it ends a device onWrittenthere.config_read(..).unwrap_or(0)and its unmasked links are as onmain, filed asissues/the-virtio-capability-walk-reads-a-refused-configuration-read-as-zeros.md.kernel/src/drivers/virtio.rs) is not shared: the track's target deletes it.Gates
At the head above unless the row says otherwise. File names are in the step-log directory the brief names.
cargo test -p toyos-virtio -p toyos-device-memory(r3-crate.log), and in the host gatetoyos-virtio, 33 teststoyos-i219, 90 tests (88 onmain)cargo test -p toyos-i219(r3-i219.log), and in the host gatecargo testinuserland/netstack(r3-netstack.log), and in the host gatecargo run -- --ci host(r3-ci-host.log)cargo run -- --build-only(r3-build-only.log)459e1f3dbcargo test --test toyos-build -- netstack_socket_churn libc_sockets iommu_virtio_platform(r2-guest-net.log)459e1f3dbcargo test(r2-guest-suite.log)459e1f3dbthe branch changed two test files of a host-tested crate and five comment lines, and the merge brought no netstack, kernel or SDK source:src/build.rsloses 47 lines (calc, snake and doom are on the AArch64 ROOT: the list that left them out is deleted, and the owner's word of 2026-10-09 ships doom there #795's deleted list) and the rest isissues/. The image builds at this head, exit 0.uptime(1-minute averages): 25.64 before, 29.84 after. Around round 2's guest suite: 47.14 before, 59.27 after.The T14 reading, taken (the orchestrator's, in the comment "The T14 on the Intel driver written against the shared boundary"). The Intel driver's crate changed, so a reading was owed. No boot on
mainruns netstack on the T14's wired card, so it was staged on a measurement branch with no pull request,wt/toyos-arm-u1-t14at795c67656:459e1f3dbwithorigin/wt/toyos-move-rows-edgemerged. The driver-path diff between the two is empty, and so is the driver-path diff from459e1f3dbto this head (above), so the reading carries.cargo test --test toyos-build -- --metal --metal-readback <dir> boot:outbound. Exit 0, 2 passed, 0 failed (outbound_router,outbound_internet), one boot. Image sha256bf95ff896355b150b39b0de9fa462ffe7de3b29e85277e1a862a228307f14122, as staged.main's driver at #782795c67656full=7 wake_armed=2 wake_taken=1 untaken=1 stranded=0 descriptors_sent=73 wire_sent=73 taken=60full=6 wake_armed=2 wake_taken=1 untaken=1 stranded=0 descriptors_sent=73 wire_sent=73 taken=60cannot be driven on/offered to a transmit ring that refuses itfullcounts askings that found no room and differs by one between two boots of the same job.Negative controls
34 mutations, each a checked patch, built (exit 0), tested, reversed, tree clean after each. Every one is red (test exit 101). m01 to m30 were run at
459e1f3dband are in the round-2 mutation comment; m31 to m34 were run at this head and are in the round-3 comment, patches included.Head,NoChain,Written,Jumpedreturned to a counted drop infinished. Each reds exactly the netstack test that names that refusal.TDTas 16. Red in 9 and 12 of its tests, at the model's width assertion.self.dma.publish();deleted beforeTDT(m31) and beforeRDT(m32),self.dma.observe();deleted in receive between the two loads of the descriptor (m33). At459e1f3db, each alone: build exit 0, test exit 0, 88 passed, 0 failed (r3-mutations/control-summary.txt). At this head: test exit 101, 89 passed, 1 failed; m31 and m32 red at the tail-register test, m33 at the descriptor-fields test (r3-mutations/head-summary.txt).self.dma.observe();deleted in the transmit reclaim. Red at the descriptor-fields test, 89 passed, 1 failed. It has no control run; at459e1f3dbthe deleted call was to an empty function.Growth
git diff --shortstat origin/main...HEAD: 25 files, 3332 insertions, 839 deletions.toyos-virtio+893 (lib.rs68,pci.rs457,queue.rs368),toyos-device-memory+96, netstack'sdevice.rs+114;virtio_net.rs810 to 453, netstack'si219.rs401 to 358,toyos-i219/src/lib.rs-36. Round 2 was +663; the five lines are the contract's sentence.stub.rsandtests.rsare 1,456 lines; netstack's go from 126 to 136; the Intel crate's stub and tests gain 231, of which 76 are trait implementations and 155 are the recorded trace and the two order tests.Unsure of
read::<W>would be shorter there and would let a width be inferred at a call site, which is the mistake §4.1.3.1 exists for. I chose names.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A