diff --git a/mk/20-build.mk b/mk/20-build.mk
index 3fa720bde4..beca8f1819 100644
--- a/mk/20-build.mk
+++ b/mk/20-build.mk
@@ -3,7 +3,7 @@
# STARK attestation for the kernel and every capsule. This is where make,
# make qemu, and make from-config all resolve their real work.
-.PHONY: nonos-mk-check-driver-ahci-keys nonos-mk-check-driver-e1000-keys nonos-mk-check-driver-hda-keys nonos-mk-check-driver-i2c-hid-keys nonos-mk-check-driver-i2c-pci-keys nonos-mk-check-driver-iwlwifi-keys nonos-mk-check-driver-nvme-keys nonos-mk-check-driver-rtl8139-keys nonos-mk-check-driver-rtl8169-keys nonos-mk-check-driver-rtl8821ce-keys nonos-mk-check-driver-usb-msc-keys nonos-mk-check-driver-virtio-gpu-keys nonos-mk-check-ps2-input-keys nonos-mk-check-ramfs-keys nonos-mk-check-virtio-blk-keys nonos-mk-check-virtio-net-keys nonos-mk-check-virtio-rng-keys nonos-mk-check-xhci-keys nonos-mk-crypto nonos-mk-driver-ahci nonos-mk-driver-ahci-sign nonos-mk-driver-e1000 nonos-mk-driver-e1000-sign nonos-mk-driver-hda nonos-mk-driver-hda-sign nonos-mk-driver-i2c-hid nonos-mk-driver-i2c-hid-sign nonos-mk-driver-i2c-pci nonos-mk-driver-i2c-pci-sign nonos-mk-driver-iwlwifi nonos-mk-driver-iwlwifi-sign nonos-mk-driver-nvme nonos-mk-driver-nvme-sign nonos-mk-driver-rtl8139 nonos-mk-driver-rtl8139-sign nonos-mk-driver-rtl8169 nonos-mk-driver-rtl8169-sign nonos-mk-driver-rtl8821ce nonos-mk-driver-rtl8821ce-sign nonos-mk-driver-usb-msc nonos-mk-driver-usb-msc-sign nonos-mk-driver-virtio-gpu nonos-mk-driver-virtio-gpu-sign nonos-mk-entropy nonos-mk-keyring nonos-mk-market nonos-mk-proof-io nonos-mk-proof-io-sign nonos-mk-ps2-input nonos-mk-ps2-input-sign nonos-mk-ramfs nonos-mk-ramfs-sign nonos-mk-vfs nonos-mk-virtio-blk nonos-mk-virtio-blk-sign nonos-mk-virtio-net nonos-mk-virtio-net-sign nonos-mk-virtio-rng nonos-mk-virtio-rng-sign nonos-mk-wallpaper nonos-mk-xhci nonos-mk-xhci-sign nonos-mk-all-capsules-attested nonos-mk-attest nonos-mk-attestation nonos-mk-attestation-receipt nonos-mk-bootloader nonos-mk-capsules nonos-mk-check nonos-mk-check-trust-keys nonos-mk-check-trust-manifest nonos-mk-core nonos-mk-core-attested nonos-mk-desktop-gui-prod nonos-mk-smp-prod nonos-mk-ensure-zk-keys nonos-mk-esp nonos-mk-from-config nonos-mk-host-trust-verify nonos-mk-libc nonos-mk-live-production-proof nonos-mk-marketplace-abi nonos-mk-marketplace-index-tool nonos-mk-menuconfig nonos-mk-sign nonos-mk-terminal-test nonos-mk-trust-policy nonos-mk-usb-img nonos-mk-userland-clean nonos-mk-verify-capsule-attest nonos-mk-verify-trust nonos-mk-zerostate nonos-mk-zk-report nonos-mk-zk-tools nonos-mk-zk-verify-live
+.PHONY: nonos-mk-check-driver-ahci-keys nonos-mk-check-driver-e1000-keys nonos-mk-check-driver-hda-keys nonos-mk-check-driver-i2c-hid-keys nonos-mk-check-driver-i2c-pci-keys nonos-mk-check-driver-iwlwifi-keys nonos-mk-check-driver-nvme-keys nonos-mk-check-driver-rtl8139-keys nonos-mk-check-driver-rtl8169-keys nonos-mk-check-driver-rtl8821ce-keys nonos-mk-check-driver-usb-msc-keys nonos-mk-check-driver-virtio-gpu-keys nonos-mk-check-ps2-input-keys nonos-mk-check-ramfs-keys nonos-mk-check-virtio-blk-keys nonos-mk-check-virtio-net-keys nonos-mk-check-virtio-rng-keys nonos-mk-check-xhci-keys nonos-mk-crypto nonos-mk-driver-ahci nonos-mk-driver-ahci-sign nonos-mk-driver-e1000 nonos-mk-driver-e1000-sign nonos-mk-driver-hda nonos-mk-driver-hda-sign nonos-mk-driver-i2c-hid nonos-mk-driver-i2c-hid-sign nonos-mk-driver-i2c-pci nonos-mk-driver-i2c-pci-sign nonos-mk-driver-iwlwifi nonos-mk-driver-iwlwifi-sign nonos-mk-driver-nvme nonos-mk-driver-nvme-sign nonos-mk-driver-rtl8139 nonos-mk-driver-rtl8139-sign nonos-mk-driver-rtl8169 nonos-mk-driver-rtl8169-sign nonos-mk-driver-rtl8821ce nonos-mk-driver-rtl8821ce-sign nonos-mk-driver-usb-msc nonos-mk-driver-usb-msc-sign nonos-mk-driver-virtio-gpu nonos-mk-driver-virtio-gpu-sign nonos-mk-entropy nonos-mk-keyring nonos-mk-market nonos-mk-proof-io nonos-mk-proof-io-sign nonos-mk-ps2-input nonos-mk-ps2-input-sign nonos-mk-ramfs nonos-mk-ramfs-sign nonos-mk-vfs nonos-mk-virtio-blk nonos-mk-virtio-blk-sign nonos-mk-virtio-net nonos-mk-virtio-net-sign nonos-mk-virtio-rng nonos-mk-virtio-rng-sign nonos-mk-wallpaper nonos-mk-xhci nonos-mk-xhci-sign nonos-mk-all-capsules-attested nonos-mk-attest nonos-mk-attestation nonos-mk-attestation-receipt nonos-mk-bootloader nonos-mk-capsules nonos-mk-check nonos-mk-check-trust-keys nonos-mk-check-trust-manifest nonos-mk-core nonos-mk-core-attested nonos-mk-desktop-gui-prod nonos-mk-smp-prod nonos-mk-ethernet-prod nonos-mk-ensure-zk-keys nonos-mk-esp nonos-mk-from-config nonos-mk-host-trust-verify nonos-mk-libc nonos-mk-live-production-proof nonos-mk-marketplace-abi nonos-mk-marketplace-index-tool nonos-mk-menuconfig nonos-mk-sign nonos-mk-terminal-test nonos-mk-trust-policy nonos-mk-usb-img nonos-mk-userland-clean nonos-mk-verify-capsule-attest nonos-mk-verify-trust nonos-mk-zerostate nonos-mk-zk-report nonos-mk-zk-tools nonos-mk-zk-verify-live
# ZK attestation: transparent enrolled-secret tools
@@ -1179,6 +1179,18 @@ nonos-mk-install-prod: $(DESKTOP_GUI_CAPSULE_ARTIFACTS) $(driver-nvme_ARTIFACTS)
nonos-mk-check-deps nonos-mk-ensure-signing-key
$(call nonos_kernel_build,microkernel-desktop-gui + nvme + install,microkernel-desktop-gui$(_boot_comma)nonos-stark-attest$(_boot_comma)nonos-capsule-driver-nvme)
+# nonos-mk-ethernet-prod: the desktop profile with the wired NIC drivers in it.
+# QEMU models the e1000 and the RTL8139, so each boots against its own device
+# and has to take a lease through it; the RTL8169 has no QEMU model and is here
+# to show a driver whose chip is absent exits and lets the boot go on.
+ETHERNET_DRIVER_ARTIFACTS := $(driver-e1000_ARTIFACTS) $(driver-rtl8139_ARTIFACTS) \
+ $(driver-rtl8169_ARTIFACTS)
+
+nonos-mk-ethernet-prod: $(DESKTOP_GUI_CAPSULE_ARTIFACTS) $(ETHERNET_DRIVER_ARTIFACTS) \
+ nonos-mk-verify-desktop-gui-capsules \
+ nonos-mk-check-deps nonos-mk-ensure-signing-key
+ $(call nonos_kernel_build,microkernel-desktop-gui + wired NICs,microkernel-desktop-gui$(_boot_comma)nonos-stark-attest$(_boot_comma)nonos-capsule-driver-e1000$(_boot_comma)nonos-capsule-driver-rtl8139$(_boot_comma)nonos-capsule-driver-rtl8169)
+
# nonos-mk-smp-prod: the desktop profile with the secondary CPUs turned on.
# Same capsule set and the same attestation, so a difference between this boot
# and the single-CPU one is the AP bring-up and nothing else.
diff --git a/src/hardware/e1000_capsule/spawn.rs b/src/hardware/e1000_capsule/spawn.rs
index 6ceb7901e6..422c3cbffe 100644
--- a/src/hardware/e1000_capsule/spawn.rs
+++ b/src/hardware/e1000_capsule/spawn.rs
@@ -15,8 +15,8 @@
// along with this program. If not, see .
//! Spawn the e1000 driver capsule with the broker capability
-//! bundle. PCI MMIO + INTx + DMA driver — needs IPC | Memory |
-//! Driver | DeviceEnum | Mmio | Irq | Dma. No Network cap: frame
+//! bundle. PCI MMIO + DMA driver, polled — needs IPC | Memory |
+//! Crypto | Driver | DeviceEnum | Mmio | Dma. No Network cap: frame
//! transport over IPC, not a network-service authority.
use super::client::REPLY_INBOX;
@@ -62,7 +62,6 @@ pub fn spawn_driver_e1000_capsule() -> Result<(), SpawnError> {
| Capability::Driver.bit()
| Capability::DeviceEnum.bit()
| Capability::Mmio.bit()
- | Capability::Irq.bit()
| Capability::Dma.bit(),
debug_tag: b"[DRIVER-E1000] load_elf_executable error:",
};
diff --git a/src/hardware/rtl8139_capsule/spawn.rs b/src/hardware/rtl8139_capsule/spawn.rs
index 52d5620307..fbd5ab12f5 100644
--- a/src/hardware/rtl8139_capsule/spawn.rs
+++ b/src/hardware/rtl8139_capsule/spawn.rs
@@ -50,9 +50,12 @@ pub fn spawn_driver_rtl8139_capsule() -> Result<(), SpawnError> {
target_triple: TARGET_TRIPLE,
requested_caps: Capability::IPC.bit()
| Capability::Memory.bit()
+ // The station address is drawn rather than read out of the IDR,
+ // and CryptoRandom is gated on this capability. The draw fails
+ // closed, so without it the card never comes up.
+ | Capability::Crypto.bit()
| Capability::Driver.bit()
| Capability::DeviceEnum.bit()
- | Capability::Irq.bit()
| Capability::Dma.bit()
| Capability::Pio.bit(),
debug_tag: b"[DRIVER-RTL8139] load_elf_executable error:",
diff --git a/src/hardware/rtl8169_capsule/spawn.rs b/src/hardware/rtl8169_capsule/spawn.rs
index 6b0da059d4..1107aaf47d 100644
--- a/src/hardware/rtl8169_capsule/spawn.rs
+++ b/src/hardware/rtl8169_capsule/spawn.rs
@@ -50,10 +50,13 @@ pub fn spawn_driver_rtl8169_capsule() -> Result<(), SpawnError> {
target_triple: TARGET_TRIPLE,
requested_caps: Capability::IPC.bit()
| Capability::Memory.bit()
+ // The station address is drawn rather than read out of the IDR,
+ // and CryptoRandom is gated on this capability. The draw fails
+ // closed, so without it the card never comes up.
+ | Capability::Crypto.bit()
| Capability::Driver.bit()
| Capability::DeviceEnum.bit()
| Capability::Mmio.bit()
- | Capability::Irq.bit()
| Capability::Dma.bit(),
debug_tag: b"[DRIVER-RTL8169] load_elf_executable error:",
};
diff --git a/userland/capsule_driver_e1000/Capsule.mk b/userland/capsule_driver_e1000/Capsule.mk
index 7ba1f5b022..611958a84e 100644
--- a/userland/capsule_driver_e1000/Capsule.mk
+++ b/userland/capsule_driver_e1000/Capsule.mk
@@ -1,4 +1,4 @@
-# e1000 — Intel 8254x gigabit NIC. PCI MMIO + INTx + DMA with
+# e1000 — Intel 8254x gigabit NIC. PCI MMIO + DMA, polled, with
# separate RX and TX rings (four DMA grants total). Frame-level
# transport over IPC; no socket or routing policy. `Network` cap
# is intentionally absent — that authority belongs to a future
@@ -15,11 +15,12 @@ CAPSULE_FEATURE := nonos-capsule-driver-e1000
CAPSULE_NAMESPACE := systems.nonos.driver.e1000_0
CAPSULE_SERVICE_ENDPOINT := service:4210:driver.e1000_0
CAPSULE_REPLY_ENDPOINT := reply:4211:endpoint.4294967308
-# IPC|Memory|Crypto|Driver|DeviceEnum|Mmio|Irq|Dma = 0xF8039
+# IPC|Memory|Crypto|Driver|DeviceEnum|Mmio|Dma = 0xB8039. No Irq: the driver
+# polls and binds no line.
# Crypto (0x20) is what the CryptoRandom syscall is gated on. The station address
# is drawn rather than read out of the EEPROM, and that draw fails closed, so
# without this the card has no address to transmit under.
-CAPSULE_REQUIRED_CAPS := 0xF8039
+CAPSULE_REQUIRED_CAPS := 0xB8039
CAPSULE_KERNEL_MIRROR := src/hardware/e1000_capsule
include nonos-mk/capsule.mk
diff --git a/userland/capsule_driver_e1000/src/constants/frame.rs b/userland/capsule_driver_e1000/src/constants/frame.rs
index d56622a152..cd258ba70c 100644
--- a/userland/capsule_driver_e1000/src/constants/frame.rs
+++ b/userland/capsule_driver_e1000/src/constants/frame.rs
@@ -27,5 +27,8 @@ const ETH_HEADER_LEN: usize = 14;
const MTU: usize = 1500;
pub const MAC_LEN: usize = 6;
-pub const MIN_ETHERNET_FRAME: usize = 60;
+/// A bare header is the shortest frame taken. TCTL.PSP has the part pad
+/// anything under 60 bytes, and an ARP (42) or a bare TCP ACK (54) is shorter
+/// than that: refusing them stranded IPv4 right after DHCP.
+pub const MIN_ETHERNET_FRAME: usize = ETH_HEADER_LEN;
pub const MAX_ETHERNET_FRAME: usize = MTU + ETH_HEADER_LEN;
diff --git a/userland/capsule_driver_e1000/src/discover.rs b/userland/capsule_driver_e1000/src/discover.rs
index 9219638ae1..9dfd3f6942 100644
--- a/userland/capsule_driver_e1000/src/discover.rs
+++ b/userland/capsule_driver_e1000/src/discover.rs
@@ -25,7 +25,6 @@ const PCI_SUBCLASS_ETHERNET: u8 = 0x00;
#[derive(Clone, Copy)]
pub struct Found {
pub device_id: u64,
- pub irq_line: u8,
pub bar0_size: u64,
}
@@ -40,14 +39,17 @@ pub fn find_e1000() -> Option {
if !is_match(r) {
continue;
}
- if r.irq_pin == 0 || r.irq_line == 0xFF || r.bar_count == 0 {
+ // Interrupt routing is not asked for: the driver polls. UEFI firmware
+ // often leaves Interrupt Line at 0xFF, and filtering on it skipped a
+ // working NIC on exactly the machines this driver is for.
+ if r.bar_count == 0 {
continue;
}
let bar0 = r.bars[0];
if bar0.kind != BAR_KIND_MMIO || bar0.size == 0 {
continue;
}
- return Some(Found { device_id: r.device_id, irq_line: r.irq_line, bar0_size: bar0.size });
+ return Some(Found { device_id: r.device_id, bar0_size: bar0.size });
}
None
}
diff --git a/userland/capsule_driver_e1000/src/init/reset.rs b/userland/capsule_driver_e1000/src/init/reset.rs
index e50fc5d781..9f0c73d5c5 100644
--- a/userland/capsule_driver_e1000/src/init/reset.rs
+++ b/userland/capsule_driver_e1000/src/init/reset.rs
@@ -14,34 +14,61 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-//! Hardware reset + IRQ quiesce + link bring-up. CTRL.RST is
-//! self-clearing; the loop bound is generous because the device
-//! takes a few microseconds to settle. After reset the firmware
-//! restores most defaults but leaves all IMS bits set, so the
-//! capsule masks every cause through IMC and reads ICR to clear
-//! any latched bits before enabling the link.
-
-use crate::constants::regs::{REG_CTRL, REG_ICR, REG_IMC};
+//! Hardware reset + IRQ quiesce + link bring-up, in the order the 8254x
+//! needs on silicon:
+//!
+//! 1. Mask every cause and stop both DMA engines, then give bus-master
+//! cycles already in flight time to drain. Firmware (PXE, UEFI UNDI) or a
+//! previous instance can leave RCTL.EN set, and a reset landing mid-DMA
+//! is a known hang on PCI-X parts.
+//! 2. Set CTRL.RST and poll for it to self-clear, reading nothing in the
+//! first microsecond the manual says the part is unreachable.
+//! 3. Wait out the EEPROM auto-load the reset starts. It rewrites RAL0/RAH0
+//! and parts of CTRL, so a MAC or SLU written before it finishes can be
+//! put back to the factory value: unicast then goes to the wrong filter.
+//! 4. Mask again, clear latched causes, and bring the link up.
+
+use nonos_libc::Deadline;
+
+use crate::constants::regs::{REG_CTRL, REG_ICR, REG_IMC, REG_RCTL, REG_STATUS, REG_TCTL};
use crate::constants::status::{CTRL_ASDE, CTRL_LRST, CTRL_RST, CTRL_SLU};
use crate::regs::Regs;
-const RESET_POLL_BUDGET: u32 = 100_000;
+/// Linux e1000_reset_hw's drain before the reset.
+const DMA_DRAIN_MS: u64 = 10;
+/// The part is not addressable for about a microsecond after RST is set.
+const RST_SETTLE_MS: u64 = 1;
+/// Bound on RST self-clearing; it takes microseconds on a working part.
+const RST_CLEAR_MS: u64 = 50;
+/// EEPROM auto-load after a global reset: 5 ms on 82540/82545/82546,
+/// 20 ms on 82541/82547, so the longer one covers every listed part.
+const EEPROM_RELOAD_MS: u64 = 20;
pub fn run(regs: &Regs) -> Result<(), &'static str> {
// SAFETY: eK@nonos.systems — `regs` carries a base from a
// valid broker MmioMap grant; offsets are 32-bit aligned per
// the 8254x manual.
unsafe {
+ // Both engines off. Nothing else is set here: a card that never gets a
+ // station address is left with neither enable bit ever written.
+ regs.w32(REG_IMC, 0xFFFF_FFFF);
+ regs.w32(REG_RCTL, 0);
+ regs.w32(REG_TCTL, 0);
+ let _ = regs.r32(REG_STATUS);
+ hold_ms(DMA_DRAIN_MS);
+
let ctrl = regs.r32(REG_CTRL);
regs.w32(REG_CTRL, ctrl | CTRL_RST);
- let mut spins = 0u32;
+ hold_ms(RST_SETTLE_MS);
+ let deadline = Deadline::after_ms(RST_CLEAR_MS);
while regs.r32(REG_CTRL) & CTRL_RST != 0 {
- spins += 1;
- if spins > RESET_POLL_BUDGET {
+ if deadline.expired() {
return Err("CTRL.RST did not self-clear");
}
core::hint::spin_loop();
}
+ hold_ms(EEPROM_RELOAD_MS);
+
regs.w32(REG_IMC, 0xFFFF_FFFF);
let _ = regs.r32(REG_ICR);
let mut ctrl = regs.r32(REG_CTRL);
@@ -51,3 +78,12 @@ pub fn run(regs: &Regs) -> Result<(), &'static str> {
}
Ok(())
}
+
+// At least `ms` milliseconds: uptime counts whole milliseconds, so a deadline
+// `ms` ahead can fall due up to one early.
+fn hold_ms(ms: u64) {
+ let until = Deadline::after_ms(ms + 1);
+ while !until.expired() {
+ core::hint::spin_loop();
+ }
+}
diff --git a/userland/capsule_driver_e1000/src/init/rx_setup.rs b/userland/capsule_driver_e1000/src/init/rx_setup.rs
index 2e5120112b..8e5438fd1b 100644
--- a/userland/capsule_driver_e1000/src/init/rx_setup.rs
+++ b/userland/capsule_driver_e1000/src/init/rx_setup.rs
@@ -19,6 +19,8 @@
//! and finally enables the receiver via RCTL. RDT points at the
//! last valid descriptor index per the 8254x manual.
+use core::sync::atomic::{fence, Ordering};
+
use crate::constants::queue::{RX_DESC_COUNT, RX_RING_BYTES};
use crate::constants::regs::{REG_RCTL, REG_RDBAH, REG_RDBAL, REG_RDH, REG_RDLEN, REG_RDT};
use crate::constants::status::{RCTL_BAM, RCTL_BSIZE_2048, RCTL_EN, RCTL_SECRC};
@@ -37,6 +39,8 @@ pub fn program(regs: &Regs, rx: &RxRing, ring_phys: u64) {
*d = RxDesc::default();
d.buffer_addr = rx.buffer_phys(i as u16);
}
+ // The ring was written with plain stores; the part reads it from here on.
+ fence(Ordering::Release);
regs.w32(REG_RDBAL, (ring_phys & 0xFFFF_FFFF) as u32);
regs.w32(REG_RDBAH, (ring_phys >> 32) as u32);
regs.w32(REG_RDLEN, RX_RING_BYTES as u32);
diff --git a/userland/capsule_driver_e1000/src/init/tx_setup.rs b/userland/capsule_driver_e1000/src/init/tx_setup.rs
index fd030aa2eb..b6e6710fee 100644
--- a/userland/capsule_driver_e1000/src/init/tx_setup.rs
+++ b/userland/capsule_driver_e1000/src/init/tx_setup.rs
@@ -19,6 +19,8 @@
//! (`0x00602008`), and enables the transmitter via TCTL with the
//! pad-short-packet bit and a 16-retry collision threshold.
+use core::sync::atomic::{fence, Ordering};
+
use crate::constants::queue::{TX_DESC_COUNT, TX_RING_BYTES};
use crate::constants::regs::{
REG_TCTL, REG_TDBAH, REG_TDBAL, REG_TDH, REG_TDLEN, REG_TDT, REG_TIPG,
@@ -39,6 +41,8 @@ pub fn program(regs: &Regs, tx: &TxRing, ring_phys: u64) {
for i in 0..TX_DESC_COUNT {
*descs.add(i) = TxDesc::default();
}
+ // The ring was written with plain stores; the part reads it from here on.
+ fence(Ordering::Release);
regs.w32(REG_TDBAL, (ring_phys & 0xFFFF_FFFF) as u32);
regs.w32(REG_TDBAH, (ring_phys >> 32) as u32);
regs.w32(REG_TDLEN, TX_RING_BYTES as u32);
diff --git a/userland/capsule_driver_e1000/src/protocol/endpoint.rs b/userland/capsule_driver_e1000/src/protocol/endpoint.rs
deleted file mode 100644
index 25488de1b5..0000000000
--- a/userland/capsule_driver_e1000/src/protocol/endpoint.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// NONOS Operating System
-// Copyright (C) 2026 NONOS Contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-//! Reply inbox the kernel-side client owns. Slot 12 in the
-//! per-service reply numbering (ramfs=1, keyring=2, entropy=3,
-//! crypto=4, vfs=5, virtio_rng=6, market=7, virtio_blk=8,
-//! virtio_net=9, ps2_input=A, xhci=B, e1000=C).
-
-pub const KERNEL_REPLY_ENDPOINT: u64 = 0x1_0000_000C;
diff --git a/userland/capsule_driver_e1000/src/protocol/header.rs b/userland/capsule_driver_e1000/src/protocol/header.rs
index 8a0162b7ff..db8682373f 100644
--- a/userland/capsule_driver_e1000/src/protocol/header.rs
+++ b/userland/capsule_driver_e1000/src/protocol/header.rs
@@ -14,7 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-pub const MAGIC: u32 = 0x4E45_3130;
+/// "NNET", the NIC protocol net_core and net_l2 speak (virtio-net's too).
+/// The per-driver tag this replaced made every request from the stack
+/// undecodable, so the wired NICs never served it.
+pub const MAGIC: u32 = 0x4E4E_4554;
pub const VERSION: u16 = 1;
pub const HDR_LEN: usize = 20;
diff --git a/userland/capsule_driver_e1000/src/protocol/mod.rs b/userland/capsule_driver_e1000/src/protocol/mod.rs
index 8f11313832..adce81f355 100644
--- a/userland/capsule_driver_e1000/src/protocol/mod.rs
+++ b/userland/capsule_driver_e1000/src/protocol/mod.rs
@@ -16,7 +16,6 @@
mod decode;
mod encode;
-mod endpoint;
mod errno;
mod header;
mod limits;
@@ -24,7 +23,6 @@ mod ops;
pub use decode::decode_request;
pub use encode::{encode_response_header, write_status};
-pub use endpoint::KERNEL_REPLY_ENDPOINT;
pub use errno::{E_AGAIN, E_INVAL, E_IO, E_MSGSIZE};
pub use header::{Request, HDR_LEN, RESP_HDR_LEN};
pub use limits::{
diff --git a/userland/capsule_driver_e1000/src/queue/rx.rs b/userland/capsule_driver_e1000/src/queue/rx.rs
index 62d0e6f489..1636f1c8d9 100644
--- a/userland/capsule_driver_e1000/src/queue/rx.rs
+++ b/userland/capsule_driver_e1000/src/queue/rx.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+use core::sync::atomic::{fence, Ordering};
+
use crate::constants::queue::{RX_BUFFER_LEN, RX_DESC_COUNT, RX_STATUS_DD, RX_STATUS_EOP};
use crate::constants::MAX_ETHERNET_FRAME;
@@ -60,6 +62,8 @@ impl RxRing {
if status & RX_STATUS_DD == 0 {
return None;
}
+ // Length, errors and the frame are only the part's once DD is seen.
+ fence(Ordering::Acquire);
let errors = unsafe { read_volatile(addr_of!((*desc).errors)) };
let len = unsafe { read_volatile(addr_of!((*desc).length)) };
let idx = self.head;
diff --git a/userland/capsule_driver_e1000/src/queue/tx.rs b/userland/capsule_driver_e1000/src/queue/tx.rs
index df4539ea73..90aac9af95 100644
--- a/userland/capsule_driver_e1000/src/queue/tx.rs
+++ b/userland/capsule_driver_e1000/src/queue/tx.rs
@@ -15,9 +15,16 @@
// along with this program. If not, see .
//! TX ring state. `post` programs the next descriptor with
-//! `EOP|IFCS|RS` and bumps the tail; `done(idx)` polls the
-//! per-slot DD bit so the server loop knows the descriptor and
-//! its buffer can be reused.
+//! `EOP|IFCS|RS` and bumps the tail; `reclaim` walks `clean` forward
+//! over descriptors the part has marked DD, and `full` refuses a post
+//! that would land on one it still owns.
+//!
+//! The part holds descriptors it cannot send: with the link down it stops
+//! DMA and sets no DD, so a slot is only reusable once `reclaim` has seen
+//! it done. One slot always stays empty, or a full ring would move TDT
+//! onto TDH, which the part reads as an empty one.
+
+use core::sync::atomic::{fence, Ordering};
use crate::constants::queue::{
TX_BUFFER_LEN, TX_CMD_EOP, TX_CMD_IFCS, TX_CMD_RS, TX_DESC_COUNT, TX_STATUS_DD,
@@ -31,6 +38,8 @@ pub struct TxRing {
pub buffer_user_va: u64,
pub buffer_device_addr: u64,
pub tail: u16,
+ /// Oldest descriptor not yet seen done; `clean == tail` is an empty ring.
+ pub clean: u16,
}
/*
@@ -41,7 +50,7 @@ pub struct TxRing {
*/
impl TxRing {
pub fn new(ring_user_va: u64, buffer_user_va: u64, buffer_device_addr: u64) -> Self {
- Self { ring_user_va, buffer_user_va, buffer_device_addr, tail: 0 }
+ Self { ring_user_va, buffer_user_va, buffer_device_addr, tail: 0, clean: 0 }
}
/// # Safety
@@ -78,6 +87,20 @@ impl TxRing {
}
pub fn done(&self, idx: u16) -> bool {
- unsafe { read_volatile(addr_of!((*self.descriptor(idx)).status)) & TX_STATUS_DD != 0 }
+ let dd = unsafe { read_volatile(addr_of!((*self.descriptor(idx)).status)) } & TX_STATUS_DD;
+ fence(Ordering::Acquire);
+ dd != 0
+ }
+
+ /// Advance `clean` over every descriptor the part has finished, in order.
+ pub fn reclaim(&mut self) {
+ while self.clean != self.tail && self.done(self.clean) {
+ self.clean = (self.clean + 1) % (TX_DESC_COUNT as u16);
+ }
+ }
+
+ /// Whether posting one more descriptor would reach one the part still owns.
+ pub fn full(&self) -> bool {
+ (self.tail + 1) % (TX_DESC_COUNT as u16) == self.clean
}
}
diff --git a/userland/capsule_driver_e1000/src/server/error.rs b/userland/capsule_driver_e1000/src/server/error.rs
index ae8968207b..c171238450 100644
--- a/userland/capsule_driver_e1000/src/server/error.rs
+++ b/userland/capsule_driver_e1000/src/server/error.rs
@@ -18,19 +18,24 @@
//! the response shape stays uniform: 20-byte echo header followed
//! by a four-byte status code.
-use nonos_libc::mk_ipc_send;
+use nonos_libc::mk_ipc_reply;
-use crate::protocol::{
- encode_response_header, write_status, Request, KERNEL_REPLY_ENDPOINT, RESP_HDR_LEN, STATUS_LEN,
-};
+use crate::protocol::{encode_response_header, write_status, Request, RESP_HDR_LEN, STATUS_LEN};
-pub fn reply_with_status(tx: &mut [u8], req: &Request, status: i32) {
+/// Answer the capsule that sent the request. Replies used to go to a fixed
+/// kernel-side inbox, which nothing reads now that the stack is a capsule,
+/// so every call from net_core timed out.
+pub fn reply(sender: u32, tx: &[u8], len: usize) {
+ let _ = mk_ipc_reply(sender, tx.as_ptr(), len);
+}
+
+pub fn reply_with_status(sender: u32, tx: &mut [u8], req: &Request, status: i32) {
encode_response_header(tx, req, STATUS_LEN as u32);
write_status(&mut tx[RESP_HDR_LEN..], status);
- let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), RESP_HDR_LEN + STATUS_LEN);
+ reply(sender, tx, RESP_HDR_LEN + STATUS_LEN);
}
-pub fn reply_decode_failed(tx: &mut [u8], status: i32) {
+pub fn reply_decode_failed(sender: u32, tx: &mut [u8], status: i32) {
let req = Request { op: 0, flags: 0, request_id: 0, payload_len: 0 };
- reply_with_status(tx, &req, status);
+ reply_with_status(sender, tx, &req, status);
}
diff --git a/userland/capsule_driver_e1000/src/server/handlers/health.rs b/userland/capsule_driver_e1000/src/server/handlers/health.rs
index 74921d372c..bf7e99f5ff 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/health.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/health.rs
@@ -19,6 +19,6 @@
use crate::protocol::Request;
use crate::server::error::reply_with_status;
-pub fn handle(req: &Request, tx: &mut [u8]) {
- reply_with_status(tx, req, 0);
+pub fn handle(sender: u32, req: &Request, tx: &mut [u8]) {
+ reply_with_status(sender, tx, req, 0);
}
diff --git a/userland/capsule_driver_e1000/src/server/handlers/link_status.rs b/userland/capsule_driver_e1000/src/server/handlers/link_status.rs
index 6c3afa4da5..1360ee74e4 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/link_status.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/link_status.rs
@@ -19,17 +19,16 @@
//! sampled on every call so a topology change between two probes
//! is observable to the kernel client.
-use nonos_libc::mk_ipc_send;
-
use crate::constants::regs::REG_STATUS;
use crate::constants::status::STATUS_LU;
use crate::protocol::{
- encode_response_header, write_status, Request, KERNEL_REPLY_ENDPOINT, LINK_STATUS_PAYLOAD_LEN,
- RESP_HDR_LEN, STATUS_LEN,
+ encode_response_header, write_status, Request, LINK_STATUS_PAYLOAD_LEN, RESP_HDR_LEN,
+ STATUS_LEN,
};
+use crate::server::error::reply;
use crate::setup::Driver;
-pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) {
+pub fn handle(sender: u32, driver: &Driver, req: &Request, tx: &mut [u8]) {
// SAFETY: eK@nonos.systems — `driver.regs` carries the broker
// MmioMap base for BAR0; `REG_STATUS` is a 4-byte-aligned offset
// documented in the 8254x manual.
@@ -39,9 +38,5 @@ pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) {
encode_response_header(tx, req, payload_len);
write_status(&mut tx[RESP_HDR_LEN..], 0);
tx[RESP_HDR_LEN + STATUS_LEN] = if up { 1 } else { 0 };
- let _ = mk_ipc_send(
- KERNEL_REPLY_ENDPOINT,
- tx.as_ptr(),
- RESP_HDR_LEN + STATUS_LEN + LINK_STATUS_PAYLOAD_LEN,
- );
+ reply(sender, tx, RESP_HDR_LEN + STATUS_LEN + LINK_STATUS_PAYLOAD_LEN);
}
diff --git a/userland/capsule_driver_e1000/src/server/handlers/mac_address.rs b/userland/capsule_driver_e1000/src/server/handlers/mac_address.rs
index 87e758c55b..a7f826e97c 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/mac_address.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/mac_address.rs
@@ -18,23 +18,18 @@
//! bring-up. The kernel client treats an all-zero MAC as a hard
//! error so a misprogrammed RAL/RAH never silently passes a validation.
-use nonos_libc::mk_ipc_send;
-
use crate::protocol::{
- encode_response_header, write_status, Request, KERNEL_REPLY_ENDPOINT, MAC_ADDRESS_PAYLOAD_LEN,
- RESP_HDR_LEN, STATUS_LEN,
+ encode_response_header, write_status, Request, MAC_ADDRESS_PAYLOAD_LEN, RESP_HDR_LEN,
+ STATUS_LEN,
};
+use crate::server::error::reply;
use crate::setup::Driver;
-pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) {
+pub fn handle(sender: u32, driver: &Driver, req: &Request, tx: &mut [u8]) {
let payload_len = STATUS_LEN as u32 + MAC_ADDRESS_PAYLOAD_LEN as u32;
encode_response_header(tx, req, payload_len);
write_status(&mut tx[RESP_HDR_LEN..], 0);
tx[RESP_HDR_LEN + STATUS_LEN..RESP_HDR_LEN + STATUS_LEN + MAC_ADDRESS_PAYLOAD_LEN]
.copy_from_slice(&driver.mac);
- let _ = mk_ipc_send(
- KERNEL_REPLY_ENDPOINT,
- tx.as_ptr(),
- RESP_HDR_LEN + STATUS_LEN + MAC_ADDRESS_PAYLOAD_LEN,
- );
+ reply(sender, tx, RESP_HDR_LEN + STATUS_LEN + MAC_ADDRESS_PAYLOAD_LEN);
}
diff --git a/userland/capsule_driver_e1000/src/server/handlers/rx_packet.rs b/userland/capsule_driver_e1000/src/server/handlers/rx_packet.rs
index 8e8edc94c7..d6a26a1175 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/rx_packet.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/rx_packet.rs
@@ -14,21 +14,21 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use nonos_libc::mk_ipc_send;
+use core::sync::atomic::{fence, Ordering};
use crate::constants::regs::REG_RDT;
use crate::protocol::{
- encode_response_header, write_status, Request, E_AGAIN, E_IO, KERNEL_REPLY_ENDPOINT,
- RESP_HDR_LEN, RX_PAYLOAD_PREFIX_LEN, STATUS_LEN,
+ encode_response_header, write_status, Request, E_AGAIN, E_IO, RESP_HDR_LEN,
+ RX_PAYLOAD_PREFIX_LEN, STATUS_LEN,
};
-use crate::server::error::reply_with_status;
+use crate::server::error::{reply, reply_with_status};
use crate::setup::Driver;
-pub fn handle(driver: &mut Driver, req: &Request, tx: &mut [u8]) {
+pub fn handle(sender: u32, driver: &mut Driver, req: &Request, tx: &mut [u8]) {
let (idx, len) = match driver.rx.consume() {
Some(p) => p,
None => {
- reply_with_status(tx, req, E_AGAIN);
+ reply_with_status(sender, tx, req, E_AGAIN);
return;
}
};
@@ -36,7 +36,7 @@ pub fn handle(driver: &mut Driver, req: &Request, tx: &mut [u8]) {
unsafe {
driver.regs.w32(REG_RDT, idx as u32);
}
- reply_with_status(tx, req, E_IO);
+ reply_with_status(sender, tx, req, E_IO);
return;
}
let body_len = RX_PAYLOAD_PREFIX_LEN + len as usize;
@@ -55,8 +55,10 @@ pub fn handle(driver: &mut Driver, req: &Request, tx: &mut [u8]) {
unsafe {
core::ptr::copy_nonoverlapping(src, tx[body_off..].as_mut_ptr(), n);
}
+ // The copy out of the buffer ends before the part may write it again.
+ fence(Ordering::Release);
unsafe {
driver.regs.w32(REG_RDT, idx as u32);
}
- let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), prefix_off + body_len);
+ reply(sender, tx, prefix_off + body_len);
}
diff --git a/userland/capsule_driver_e1000/src/server/handlers/stats.rs b/userland/capsule_driver_e1000/src/server/handlers/stats.rs
index f644243a21..d4b56c9713 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/stats.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/stats.rs
@@ -14,17 +14,15 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use nonos_libc::mk_ipc_send;
-
use crate::constants::queue::{RX_DESC_COUNT, TX_DESC_COUNT};
use crate::constants::regs::{REG_RCTL, REG_RDH, REG_RDT, REG_STATUS, REG_TCTL, REG_TDH, REG_TDT};
use crate::protocol::{
- encode_response_header, write_status, Request, KERNEL_REPLY_ENDPOINT, RESP_HDR_LEN,
- STATS_PAYLOAD_LEN, STATUS_LEN,
+ encode_response_header, write_status, Request, RESP_HDR_LEN, STATS_PAYLOAD_LEN, STATUS_LEN,
};
+use crate::server::error::reply;
use crate::setup::Driver;
-pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) {
+pub fn handle(sender: u32, driver: &Driver, req: &Request, tx: &mut [u8]) {
let payload_len = STATUS_LEN as u32 + STATS_PAYLOAD_LEN as u32;
encode_response_header(tx, req, payload_len);
write_status(&mut tx[RESP_HDR_LEN..], 0);
@@ -32,7 +30,7 @@ pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) {
for v in live_regs(driver) {
put32(tx, &mut o, v);
}
- let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), RESP_HDR_LEN + payload_len as usize);
+ reply(sender, tx, RESP_HDR_LEN + payload_len as usize);
}
fn live_regs(driver: &Driver) -> [u32; 12] {
diff --git a/userland/capsule_driver_e1000/src/server/handlers/tx_packet.rs b/userland/capsule_driver_e1000/src/server/handlers/tx_packet.rs
index 434b876f60..c0db5d640f 100644
--- a/userland/capsule_driver_e1000/src/server/handlers/tx_packet.rs
+++ b/userland/capsule_driver_e1000/src/server/handlers/tx_packet.rs
@@ -14,25 +14,37 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+use core::sync::atomic::{fence, Ordering};
+
use crate::constants::queue::TX_DESC_COUNT;
use crate::constants::regs::REG_TDT;
use crate::constants::{MAX_ETHERNET_FRAME, MIN_ETHERNET_FRAME};
-use crate::protocol::{Request, E_INVAL, E_IO, E_MSGSIZE, MAX_TX_PAYLOAD_BYTES};
+use crate::protocol::{Request, E_AGAIN, E_INVAL, E_MSGSIZE, MAX_TX_PAYLOAD_BYTES};
use crate::server::error::reply_with_status;
use crate::setup::Driver;
-const TX_DD_POLL_BUDGET: u32 = 1_000_000;
-
-pub fn handle(driver: &mut Driver, req: &Request, body: &[u8], tx: &mut [u8]) {
+/*
+ * A frame is answered once it is queued, not once it is on the wire. Waiting
+ * for DD held the caller for as long as the link was down, reported E_IO for a
+ * frame the part still sent later (so a retry sent it twice), and left the
+ * descriptor posted for the next call to overwrite. Completion is now what
+ * `reclaim` observes, and a ring with no free slot says so.
+ */
+pub fn handle(sender: u32, driver: &mut Driver, req: &Request, body: &[u8], tx: &mut [u8]) {
if req.payload_len as usize != body.len() {
- reply_with_status(tx, req, E_MSGSIZE);
+ reply_with_status(sender, tx, req, E_MSGSIZE);
return;
}
if body.len() < MIN_ETHERNET_FRAME
|| body.len() > MAX_ETHERNET_FRAME
|| body.len() as u32 > MAX_TX_PAYLOAD_BYTES
{
- reply_with_status(tx, req, E_INVAL);
+ reply_with_status(sender, tx, req, E_INVAL);
+ return;
+ }
+ driver.tx.reclaim();
+ if driver.tx.full() {
+ reply_with_status(sender, tx, req, E_AGAIN);
return;
}
let dst = driver.tx.buffer_va(driver.tx.tail) as *mut u8;
@@ -41,17 +53,10 @@ pub fn handle(driver: &mut Driver, req: &Request, body: &[u8], tx: &mut [u8]) {
}
let idx = driver.tx.post(body.len() as u16);
let next_tdt = ((idx as u32) + 1) % (TX_DESC_COUNT as u32);
+ // The frame bytes are plain stores; the tail write is what lets the part read them.
+ fence(Ordering::Release);
unsafe {
driver.regs.w32(REG_TDT, next_tdt);
}
- let mut spins = 0u32;
- while !driver.tx.done(idx) {
- spins += 1;
- if spins > TX_DD_POLL_BUDGET {
- reply_with_status(tx, req, E_IO);
- return;
- }
- core::hint::spin_loop();
- }
- reply_with_status(tx, req, 0);
+ reply_with_status(sender, tx, req, 0);
}
diff --git a/userland/capsule_driver_e1000/src/server/runner.rs b/userland/capsule_driver_e1000/src/server/runner.rs
index e12d35eaa0..2b8037790f 100644
--- a/userland/capsule_driver_e1000/src/server/runner.rs
+++ b/userland/capsule_driver_e1000/src/server/runner.rs
@@ -16,7 +16,7 @@
use alloc::vec;
-use nonos_libc::mk_ipc_recv;
+use nonos_libc::mk_ipc_recv_from;
use crate::constants::MAX_ETHERNET_FRAME;
use crate::protocol::{
@@ -39,32 +39,33 @@ pub fn run(driver: &mut Driver) -> ! {
let mut tx = vec![0u8; tx_len];
loop {
- let n = mk_ipc_recv(SERVICE_INBOX, rx.as_mut_ptr(), rx_len, 0);
- if n <= 0 {
+ let mut sender: u32 = 0;
+ let n = mk_ipc_recv_from(SERVICE_INBOX, rx.as_mut_ptr(), rx_len, 0, &mut sender);
+ if n <= 0 || sender == 0 {
continue;
}
let len = n as usize;
let req = match decode_request(&rx[..len]) {
Some(r) => r,
None => {
- reply_decode_failed(&mut tx, E_INVAL);
+ reply_decode_failed(sender, &mut tx, E_INVAL);
continue;
}
};
let body_end = HDR_LEN.saturating_add(req.payload_len as usize);
if body_end != len {
- reply_with_status(&mut tx, &req, E_MSGSIZE);
+ reply_with_status(sender, &mut tx, &req, E_MSGSIZE);
continue;
}
let body = &rx[HDR_LEN..body_end];
match req.op {
- OP_HEALTHCHECK => handlers::health::handle(&req, &mut tx),
- OP_LINK_STATUS => handlers::link_status::handle(driver, &req, &mut tx),
- OP_MAC_ADDRESS => handlers::mac_address::handle(driver, &req, &mut tx),
- OP_TX_PACKET => handlers::tx_packet::handle(driver, &req, body, &mut tx),
- OP_RX_PACKET => handlers::rx_packet::handle(driver, &req, &mut tx),
- OP_STATS => handlers::stats::handle(driver, &req, &mut tx),
- _ => reply_with_status(&mut tx, &req, E_INVAL),
+ OP_HEALTHCHECK => handlers::health::handle(sender, &req, &mut tx),
+ OP_LINK_STATUS => handlers::link_status::handle(sender, driver, &req, &mut tx),
+ OP_MAC_ADDRESS => handlers::mac_address::handle(sender, driver, &req, &mut tx),
+ OP_TX_PACKET => handlers::tx_packet::handle(sender, driver, &req, body, &mut tx),
+ OP_RX_PACKET => handlers::rx_packet::handle(sender, driver, &req, &mut tx),
+ OP_STATS => handlers::stats::handle(sender, driver, &req, &mut tx),
+ _ => reply_with_status(sender, &mut tx, &req, E_INVAL),
}
}
}
diff --git a/userland/capsule_driver_e1000/src/setup/dma.rs b/userland/capsule_driver_e1000/src/setup/dma.rs
index 3a1cea2ffa..ad572b6483 100644
--- a/userland/capsule_driver_e1000/src/setup/dma.rs
+++ b/userland/capsule_driver_e1000/src/setup/dma.rs
@@ -19,7 +19,7 @@
//! every prior grant in reverse on failure so the broker never
//! holds a partial setup.
-use nonos_libc::{mk_dma_map, DmaMapOut, IrqBindOut, MmioMapOut};
+use nonos_libc::{mk_dma_map, DmaMapOut, MmioMapOut};
use crate::constants::queue::{
RX_BUFFER_POOL_BYTES, RX_RING_BYTES, TX_BUFFER_POOL_BYTES, TX_RING_BYTES,
@@ -48,27 +48,21 @@ pub fn map_rings_and_buffers(
device_id: u64,
claim_epoch: u64,
mmio: &MmioMapOut,
- irq: &IrqBindOut,
) -> Result<(DmaMapOut, DmaMapOut, DmaMapOut, DmaMapOut), &'static str> {
let rx_ring = alloc(device_id, claim_epoch, RX_RING_BYTES as u64).ok_or_else(|| {
- rollback::after(device_id, mmio, irq, &[]);
+ rollback::after(device_id, mmio, &[]);
"dma map failed (rx ring)"
})?;
let rx_buf = alloc(device_id, claim_epoch, RX_BUFFER_POOL_BYTES as u64).ok_or_else(|| {
- rollback::after(device_id, mmio, irq, &[rx_ring.grant_id]);
+ rollback::after(device_id, mmio, &[rx_ring.grant_id]);
"dma map failed (rx buffers)"
})?;
let tx_ring = alloc(device_id, claim_epoch, TX_RING_BYTES as u64).ok_or_else(|| {
- rollback::after(device_id, mmio, irq, &[rx_buf.grant_id, rx_ring.grant_id]);
+ rollback::after(device_id, mmio, &[rx_buf.grant_id, rx_ring.grant_id]);
"dma map failed (tx ring)"
})?;
let tx_buf = alloc(device_id, claim_epoch, TX_BUFFER_POOL_BYTES as u64).ok_or_else(|| {
- rollback::after(
- device_id,
- mmio,
- irq,
- &[tx_ring.grant_id, rx_buf.grant_id, rx_ring.grant_id],
- );
+ rollback::after(device_id, mmio, &[tx_ring.grant_id, rx_buf.grant_id, rx_ring.grant_id]);
"dma map failed (tx buffers)"
})?;
Ok((rx_ring, rx_buf, tx_ring, tx_buf))
diff --git a/userland/capsule_driver_e1000/src/setup/driver.rs b/userland/capsule_driver_e1000/src/setup/driver.rs
index dfd9be9c89..e406025be1 100644
--- a/userland/capsule_driver_e1000/src/setup/driver.rs
+++ b/userland/capsule_driver_e1000/src/setup/driver.rs
@@ -19,7 +19,7 @@
//! shutdown releases the grants in reverse order so the broker
//! sees a clean teardown even when the capsule exits voluntarily.
-use nonos_libc::{mk_device_release, mk_dma_unmap, mk_irq_unbind, mk_mmio_unmap};
+use nonos_libc::{mk_device_release, mk_dma_unmap, mk_mmio_unmap};
use crate::constants::MAC_LEN;
use crate::queue::{RxRing, TxRing};
@@ -28,7 +28,6 @@ use crate::regs::Regs;
pub struct Driver {
pub device_id: u64,
pub mmio_grant: u64,
- pub irq_grant: u64,
pub rx_ring_grant: u64,
pub rx_buffer_grant: u64,
pub tx_ring_grant: u64,
@@ -51,7 +50,6 @@ impl Driver {
let _ = mk_dma_unmap(self.tx_ring_grant);
let _ = mk_dma_unmap(self.rx_buffer_grant);
let _ = mk_dma_unmap(self.rx_ring_grant);
- let _ = mk_irq_unbind(self.irq_grant);
let _ = mk_mmio_unmap(self.mmio_grant);
let _ = mk_device_release(self.device_id);
}
diff --git a/userland/capsule_driver_e1000/src/setup/irq.rs b/userland/capsule_driver_e1000/src/setup/irq.rs
deleted file mode 100644
index 6934759091..0000000000
--- a/userland/capsule_driver_e1000/src/setup/irq.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-// NONOS Operating System
-// Copyright (C) 2026 NONOS Contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-//! IRQ phase. Bind the device's INTx line to a broker IRQ slot.
-//! On failure the prior MMIO grant is unmapped and the device
-//! claim released so the broker is never left holding a partial
-//! setup. MSI-X migration is a separate slice.
-
-use nonos_libc::{mk_device_release, mk_irq_bind, mk_mmio_unmap, IrqBindOut, MmioMapOut};
-
-use crate::discover::Found;
-
-pub fn bind(dev: Found, claim_epoch: u64, mmio: &MmioMapOut) -> Result {
- let mut out = IrqBindOut { grant_id: 0, vector: 0 };
- let r = mk_irq_bind(dev.device_id, claim_epoch, dev.irq_line as u32, 0, 0, &mut out);
- if r < 0 {
- let _ = mk_mmio_unmap(mmio.grant_id);
- let _ = mk_device_release(dev.device_id);
- return Err("irq bind failed");
- }
- Ok(out)
-}
diff --git a/userland/capsule_driver_e1000/src/setup/mod.rs b/userland/capsule_driver_e1000/src/setup/mod.rs
index 0424c3f97e..3edb2f738f 100644
--- a/userland/capsule_driver_e1000/src/setup/mod.rs
+++ b/userland/capsule_driver_e1000/src/setup/mod.rs
@@ -17,7 +17,6 @@
mod claim;
mod dma;
mod driver;
-mod irq;
mod mmio;
mod rollback;
mod sequence;
diff --git a/userland/capsule_driver_e1000/src/setup/rollback.rs b/userland/capsule_driver_e1000/src/setup/rollback.rs
index 9836204b80..831df63335 100644
--- a/userland/capsule_driver_e1000/src/setup/rollback.rs
+++ b/userland/capsule_driver_e1000/src/setup/rollback.rs
@@ -18,15 +18,12 @@
//! fails partway. Best-effort: an `EINVAL` from a doubly-released
//! grant is harmless because the broker has already revoked it.
-use nonos_libc::{
- mk_device_release, mk_dma_unmap, mk_irq_unbind, mk_mmio_unmap, IrqBindOut, MmioMapOut,
-};
+use nonos_libc::{mk_device_release, mk_dma_unmap, mk_mmio_unmap, MmioMapOut};
-pub fn after(device_id: u64, mmio: &MmioMapOut, irq: &IrqBindOut, dma_grants: &[u64]) {
+pub fn after(device_id: u64, mmio: &MmioMapOut, dma_grants: &[u64]) {
for &g in dma_grants.iter().rev() {
let _ = mk_dma_unmap(g);
}
- let _ = mk_irq_unbind(irq.grant_id);
let _ = mk_mmio_unmap(mmio.grant_id);
let _ = mk_device_release(device_id);
}
diff --git a/userland/capsule_driver_e1000/src/setup/sequence.rs b/userland/capsule_driver_e1000/src/setup/sequence.rs
index 93ea12ca45..b123c3c50b 100644
--- a/userland/capsule_driver_e1000/src/setup/sequence.rs
+++ b/userland/capsule_driver_e1000/src/setup/sequence.rs
@@ -14,11 +14,16 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-//! End-to-end broker handshake: discover -> claim -> MMIO -> IRQ
-//! -> RX ring DMA -> RX buffer DMA -> TX ring DMA -> TX buffer
-//! DMA. Returns a `Driver` with all grants taken and ring states
-//! initialised; the hardware bring-up step in `init` programs the
-//! device against those rings.
+//! End-to-end broker handshake: discover -> claim -> MMIO -> RX ring
+//! DMA -> RX buffer DMA -> TX ring DMA -> TX buffer DMA. Returns a
+//! `Driver` with all grants taken and ring states initialised; the
+//! hardware bring-up step in `init` programs the device against those
+//! rings.
+//!
+//! No interrupt line is bound. The driver polls and never sets IMS, and a
+//! bound INTx line is masked until acked: holding one it never services
+//! starved any other device sharing that line, and a failed bind (line
+//! already held, or reserved) took down a NIC that needed no interrupt.
use crate::constants::MAC_LEN;
use crate::discover::find_e1000;
@@ -26,19 +31,17 @@ use crate::queue::{RxRing, TxRing};
use crate::regs::Regs;
use super::driver::Driver;
-use super::{claim, dma, irq, mmio};
+use super::{claim, dma, mmio};
pub fn run() -> Result {
let dev = find_e1000().ok_or("no e1000 device")?;
let claim_epoch = claim::claim(dev.device_id)?;
let mmio_grant = mmio::map(dev, claim_epoch)?;
- let irq_grant = irq::bind(dev, claim_epoch, &mmio_grant)?;
let (rx_ring, rx_buf, tx_ring, tx_buf) =
- dma::map_rings_and_buffers(dev.device_id, claim_epoch, &mmio_grant, &irq_grant)?;
+ dma::map_rings_and_buffers(dev.device_id, claim_epoch, &mmio_grant)?;
Ok(Driver {
device_id: dev.device_id,
mmio_grant: mmio_grant.grant_id,
- irq_grant: irq_grant.grant_id,
rx_ring_grant: rx_ring.grant_id,
rx_buffer_grant: rx_buf.grant_id,
tx_ring_grant: tx_ring.grant_id,
diff --git a/userland/capsule_driver_iwlwifi/src/constants/mod.rs b/userland/capsule_driver_iwlwifi/src/constants/mod.rs
index 12c3ccb2ea..ceb6063ce4 100644
--- a/userland/capsule_driver_iwlwifi/src/constants/mod.rs
+++ b/userland/capsule_driver_iwlwifi/src/constants/mod.rs
@@ -53,10 +53,16 @@ pub const CSR_INT_MASK: usize = 0x00C;
pub const CSR_FH_INT_STATUS: usize = 0x010;
pub const CSR_GP_CNTRL: usize = 0x024;
pub const CSR_HW_REV: usize = 0x028;
-pub const GP_CNTRL_MAC_CLOCK_READY: u32 = 0x0000_0002;
+// The CSR_GP_CNTRL layout of Linux iwl_csr_v1, which covers every family
+// probed here up to AX210. Bit 1 is undefined there: polling it for the MAC
+// clock timed out on every card, so setup never got past this register.
+// Bz-family parts (BE200) use the v2 layout, which is not implemented.
+pub const GP_CNTRL_MAC_CLOCK_READY: u32 = 0x0000_0001;
pub const GP_CNTRL_INIT_DONE: u32 = 0x0000_0004;
pub const GP_CNTRL_MAC_ACCESS_REQ: u32 = 0x0000_0008;
pub const GP_CNTRL_XTAL_ON: u32 = 0x0000_0400;
+/// Set while the hardware RF-kill switch lets the radio on.
+pub const GP_CNTRL_HW_RF_KILL_SW: u32 = 0x0800_0000;
pub const ALL_INTS_MASK: u32 = 0xFFFF_FFFF;
pub const INT_MASK_DISABLED: u32 = 0;
pub const INT_COALESCING_TIMEOUT: u32 = 64;
@@ -66,7 +72,8 @@ pub const ALIVE_POLL_ITERS: usize = 2_000_000;
pub const IWL_FW_MAGIC: u32 = 0x0A4C_5749;
pub const FW_API_VERSION_MASK: u32 = 0xFFFF;
pub const MIN_FW_API_VERSION: u16 = 22;
-pub const MAX_FW_API_VERSION: u16 = 77;
+/// The newest image bundled (so-a0-gf-a0-86); 77 refused it outright.
+pub const MAX_FW_API_VERSION: u16 = 86;
// Host-command / transmit-queue interface. Once the firmware is alive, the
// driver hands it commands through a TFD ring per transmit queue. The
diff --git a/userland/capsule_driver_iwlwifi/src/firmware/stage/stage_firmware.rs b/userland/capsule_driver_iwlwifi/src/firmware/stage/stage_firmware.rs
index d084606842..8b1f0632ad 100644
--- a/userland/capsule_driver_iwlwifi/src/firmware/stage/stage_firmware.rs
+++ b/userland/capsule_driver_iwlwifi/src/firmware/stage/stage_firmware.rs
@@ -17,7 +17,9 @@
use super::count_section::count_section;
use super::stage_section::stage_section;
use super::state::FirmwareStageState;
-use crate::firmware::tlv::{le32, parse_header, TLV_PAGING, TLV_SEC_INIT, TLV_SEC_RT};
+use crate::firmware::tlv::{
+ le32, parse_header, TLV_HEADER_LEN, TLV_PAGING, TLV_SEC_INIT, TLV_SEC_RT,
+};
pub fn stage_firmware(data: &[u8], dma_user_va: u64, dma_len: u64) -> Option {
let h = parse_header(data)?;
@@ -28,7 +30,7 @@ pub fn stage_firmware(data: &[u8], dma_user_va: u64, dma_len: u64) -> Option Option {
- if data.len() < 20 {
+ if data.len() < TLV_HEADER_LEN {
return None;
}
let zero = le32(data, 0)?;
@@ -23,7 +35,7 @@ pub fn parse_header(data: &[u8]) -> Option {
if zero != 0 || magic != IWL_FW_MAGIC {
return None;
}
- let ver = le32(data, 8)?;
+ let ver = le32(data, VER_OFF)?;
let api = (ver & FW_API_VERSION_MASK) as u16;
if !(MIN_FW_API_VERSION..=MAX_FW_API_VERSION).contains(&api) {
return None;
@@ -32,7 +44,7 @@ pub fn parse_header(data: &[u8]) -> Option {
major: ((ver >> 24) & 0xFF) as u16,
minor: ((ver >> 16) & 0xFF) as u16,
api,
- build: le32(data, 12)?,
+ build: le32(data, BUILD_OFF)?,
})
}
diff --git a/userland/capsule_driver_iwlwifi/src/init.rs b/userland/capsule_driver_iwlwifi/src/init.rs
index be4b28a087..9629ef0af4 100644
--- a/userland/capsule_driver_iwlwifi/src/init.rs
+++ b/userland/capsule_driver_iwlwifi/src/init.rs
@@ -8,8 +8,9 @@
use crate::constants::{
ALL_INTS_MASK, APM_POLL_ITERS, CSR_FH_INT_STATUS, CSR_GP_CNTRL, CSR_HW_REV, CSR_INT,
- CSR_INT_COALESCING, CSR_INT_MASK, GP_CNTRL_INIT_DONE, GP_CNTRL_MAC_ACCESS_REQ,
- GP_CNTRL_MAC_CLOCK_READY, GP_CNTRL_XTAL_ON, INT_COALESCING_TIMEOUT, INT_MASK_DISABLED,
+ CSR_INT_COALESCING, CSR_INT_MASK, GP_CNTRL_HW_RF_KILL_SW, GP_CNTRL_INIT_DONE,
+ GP_CNTRL_MAC_ACCESS_REQ, GP_CNTRL_MAC_CLOCK_READY, GP_CNTRL_XTAL_ON, INT_COALESCING_TIMEOUT,
+ INT_MASK_DISABLED,
};
use crate::regs::Regs;
@@ -34,6 +35,8 @@ pub fn bring_up(regs: Regs) -> Result {
Ok(InitState {
hw_rev: regs.read32(CSR_HW_REV),
gp_cntrl,
- rf_kill: gp_cntrl & GP_CNTRL_INIT_DONE == 0,
+ // INIT_DONE is the bit this function just set, so it said nothing
+ // about the airplane-mode switch; this is the bit that does.
+ rf_kill: gp_cntrl & GP_CNTRL_HW_RF_KILL_SW == 0,
})
}
diff --git a/userland/capsule_driver_rtl8139/Capsule.mk b/userland/capsule_driver_rtl8139/Capsule.mk
index 4e73684ba8..b476afe6d3 100644
--- a/userland/capsule_driver_rtl8139/Capsule.mk
+++ b/userland/capsule_driver_rtl8139/Capsule.mk
@@ -1,4 +1,4 @@
-# RTL8139 — Realtek 8139 Fast Ethernet NIC. PCI PIO + INTx + DMA.
+# RTL8139 — Realtek 8139 Fast Ethernet NIC. PCI PIO + DMA, polled.
# Frame-level transport only: no socket, routing, ARP, or IP policy.
# Signing, certificate, manifest, and trust-anchor flow are inherited
# from nonos-mk/capsule.mk.
@@ -12,7 +12,10 @@ CAPSULE_FEATURE := nonos-capsule-driver-rtl8139
CAPSULE_NAMESPACE := systems.nonos.driver.rtl8139_0
CAPSULE_SERVICE_ENDPOINT := service:4212:driver.rtl8139_0
CAPSULE_REPLY_ENDPOINT := reply:4213:endpoint.4294967309
-# IPC|Memory|Driver|DeviceEnum|Irq|Dma|Pio = 0x1D8019
-CAPSULE_REQUIRED_CAPS := 0x1D8019
+# IPC|Memory|Crypto|Driver|DeviceEnum|Dma|Pio = 0x198039
+# Crypto (0x20) is what the CryptoRandom syscall is gated on. The station address
+# is drawn rather than read out of the IDR, and that draw fails closed, so
+# without this the card never comes up. No Irq: the driver polls.
+CAPSULE_REQUIRED_CAPS := 0x198039
include nonos-mk/capsule.mk
diff --git a/userland/capsule_driver_rtl8139/Cargo.lock b/userland/capsule_driver_rtl8139/Cargo.lock
index 4d4de1200e..4213cd65e0 100644
--- a/userland/capsule_driver_rtl8139/Cargo.lock
+++ b/userland/capsule_driver_rtl8139/Cargo.lock
@@ -24,9 +24,14 @@ dependencies = [
name = "nonos_capsule_driver_rtl8139"
version = "0.3.0"
dependencies = [
+ "nonos_mac",
"nonos_userland_libc",
]
+[[package]]
+name = "nonos_mac"
+version = "0.3.0"
+
[[package]]
name = "nonos_userland_libc"
version = "0.3.0"
diff --git a/userland/capsule_driver_rtl8139/Cargo.toml b/userland/capsule_driver_rtl8139/Cargo.toml
index 549d0914cc..b0f682d287 100644
--- a/userland/capsule_driver_rtl8139/Cargo.toml
+++ b/userland/capsule_driver_rtl8139/Cargo.toml
@@ -16,6 +16,7 @@ path = "src/main.rs"
[dependencies]
nonos_libc = { package = "nonos_userland_libc", path = "../libc" }
+nonos_mac = { path = "../nonos_mac" }
[profile.release]
panic = "abort"
diff --git a/userland/capsule_driver_rtl8139/src/constants/frame.rs b/userland/capsule_driver_rtl8139/src/constants/frame.rs
index 7414c0b3ad..3e464523c2 100644
--- a/userland/capsule_driver_rtl8139/src/constants/frame.rs
+++ b/userland/capsule_driver_rtl8139/src/constants/frame.rs
@@ -15,5 +15,10 @@
// along with this program. If not, see .
pub const MAC_LEN: usize = 6;
-pub const MIN_ETHERNET_FRAME: usize = 60;
+/// A bare header is the shortest frame taken. ARP (42 bytes) and a bare TCP
+/// ACK (54) are shorter than the wire minimum, and refusing them stranded
+/// IPv4 right after DHCP.
+pub const MIN_ETHERNET_FRAME: usize = 14;
+/// The part does not pad short frames itself, so `send` does, to this.
+pub const MIN_WIRE_FRAME: usize = 60;
pub const MAX_ETHERNET_FRAME: usize = 1514;
diff --git a/userland/capsule_driver_rtl8139/src/constants/mod.rs b/userland/capsule_driver_rtl8139/src/constants/mod.rs
index 6a15762bdd..a6ce0b0994 100644
--- a/userland/capsule_driver_rtl8139/src/constants/mod.rs
+++ b/userland/capsule_driver_rtl8139/src/constants/mod.rs
@@ -19,4 +19,4 @@ mod frame;
pub mod pci;
pub mod regs;
-pub use frame::{MAC_LEN, MAX_ETHERNET_FRAME, MIN_ETHERNET_FRAME};
+pub use frame::{MAC_LEN, MAX_ETHERNET_FRAME, MIN_ETHERNET_FRAME, MIN_WIRE_FRAME};
diff --git a/userland/capsule_driver_rtl8139/src/constants/regs.rs b/userland/capsule_driver_rtl8139/src/constants/regs.rs
index 5735373bda..4359bce24b 100644
--- a/userland/capsule_driver_rtl8139/src/constants/regs.rs
+++ b/userland/capsule_driver_rtl8139/src/constants/regs.rs
@@ -24,8 +24,13 @@ pub const REG_IMR: u16 = 0x3C;
pub const REG_ISR: u16 = 0x3E;
pub const REG_TCR: u16 = 0x40;
pub const REG_RCR: u16 = 0x44;
+/// EEPROM command register, which holds the config-write lock over IDR.
+pub const REG_CFG9346: u16 = 0x50;
pub const REG_MSR: u16 = 0x58;
+pub const CFG9346_UNLOCK: u8 = 0xC0;
+pub const CFG9346_LOCK: u8 = 0x00;
+
pub const CMD_RESET: u8 = 0x10;
pub const CMD_RX_ENABLE: u8 = 0x08;
pub const CMD_TX_ENABLE: u8 = 0x04;
@@ -47,8 +52,16 @@ pub const RCR_ACCEPT_MULTI: u32 = 1 << 2;
pub const RCR_ACCEPT_BCAST: u32 = 1 << 3;
pub const RCR_WRAP: u32 = 1 << 7;
pub const RCR_MXDMA_UNLIMITED: u32 = 7 << 8;
+/// RBLEN = 10, a 32K+16 ring. Left at 00 the part wraps at 8K while every
+/// offset here is taken against 32K, and receive stops after about 8 KB.
+pub const RCR_RBLEN_32K: u32 = 0b10 << 11;
pub const TCR_MXDMA_UNLIMITED: u32 = 7 << 8;
+/// Restarts a transmitter halted by an aborted frame.
+pub const TCR_CLEAR_ABORT: u32 = 1 << 0;
pub const TX_STATUS_OK: u32 = 1 << 15;
pub const TX_STATUS_UNDERRUN: u32 = 1 << 14;
pub const TX_STATUS_ABORT: u32 = 1 << 30;
+/// TSD early-transmit threshold in 32-byte units: 8 is 256 bytes, where
+/// Linux 8139too starts. Zero is 8 bytes, which underruns on a busy bus.
+pub const TSD_ERTXTH_256: u32 = 8 << 16;
diff --git a/userland/capsule_driver_rtl8139/src/discover.rs b/userland/capsule_driver_rtl8139/src/discover.rs
index 3c139eeb72..a79bcb8d2a 100644
--- a/userland/capsule_driver_rtl8139/src/discover.rs
+++ b/userland/capsule_driver_rtl8139/src/discover.rs
@@ -28,7 +28,6 @@ const MAX_DEVICES: usize = 32;
#[derive(Debug, Clone, Copy)]
pub struct Found {
pub device_id: u64,
- pub irq_line: u8,
pub pio_bar_index: u8,
pub command_bits: u16,
}
@@ -43,13 +42,12 @@ pub fn find_rtl8139() -> Option {
if !is_supported(r) {
continue;
}
- if r.irq_pin == 0 || r.irq_line == 0xFF {
- continue;
- }
+ // Interrupt routing is not asked for: the driver polls. UEFI firmware
+ // often leaves Interrupt Line at 0xFF, and filtering on it skipped a
+ // present RTL8139 as absent.
if let Some(pio_bar_index) = first_pio_bar(r) {
return Some(Found {
device_id: r.device_id,
- irq_line: r.irq_line,
pio_bar_index,
command_bits: command_bits(r),
});
diff --git a/userland/capsule_driver_rtl8139/src/init/mac.rs b/userland/capsule_driver_rtl8139/src/init/mac.rs
index 5b55b88bf9..976ff4a405 100644
--- a/userland/capsule_driver_rtl8139/src/init/mac.rs
+++ b/userland/capsule_driver_rtl8139/src/init/mac.rs
@@ -14,18 +14,43 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use crate::constants::regs::REG_MAC0;
+use nonos_mac::apply;
+
+use crate::constants::regs::{CFG9346_LOCK, CFG9346_UNLOCK, REG_CFG9346, REG_MAC0};
use crate::constants::MAC_LEN;
use crate::pio::Pio;
-pub fn read(pio: &Pio) -> Result<[u8; MAC_LEN], &'static str> {
+/// Draw a station address and program it into the IDR registers.
+///
+/// Replaces reading the factory address out of them: that address is unique to
+/// the chip and every network the machine joins would log it. Fails closed, as
+/// the other drivers do; the factory address is not a fallback.
+pub fn program(pio: &Pio) -> Result<[u8; MAC_LEN], &'static str> {
let mut mac = [0u8; MAC_LEN];
- for (i, byte) in mac.iter_mut().enumerate() {
+ let rc = nonos_libc::crypto_random(mac.as_mut_ptr(), MAC_LEN);
+ if rc < 0 || (rc as usize) != MAC_LEN {
+ return Err("rtl8139 no entropy for station address");
+ }
+ apply(&mut mac);
+
+ // IDR takes writes only with the config lock open, as dwords, the way
+ // 8139too's set_mac_address writes it; the lock closes on every path.
+ pio.w8(REG_CFG9346, CFG9346_UNLOCK)?;
+ let wrote = write_idr(pio, &mac);
+ pio.w8(REG_CFG9346, CFG9346_LOCK)?;
+ wrote?;
+
+ let mut readback = [0u8; MAC_LEN];
+ for (i, byte) in readback.iter_mut().enumerate() {
*byte = pio.r8(REG_MAC0 + i as u16)?;
}
- if mac == [0; MAC_LEN] || mac == [0xFF; MAC_LEN] {
- Err("rtl8139 invalid mac")
- } else {
- Ok(mac)
+ if readback != mac {
+ return Err("rtl8139 station address did not take");
}
+ Ok(mac)
+}
+
+fn write_idr(pio: &Pio, mac: &[u8; MAC_LEN]) -> Result<(), &'static str> {
+ pio.w32(REG_MAC0, u32::from_le_bytes([mac[0], mac[1], mac[2], mac[3]]))?;
+ pio.w32(REG_MAC0 + 4, mac[4] as u32 | (mac[5] as u32) << 8)
}
diff --git a/userland/capsule_driver_rtl8139/src/init/mod.rs b/userland/capsule_driver_rtl8139/src/init/mod.rs
index 4b46526d8c..86000769a7 100644
--- a/userland/capsule_driver_rtl8139/src/init/mod.rs
+++ b/userland/capsule_driver_rtl8139/src/init/mod.rs
@@ -21,3 +21,5 @@ mod rx_setup;
mod tx_setup;
pub use run::bring_up;
+pub use rx_setup::restart as restart_rx;
+pub use tx_setup::TCR;
diff --git a/userland/capsule_driver_rtl8139/src/init/run.rs b/userland/capsule_driver_rtl8139/src/init/run.rs
index fe4b82d7db..b4d31f133e 100644
--- a/userland/capsule_driver_rtl8139/src/init/run.rs
+++ b/userland/capsule_driver_rtl8139/src/init/run.rs
@@ -15,18 +15,30 @@
// along with this program. If not, see .
use crate::constants::regs::{
- CMD_RX_ENABLE, CMD_TX_ENABLE, ISR_ENABLED, REG_CMD, REG_IMR, REG_ISR,
+ CMD_RX_ENABLE, CMD_TX_ENABLE, REG_CMD, REG_IMR, REG_ISR,
};
use crate::setup::Driver;
use super::{mac, reset, rx_setup, tx_setup};
+/*
+ * Rx and Tx are enabled before RCR and TCR are written. Linux 8139too, and
+ * the BSD rl and rtk drivers, all do it in this order ("Must enable Tx/Rx
+ * before setting transfer thresholds!"): on silicon where those writes do
+ * not take while the engines are off, RCR keeps its reset value and accepts
+ * nothing.
+ */
pub fn bring_up(driver: &mut Driver) -> Result<(), &'static str> {
reset::run(&driver.pio)?;
- driver.mac = mac::read(&driver.pio)?;
+ driver.mac = mac::program(&driver.pio)?;
rx_setup::program(driver)?;
tx_setup::program(driver)?;
+ driver.pio.w8(REG_CMD, CMD_RX_ENABLE | CMD_TX_ENABLE)?;
+ rx_setup::configure(driver)?;
+ tx_setup::configure(driver)?;
driver.pio.w16(REG_ISR, 0xFFFF)?;
- driver.pio.w16(REG_IMR, ISR_ENABLED)?;
- driver.pio.w8(REG_CMD, CMD_RX_ENABLE | CMD_TX_ENABLE)
+ // The driver polls and binds no line, so the part raises none: an
+ // unmasked source nobody services holds a shared INTx asserted for
+ // every other device on it. ISR still latches, which is all it reads.
+ driver.pio.w16(REG_IMR, 0)
}
diff --git a/userland/capsule_driver_rtl8139/src/init/rx_setup.rs b/userland/capsule_driver_rtl8139/src/init/rx_setup.rs
index 59aa1b06c6..8b6f8a937f 100644
--- a/userland/capsule_driver_rtl8139/src/init/rx_setup.rs
+++ b/userland/capsule_driver_rtl8139/src/init/rx_setup.rs
@@ -16,19 +16,42 @@
use crate::constants::dma::RX_BUF_DATA_BYTES;
use crate::constants::regs::{
- RCR_ACCEPT_BCAST, RCR_ACCEPT_MULTI, RCR_ACCEPT_PHYS, RCR_MXDMA_UNLIMITED, RCR_WRAP, REG_CAPR,
- REG_RBSTART, REG_RCR,
+ CMD_RX_ENABLE, CMD_TX_ENABLE, RCR_ACCEPT_BCAST, RCR_ACCEPT_MULTI, RCR_ACCEPT_PHYS,
+ RCR_MXDMA_UNLIMITED, RCR_RBLEN_32K, RCR_WRAP, REG_CAPR, REG_CMD, REG_RBSTART, REG_RCR,
};
use crate::setup::Driver;
+/// Receive configuration. Written only once the receiver is enabled (see
+/// `run`): on parts where RCR does not take while RE is clear, the accept
+/// bits would otherwise fall back to their reset value and nothing arrives.
+pub const RCR: u32 = RCR_ACCEPT_PHYS
+ | RCR_ACCEPT_MULTI
+ | RCR_ACCEPT_BCAST
+ | RCR_WRAP
+ | RCR_MXDMA_UNLIMITED
+ | RCR_RBLEN_32K;
+
pub fn program(driver: &mut Driver) -> Result<(), &'static str> {
driver.rx_offset = 0;
driver.pio.w32(REG_RBSTART, driver.rx_device_addr as u32)?;
- driver.pio.w16(REG_CAPR, capr_for(0))?;
- driver.pio.w32(
- REG_RCR,
- RCR_ACCEPT_PHYS | RCR_ACCEPT_MULTI | RCR_ACCEPT_BCAST | RCR_WRAP | RCR_MXDMA_UNLIMITED,
- )
+ driver.pio.w16(REG_CAPR, capr_for(0))
+}
+
+pub fn configure(driver: &Driver) -> Result<(), &'static str> {
+ driver.pio.w32(REG_RCR, RCR)
+}
+
+/*
+ * Receive from the top of the ring again, the way Linux 8139too's rx_err
+ * does. Used when the header at the read position is one the part never
+ * writes for a good frame: after a FIFO overrun real silicon can leave the
+ * ring position lost, and waiting on that header would stop receive for good.
+ */
+pub fn restart(driver: &mut Driver) -> Result<(), &'static str> {
+ driver.pio.w8(REG_CMD, CMD_TX_ENABLE)?;
+ driver.pio.w8(REG_CMD, CMD_RX_ENABLE | CMD_TX_ENABLE)?;
+ configure(driver)?;
+ program(driver)
}
fn capr_for(offset: usize) -> u16 {
diff --git a/userland/capsule_driver_rtl8139/src/init/tx_setup.rs b/userland/capsule_driver_rtl8139/src/init/tx_setup.rs
index 1b5cb69a20..40845ec88a 100644
--- a/userland/capsule_driver_rtl8139/src/init/tx_setup.rs
+++ b/userland/capsule_driver_rtl8139/src/init/tx_setup.rs
@@ -18,11 +18,19 @@ use crate::constants::dma::{TX_SLOT_BYTES, TX_SLOT_COUNT};
use crate::constants::regs::{REG_TCR, REG_TXADDR0, TCR_MXDMA_UNLIMITED};
use crate::setup::Driver;
+/// Transmit configuration, written once the transmitter is enabled (see `run`).
+pub const TCR: u32 = TCR_MXDMA_UNLIMITED;
+
pub fn program(driver: &mut Driver) -> Result<(), &'static str> {
for idx in 0..TX_SLOT_COUNT {
let addr = driver.tx_device_addr + (idx * TX_SLOT_BYTES) as u64;
driver.pio.w32(REG_TXADDR0 + (idx as u16 * 4), addr as u32)?;
}
driver.tx_cur = 0;
- driver.pio.w32(REG_TCR, TCR_MXDMA_UNLIMITED)
+ driver.tx_dirty = 0;
+ Ok(())
+}
+
+pub fn configure(driver: &Driver) -> Result<(), &'static str> {
+ driver.pio.w32(REG_TCR, TCR)
}
diff --git a/userland/capsule_driver_rtl8139/src/protocol/endpoint.rs b/userland/capsule_driver_rtl8139/src/protocol/endpoint.rs
deleted file mode 100644
index 2aedb75622..0000000000
--- a/userland/capsule_driver_rtl8139/src/protocol/endpoint.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-// NONOS Operating System
-// Copyright (C) 2026 NONOS Contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-pub const KERNEL_REPLY_ENDPOINT: u64 = 0x1_0000_000D;
diff --git a/userland/capsule_driver_rtl8139/src/protocol/header.rs b/userland/capsule_driver_rtl8139/src/protocol/header.rs
index 83787ec166..f610b5fa5e 100644
--- a/userland/capsule_driver_rtl8139/src/protocol/header.rs
+++ b/userland/capsule_driver_rtl8139/src/protocol/header.rs
@@ -14,7 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-pub const MAGIC: u32 = 0x4E52_3839;
+/// "NNET", the NIC protocol net_core and net_l2 speak (virtio-net's too).
+/// The per-driver tag this replaced made every request from the stack
+/// undecodable, so the wired NICs never served it.
+pub const MAGIC: u32 = 0x4E4E_4554;
pub const VERSION: u16 = 1;
pub const HDR_LEN: usize = 20;
pub const RESP_HDR_LEN: usize = HDR_LEN;
diff --git a/userland/capsule_driver_rtl8139/src/protocol/mod.rs b/userland/capsule_driver_rtl8139/src/protocol/mod.rs
index 8f11313832..adce81f355 100644
--- a/userland/capsule_driver_rtl8139/src/protocol/mod.rs
+++ b/userland/capsule_driver_rtl8139/src/protocol/mod.rs
@@ -16,7 +16,6 @@
mod decode;
mod encode;
-mod endpoint;
mod errno;
mod header;
mod limits;
@@ -24,7 +23,6 @@ mod ops;
pub use decode::decode_request;
pub use encode::{encode_response_header, write_status};
-pub use endpoint::KERNEL_REPLY_ENDPOINT;
pub use errno::{E_AGAIN, E_INVAL, E_IO, E_MSGSIZE};
pub use header::{Request, HDR_LEN, RESP_HDR_LEN};
pub use limits::{
diff --git a/userland/capsule_driver_rtl8139/src/rx/read_frame.rs b/userland/capsule_driver_rtl8139/src/rx/read_frame.rs
index 3b7b812223..c3e0cae7d5 100644
--- a/userland/capsule_driver_rtl8139/src/rx/read_frame.rs
+++ b/userland/capsule_driver_rtl8139/src/rx/read_frame.rs
@@ -16,15 +16,22 @@
use core::sync::atomic::{compiler_fence, Ordering};
-use nonos_libc::mk_irq_ack;
-
use super::advance::advance;
use super::copy_ring::copy_ring;
use super::ring_u16::ring_u16;
use crate::constants::regs::RX_STATUS_OK;
use crate::constants::MAX_ETHERNET_FRAME;
+use crate::init::restart_rx;
use crate::setup::Driver;
+/// The length the part shows while a frame is still being written (early RX).
+const RX_STILL_ARRIVING: usize = 0xFFF0;
+/// Longest frame plus CRC the part hands up with ROK set (Linux 8139too's
+/// MAX_ETH_FRAME_SIZE + 4); a longer length is a corrupt header.
+const RX_MAX_RAW: usize = 1792 + 4;
+/// Shortest raw length a real header carries.
+const RX_MIN_RAW: usize = 8;
+
pub(super) fn read_frame(
driver: &mut Driver,
out: &mut [u8],
@@ -34,17 +41,25 @@ pub(super) fn read_frame(
let off = driver.rx_offset;
let status = ring_u16(base, off);
let raw_len = ring_u16(base, off + 2) as usize;
- if (status & RX_STATUS_OK) == 0 || raw_len <= 4 {
- let _ = mk_irq_ack(driver.irq_grant);
- return Err("rtl8139 rx descriptor error");
+ if raw_len == RX_STILL_ARRIVING {
+ return Ok(None);
+ }
+ /*
+ * Returning here without moving on read the same header forever: one bad
+ * header ended receive until the capsule restarted. A header no good frame
+ * carries means the position is lost, so receive starts over.
+ */
+ if (status & RX_STATUS_OK) == 0 || raw_len < RX_MIN_RAW || raw_len > RX_MAX_RAW {
+ restart_rx(driver)?;
+ return Err("rtl8139 rx ring restarted");
}
let frame_len = raw_len - 4;
+ // A good frame the caller cannot take (a tagged full-size one): skip it.
if frame_len > MAX_ETHERNET_FRAME || frame_len > out.len() {
- let _ = mk_irq_ack(driver.irq_grant);
+ advance(driver, raw_len)?;
return Err("rtl8139 rx frame too large");
}
copy_ring(base, off + 4, out, frame_len);
advance(driver, raw_len)?;
- let _ = mk_irq_ack(driver.irq_grant);
Ok(Some(frame_len))
}
diff --git a/userland/capsule_driver_rtl8139/src/rx/recv_one.rs b/userland/capsule_driver_rtl8139/src/rx/recv_one.rs
index 5ebb8dbd53..6b98b47344 100644
--- a/userland/capsule_driver_rtl8139/src/rx/recv_one.rs
+++ b/userland/capsule_driver_rtl8139/src/rx/recv_one.rs
@@ -14,8 +14,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use nonos_libc::mk_irq_ack;
-
use super::read_frame::read_frame;
use crate::constants::regs::{
CMD_RX_BUF_EMPTY, ISR_ENABLED, ISR_RX_ERR, ISR_RX_FIFO_OVERFLOW, ISR_RX_OVERFLOW, REG_CMD,
@@ -29,11 +27,9 @@ pub fn recv_one(driver: &mut Driver, out: &mut [u8]) -> Result