Skip to content

Wired NICs reach the stack; drivers come up on UEFI PCs; wifi key index; iwlwifi first steps - #592

Closed
eKisNonos wants to merge 10 commits into
mainfrom
drivers/net-real-hardware
Closed

eKisNonos wants to merge 10 commits into
mainfrom
drivers/net-real-hardware

Conversation

@eKisNonos

@eKisNonos eKisNonos commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What was wrong

The zerostate image (microkernel-full-gui) ships e1000, rtl8139, rtl8169, iwlwifi and rtl8821ce. None of the three wired drivers could carry traffic for the stack:

  • They never answered it. Each tagged its replies with its own magic and sent them to a kernel reply inbox nothing reads. net_core and net_l2 speak NNET and wait for the reply to their own call. Booted as the only NIC, the e1000 came up and net_core logged link probe driver.e1000_0 no-answer.
  • Two of them skipped the card on UEFI PCs. rtl8139 and rtl8169 (and e1000 before this branch) skipped any device whose PCI Interrupt Line reads 0xFF, which UEFI firmware commonly leaves there. Under OVMF, with an RTL8139 as the only NIC, the driver exited 2 (absent).
  • rtl8169 could not come up on any card. It draws its station address with CryptoRandom, and neither its manifest nor the kernel spawn spec granted Crypto, so bring-up stopped at "no entropy for station address".
  • rtl8139 transmitted the factory MAC. nonos_mac exists to keep that address off the wire.

On top of that, each driver had ring and descriptor bugs that only show on silicon; they are listed in its commit.

What changed

Commit Change
linux: connect() No route out (the mixnet holds no gateway) is ENETUNREACH, not ECONNREFUSED. The mixnet stays the only way out.
virtio-net, virtio-gpu: fences Release before every avail-index store, acquire after the used index moves.
virtio-net, virtio-gpu: absent device Exit 2 at once instead of spinning ten seconds of boot; the broker is filled before any capsule starts.
e1000 Answers the stack; TX reclaim with E_AGAIN; reset per the manual; short frames; polled, no Irq cap.
rtl8139 Answers the stack; found under UEFI; polled with IMR 0, no Irq cap; drawn MAC plus Crypto cap; 32K RX ring; linear reads; TX pad and recovery.
rtl8169 Answers the stack; Crypto cap; found under UEFI; polled with IMR 0, no Irq cap; NPQ doorbell; ring cursors; 32-bit IDR writes; TE/RE before configs; short-frame pad.
net_core The bind line names the NIC.
build nonos-mk-ethernet-prod: the desktop profile plus the three wired drivers.
iwlwifi MAC clock bit, RF-kill bit, firmware header layout, section types.
rtl8821ce GTK installed at the key index the AP sent; no hardware CCMP tag on frames already encrypted in software.

Evidence

Each boot runs nonos-mk-ethernet-prod under QEMU q35 + OVMF with one NIC, so a lease can only have come through the driver named. The disk is opened snapshot=on.

Boot NIC / display Result
before e1000 / virtio-gpu e1000 up, link probe driver.e1000_0 no-answer, no lease
before rtl8139 / virtio-gpu [EXIT] code=2 driver.rtl8139_0 (skipped: Interrupt Line 0xFF)
after e1000 / virtio-gpu bind: interface up on driver.e1000_0, lease 10.0.2.15/24 gw 10.0.2.2; mixnet directory fetch connects and handshakes over it
after rtl8139 / virtio-gpu bind: interface up on driver.rtl8139_0, lease 10.0.2.15/24 gw 10.0.2.2, handshake; QEMU info network shows b6:54:0b:29:cc:83 (drawn), not 52:54:00:12:34:56
after e1000 / plain VGA (GOP only) virtio-gpu and virtio-net exit 2 as they start; lease through e1000; desktop renders at 1920x1080 on the firmware framebuffer

In every boot rtl8169 exits 2: QEMU has no model for it. After the handshake the directory sync fails with code 20 over every NIC. virtio-net fails the same way, so the cause is not these drivers.

Proofs: e1000_proofs 14, rtl8139_proofs 7, rtl8169_proofs 8, iwlwifi_proofs 76, nonos_wifi_core_proofs 14 passed.

Not done

  • iwlwifi cannot associate: the per-family firmware load, RX/TX queues and post-ALIVE sequence are still to be written. Its discovery also still skips a card with Interrupt Line 0xFF.
  • No QEMU model exists for the RTL8111/8168, so rtl8169 is checked by its proofs and the capsule build, not by a boot.
  • e1000 covers the 8254x family. The e1000e parts in current Intel PCs (82574, I217/I218/I219) are not matched.
  • virtio-net still uses the address the host assigns rather than a drawn one.
  • The kernel-side client modules under src/hardware/{e1000,rtl8139,rtl8169}_capsule/client still speak the old per-driver magic. Nothing calls them.

Guest TCP leaves through the mixnet and nowhere else. When the mixnet holds no
gateway, net.sockets answers the connect with E_NO_TRANSPORT (6); the
personality turned every non-zero status into ECONNREFUSED, so a program was
told a peer had answered and refused it when no packet could have left the
machine. Status 6 is now ENETUNREACH, the errno Linux gives when there is no
route. Every other refusal stays ECONNREFUSED, and a call that got no reply
stays EIO.

The mixnet stays the only way out: nothing here opens a direct path.
…ishes them

virtio-net wrote descriptors, avail ring slots and the frame itself, then the
avail index, with no fence anywhere. The descriptor and ring stores are
volatile, but the frame bytes on the TX side are a plain copy, and Rust only
orders volatile accesses against each other: the compiler is free to sink the
copy past the index store that tells the device to read it. On the RX side the
used element and the frame were read after the used index with nothing tying
them to it. x86 hardware keeps these in order today; the language does not, and
a weakly ordered CPU would not either.

A release fence now sits before every avail index store (prime, refill, TX
post) and an acquire fence after the used index is seen to move, the same
pairing virtio-gpu already used.

virtio-gpu had the acquire in the wrong place: after reading the used entry
rather than before, so it ordered nothing the entry depended on. It moves ahead
of the reads.
Both capsules re-ran their whole setup in a yield loop for ten seconds when no
device matched, then exited 0. The broker is filled from PCI during kernel
init, before the first capsule starts, so discovery gives the same answer on
every pass; the loop only burned the CPU through that part of boot. On a PC,
which has neither device, that was ten seconds of every boot. While
virtio-net sat in it, net_core's link probes to driver.virtio_net0 went
unanswered and each one waited out its timeout.

A failed discovery now exits 2, as e1000, rtl8139 and rtl8169 do for an
absent chip. The bounded retry stays for a device that is present and fails a
later step.

Booted with an e1000 on plain VGA, so with neither device present, both now
leave with code 2 as they start and the desktop comes up on the GOP
framebuffer. With no virtio-net present, net_core had logged "ipc.call
unanswered driver.virtio_net0" five times in a boot; with this it logs it
none.
…al, take short frames

The driver never answered the stack. It tagged replies with its own magic
(0x4E45_3130) and sent them to KERNEL_REPLY_ENDPOINT, an inbox nothing has read
since the stack moved into capsules. net_core and net_l2 speak NNET
(0x4E4E_4554) and wait for the answer to their own call, which is how
virtio-net replies. Requests are now taken with mk_ipc_recv_from and every
answer goes back to the capsule that asked, with mk_ipc_reply, under the
NNET tag. The ops and payload layouts already matched.

Booted as the only NIC, the e1000 came up and net_core's probe got nothing
("link probe driver.e1000_0 no-answer"). With this it binds and takes the
lease itself: "bind: interface up on driver.e1000_0", "lease 10.0.2.15/24 gw
10.0.2.2", and the mixnet directory fetch connects and handshakes over it as
it does over virtio-net.

TX posted every frame unconditionally and then spun on DD. When the spin ran
out the handler answered E_IO but left the descriptor live with TDT already
moved, and the next call wrote over the same slot without looking. On silicon
the 8254x sets no DD while the link is down (auto-negotiation after SLU, a
pulled cable, a blocking switch port), so every send timed out; after 31 of
them TDT caught up with TDH, which the part reads as an empty ring, and later
posts overwrote buffers it might still be fetching. A frame answered E_IO was
also still sent once the link came up, so a retry went out twice. QEMU's model
sets DD with the link down, which is why it never showed. TX now keeps a clean
index: reclaim walks it over finished descriptors, a ring with no free slot
answers E_AGAIN (one slot always stays empty), and a frame is answered once it
is queued.

The reset wrote CTRL and the receive address within microseconds of RST
clearing. A global reset starts an EEPROM auto-load that rewrites RAL0/RAH0 and
parts of CTRL, so the drawn station address could be replaced by the factory
one afterwards and unicast would go to the wrong filter. The sequence now
follows e1000_reset_hw: mask, stop RX and TX, wait 10 ms for bus-master cycles
in flight, reset, keep off the part for the first millisecond, poll RST clear
against a time bound rather than a spin count, then wait 20 ms for the
auto-load (5 ms on 82540/5/6, 20 on 82541/7) before anything is written.

Frames under 60 bytes were refused. TCTL.PSP has the part pad them, and ARP
(42) and a bare TCP ACK (54) are shorter than that, so IPv4 stopped right after
DHCP. A bare header is now the minimum.

The driver bound its INTx line and never used it: it polls and IMS is never
set. The discovery filter also skipped the NIC when firmware left Interrupt
Line at 0xFF, which UEFI commonly does, and a bound line is masked until acked,
so a shared GSI stayed masked for any other device on it. No line is bound
now, and the capsule gives up the Irq capability in its manifest and in the
kernel's spawn spec.

Release fences sit before the TDT and RDT writes and the ring hand-over, and an
acquire follows the DD reads, as in virtio-net.

e1000_proofs: 14 passed. The host shim gains Deadline, the fixture drops the
IRQ grant, and the request fixture carries the NNET tag. The no-entropy test
still holds the transmitter and receiver to never having been written, so
the quiesce writes TCTL as 0.
…the ring

The driver never answered the stack. It tagged replies with its own magic
(0x4E52_3839) and sent them to KERNEL_REPLY_ENDPOINT, an inbox nothing has read
since the stack moved into capsules. net_core and net_l2 speak NNET
(0x4E4E_4554) and wait for the answer to their own call, which is how
virtio-net replies. Requests are now taken with mk_ipc_recv_from and every
answer goes back to the capsule that asked, with mk_ipc_reply, under the
NNET tag. The ops and payload layouts already matched.

Discovery skipped any card whose PCI Interrupt Line read 0xFF. OVMF leaves
it there, as UEFI firmware commonly does, so booted under OVMF with an
RTL8139 as its only NIC the driver exited 2, absent. The line was only ever
bound to be acked; the driver polls. It binds none now, sets IMR to 0 so an
unserviced source cannot hold a shared INTx asserted for another device,
and gives up the Irq capability.

The station address was the factory one, read out of IDR: the identifier
nonos_mac exists to keep off the wire, where e1000 and rtl8169 already drew
theirs. It is now drawn through nonos_mac too, written as dwords
with the config lock open as 8139too writes it, read back, and fails
closed. The capsule gains Crypto, which CryptoRandom is gated on, in its
manifest and in the kernel's spawn spec.

Booted under OVMF as the only NIC, it now binds and takes the lease itself:
"bind: interface up on driver.rtl8139_0", "lease 10.0.2.15/24 gw 10.0.2.2",
and the mixnet directory fetch connects and handshakes over it. QEMU's
`info network` shows the card at b6:54:0b:29:cc:83 rather than its
configured 52:54:00:12:34:56, so the lease was taken under the drawn
address.

RCR set no RBLEN bits, so the chip used an 8K+16 ring while every offset here
was taken against 32K. After about 8 KB received the chip wrote at the start of
the ring and the driver read zeros at 8K: ROK clear, an error, and every later
receive failed the same way. QEMU honours RBLEN too (8192 << RBLEN), so this
was reachable in emulation. RBLEN is now 10, the 32K+16 ring the allocation was
already sized for.

With RCR.WRAP set, a frame that crosses the end of the ring is written on past
it into the slack, not back at the start, but reads wrapped modulo the ring and
took the tail of that frame from offset 0: stale bytes once a lap. Reads are
linear now, and a read past the allocation returns zero. The ring proofs held
the modulo behaviour; they now hold the linear one, over a buffer the size of
the real allocation, and still check that no read leaves it.

The chip does not pad short frames and frames under 60 bytes were refused, so
ARP (42) and bare TCP ACKs (54) were never sent. A bare header is the minimum
now and send zero-pads to 60.

A bad header (ROK clear, or a length no good frame carries) returned without
moving, so the same header was read forever. Receive now restarts from the top
of the ring the way 8139too's rx_err does. A good frame too big for the caller
(a tagged full-size one) is skipped instead of stalling, and 0xFFF0 (still
arriving) is left for the next poll.

TX walked TSD0-3 with a cursor that stayed put when a send failed, while the
chip's own pointer moved on, so every later send waited on a descriptor the
chip would never look at again. TX is now four slots with a cursor and a
reclaim index, as 8139too keeps cur_tx and dirty_tx: a slot is finished on
TOK, TUN or TABT, an abort writes TCR.CLRABT to restart the transmitter, a
full ring answers E_AGAIN, and the early-TX threshold starts at 256 bytes
rather than 8.

Rx and Tx are enabled before RCR and TCR are written, as 8139too and the BSD
drivers do.

rtl8139_proofs: 7 passed.
The driver never answered the stack. It tagged replies with its own magic
(0x4E52_3639) and sent them to KERNEL_REPLY_ENDPOINT, an inbox nothing has read
since the stack moved into capsules. net_core and net_l2 speak NNET
(0x4E4E_4554) and wait for the answer to their own call, which is how
virtio-net replies. Requests are now taken with mk_ipc_recv_from and every
answer goes back to the capsule that asked, with mk_ipc_reply, under the
NNET tag. The ops and payload layouts already matched.

It could not have come up on any card in any case. The station address is
drawn with CryptoRandom, which is gated on the Crypto capability, and neither
the manifest nor the kernel's spawn spec granted it: the draw failed closed
and bring-up stopped with "no entropy for station address". Both grant it
now, as they already did for e1000 and rtl8821ce.

Discovery skipped any card whose PCI Interrupt Line read 0xFF, which is what
UEFI firmware commonly leaves there, so on those PCs a present RTL8111/8168
was reported absent. The line was only ever bound to be acked; the driver
polls. It binds none now, sets IMR to 0 so an unserviced source cannot hold
a shared INTx asserted for another device, and gives up the Irq capability.

The TX doorbell wrote TPPoll bit 7, which polls the high-priority ring. Only
the normal-priority ring (TNPDS) is ever set up; THPDS is never written. On a
real card the part fetched a descriptor from whatever THPDS held after reset,
the real descriptor kept OWN, the first send timed out and every later one saw
"busy". No frame was ever transmitted. The doorbell is bit 6, NPQ, as in Linux
r8169.

Both rings could fall a slot behind the part for good. On an RX descriptor
error the slot was re-armed but the cursor stayed, while the part had already
moved on; low-rate traffic like ARP then sat unread until fifteen more frames
wrapped the ring. On TX the cursor moved only after a clean completion, so a
TER or a slow completion (link still negotiating) left it on a slot the part
had left. The RX cursor now advances past a bad descriptor, and the TX cursor
advances the moment OWN is handed over; the OWN check on the next slot is the
full-ring test, answered with E_AGAIN.

The station address went into IDR with byte writes. The part takes IDR as
dwords; byte writes are dropped on silicon, the readback check failed, and
bring-up stopped there. It is now two 32-bit writes, high dword first, each
read back to post it, as rtl_rar_set does.

RxConfig and TxConfig were written with the receiver and transmitter off. On
the 8169 and 8168B-F those writes can be lost and the accept bits never take.
TE|RE now go on first, then the two configs, still after the station address,
so the part is never enabled under the factory one (the bring-up proof that
watches for that still passes).

Frames under 60 bytes were refused. ARP (42) and a bare TCP ACK (54) are
shorter than that; they are now zero-padded to 60, which also covers the 8168
revisions that pad short frames wrong.

No QEMU model exists for this chip. rtl8169_proofs: 8 passed; the bring-up
proof now holds IMR at 0.
"bind: interface up" did not say which interface. With a wired port and a WiFi
link both present the choice is the first thing to know, and on a boot with
one NIC it is what ties the lease that follows to a driver. The line now names
the candidate, for example "bind: interface up on driver.e1000_0".
No profile carried e1000, rtl8139 or rtl8169, so none of them had ever been
booted with the stack. This target is the desktop profile plus those three,
under the same attestation. QEMU models the e1000 and the RTL8139, so each can
be booted as the only NIC and has to take the lease itself; the RTL8169 has no
QEMU model and shows that a driver whose chip is absent exits and the boot
goes on.
…yout

Setup polled CSR_GP_CNTRL bit 1 for MAC clock ready. In the v1 CSR layout
that every family probed here uses, bit 1 is undefined and the flag is bit 0,
so the poll timed out on every card and the capsule exited before serving
anything. RF-kill was read from INIT_DONE, the bit setup had just set itself,
so the airplane-mode switch was never reported; it is HW_RF_KILL_SW, bit 27,
set while the radio is allowed on. Bz-family parts (BE200) use the v2 layout
and are still not handled.

The legacy firmware parser read the version at offset 8, which is the start of
the 64-byte name ("Core", "rele", "jenk" in the bundled images), so every image
was refused as an unknown API. The header is 88 bytes: version at 72, build at
76, TLVs from 88. The section types were one off (20/21/33): 19 is the runtime
image, 20 INIT, 32 paging, so the INIT and WoWLAN images were staged and the
runtime one never was. The gen3 path already had 19. The API ceiling moves to
86 to take the newest bundled image.

Checked against the six bundled .ucode files: the version at 72 is the API in
each file name (29, 36, 46, 77, 84, 86) and the TLV walk from 88 ends exactly
at the end of every file. The blob test written for this was never declared in
the proofs crate; it is wired in now and runs against the real 7265D-29 image
(four runtime sections, 364400 bytes). iwlwifi_proofs: 76 passed.

This gets the capsule past its first register and its firmware file. It does
not boot the firmware: the per-family load paths, the RX and TX queues and the
post-ALIVE command sequence are still to be written.
The GTK KDE in message 3 carries the key's index, and find_gtk threw it away.
The driver then installed every group key in CAM slot 1. With the sec engine
looking group-addressed frames up by the CCMP KeyID, that works until the AP
rekeys: hostapd starts at 1 and swaps between 1 and 2 on every rekey, so after
the first one every broadcast frame (ARP requests, DHCP, router adverts) came
in under index 2, found an empty slot, and was dropped. The index now travels
from the supplicant through the MLME and the join outcome to install_gtk and
the session that removes it.

TX data frames were also tagged SEC_TYPE_CCMP in the descriptor after the
station had already encrypted them in software (header, CCMP header, MIC).
rtw88 sets the security type only for frames with a hardware key; a frame
tagged for hardware CCMP after software encryption risks being encrypted
twice and failing the AP's MIC check. The tag is now zero.

nonos_wifi_core_proofs gains a handshake test that drives the real supplicant
with the KDE naming index 1, 2 and 3 (and with the Tx bit set) and holds it to
the index sent: 14 passed. rtl8821ce_proofs does not build on main (sec.rs
wants a crate::constants the proofs crate never declares, and its tests look
for the firmware under the capsule directory rather than
nonos-bootloader/firmware/realtek, where it is), so these edits are checked by
the capsule build.
@senseix21

Copy link
Copy Markdown
Collaborator

Reviewed at 180b1ec97 (not a draft, base main, 125 files, +1004/−720, 49 commits behind main, 10 commits of its own). Entirely userland drivers: rtl8139 +312/−223, e1000 +198/−185, rtl8169 +159/−220, plus iwlwifi, rtl8821ce, virtio_net, net_core and four proof crates.

Verdict: Request changes. Two red lanes, both small, but one of them is a formal confinement proof that no longer holds over code this PR reworked — that should not merge without a sentence saying which side is wrong.

The virtqueue ordering fix is the best thing in here

f64c1cdab is a real memory-model bug found by reading the language spec rather than by watching something break, and the commit message gets the reasoning exactly right:

The descriptor and ring stores are volatile, but the frame bytes on the TX side are a plain copy, and Rust only orders volatile accesses against each other: the compiler is free to sink the copy past the index store that tells the device to read it.

That is correct and it is the subtle part. read_volatile/write_volatile are ordered with respect to other volatile accesses, not with respect to ordinary stores, so a plain copy of the frame payload can legally be sunk past the volatile write of avail->idx that publishes it. On x86 TSO you get away with it; the commit says so — "x86 hardware keeps these in order today; the language does not, and a weakly ordered CPU would not either" — rather than claiming a bug it had observed.

The fix is the textbook pairing: fence(Release) before every avail-index store (prime, refill, TX post), fence(Acquire) after the used index is seen to move. I checked the acquire side, because that is the one usually put in the wrong place, and it is right — rx.rs:39-47:

let used = rx.used_idx();
if used == rx.last_used { return None; }
// The element and the frame are only valid once the index is seen.
fence(Ordering::Acquire);
let (desc_id, used_len) = rx.used_elem_at(ring_pos);

The fence sits between observing the index and reading what the index publishes, which is the only placement that does anything.

And it found the same mistake already shipped in virtio-gpu: "virtio-gpu had the acquire in the wrong place: after reading the used entry rather than before, so it ordered nothing the entry depended on." A fence that orders nothing is worse than no fence, because it reads as handled.

The driver work around it is the same register-level care — b12f1c8a5 traces rtl8139 never answering the stack to a reply tagged with its own magic (0x4E52_3839) sent to KERNEL_REPLY_ENDPOINT, "an inbox nothing has read since the stack moved into capsules", against net_core's NNET; and it stops skipping cards whose PCI Interrupt Line reads 0xFF, which is what OVMF leaves it as, so an RTL8139 under UEFI was simply invisible.

Important

1. Three Kani harnesses fail, and they are the ones this PR's ring rework should have updated.

proof-crates-kani reports 1 successfully verified harnesses, 3 failures, 4 total:

Verification failed for - kani_proofs::the_copy_is_confined_to_ring_and_buffer
Verification failed for - kani_proofs::u16_reads_are_total_and_wrapped
Verification failed for - kani_proofs::byte_reads_are_total_and_wrapped

All three live in userland/rtl8139_proofs/src/kani_proofs.rs. This PR's diff to rtl8139_proofs touches src/rtl_tests.rs (+38/−27) and Cargo.lock — and not kani_proofs.rs. So the driver's ring handling was reworked, the ordinary tests were updated to match, and the formal harnesses were left on the old model.

They share one assumption, which is why I think this is a stale model rather than three unrelated regressions. the_copy_is_confined_to_ring_and_buffer asserts (:52):

assert!(out[i] == ring[(start + i) % RX_BUF_DATA_BYTES]);

— that is, the copy wraps modulo the nominal ring size, and the two *_reads_are_total_and_wrapped harnesses encode the same wrap. If the rework changed where the wrap happens, all three fail together and the driver is fine. The RTL8139's own WRAP-bit behaviour is the obvious candidate: with WRAP set the card is allowed to run past the nominal end into the overflow area, so a copy that deliberately reads beyond RX_BUF_DATA_BYTES would break a % RX_BUF_DATA_BYTES assertion while being correct.

That is a guess, and it is the PR's to confirm rather than mine. What should not happen is this landing with a buffer-confinement proof red and no statement either way: if the model is stale, update the harnesses alongside rtl_tests.rs; if the wrap really did change in a way the proof correctly rejects, that is an out-of-bounds read in a NIC receive path.

2. The stub ratchet has one new admission, and it wants a baseline decision.

hygiene fails at its third step, which is the part of that job the whole Linux guest lane never reaches — there, step 2 fails on a doc-comment false positive and skips everything after it. Here step 2 passes (I reproduced the scanner over src and userland at this head: no violations) and scripts/check_stubs.py runs:

stubs: self-test passed, the decoy was reported
stubs: new admission of unsupported work at userland/capsule_driver_iwlwifi/src/constants/mod.rs:59
stubs: 335 sites, 2 closed since the baseline, 1 new

The site is honest and specific — // Bz-family parts (BE200) use the v2 layout, which is not implemented. — sitting under a comment that explains why it matters: the iwl_csr_v1 layout covers every family probed up to AX210, bit 1 is undefined there, and "polling it for the MAC clock timed out on every card, so setup never got past this register."

So the ratchet is doing its job: net -1 on stub count, but one new admission that a human is being asked to accept. Given the title says "iwlwifi first steps", recording the baseline move with that sentence as the justification looks like the right answer rather than deleting the comment — the comment is the most useful line in the file for the next person to pick up BE200.

Worth noting the ratchet is bidirectional on new sites only, so the two closures do not offset the addition. If that asymmetry is deliberate, fine; if the intent was a net-count ratchet, this PR is the case that shows it is not behaving that way.

Minor

  1. 49 commits behind main. main has since wired build-aarch64 and boot-aarch64, and linux: land the Linux lane on main; terminal linux command, signals to running threads, pipe waits, madvise, VFS store, Process Manager #593 has just shown that first contact with those lanes surfaces a drifted foreign_absent.rs stub. A driver-only change is unlikely to meet the same thing, but these lanes do not appear in this PR's checks at all, so the rebase is where that gets answered.
  2. 5afe5ed38 adds nonos-mk-ethernet-prod, "the desktop with the wired NIC drivers", as a build target. Like NONOS_LINUX_GUESTS and NONOS_LINUX_GO_SUITE elsewhere in this queue, it is a profile nothing in CI invokes — so the three reworked wired drivers are exercised by the author's boots and by virtio_net in the default lane, not by a lane of their own.

Questions

  1. For item 1: did the wrap point or the buffer geometry change in b12f1c8a5's ring fix? That single answer decides whether the Kani failures are a harness update or a receive-path bug.
  2. b12f1c8a5 has the rtl8139 capsule gain the Crypto capability so CryptoRandom can draw the station address through nonos_mac. That is a new capability on a NIC driver. Is Crypto the narrowest thing that gates CryptoRandom, or is there a randomness-only capability — a driver that can reach the full crypto surface is a wider blast radius than one that can only ask for bytes.

Verified correct

  • The acquire fence is in the right place, and the release fences are before the publishing store. Checked rx.rs directly rather than trusting the commit message, because this is the mistake the same commit documents virtio-gpu having made.
  • The ordering fix is justified by the language, not by a symptom. "x86 hardware keeps these in order today; the language does not" is the honest version of this claim, and it is the right reason to fix it before a weakly-ordered target exists.
  • Step 2 of hygiene is genuinely clean here. I ran a faithful reimplementation of nonos-verify hygiene's scanner over src and userland at this head and it reports nothing, which is what lets check_stubs.py run at all. Every other PR in this queue fails at step 2 on the same fact.rs:18 doc comment.
  • The stub checker's self-test passed before it reported. stubs: self-test passed, the decoy was reported means the tool proved it can still detect a planted stub before trusting its own scan — so item 2's finding is a real admission, not a tool malfunction.
  • 45 capsules attest with no faults. zk_attest_ok: 45, zk_attest_fail: 0, fatal: 0, panic: 0, and benchmark passes. Reworking three NIC drivers and the virtio ring without a boot regression is the signal worth having here.
  • 64 lanes pass. Including build, production-build, boot-smoke, abi-contracts, crypto-proofs, evidence-manifest and runnable-proofs — all of which are red across the entire Linux guest lane. The two failures here are specific and local.
  • 673f49f85 makes absence an exit, not a hang. "leave at once when the device is not there" for virtio-net and virtio-gpu is the right shape for a capsule whose device is missing, and it pairs with the 0xFF interrupt-line fix: one stops a driver waiting for hardware that is absent, the other stops it declaring hardware absent that is present.

CI at this head

Two real failures. proof-crates-kani is item 1 and is the one that should block. hygiene is item 2 and is a baseline decision with the justification already written in the code.

@eKisNonos

Copy link
Copy Markdown
Contributor Author

Superseded. This work is integrated into the 0.9.2 release and ships in the current tree. Closing as part of the 0.9.2 consolidation.

@eKisNonos eKisNonos closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants