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, &' driver.pio.w16(REG_ISR, isr & ISR_ENABLED)?; } if (isr & (ISR_RX_ERR | ISR_RX_OVERFLOW | ISR_RX_FIFO_OVERFLOW)) != 0 { - let _ = mk_irq_ack(driver.irq_grant); return Err("rtl8139 rx interrupt error"); } if (driver.pio.r8(REG_CMD)? & CMD_RX_BUF_EMPTY) != 0 { - let _ = mk_irq_ack(driver.irq_grant); return Ok(None); } read_frame(driver, out) diff --git a/userland/capsule_driver_rtl8139/src/rx/ring_u8.rs b/userland/capsule_driver_rtl8139/src/rx/ring_u8.rs index 2068a24101..54dc4ef59e 100644 --- a/userland/capsule_driver_rtl8139/src/rx/ring_u8.rs +++ b/userland/capsule_driver_rtl8139/src/rx/ring_u8.rs @@ -14,8 +14,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::constants::dma::RX_BUF_DATA_BYTES; +use crate::constants::dma::RX_BUF_BYTES; +/* + * Linear, not modulo the ring. RCR.WRAP is set, so a frame that crosses the + * end of the ring is written on past it into the slack after, not back at + * the start: taking its tail from offset 0 handed up stale bytes once a lap. + * Offsets stay below the allocation (the header is inside the ring and the + * frame is length-checked first), and anything that would not reads as zero. + */ pub(super) fn ring_u8(base: u64, off: usize) -> u8 { - unsafe { core::ptr::read_volatile((base + (off % RX_BUF_DATA_BYTES) as u64) as *const u8) } + if off >= RX_BUF_BYTES { + return 0; + } + unsafe { core::ptr::read_volatile((base + off as u64) as *const u8) } } diff --git a/userland/capsule_driver_rtl8139/src/server/error.rs b/userland/capsule_driver_rtl8139/src/server/error.rs index 7eedd252d3..6bebade1fc 100644 --- a/userland/capsule_driver_rtl8139/src/server/error.rs +++ b/userland/capsule_driver_rtl8139/src/server/error.rs @@ -14,19 +14,24 @@ // 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 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_rtl8139/src/server/handlers/health.rs b/userland/capsule_driver_rtl8139/src/server/handlers/health.rs index 09630f8dc9..4e484ff65e 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/health.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/health.rs @@ -17,6 +17,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_rtl8139/src/server/handlers/link_status.rs b/userland/capsule_driver_rtl8139/src/server/handlers/link_status.rs index 9969ae8999..ed81993846 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/link_status.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/link_status.rs @@ -14,30 +14,24 @@ // 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::regs::{MSR_LINK_BAD, REG_MSR}; 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_with_status; +use crate::server::error::{reply, reply_with_status}; 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 link_up = match driver.pio.r8(REG_MSR) { Ok(v) => ((v & MSR_LINK_BAD) == 0) as u8, Err(_) => { - reply_with_status(tx, req, -5); + reply_with_status(sender, tx, req, -5); return; } }; encode_response_header(tx, req, (STATUS_LEN + LINK_STATUS_PAYLOAD_LEN) as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); tx[RESP_HDR_LEN + STATUS_LEN] = link_up; - 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_rtl8139/src/server/handlers/mac_address.rs b/userland/capsule_driver_rtl8139/src/server/handlers/mac_address.rs index 1a5711b4a3..35a232d54b 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/mac_address.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/mac_address.rs @@ -14,18 +14,17 @@ // 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::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]) { encode_response_header(tx, req, (STATUS_LEN + MAC_ADDRESS_PAYLOAD_LEN) as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); let off = RESP_HDR_LEN + STATUS_LEN; tx[off..off + MAC_ADDRESS_PAYLOAD_LEN].copy_from_slice(&driver.mac); - let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), off + MAC_ADDRESS_PAYLOAD_LEN); + reply(sender, tx, off + MAC_ADDRESS_PAYLOAD_LEN); } diff --git a/userland/capsule_driver_rtl8139/src/server/handlers/rx_packet.rs b/userland/capsule_driver_rtl8139/src/server/handlers/rx_packet.rs index 3832172364..c07d8c76a8 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/rx_packet.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/rx_packet.rs @@ -14,26 +14,24 @@ // 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::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::rx::recv_one; -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 body_off = RESP_HDR_LEN + STATUS_LEN + RX_PAYLOAD_PREFIX_LEN; let frame_len = match recv_one(driver, &mut tx[body_off..]) { Ok(Some(n)) => n, Ok(None) => { - reply_with_status(tx, req, E_AGAIN); + reply_with_status(sender, tx, req, E_AGAIN); return; } Err(_) => { - reply_with_status(tx, req, E_IO); + reply_with_status(sender, tx, req, E_IO); return; } }; @@ -41,5 +39,5 @@ pub fn handle(driver: &mut Driver, req: &Request, tx: &mut [u8]) { encode_response_header(tx, req, STATUS_LEN as u32 + body_len as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); tx[RESP_HDR_LEN + STATUS_LEN..body_off].copy_from_slice(&(frame_len as u32).to_le_bytes()); - let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), body_off + frame_len); + reply(sender, tx, body_off + frame_len); } diff --git a/userland/capsule_driver_rtl8139/src/server/handlers/stats.rs b/userland/capsule_driver_rtl8139/src/server/handlers/stats.rs index 8351d20f06..6bec8e4829 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/stats.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/stats.rs @@ -14,23 +14,22 @@ // 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::dma::TX_SLOT_COUNT; use crate::constants::regs::{ REG_CAPR, REG_CMD, REG_ISR, REG_MSR, REG_RCR, REG_TCR, REG_TXSTATUS0, }; use crate::protocol::{ - encode_response_header, write_status, Request, E_IO, KERNEL_REPLY_ENDPOINT, RESP_HDR_LEN, - STATS_PAYLOAD_LEN, STATUS_LEN, + encode_response_header, write_status, Request, E_IO, RESP_HDR_LEN, STATS_PAYLOAD_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: &Driver, req: &Request, tx: &mut [u8]) { +pub fn handle(sender: u32, driver: &Driver, req: &Request, tx: &mut [u8]) { let regs = match live_regs(driver) { Ok(v) => v, Err(()) => { - reply_with_status(tx, req, E_IO); + reply_with_status(sender, tx, req, E_IO); return; } }; @@ -41,7 +40,7 @@ pub fn handle(driver: &Driver, req: &Request, tx: &mut [u8]) { for v in regs { 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) -> Result<[u32; 12], ()> { @@ -57,7 +56,7 @@ fn live_regs(driver: &Driver) -> Result<[u32; 12], ()> { driver.pio.r32(REG_TXSTATUS0 + 8).map_err(|_| ())?, driver.pio.r32(REG_TXSTATUS0 + 12).map_err(|_| ())?, driver.rx_offset as u32, - driver.tx_cur as u32, + (driver.tx_cur % TX_SLOT_COUNT) as u32, ]) } diff --git a/userland/capsule_driver_rtl8139/src/server/handlers/tx_packet.rs b/userland/capsule_driver_rtl8139/src/server/handlers/tx_packet.rs index 38185d2841..a90d6a0ada 100644 --- a/userland/capsule_driver_rtl8139/src/server/handlers/tx_packet.rs +++ b/userland/capsule_driver_rtl8139/src/server/handlers/tx_packet.rs @@ -15,25 +15,33 @@ // along with this program. If not, see . 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_IO, E_MSGSIZE, MAX_TX_PAYLOAD_BYTES}; use crate::server::error::reply_with_status; use crate::setup::Driver; -use crate::tx::send; +use crate::tx::{full, reclaim, send}; -pub fn handle(driver: &mut Driver, req: &Request, body: &[u8], tx: &mut [u8]) { +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; + } + if reclaim(driver).is_err() { + reply_with_status(sender, tx, req, E_IO); + return; + } + if full(driver) { + reply_with_status(sender, tx, req, E_AGAIN); return; } match send(driver, body) { - Ok(()) => reply_with_status(tx, req, 0), - Err(_) => reply_with_status(tx, req, E_IO), + Ok(()) => reply_with_status(sender, tx, req, 0), + Err(_) => reply_with_status(sender, tx, req, E_IO), } } diff --git a/userland/capsule_driver_rtl8139/src/server/runner.rs b/userland/capsule_driver_rtl8139/src/server/runner.rs index 5506760f42..b65613da2d 100644 --- a/userland/capsule_driver_rtl8139/src/server/runner.rs +++ b/userland/capsule_driver_rtl8139/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::{ @@ -42,26 +42,27 @@ pub fn run(driver: &mut Driver) -> ! { } fn dispatch_once(driver: &mut Driver, rx: &mut [u8], tx: &mut [u8]) { - 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 { return; } let len = n as usize; let req = match decode_request(&rx[..len]) { Some(r) => r, None => { - reply_decode_failed(tx, E_INVAL); + reply_decode_failed(sender, tx, E_INVAL); return; } }; let body = &rx[HDR_LEN..len]; match req.op { - OP_HEALTHCHECK => handlers::health::handle(&req, tx), - OP_LINK_STATUS => handlers::link_status::handle(driver, &req, tx), - OP_MAC_ADDRESS => handlers::mac_address::handle(driver, &req, tx), - OP_TX_PACKET => handlers::tx_packet::handle(driver, &req, body, tx), - OP_RX_PACKET => handlers::rx_packet::handle(driver, &req, tx), - OP_STATS => handlers::stats::handle(driver, &req, tx), - _ => reply_with_status(tx, &req, E_INVAL), + OP_HEALTHCHECK => handlers::health::handle(sender, &req, tx), + OP_LINK_STATUS => handlers::link_status::handle(sender, driver, &req, tx), + OP_MAC_ADDRESS => handlers::mac_address::handle(sender, driver, &req, tx), + OP_TX_PACKET => handlers::tx_packet::handle(sender, driver, &req, body, tx), + OP_RX_PACKET => handlers::rx_packet::handle(sender, driver, &req, tx), + OP_STATS => handlers::stats::handle(sender, driver, &req, tx), + _ => reply_with_status(sender, tx, &req, E_INVAL), } } diff --git a/userland/capsule_driver_rtl8139/src/setup/dma.rs b/userland/capsule_driver_rtl8139/src/setup/dma.rs index 0fe07ce0db..f1d795f1f7 100644 --- a/userland/capsule_driver_rtl8139/src/setup/dma.rs +++ b/userland/capsule_driver_rtl8139/src/setup/dma.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use nonos_libc::{mk_dma_map, DmaMapOut, IrqBindOut, PioGrantOut}; +use nonos_libc::{mk_dma_map, DmaMapOut, PioGrantOut}; use crate::constants::dma::{RX_BUF_BYTES, TX_BUF_BYTES}; @@ -40,18 +40,17 @@ pub fn map_all( device_id: u64, epoch: u64, pio: &PioGrantOut, - irq: &IrqBindOut, ) -> Result<(DmaMapOut, DmaMapOut), &'static str> { let rx = alloc(device_id, epoch, RX_BUF_BYTES as u64).ok_or_else(|| { - rollback::after_irq(device_id, pio, irq, &[]); + rollback::after_pio(device_id, pio, &[]); "rx dma failed" })?; let tx = alloc(device_id, epoch, TX_BUF_BYTES as u64).ok_or_else(|| { - rollback::after_irq(device_id, pio, irq, &[rx.grant_id]); + rollback::after_pio(device_id, pio, &[rx.grant_id]); "tx dma failed" })?; if rx.device_addr > u32::MAX as u64 || tx.device_addr > u32::MAX as u64 { - rollback::after_irq(device_id, pio, irq, &[tx.grant_id, rx.grant_id]); + rollback::after_pio(device_id, pio, &[tx.grant_id, rx.grant_id]); return Err("rtl8139 requires 32-bit dma"); } Ok((rx, tx)) diff --git a/userland/capsule_driver_rtl8139/src/setup/driver.rs b/userland/capsule_driver_rtl8139/src/setup/driver.rs index eaa658e542..f59afa0a6d 100644 --- a/userland/capsule_driver_rtl8139/src/setup/driver.rs +++ b/userland/capsule_driver_rtl8139/src/setup/driver.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use nonos_libc::{mk_device_release, mk_dma_unmap, mk_irq_unbind, mk_pio_release}; +use nonos_libc::{mk_device_release, mk_dma_unmap, mk_pio_release}; use crate::constants::MAC_LEN; use crate::pio::Pio; @@ -22,7 +22,6 @@ use crate::pio::Pio; pub struct Driver { pub device_id: u64, pub pio_grant: u64, - pub irq_grant: u64, pub rx_grant: u64, pub tx_grant: u64, pub rx_user_va: u64, @@ -30,7 +29,10 @@ pub struct Driver { pub tx_user_va: u64, pub tx_device_addr: u64, pub rx_offset: usize, + /// Frames handed to the part, counting up; the slot is `tx_cur % 4`. pub tx_cur: usize, + /// Frames the part has finished with; `tx_cur - tx_dirty` are in flight. + pub tx_dirty: usize, pub pio: Pio, pub mac: [u8; MAC_LEN], } @@ -39,7 +41,6 @@ impl Driver { pub fn release(&self) { let _ = mk_dma_unmap(self.tx_grant); let _ = mk_dma_unmap(self.rx_grant); - let _ = mk_irq_unbind(self.irq_grant); let _ = mk_pio_release(self.pio_grant); let _ = mk_device_release(self.device_id); } diff --git a/userland/capsule_driver_rtl8139/src/setup/irq.rs b/userland/capsule_driver_rtl8139/src/setup/irq.rs deleted file mode 100644 index 212204ea53..0000000000 --- a/userland/capsule_driver_rtl8139/src/setup/irq.rs +++ /dev/null @@ -1,34 +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 . - -use nonos_libc::{mk_device_release, mk_irq_bind, mk_pio_release, IrqBindOut, PioGrantOut}; - -use crate::discover::Found; - -pub fn bind(dev: Found, claim_epoch: u64, pio: &PioGrantOut) -> 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 { - after_pio(dev.device_id, pio); - return Err("irq bind failed"); - } - Ok(out) -} - -pub fn after_pio(device_id: u64, pio: &PioGrantOut) { - let _ = mk_pio_release(pio.grant_id); - let _ = mk_device_release(device_id); -} diff --git a/userland/capsule_driver_rtl8139/src/setup/mod.rs b/userland/capsule_driver_rtl8139/src/setup/mod.rs index c3dd5c6e41..a9315f5d1c 100644 --- a/userland/capsule_driver_rtl8139/src/setup/mod.rs +++ b/userland/capsule_driver_rtl8139/src/setup/mod.rs @@ -17,7 +17,6 @@ mod claim; mod dma; mod driver; -mod irq; mod pci; mod pio_grant; mod rollback; diff --git a/userland/capsule_driver_rtl8139/src/setup/rollback.rs b/userland/capsule_driver_rtl8139/src/setup/rollback.rs index 8b1a240c18..dba4df3083 100644 --- a/userland/capsule_driver_rtl8139/src/setup/rollback.rs +++ b/userland/capsule_driver_rtl8139/src/setup/rollback.rs @@ -14,15 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use nonos_libc::{ - mk_device_release, mk_dma_unmap, mk_irq_unbind, mk_pio_release, IrqBindOut, PioGrantOut, -}; +use nonos_libc::{mk_device_release, mk_dma_unmap, mk_pio_release, PioGrantOut}; -pub fn after_irq(device_id: u64, pio: &PioGrantOut, irq: &IrqBindOut, dma_grants: &[u64]) { +pub fn after_pio(device_id: u64, pio: &PioGrantOut, dma_grants: &[u64]) { for grant in dma_grants { let _ = mk_dma_unmap(*grant); } - let _ = mk_irq_unbind(irq.grant_id); let _ = mk_pio_release(pio.grant_id); let _ = mk_device_release(device_id); } diff --git a/userland/capsule_driver_rtl8139/src/setup/sequence.rs b/userland/capsule_driver_rtl8139/src/setup/sequence.rs index e44fc5460a..04743684d0 100644 --- a/userland/capsule_driver_rtl8139/src/setup/sequence.rs +++ b/userland/capsule_driver_rtl8139/src/setup/sequence.rs @@ -19,19 +19,17 @@ use crate::discover::find_rtl8139; use crate::pio::Pio; use super::driver::Driver; -use super::{claim, dma, irq, pci, pio_grant}; +use super::{claim, dma, pci, pio_grant}; pub fn run() -> Result { let dev = find_rtl8139().ok_or("no rtl8139 device")?; let epoch = claim::claim(dev.device_id)?; pci::enable(dev, epoch)?; let pio = pio_grant::grant(dev, epoch)?; - let irq = irq::bind(dev, epoch, &pio)?; - let (rx, tx) = dma::map_all(dev.device_id, epoch, &pio, &irq)?; + let (rx, tx) = dma::map_all(dev.device_id, epoch, &pio)?; Ok(Driver { device_id: dev.device_id, pio_grant: pio.grant_id, - irq_grant: irq.grant_id, rx_grant: rx.grant_id, tx_grant: tx.grant_id, rx_user_va: rx.user_va, @@ -40,6 +38,7 @@ pub fn run() -> Result { tx_device_addr: tx.device_addr, rx_offset: 0, tx_cur: 0, + tx_dirty: 0, pio: Pio::new(pio.grant_id), mac: [0u8; MAC_LEN], }) diff --git a/userland/capsule_driver_rtl8139/src/tx/mod.rs b/userland/capsule_driver_rtl8139/src/tx/mod.rs index 9ba7f0c1da..2afb0f51b8 100644 --- a/userland/capsule_driver_rtl8139/src/tx/mod.rs +++ b/userland/capsule_driver_rtl8139/src/tx/mod.rs @@ -14,7 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -mod poll_done; +mod reclaim; mod send; +pub use reclaim::{full, reclaim}; pub use send::send; diff --git a/userland/capsule_driver_rtl8139/src/tx/poll_done.rs b/userland/capsule_driver_rtl8139/src/tx/poll_done.rs deleted file mode 100644 index f3cd58557a..0000000000 --- a/userland/capsule_driver_rtl8139/src/tx/poll_done.rs +++ /dev/null @@ -1,37 +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 . - -use core::sync::atomic::{compiler_fence, Ordering}; - -use crate::constants::regs::{TX_STATUS_ABORT, TX_STATUS_OK, TX_STATUS_UNDERRUN}; -use crate::setup::Driver; - -const TX_POLL_BUDGET: u32 = 1_000_000; - -pub(super) fn poll_done(driver: &Driver, status_reg: u16) -> Result<(), &'static str> { - for _ in 0..TX_POLL_BUDGET { - compiler_fence(Ordering::Acquire); - let status = driver.pio.r32(status_reg)?; - if (status & (TX_STATUS_ABORT | TX_STATUS_UNDERRUN)) != 0 { - return Err("rtl8139 tx error"); - } - if (status & TX_STATUS_OK) != 0 { - return Ok(()); - } - core::hint::spin_loop(); - } - Err("rtl8139 tx timeout") -} diff --git a/userland/capsule_driver_rtl8139/src/tx/reclaim.rs b/userland/capsule_driver_rtl8139/src/tx/reclaim.rs new file mode 100644 index 0000000000..b416352aaa --- /dev/null +++ b/userland/capsule_driver_rtl8139/src/tx/reclaim.rs @@ -0,0 +1,52 @@ +// 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 . + +//! Walk `tx_dirty` over the descriptors the part has finished with. +//! +//! The part works through TSD0-3 strictly in order with a pointer of its own, +//! so the driver has to move with it: a descriptor is finished once its status +//! says the frame went (TOK), went after an underrun re-fetch (TUN), or was +//! given up on (TABT), and each of those moves the part on to the next one. +//! Staying on a slot the part has left made every later send wait on a +//! descriptor it would never look at again. An abort also halts the +//! transmitter until TCR.CLRABT is written, as Linux 8139too does. + +use crate::constants::dma::TX_SLOT_COUNT; +use crate::constants::regs::{ + REG_TCR, REG_TXSTATUS0, TCR_CLEAR_ABORT, TX_STATUS_ABORT, TX_STATUS_OK, TX_STATUS_UNDERRUN, +}; +use crate::init::TCR; +use crate::setup::Driver; + +pub fn reclaim(driver: &mut Driver) -> Result<(), &'static str> { + while driver.tx_dirty != driver.tx_cur { + let idx = driver.tx_dirty % TX_SLOT_COUNT; + let status = driver.pio.r32(REG_TXSTATUS0 + (idx as u16 * 4))?; + if status & (TX_STATUS_OK | TX_STATUS_UNDERRUN | TX_STATUS_ABORT) == 0 { + break; + } + if status & TX_STATUS_ABORT != 0 { + driver.pio.w32(REG_TCR, TCR | TCR_CLEAR_ABORT)?; + } + driver.tx_dirty = driver.tx_dirty.wrapping_add(1); + } + Ok(()) +} + +/// Whether every slot holds a frame the part has not finished with. +pub fn full(driver: &Driver) -> bool { + driver.tx_cur.wrapping_sub(driver.tx_dirty) >= TX_SLOT_COUNT +} diff --git a/userland/capsule_driver_rtl8139/src/tx/send.rs b/userland/capsule_driver_rtl8139/src/tx/send.rs index 97190566ec..151cfc6cae 100644 --- a/userland/capsule_driver_rtl8139/src/tx/send.rs +++ b/userland/capsule_driver_rtl8139/src/tx/send.rs @@ -16,21 +16,29 @@ use core::sync::atomic::{compiler_fence, Ordering}; -use super::poll_done::poll_done; use crate::constants::dma::{TX_SLOT_BYTES, TX_SLOT_COUNT}; -use crate::constants::regs::REG_TXSTATUS0; +use crate::constants::regs::{REG_TXSTATUS0, TSD_ERTXTH_256}; +use crate::constants::MIN_WIRE_FRAME; use crate::setup::Driver; +/* + * Hand one frame to the next slot. The caller has reclaimed and checked for + * room, so the part is finished with this slot. The frame is answered once it + * is queued: the part sends it when it can, and `reclaim` sees it done. The + * part does not pad, so a short frame is zero-filled to the 60-byte minimum. + */ pub fn send(driver: &mut Driver, frame: &[u8]) -> Result<(), &'static str> { - let idx = driver.tx_cur; + let idx = driver.tx_cur % TX_SLOT_COUNT; let va = driver.tx_user_va + (idx * TX_SLOT_BYTES) as u64; + let wire = frame.len().max(MIN_WIRE_FRAME); unsafe { - core::ptr::copy_nonoverlapping(frame.as_ptr(), va as *mut u8, frame.len()); + let dst = va as *mut u8; + core::ptr::copy_nonoverlapping(frame.as_ptr(), dst, frame.len()); + core::ptr::write_bytes(dst.add(frame.len()), 0, wire - frame.len()); } compiler_fence(Ordering::Release); let status_reg = REG_TXSTATUS0 + (idx as u16 * 4); - driver.pio.w32(status_reg, frame.len() as u32)?; - poll_done(driver, status_reg)?; - driver.tx_cur = (idx + 1) % TX_SLOT_COUNT; + driver.pio.w32(status_reg, wire as u32 | TSD_ERTXTH_256)?; + driver.tx_cur = driver.tx_cur.wrapping_add(1); Ok(()) } diff --git a/userland/capsule_driver_rtl8169/Capsule.mk b/userland/capsule_driver_rtl8169/Capsule.mk index a108f45f72..5b8cd895ae 100644 --- a/userland/capsule_driver_rtl8169/Capsule.mk +++ b/userland/capsule_driver_rtl8169/Capsule.mk @@ -1,4 +1,4 @@ -# RTL8169 — Realtek 8168/8169 gigabit NIC. PCI MMIO + INTx + DMA. +# RTL8169 — Realtek 8168/8169 gigabit NIC. PCI MMIO + DMA, polled. # Raw Ethernet frames only; network policy belongs to the net-stack # capsule. Signing, certificate, and manifest rules come from the # shared hybrid-signature capsule macro. @@ -12,7 +12,10 @@ CAPSULE_FEATURE := nonos-capsule-driver-rtl8169 CAPSULE_NAMESPACE := systems.nonos.driver.rtl8169_0 CAPSULE_SERVICE_ENDPOINT := service:4214:driver.rtl8169_0 CAPSULE_REPLY_ENDPOINT := reply:4215:endpoint.4294967310 -# IPC|Memory|Driver|DeviceEnum|Mmio|Irq|Dma = 0xF8019 -CAPSULE_REQUIRED_CAPS := 0xF8019 +# IPC|Memory|Crypto|Driver|DeviceEnum|Mmio|Dma = 0xB8039 +# 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 := 0xB8039 include nonos-mk/capsule.mk diff --git a/userland/capsule_driver_rtl8169/src/constants/frame.rs b/userland/capsule_driver_rtl8169/src/constants/frame.rs index f7eac730bb..413cb0c280 100644 --- a/userland/capsule_driver_rtl8169/src/constants/frame.rs +++ b/userland/capsule_driver_rtl8169/src/constants/frame.rs @@ -18,5 +18,10 @@ 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; ARP (42) and a bare TCP ACK (54) +/// are shorter than the wire minimum, and refusing them stranded IPv4. +pub const MIN_ETHERNET_FRAME: usize = ETH_HEADER_LEN; +/// `send` pads to this: several 8168 revisions do not pad short frames right +/// (Linux pads them in software for the same reason). +pub const MIN_WIRE_FRAME: usize = 60; pub const MAX_ETHERNET_FRAME: usize = MTU + ETH_HEADER_LEN; diff --git a/userland/capsule_driver_rtl8169/src/constants/mod.rs b/userland/capsule_driver_rtl8169/src/constants/mod.rs index 5c8fd24bcb..ec42a691d8 100644 --- a/userland/capsule_driver_rtl8169/src/constants/mod.rs +++ b/userland/capsule_driver_rtl8169/src/constants/mod.rs @@ -19,4 +19,4 @@ pub mod pci; pub mod queue; 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_rtl8169/src/constants/regs.rs b/userland/capsule_driver_rtl8169/src/constants/regs.rs index 12f0a47643..6a1351029a 100644 --- a/userland/capsule_driver_rtl8169/src/constants/regs.rs +++ b/userland/capsule_driver_rtl8169/src/constants/regs.rs @@ -36,7 +36,11 @@ pub const REG_RXDESC_ADDR_HI: usize = 0xE8; pub const CMD_RESET: u8 = 0x10; pub const CMD_RX_ENABLE: u8 = 0x08; pub const CMD_TX_ENABLE: u8 = 0x04; -pub const TX_POLL_HPQ: u8 = 0x80; +/// TPPoll bit 6 polls the normal-priority ring, the one TNPDS points at and +/// the only one set up. Bit 7 (0x80) is the high-priority ring, whose base +/// (THPDS) is never written: ringing it sent the part to fetch from zero, so +/// no frame was ever sent. +pub const TX_POLL_NPQ: u8 = 0x40; pub const TX_CONFIG_IFG: u32 = 3 << 24; pub const TX_CONFIG_DMA: u32 = 7 << 8; diff --git a/userland/capsule_driver_rtl8169/src/discover.rs b/userland/capsule_driver_rtl8169/src/discover.rs index f386712633..51c5583bcc 100644 --- a/userland/capsule_driver_rtl8169/src/discover.rs +++ b/userland/capsule_driver_rtl8169/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 bar_index: u8, pub bar_size: u64, pub command_bits: u16, @@ -44,13 +43,12 @@ pub fn find_rtl8169() -> 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 card as absent on exactly the PCs this driver is for. if let Some((bar_index, bar_size)) = first_mmio_bar(r) { return Some(Found { device_id: r.device_id, - irq_line: r.irq_line, bar_index, bar_size, command_bits: command_bits(r), diff --git a/userland/capsule_driver_rtl8169/src/init/mac.rs b/userland/capsule_driver_rtl8169/src/init/mac.rs index 44bd950f4f..e8b7b6e9ff 100644 --- a/userland/capsule_driver_rtl8169/src/init/mac.rs +++ b/userland/capsule_driver_rtl8169/src/init/mac.rs @@ -32,12 +32,18 @@ pub fn program(regs: &Regs) -> Result<[u8; MAC_LEN], &'static str> { } apply(&mut mac); - // IDR writes are dropped while the config lock is set. + /* + * IDR writes are dropped while the config lock is set, and the part takes + * them only as whole dwords (reads may be any width): byte writes were + * dropped on silicon, the readback below failed, and the NIC never came up. + * High dword first, each read back to post it, as Linux rtl_rar_set does. + */ unsafe { regs.w8(REG_CFG9346, CFG9346_UNLOCK); - for (i, byte) in mac.iter().enumerate() { - regs.w8(REG_MAC0 + i, *byte); - } + regs.w32(REG_MAC0 + 4, mac[4] as u32 | (mac[5] as u32) << 8); + let _ = regs.r32(REG_MAC0 + 4); + regs.w32(REG_MAC0, u32::from_le_bytes([mac[0], mac[1], mac[2], mac[3]])); + let _ = regs.r32(REG_MAC0); regs.w8(REG_CFG9346, CFG9346_LOCK); } diff --git a/userland/capsule_driver_rtl8169/src/init/run.rs b/userland/capsule_driver_rtl8169/src/init/run.rs index f0dca67882..7b32d54472 100644 --- a/userland/capsule_driver_rtl8169/src/init/run.rs +++ b/userland/capsule_driver_rtl8169/src/init/run.rs @@ -15,21 +15,35 @@ // 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}; +/* + * TE|RE go on before RxConfig and TxConfig are written, as Linux rtl_hw_start + * does on every chip: on the 8169 and the 8168B-F an RxConfig written with + * the receiver off can be lost, the accept bits never take, and nothing is + * received. The station address is still programmed first, so the part is + * never enabled under the factory one. + */ pub fn bring_up(driver: &mut Driver) -> Result<(), &'static str> { reset::run(&driver.regs)?; driver.mac = mac::program(&driver.regs)?; rx_setup::program(&driver.regs, &driver.rx); tx_setup::program(&driver.regs, &driver.tx); unsafe { - driver.regs.w16(REG_ISR, 0xFFFF); - driver.regs.w16(REG_IMR, ISR_ENABLED); driver.regs.w8(REG_CMD, CMD_RX_ENABLE | CMD_TX_ENABLE); } + rx_setup::configure(&driver.regs); + tx_setup::configure(&driver.regs); + unsafe { + driver.regs.w16(REG_ISR, 0xFFFF); + // 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.regs.w16(REG_IMR, 0); + } Ok(()) } diff --git a/userland/capsule_driver_rtl8169/src/init/rx_setup.rs b/userland/capsule_driver_rtl8169/src/init/rx_setup.rs index 2bfa444649..0b672fdd2c 100644 --- a/userland/capsule_driver_rtl8169/src/init/rx_setup.rs +++ b/userland/capsule_driver_rtl8169/src/init/rx_setup.rs @@ -42,6 +42,12 @@ pub fn program(regs: &Regs, rx: &RxRing) { regs.w16(REG_RMS, BUFFER_SIZE as u16); regs.w32(REG_RXDESC_ADDR_LO, rx.desc_da as u32); regs.w32(REG_RXDESC_ADDR_HI, (rx.desc_da >> 32) as u32); + } +} + +/// RxConfig, written once the receiver is enabled (see `run`). +pub fn configure(regs: &Regs) { + unsafe { regs.w32( REG_RX_CONFIG, RX_CONFIG_ACCEPT_PHYS diff --git a/userland/capsule_driver_rtl8169/src/init/tx_setup.rs b/userland/capsule_driver_rtl8169/src/init/tx_setup.rs index 2db4568624..a3d8c49216 100644 --- a/userland/capsule_driver_rtl8169/src/init/tx_setup.rs +++ b/userland/capsule_driver_rtl8169/src/init/tx_setup.rs @@ -35,6 +35,12 @@ pub fn program(regs: &Regs, tx: &TxRing) { unsafe { regs.w32(REG_TXDESC_ADDR_LO, tx.desc_da as u32); regs.w32(REG_TXDESC_ADDR_HI, (tx.desc_da >> 32) as u32); + } +} + +/// TxConfig, written once the transmitter is enabled (see `run`). +pub fn configure(regs: &Regs) { + unsafe { regs.w32(REG_TX_CONFIG, TX_CONFIG_IFG | TX_CONFIG_DMA); } } diff --git a/userland/capsule_driver_rtl8169/src/protocol/endpoint.rs b/userland/capsule_driver_rtl8169/src/protocol/endpoint.rs deleted file mode 100644 index d59bb1d80f..0000000000 --- a/userland/capsule_driver_rtl8169/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_000E; diff --git a/userland/capsule_driver_rtl8169/src/protocol/header.rs b/userland/capsule_driver_rtl8169/src/protocol/header.rs index dda75274ce..f610b5fa5e 100644 --- a/userland/capsule_driver_rtl8169/src/protocol/header.rs +++ b/userland/capsule_driver_rtl8169/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_3639; +/// "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_rtl8169/src/protocol/mod.rs b/userland/capsule_driver_rtl8169/src/protocol/mod.rs index 8f11313832..adce81f355 100644 --- a/userland/capsule_driver_rtl8169/src/protocol/mod.rs +++ b/userland/capsule_driver_rtl8169/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_rtl8169/src/rx/recv_one.rs b/userland/capsule_driver_rtl8169/src/rx/recv_one.rs index 8e28a1494c..79de2a6dfa 100644 --- a/userland/capsule_driver_rtl8169/src/rx/recv_one.rs +++ b/userland/capsule_driver_rtl8169/src/rx/recv_one.rs @@ -16,8 +16,6 @@ use core::sync::atomic::{compiler_fence, Ordering}; -use nonos_libc::mk_irq_ack; - use super::rearm::rearm; use crate::constants::queue::{BUFFER_SIZE, RX_DESC_COUNT}; use crate::constants::regs::{ @@ -35,14 +33,12 @@ pub fn recv_one(driver: &mut Driver, out: &mut [u8]) -> Result, &' } } if (isr & ISR_RER) != 0 { - let _ = mk_irq_ack(driver.irq_grant); return Err("rtl8169 rx interrupt error"); } compiler_fence(Ordering::Acquire); let idx = driver.rx.cur; let d = unsafe { desc(driver.rx.desc_va, idx) }; if (d.opts1 & DESC_OWN) != 0 { - let _ = mk_irq_ack(driver.irq_grant); return Ok(None); } let len = (d.opts1 & DESC_LEN_MASK) as usize; @@ -52,8 +48,10 @@ pub fn recv_one(driver: &mut Driver, out: &mut [u8]) -> Result, &' || len - 4 > MAX_ETHERNET_FRAME || len - 4 > out.len() { + // The part has already moved past this slot; staying on it left the + // cursor one behind the part until the whole ring had filled again. rearm(driver, idx); - let _ = mk_irq_ack(driver.irq_grant); + driver.rx.cur = (idx + 1) % RX_DESC_COUNT; return Err("rtl8169 rx descriptor error"); } let frame_len = len - 4; @@ -66,6 +64,5 @@ pub fn recv_one(driver: &mut Driver, out: &mut [u8]) -> Result, &' } rearm(driver, idx); driver.rx.cur = (idx + 1) % RX_DESC_COUNT; - let _ = mk_irq_ack(driver.irq_grant); Ok(Some(frame_len)) } diff --git a/userland/capsule_driver_rtl8169/src/server/error.rs b/userland/capsule_driver_rtl8169/src/server/error.rs index 7eedd252d3..6bebade1fc 100644 --- a/userland/capsule_driver_rtl8169/src/server/error.rs +++ b/userland/capsule_driver_rtl8169/src/server/error.rs @@ -14,19 +14,24 @@ // 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 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_rtl8169/src/server/handlers/health.rs b/userland/capsule_driver_rtl8169/src/server/handlers/health.rs index 09630f8dc9..4e484ff65e 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/health.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/health.rs @@ -17,6 +17,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_rtl8169/src/server/handlers/link_status.rs b/userland/capsule_driver_rtl8169/src/server/handlers/link_status.rs index 87bd1d720d..db582110f2 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/link_status.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/link_status.rs @@ -14,23 +14,18 @@ // 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::regs::{PHY_STATUS_LINK_UP, REG_PHY_STATUS}; 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]) { let link = unsafe { driver.regs.r8(REG_PHY_STATUS) } & PHY_STATUS_LINK_UP; encode_response_header(tx, req, STATUS_LEN as u32 + LINK_STATUS_PAYLOAD_LEN as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); tx[RESP_HDR_LEN + STATUS_LEN] = if link != 0 { 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_rtl8169/src/server/handlers/mac_address.rs b/userland/capsule_driver_rtl8169/src/server/handlers/mac_address.rs index d843d0262b..b2db638743 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/mac_address.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/mac_address.rs @@ -14,18 +14,17 @@ // 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::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]) { encode_response_header(tx, req, STATUS_LEN as u32 + MAC_ADDRESS_PAYLOAD_LEN as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); let off = RESP_HDR_LEN + STATUS_LEN; tx[off..off + MAC_ADDRESS_PAYLOAD_LEN].copy_from_slice(&driver.mac); - let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), off + MAC_ADDRESS_PAYLOAD_LEN); + reply(sender, tx, off + MAC_ADDRESS_PAYLOAD_LEN); } diff --git a/userland/capsule_driver_rtl8169/src/server/handlers/rx_packet.rs b/userland/capsule_driver_rtl8169/src/server/handlers/rx_packet.rs index 3832172364..c07d8c76a8 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/rx_packet.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/rx_packet.rs @@ -14,26 +14,24 @@ // 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::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::rx::recv_one; -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 body_off = RESP_HDR_LEN + STATUS_LEN + RX_PAYLOAD_PREFIX_LEN; let frame_len = match recv_one(driver, &mut tx[body_off..]) { Ok(Some(n)) => n, Ok(None) => { - reply_with_status(tx, req, E_AGAIN); + reply_with_status(sender, tx, req, E_AGAIN); return; } Err(_) => { - reply_with_status(tx, req, E_IO); + reply_with_status(sender, tx, req, E_IO); return; } }; @@ -41,5 +39,5 @@ pub fn handle(driver: &mut Driver, req: &Request, tx: &mut [u8]) { encode_response_header(tx, req, STATUS_LEN as u32 + body_len as u32); write_status(&mut tx[RESP_HDR_LEN..], 0); tx[RESP_HDR_LEN + STATUS_LEN..body_off].copy_from_slice(&(frame_len as u32).to_le_bytes()); - let _ = mk_ipc_send(KERNEL_REPLY_ENDPOINT, tx.as_ptr(), body_off + frame_len); + reply(sender, tx, body_off + frame_len); } diff --git a/userland/capsule_driver_rtl8169/src/server/handlers/stats.rs b/userland/capsule_driver_rtl8169/src/server/handlers/stats.rs index 12cee3924f..be2a438388 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/stats.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/stats.rs @@ -14,19 +14,17 @@ // 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_CMD, REG_IMR, REG_ISR, REG_PHY_STATUS, REG_RMS, REG_RX_CONFIG, REG_TX_CONFIG, }; 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); @@ -34,7 +32,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_rtl8169/src/server/handlers/tx_packet.rs b/userland/capsule_driver_rtl8169/src/server/handlers/tx_packet.rs index 38185d2841..8ddf898b84 100644 --- a/userland/capsule_driver_rtl8169/src/server/handlers/tx_packet.rs +++ b/userland/capsule_driver_rtl8169/src/server/handlers/tx_packet.rs @@ -15,25 +15,27 @@ // along with this program. If not, see . 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; -use crate::tx::send; +use crate::tx::{busy, send}; -pub fn handle(driver: &mut Driver, req: &Request, body: &[u8], tx: &mut [u8]) { +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; } - match send(driver, body) { - Ok(()) => reply_with_status(tx, req, 0), - Err(_) => reply_with_status(tx, req, E_IO), + if busy(driver) { + reply_with_status(sender, tx, req, E_AGAIN); + return; } + send(driver, body); + reply_with_status(sender, tx, req, 0); } diff --git a/userland/capsule_driver_rtl8169/src/server/runner.rs b/userland/capsule_driver_rtl8169/src/server/runner.rs index 81e23510f2..bcbeccaf4c 100644 --- a/userland/capsule_driver_rtl8169/src/server/runner.rs +++ b/userland/capsule_driver_rtl8169/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::{ @@ -37,27 +37,28 @@ pub fn run(driver: &mut Driver) -> ! { let mut rx = vec![0u8; rx_len]; 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 = &rx[HDR_LEN..len]; 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_rtl8169/src/setup/dma.rs b/userland/capsule_driver_rtl8169/src/setup/dma.rs index 854593a715..1f70190a9e 100644 --- a/userland/capsule_driver_rtl8169/src/setup/dma.rs +++ b/userland/capsule_driver_rtl8169/src/setup/dma.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use nonos_libc::{mk_dma_map, DmaMapOut, IrqBindOut, MmioMapOut}; +use nonos_libc::{mk_dma_map, DmaMapOut, MmioMapOut}; use crate::constants::queue::{RX_BUFFER_BYTES, RX_RING_BYTES, TX_BUFFER_BYTES, TX_RING_BYTES}; @@ -40,27 +40,21 @@ pub fn map_all( device_id: u64, epoch: u64, mmio: &MmioMapOut, - irq: &IrqBindOut, ) -> Result<(DmaMapOut, DmaMapOut, DmaMapOut, DmaMapOut), &'static str> { let rx_ring = alloc(device_id, epoch, RX_RING_BYTES as u64).ok_or_else(|| { - rollback::after(device_id, mmio, irq, &[]); + rollback::after(device_id, mmio, &[]); "rx ring dma failed" })?; let rx_buf = alloc(device_id, epoch, RX_BUFFER_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]); "rx buffer dma failed" })?; let tx_ring = alloc(device_id, 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]); "tx ring dma failed" })?; let tx_buf = alloc(device_id, epoch, TX_BUFFER_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]); "tx buffer dma failed" })?; Ok((rx_ring, rx_buf, tx_ring, tx_buf)) diff --git a/userland/capsule_driver_rtl8169/src/setup/driver.rs b/userland/capsule_driver_rtl8169/src/setup/driver.rs index 9841368998..61fd4455b4 100644 --- a/userland/capsule_driver_rtl8169/src/setup/driver.rs +++ b/userland/capsule_driver_rtl8169/src/setup/driver.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -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}; @@ -23,7 +23,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, @@ -40,7 +39,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_rtl8169/src/setup/irq.rs b/userland/capsule_driver_rtl8169/src/setup/irq.rs deleted file mode 100644 index ed2806b292..0000000000 --- a/userland/capsule_driver_rtl8169/src/setup/irq.rs +++ /dev/null @@ -1,30 +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 . - -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_rtl8169/src/setup/mod.rs b/userland/capsule_driver_rtl8169/src/setup/mod.rs index 6ef5e2e8a0..876531e5d4 100644 --- a/userland/capsule_driver_rtl8169/src/setup/mod.rs +++ b/userland/capsule_driver_rtl8169/src/setup/mod.rs @@ -17,7 +17,6 @@ mod claim; mod dma; mod driver; -mod irq; mod mmio; mod pci; mod rollback; diff --git a/userland/capsule_driver_rtl8169/src/setup/rollback.rs b/userland/capsule_driver_rtl8169/src/setup/rollback.rs index 146beeef3d..28d50174d0 100644 --- a/userland/capsule_driver_rtl8169/src/setup/rollback.rs +++ b/userland/capsule_driver_rtl8169/src/setup/rollback.rs @@ -14,15 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -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 grant in dma_grants { let _ = mk_dma_unmap(*grant); } - 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_rtl8169/src/setup/sequence.rs b/userland/capsule_driver_rtl8169/src/setup/sequence.rs index c8e84b69cf..6efef7fd55 100644 --- a/userland/capsule_driver_rtl8169/src/setup/sequence.rs +++ b/userland/capsule_driver_rtl8169/src/setup/sequence.rs @@ -20,19 +20,17 @@ use crate::queue::{RxRing, TxRing}; use crate::regs::Regs; use super::driver::Driver; -use super::{claim, dma, irq, mmio, pci}; +use super::{claim, dma, mmio, pci}; pub fn run() -> Result { let dev = find_rtl8169().ok_or("no rtl8169 device")?; let claim_epoch = claim::claim(dev.device_id)?; pci::enable_bus_master(dev, claim_epoch)?; let mmio = mmio::map(dev, claim_epoch)?; - let irq = irq::bind(dev, claim_epoch, &mmio)?; - let (rx_ring, rx_buf, tx_ring, tx_buf) = dma::map_all(dev.device_id, claim_epoch, &mmio, &irq)?; + let (rx_ring, rx_buf, tx_ring, tx_buf) = dma::map_all(dev.device_id, claim_epoch, &mmio)?; Ok(Driver { device_id: dev.device_id, mmio_grant: mmio.grant_id, - irq_grant: irq.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_rtl8169/src/tx/mod.rs b/userland/capsule_driver_rtl8169/src/tx/mod.rs index 9ba7f0c1da..66c892cd69 100644 --- a/userland/capsule_driver_rtl8169/src/tx/mod.rs +++ b/userland/capsule_driver_rtl8169/src/tx/mod.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -mod poll_done; mod send; -pub use send::send; +pub use send::{busy, send}; diff --git a/userland/capsule_driver_rtl8169/src/tx/poll_done.rs b/userland/capsule_driver_rtl8169/src/tx/poll_done.rs deleted file mode 100644 index 677072358e..0000000000 --- a/userland/capsule_driver_rtl8169/src/tx/poll_done.rs +++ /dev/null @@ -1,34 +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 . - -use core::sync::atomic::{compiler_fence, Ordering}; - -use crate::constants::regs::DESC_OWN; -use crate::queue::desc::desc; -use crate::setup::Driver; - -const TX_POLL_BUDGET: u32 = 1_000_000; - -pub(super) fn poll_done(driver: &Driver, idx: usize) -> Result<(), &'static str> { - for _ in 0..TX_POLL_BUDGET { - compiler_fence(Ordering::Acquire); - if (unsafe { desc(driver.tx.desc_va, idx) }.opts1 & DESC_OWN) == 0 { - return Ok(()); - } - core::hint::spin_loop(); - } - Err("rtl8169 tx timeout") -} diff --git a/userland/capsule_driver_rtl8169/src/tx/send.rs b/userland/capsule_driver_rtl8169/src/tx/send.rs index faaf7d0662..d7f8c15b26 100644 --- a/userland/capsule_driver_rtl8169/src/tx/send.rs +++ b/userland/capsule_driver_rtl8169/src/tx/send.rs @@ -16,49 +16,46 @@ use core::sync::atomic::{compiler_fence, Ordering}; -use super::poll_done::poll_done; use crate::constants::queue::TX_DESC_COUNT; -use crate::constants::regs::{ - DESC_EOR, DESC_FS, DESC_LS, DESC_OWN, ISR_ENABLED, ISR_TER, REG_ISR, REG_TX_POLL, TX_POLL_HPQ, -}; +use crate::constants::regs::{DESC_EOR, DESC_FS, DESC_LS, DESC_OWN, REG_TX_POLL, TX_POLL_NPQ}; +use crate::constants::MIN_WIRE_FRAME; use crate::queue::desc::{desc, desc_mut, Descriptor}; use crate::setup::Driver; -pub fn send(driver: &mut Driver, frame: &[u8]) -> Result<(), &'static str> { +/// Whether the part still owns the next slot, so a frame has nowhere to go. +pub fn busy(driver: &Driver) -> bool { + (unsafe { desc(driver.tx.desc_va, driver.tx.cur) }.opts1 & DESC_OWN) != 0 +} + +/* + * Hand one frame to the part and answer once it is queued. The cursor moves + * on the moment OWN goes over: the part walks the ring with a pointer of its + * own and moves past the slot when it finishes, so a cursor held back on a + * TX error or a slow completion (link still negotiating, PAUSE frames) was + * one slot behind the part for good, and every later send saw "busy". + * The caller checks `busy` first; OWN still set on the next slot is a full + * ring. + */ +pub fn send(driver: &mut Driver, frame: &[u8]) { let idx = driver.tx.cur; - if (unsafe { desc(driver.tx.desc_va, idx) }.opts1 & DESC_OWN) != 0 { - return Err("rtl8169 tx descriptor busy"); - } + let wire = frame.len().max(MIN_WIRE_FRAME); unsafe { - core::ptr::copy_nonoverlapping( - frame.as_ptr(), - driver.tx.buffer_va(idx) as *mut u8, - frame.len(), - ); + let dst = driver.tx.buffer_va(idx) as *mut u8; + core::ptr::copy_nonoverlapping(frame.as_ptr(), dst, frame.len()); + core::ptr::write_bytes(dst.add(frame.len()), 0, wire - frame.len()); } compiler_fence(Ordering::Release); let eor = if idx == TX_DESC_COUNT - 1 { DESC_EOR } else { 0 }; let addr = driver.tx.buffer_da(idx); let d = Descriptor { - opts1: DESC_OWN | DESC_FS | DESC_LS | eor | frame.len() as u32, + opts1: DESC_OWN | DESC_FS | DESC_LS | eor | wire as u32, opts2: 0, addr_lo: addr as u32, addr_hi: (addr >> 32) as u32, }; unsafe { desc_mut(driver.tx.desc_va, idx, d); - driver.regs.w8(REG_TX_POLL, TX_POLL_HPQ); - } - poll_done(driver, idx)?; - let isr = unsafe { driver.regs.r16(REG_ISR) }; - if isr != 0 { - unsafe { - driver.regs.w16(REG_ISR, isr & ISR_ENABLED); - } - } - if (isr & ISR_TER) != 0 { - return Err("rtl8169 tx interrupt error"); + driver.regs.w8(REG_TX_POLL, TX_POLL_NPQ); } driver.tx.cur = (idx + 1) % TX_DESC_COUNT; - Ok(()) } diff --git a/userland/capsule_driver_rtl8821ce/src/assoc.rs b/userland/capsule_driver_rtl8821ce/src/assoc.rs index 31e023e836..054c493b3d 100644 --- a/userland/capsule_driver_rtl8821ce/src/assoc.rs +++ b/userland/capsule_driver_rtl8821ce/src/assoc.rs @@ -58,7 +58,7 @@ pub trait Radio { /// How a join attempt ended. pub enum Outcome { /// Associated: the pairwise and group keys and the AP to install them for. - Joined { bssid: [u8; 6], channel: u8, ptk: [u8; 16], gtk: [u8; 16] }, + Joined { bssid: [u8; 6], channel: u8, ptk: [u8; 16], gtk: [u8; 16], gtk_id: u8 }, /// The association was refused or the handshake broke. Refused, /// The AP stopped responding before the join completed. @@ -351,7 +351,7 @@ fn report(mlme: &Mlme, outcome: Outcome, c: Counters) -> Report { // Pull the negotiated keys out of a connected machine. fn finish(mlme: &Mlme) -> Outcome { - let (Some(tk), Some(gtk)) = (mlme.tk(), mlme.gtk()) else { + let (Some(tk), Some(gtk), Some(gtk_id)) = (mlme.tk(), mlme.gtk(), mlme.gtk_id()) else { return Outcome::Refused; }; let mut ptk = [0u8; 16]; @@ -361,5 +361,5 @@ fn finish(mlme: &Mlme) -> Outcome { } ptk.copy_from_slice(&tk[..16]); group.copy_from_slice(>k[..16]); - Outcome::Joined { bssid: mlme.bssid(), channel: mlme.channel(), ptk, gtk: group } + Outcome::Joined { bssid: mlme.bssid(), channel: mlme.channel(), ptk, gtk: group, gtk_id } } diff --git a/userland/capsule_driver_rtl8821ce/src/link.rs b/userland/capsule_driver_rtl8821ce/src/link.rs index 15a67c0c3e..5236e39b7b 100644 --- a/userland/capsule_driver_rtl8821ce/src/link.rs +++ b/userland/capsule_driver_rtl8821ce/src/link.rs @@ -38,7 +38,7 @@ use crate::regs::Mmio; use crate::rx::ring::{RxState, RX_BUF_STRIDE, RX_DESC_COUNT}; use crate::rx::{poll_one, program as rx_program}; use crate::sec::{clear_cam, write_cam, Key, CAM_AES}; -use crate::tx::desc::{FrameMeta, DESC_RATE_6M, SEC_TYPE_CCMP}; +use crate::tx::desc::{FrameMeta, DESC_RATE_6M}; use crate::tx::regs::QSEL_BE; use crate::tx::ring::{TxState, TX_DESC_COUNT}; use crate::tx::{enqueue, program as tx_program}; @@ -308,13 +308,11 @@ impl LinkPort for RtlLink { // never registers one after association), so a rate-controlled data frame // is dropped before it reaches the air; a fixed rate transmits, exactly as // the fixed-rate handshake frames already do. - let meta = FrameMeta { - qsel: QSEL_BE, - bmc: false, - rate: Some(DESC_RATE_6M), - seq, - sec_type: SEC_TYPE_CCMP, - }; + // No security type: `tx_frame` has already encrypted the frame in + // software (header, CCMP header, MIC). A descriptor tagged CCMP asks + // the MAC to encrypt it again, as rtw88 does only for frames with a + // hardware key, and the AP then fails the MIC on every data frame. + let meta = FrameMeta { qsel: QSEL_BE, bmc: false, rate: Some(DESC_RATE_6M), seq, sec_type: 0 }; let ok = enqueue(&self.mmio, &self.tx_ring, &self.tx_buffers, &mut self.tx_state, &mpdu, &meta); if ok { diff --git a/userland/capsule_driver_rtl8821ce/src/serve/connect.rs b/userland/capsule_driver_rtl8821ce/src/serve/connect.rs index 31ecf50d1f..b934a7f820 100644 --- a/userland/capsule_driver_rtl8821ce/src/serve/connect.rs +++ b/userland/capsule_driver_rtl8821ce/src/serve/connect.rs @@ -34,9 +34,10 @@ use crate::status; use super::radio::read_mac; use super::{SCAN_CHANNELS, SCAN_FRAME_MAX}; -/// The pairwise and group key slots a connection installs into the CAM. +/// The pairwise key's index. The group key goes in the slot its own index +/// names: with the engine looking group-addressed frames up by the CCMP +/// KeyID, a fixed slot 1 went dark after the AP's first rekey moved it to 2. const PAIRWISE_KEY_ID: u8 = 0; -const GROUP_KEY_ID: u8 = 1; /// Receive passes to spend joining before giving up. The driver's clock is not /// reliable in this capsule (all its other timeouts are poll counts), so the /// join is bounded in passes. Large enough to cover authentication, association @@ -142,11 +143,11 @@ pub(super) fn connect( report.state, ); let code = match report.outcome { - Outcome::Joined { bssid, channel, ptk, gtk } => { + Outcome::Joined { bssid, channel, ptk, gtk, gtk_id } => { set_rf(regs, channel, Bw::W20); if !keys.install_ptk(&ptk, PAIRWISE_KEY_ID, &bssid) { -3 - } else if !keys.install_gtk(>k, GROUP_KEY_ID) { + } else if !keys.install_gtk(>k, gtk_id) { -4 } else { // The keys are in the CAM, so turn the sec engine on for receive @@ -157,7 +158,7 @@ pub(super) fn connect( // but unencrypted and the access point dropped them. crate::sec::enable_sec_engine(regs); link.associate(bssid, ptk); - *session = Some(Session { bssid, key_id: PAIRWISE_KEY_ID, gtk_id: GROUP_KEY_ID }); + *session = Some(Session { bssid, key_id: PAIRWISE_KEY_ID, gtk_id }); status::debug(b"[rtl8821ce] connect: associated\n"); 0 } diff --git a/userland/capsule_driver_virtio_gpu/src/device/virtqueue/used.rs b/userland/capsule_driver_virtio_gpu/src/device/virtqueue/used.rs index 07bd58e1bc..b0f335b3ed 100644 --- a/userland/capsule_driver_virtio_gpu/src/device/virtqueue/used.rs +++ b/userland/capsule_driver_virtio_gpu/src/device/virtqueue/used.rs @@ -34,10 +34,11 @@ pub struct UsedEntry { pub fn read_entry(layout: QueueLayout, ring_slot: u16) -> UsedEntry { let slot = ring_slot % layout.queue_size; let p = used_ring_entry(layout, slot); + // Order these reads after the used index that proved them written. + fence(Ordering::Acquire); unsafe { let id = read_volatile(p); let len = read_volatile(p.add(1)); - fence(Ordering::Acquire); UsedEntry { id, len } } } diff --git a/userland/capsule_driver_virtio_gpu/src/main.rs b/userland/capsule_driver_virtio_gpu/src/main.rs index fa103d135c..809b0bb8ac 100644 --- a/userland/capsule_driver_virtio_gpu/src/main.rs +++ b/userland/capsule_driver_virtio_gpu/src/main.rs @@ -31,10 +31,8 @@ use nonos_libc::{heap_init, mk_exit, mk_service_register, mk_time_millis, mk_yie const SERVICE_NAME: &[u8] = b"driver.virtio_gpu0"; const SERVICE_PORT: u32 = 4226; -// Give the device a bounded window to appear, then exit cleanly. On hardware -// with no virtio-gpu (real hardware presents through the GOP framebuffer) it would -// otherwise retry forever; degrading to a clean exit frees the slot and lets -// the compositor fall back. +// Bounded retry for a device that is present but fails a setup step; a clean +// exit after it frees the slot and lets the compositor fall back to GOP. const PROBE_DEADLINE_MS: i64 = 10_000; #[no_mangle] @@ -42,6 +40,12 @@ pub unsafe extern "C" fn _start() -> ! { if heap_init().is_err() { mk_exit(1); } + // The broker lists every PCI function before the first capsule starts, so + // with no virtio-gpu the retry below only spun through ten seconds of boot + // on real hardware. Leave at once (2, absent); the compositor takes GOP. + if discover::find_virtio_gpu().is_none() { + mk_exit(2); + } let start = mk_time_millis(); let driver = loop { match setup::run() { diff --git a/userland/capsule_driver_virtio_net/src/main.rs b/userland/capsule_driver_virtio_net/src/main.rs index 70930d07f1..67ba2e2a13 100644 --- a/userland/capsule_driver_virtio_net/src/main.rs +++ b/userland/capsule_driver_virtio_net/src/main.rs @@ -32,8 +32,7 @@ mod tx; use nonos_libc::{heap_init, mk_exit, mk_time_millis, mk_yield}; -// Bounded probe: exit cleanly if no virtio-net appears, instead of spinning -// forever on hardware that has none (real machines use their physical NIC). +// Bounded retry for a device that is present but fails a setup step. const PROBE_DEADLINE_MS: i64 = 10_000; #[no_mangle] @@ -42,6 +41,16 @@ pub unsafe extern "C" fn _start() -> ! { mk_exit(1); } + /* + * The broker lists every PCI function before the first capsule starts, so + * a machine without a virtio-net has none to wait for. Retrying discovery + * here spun for ten seconds of boot while net_core's link probes to this + * name went unanswered; leave the way the wired drivers do (2, absent). + */ + if discover::find_virtio_net().is_none() { + mk_exit(2); + } + let start = mk_time_millis(); let mut driver = loop { match setup::run() { diff --git a/userland/capsule_driver_virtio_net/src/queue/post.rs b/userland/capsule_driver_virtio_net/src/queue/post.rs index 4e0f7a8db7..2edc07f521 100644 --- a/userland/capsule_driver_virtio_net/src/queue/post.rs +++ b/userland/capsule_driver_virtio_net/src/queue/post.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{fence, Ordering}; use super::RxQueue; use crate::constants::{RING_SLOTS, VQ_AVAIL_OFFSET, VQ_DESC_OFFSET, VRING_DESC_F_WRITE}; @@ -35,6 +36,8 @@ impl RxQueue { write_volatile(slot.add(14).cast::(), 0u16); write_volatile(avail.add(2 + i as usize), i); } + // The device may read a slot the moment the index covers it. + fence(Ordering::Release); write_volatile(avail.add(1), self.buf_count); } } @@ -45,6 +48,7 @@ impl RxQueue { let idx = read_volatile(avail.add(1)); let pos = (idx % RING_SLOTS) as usize; write_volatile(avail.add(2 + pos), slot); + fence(Ordering::Release); write_volatile(avail.add(1), idx.wrapping_add(1)); } } diff --git a/userland/capsule_driver_virtio_net/src/queue/post_packet.rs b/userland/capsule_driver_virtio_net/src/queue/post_packet.rs index 86c3fe0902..707a8e7e97 100644 --- a/userland/capsule_driver_virtio_net/src/queue/post_packet.rs +++ b/userland/capsule_driver_virtio_net/src/queue/post_packet.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{fence, Ordering}; use super::TxQueue; use crate::constants::{RING_SLOTS, VQ_AVAIL_OFFSET, VQ_DESC_OFFSET}; @@ -34,6 +35,9 @@ impl TxQueue { let idx = read_volatile(avail.add(1)); let pos = (idx % RING_SLOTS) as usize; write_volatile(avail.add(AVAIL_RING_OFFSET / 2 + pos), slot); + // The frame bytes are plain stores; volatile alone would let them + // land after the index that tells the device to read them. + fence(Ordering::Release); write_volatile(avail.add(1), idx.wrapping_add(1)); } } diff --git a/userland/capsule_driver_virtio_net/src/rx.rs b/userland/capsule_driver_virtio_net/src/rx.rs index fdf89cbdf8..d296145c43 100644 --- a/userland/capsule_driver_virtio_net/src/rx.rs +++ b/userland/capsule_driver_virtio_net/src/rx.rs @@ -20,6 +20,8 @@ +use core::sync::atomic::{fence, Ordering}; + use crate::constants::{RING_SLOTS, VIRTIO_NET_HDR_LEN}; use crate::queue::RxQueue; @@ -39,6 +41,8 @@ pub unsafe fn take_one(rx: &mut RxQueue) -> Option> { if used == rx.last_used { return None; } + // The element and the frame are only valid once the index is seen. + fence(Ordering::Acquire); let ring_pos = rx.last_used % RING_SLOTS; let (desc_id, used_len) = rx.used_elem_at(ring_pos); diff --git a/userland/capsule_linux/src/linux/abi/errno.rs b/userland/capsule_linux/src/linux/abi/errno.rs index 7cbcaebde8..4e4ce63f95 100644 --- a/userland/capsule_linux/src/linux/abi/errno.rs +++ b/userland/capsule_linux/src/linux/abi/errno.rs @@ -49,6 +49,7 @@ pub const ENOTCONN: i64 = 107; pub const ENOTSOCK: i64 = 88; pub const ENOTSUP: i64 = 95; pub const EAFNOSUPPORT: i64 = 97; +pub const ENETUNREACH: i64 = 101; pub const ECONNREFUSED: i64 = 111; pub const EINPROGRESS: i64 = 115; diff --git a/userland/capsule_linux/src/linux/net/connect.rs b/userland/capsule_linux/src/linux/net/connect.rs index ce5deac6d9..82b62343d7 100644 --- a/userland/capsule_linux/src/linux/net/connect.rs +++ b/userland/capsule_linux/src/linux/net/connect.rs @@ -24,7 +24,7 @@ use crate::linux::guest::{Guest, Kind}; use super::addr::inet; use super::call::call; use super::dns::host_for; -use super::ops::{OP_CONNECT, OP_CONNECT_HOST}; +use super::ops::{NET_E_NO_TRANSPORT, OP_CONNECT, OP_CONNECT_HOST}; pub fn connect(guest: &mut Guest, fd: u64, at: u64, len: u64) -> u64 { /* @@ -48,11 +48,7 @@ pub fn connect(guest: &mut Guest, fd: u64, at: u64, len: u64) -> u64 { body.extend_from_slice(&handle.to_le_bytes()); body.extend_from_slice(&ip); body.extend_from_slice(&port.to_le_bytes()); - match call(OP_CONNECT, &body, 0) { - Some((0, _)) => errno::ok(0), - Some(_) => errno::fail(errno::ECONNREFUSED), - None => errno::fail(errno::EIO), - } + outcome(call(OP_CONNECT, &body, 0)) } fn by_host(handle: u32, host: &[u8], port: u16) -> u64 { @@ -64,8 +60,18 @@ fn by_host(handle: u32, host: &[u8], port: u16) -> u64 { body.extend_from_slice(&port.to_le_bytes()); body.push(host.len() as u8); body.extend_from_slice(host); - match call(OP_CONNECT_HOST, &body, 0) { + outcome(call(OP_CONNECT_HOST, &body, 0)) +} + +/* + * What a connect reply means to the guest. No transport is a mixnet holding + * no gateway: there is no route out, which a tool has to read as unreachable + * and not as a peer that answered and refused. + */ +fn outcome(reply: Option<(u16, Vec)>) -> u64 { + match reply { Some((0, _)) => errno::ok(0), + Some((NET_E_NO_TRANSPORT, _)) => errno::fail(errno::ENETUNREACH), Some(_) => errno::fail(errno::ECONNREFUSED), None => errno::fail(errno::EIO), } diff --git a/userland/capsule_linux/src/linux/net/ops.rs b/userland/capsule_linux/src/linux/net/ops.rs index b5392771c1..782c0f64c9 100644 --- a/userland/capsule_linux/src/linux/net/ops.rs +++ b/userland/capsule_linux/src/linux/net/ops.rs @@ -27,6 +27,12 @@ pub const OP_POLL: u16 = 13; /// The socket kinds the server offers: 1 stream, 2 datagram, 3 mixnet. pub const KIND_MIXNET: u16 = 3; +/// net.sockets' status for "connect had no transport to give the socket": the +/// mixnet holds no gateway, so there is no route out, not a peer that refused. +/// Kept in sync with E_NO_TRANSPORT in +/// userland/capsule_net_sockets/src/protocol/errno.rs. +pub const NET_E_NO_TRANSPORT: u16 = 6; + /// The address family the server takes. It is not AF_INET: the number /// is the server's own and the two only look alike. pub const DOMAIN: u16 = 4; diff --git a/userland/capsule_net_core/src/setup.rs b/userland/capsule_net_core/src/setup.rs index c6a85b42e2..e74ed69c04 100644 --- a/userland/capsule_net_core/src/setup.rs +++ b/userland/capsule_net_core/src/setup.rs @@ -69,7 +69,7 @@ pub fn bound_port() -> u32 { // position in the WiFi-then-wired order, so a change logs once, not per tick. static PROBE_SEEN: [AtomicU32; 8] = [const { AtomicU32::new(0) }; 8]; -fn discover_nic() -> Option { +fn discover_nic() -> Option<(u32, &'static str)> { let count = WIFI_NICS.len() + WIRED_NICS.len(); let start = PROBE_CURSOR.load(Ordering::Relaxed); let mut probes = 0usize; @@ -85,7 +85,7 @@ fn discover_nic() -> Option { match device::link_up(port) { Some(true) => { PROBE_CURSOR.store(idx, Ordering::Relaxed); - return Some(port); + return Some((port, name)); } verdict => { let code = if verdict.is_none() { 2 } else { 1 }; @@ -136,7 +136,7 @@ fn probe_log(name: &str, verdict: Option) { /// or the bound link drops. A no-op once bound to the best link, so it is cheap to /// call on a timer from the server loop. pub fn reevaluate() { - let Some(best) = discover_nic() else { + let Some((best, name)) = discover_nic() else { return; }; if best == BOUND_PORT.load(Ordering::Acquire) { @@ -155,9 +155,22 @@ pub fn reevaluate() { }; state::store(net_state); BOUND_PORT.store(best, Ordering::Release); - bind_log(b"[NET-CORE] bind: interface up"); + bind_up_log(name); } fn bind_log(msg: &[u8]) { let _ = nonos_libc::mk_debug(msg.as_ptr(), msg.len()); } + +// Which NIC the stack bound. With a wired port and a WiFi link both present +// the choice is the first thing to know, and "interface up" alone does not +// say it. +fn bind_up_log(name: &str) { + let mut line = [0u8; 96]; + let tag: &[u8] = b"[NET-CORE] bind: interface up on "; + let n = tag.len(); + line[..n].copy_from_slice(tag); + let m = name.len().min(line.len() - n); + line[n..n + m].copy_from_slice(&name.as_bytes()[..m]); + bind_log(&line[..n + m]); +} diff --git a/userland/e1000_proofs/libc_shim/src/lib.rs b/userland/e1000_proofs/libc_shim/src/lib.rs index 9536cc0cba..f3b3b94c2d 100644 --- a/userland/e1000_proofs/libc_shim/src/lib.rs +++ b/userland/e1000_proofs/libc_shim/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! The five things the included driver files take from `nonos_libc`. +//! The things the included driver files take from `nonos_libc`. //! //! `crypto_random` is the one that matters. The station address is drawn //! from it, and the driver's promise is that when there is no entropy the @@ -23,6 +23,23 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, MutexGuard}; +use std::time::{Duration, Instant}; + +/// A deadline on the host clock, with the capsule's API. The reset holds the +/// part for the milliseconds the 8254x manual asks, and the tests wait them. +pub struct Deadline { + end: Instant, +} + +impl Deadline { + pub fn after_ms(timeout_ms: u64) -> Self { + Self { end: Instant::now() + Duration::from_millis(timeout_ms) } + } + + pub fn expired(&self) -> bool { + Instant::now() >= self.end + } +} static ENTROPY: AtomicBool = AtomicBool::new(true); static TURN: Mutex<()> = Mutex::new(()); diff --git a/userland/e1000_proofs/src/conformance/memory.rs b/userland/e1000_proofs/src/conformance/memory.rs index 28b59615ea..f20f0766d8 100644 --- a/userland/e1000_proofs/src/conformance/memory.rs +++ b/userland/e1000_proofs/src/conformance/memory.rs @@ -56,7 +56,6 @@ impl Memory { Driver { device_id: 1, mmio_grant: 0, - irq_grant: 0, rx_ring_grant: 0, rx_buffer_grant: 0, tx_ring_grant: 0, diff --git a/userland/e1000_proofs/src/e1000_tests.rs b/userland/e1000_proofs/src/e1000_tests.rs index 03ef154810..f021b5afa2 100644 --- a/userland/e1000_proofs/src/e1000_tests.rs +++ b/userland/e1000_proofs/src/e1000_tests.rs @@ -110,7 +110,7 @@ fn post_programs_exactly_the_tail_descriptor_and_wraps() { #[test] fn decode_never_panics_and_reads_fields_from_their_offsets() { - const MAGIC: u32 = 0x4E45_3130; // the wire tag from protocol/header.rs + const MAGIC: u32 = 0x4E4E_4554; // the NNET wire tag from protocol/header.rs for seed in 1..100_000u64 { let mut s = seed; let blen = (xorshift(&mut s) % 40) as usize; diff --git a/userland/iwlwifi_proofs/src/lib.rs b/userland/iwlwifi_proofs/src/lib.rs index f0888c3f45..e6821ea568 100644 --- a/userland/iwlwifi_proofs/src/lib.rs +++ b/userland/iwlwifi_proofs/src/lib.rs @@ -22,6 +22,8 @@ pub mod constants; pub mod load; #[path = "../../capsule_driver_iwlwifi/src/regs.rs"] pub mod regs; +#[path = "../../capsule_driver_iwlwifi/src/firmware/tlv.rs"] +pub mod tlv; // The 802.11 frame layer: pure IEEE encoding, no hardware, so it is checked // exactly rather than modeled. `src/dot11/mod.rs` pulls in the real files. @@ -91,3 +93,5 @@ mod gen3_image_tests; mod gen3_tests; #[cfg(test)] mod prph_scratch_tests; +#[cfg(test)] +mod blob_scan_tests; diff --git a/userland/nonos_wifi_core/src/mlme/state.rs b/userland/nonos_wifi_core/src/mlme/state.rs index 8913c3686d..d0d36cd818 100644 --- a/userland/nonos_wifi_core/src/mlme/state.rs +++ b/userland/nonos_wifi_core/src/mlme/state.rs @@ -105,6 +105,11 @@ impl Mlme { self.supplicant.as_ref().filter(|_| self.state == MlmeState::Connected).map(|s| s.gtk()) } + /// The group key's index, valid once Connected. + pub fn gtk_id(&self) -> Option { + self.supplicant.as_ref().filter(|_| self.state == MlmeState::Connected).map(|s| s.gtk_id()) + } + /// The station MAC and the joined AP's BSSID, needed by the data path to /// address encrypted frames. Meaningful once a BSS has been selected. pub fn our_mac(&self) -> [u8; 6] { diff --git a/userland/nonos_wifi_core/src/wpa/supplicant/state.rs b/userland/nonos_wifi_core/src/wpa/supplicant/state.rs index a7f107bbe9..b607444b24 100644 --- a/userland/nonos_wifi_core/src/wpa/supplicant/state.rs +++ b/userland/nonos_wifi_core/src/wpa/supplicant/state.rs @@ -49,6 +49,8 @@ pub struct Supplicant { /// Group temporal key, unwrapped from message 3. pub(super) gtk: [u8; 32], pub(super) gtk_len: usize, + /// The group key's index from its KDE. + pub(super) gtk_id: u8, } impl Supplicant { @@ -65,6 +67,7 @@ impl Supplicant { ptk: [0u8; 48], gtk: [0u8; 32], gtk_len: 0, + gtk_id: 0, } } @@ -82,6 +85,13 @@ impl Supplicant { &self.gtk[..self.gtk_len] } + /// The group key's index (1-3), valid once Connected. Group-addressed + /// frames name their key by it, and an AP moves it between 1 and 2 on + /// every rekey, so it has to be installed where the AP says. + pub fn gtk_id(&self) -> u8 { + self.gtk_id + } + // KCK: signs EAPOL MICs. KEK: unwraps the group key. Both are slices of the // PTK, named here so the handshake code reads like the standard. pub(super) fn kck(&self) -> &[u8] { diff --git a/userland/nonos_wifi_core/src/wpa/supplicant/step.rs b/userland/nonos_wifi_core/src/wpa/supplicant/step.rs index 6c0e71c18f..ee7e67cea7 100644 --- a/userland/nonos_wifi_core/src/wpa/supplicant/step.rs +++ b/userland/nonos_wifi_core/src/wpa/supplicant/step.rs @@ -117,14 +117,19 @@ impl Supplicant { let Some(len) = aes_unwrap(&self.kek(), wrapped, &mut plain) else { return false; }; - find_gtk(&plain[..len], &mut self.gtk).map(|n| self.gtk_len = n).is_some() + find_gtk(&plain[..len], &mut self.gtk) + .map(|(n, id)| { + self.gtk_len = n; + self.gtk_id = id; + }) + .is_some() } } -// Scan an unwrapped key-data buffer for the GTK KDE and copy the key out. -// KDE: 0xDD, length, 00 0F AC (RSN OUI), 0x01 (GTK), key-id byte, reserved, -// then the group key. -fn find_gtk(data: &[u8], out: &mut [u8; 32]) -> Option { +// Scan an unwrapped key-data buffer for the GTK KDE and copy the key out, +// returning its length and index. KDE: 0xDD, length, 00 0F AC (RSN OUI), +// 0x01 (GTK), key-id byte (index in bits 0-1), reserved, then the group key. +fn find_gtk(data: &[u8], out: &mut [u8; 32]) -> Option<(usize, u8)> { let mut i = 0usize; while i + 2 <= data.len() { let tag = data[i]; @@ -143,7 +148,7 @@ fn find_gtk(data: &[u8], out: &mut [u8; 32]) -> Option { return None; } out[..gtk.len()].copy_from_slice(gtk); - return Some(gtk.len()); + return Some((gtk.len(), data[i + 6] & 0x03)); } if tag == 0x00 { break; // padding diff --git a/userland/nonos_wifi_core_proofs/src/lib.rs b/userland/nonos_wifi_core_proofs/src/lib.rs index b26b7b6eb4..51a0164db2 100644 --- a/userland/nonos_wifi_core_proofs/src/lib.rs +++ b/userland/nonos_wifi_core_proofs/src/lib.rs @@ -21,3 +21,5 @@ mod netif_tests; #[cfg(test)] mod station_tests; +#[cfg(test)] +mod supplicant_tests; diff --git a/userland/nonos_wifi_core_proofs/src/supplicant_tests.rs b/userland/nonos_wifi_core_proofs/src/supplicant_tests.rs new file mode 100644 index 0000000000..dbe6ce8ae5 --- /dev/null +++ b/userland/nonos_wifi_core_proofs/src/supplicant_tests.rs @@ -0,0 +1,99 @@ +// NONOS Operating System (AGPL-3.0-or-later) +//! The group key's index survives the four-way handshake. A simulated AP +//! drives the real `Supplicant` through messages 1 and 3 with the GTK KDE +//! naming index 1, then index 2, and the proof holds the supplicant (and the +//! MLME accessor the drivers read) to the index the AP sent. Group-addressed +//! frames name their key by this index and an AP moves it between 1 and 2 on +//! every rekey, so a driver that installs the group key anywhere else stops +//! decrypting broadcast (ARP, DHCP, router advertisements) after the first one. + +use nonos_wifi_core::ccmp::aes::Aes128; +use nonos_wifi_core::eapol::build::build_key_frame; +use nonos_wifi_core::eapol::parse::{KEY_INFO_MIC, KEY_INFO_PAIRWISE, KEY_INFO_VERSION2}; +use nonos_wifi_core::wpa::ptk::{pmk, ptk}; +use nonos_wifi_core::wpa::supplicant::{State, Supplicant}; + +const AA: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01]; +const SPA: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02]; +const ANONCE: [u8; 32] = [0xA1; 32]; +const SNONCE: [u8; 32] = [0x52; 32]; +const GTK: [u8; 16] = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, +]; + +// RFC 3394 AES key wrap: the AP's half of the group-key delivery. +fn aes_wrap(kek: &[u8; 16], plain: &[u8]) -> Vec { + assert!(plain.len().is_multiple_of(8) && !plain.is_empty()); + let n = plain.len() / 8; + let aes = Aes128::new(kek); + let mut a = [0xA6u8; 8]; + let mut r: Vec<[u8; 8]> = + (0..n).map(|i| plain[i * 8..i * 8 + 8].try_into().unwrap()).collect(); + for j in 0..6u64 { + for (i, ri) in r.iter_mut().enumerate() { + let mut block = [0u8; 16]; + block[..8].copy_from_slice(&a); + block[8..].copy_from_slice(&ri[..]); + aes.encrypt_block(&mut block); + let t = (n as u64) * j + (i as u64) + 1; + a.copy_from_slice(&block[..8]); + for (k, ak) in a.iter_mut().enumerate() { + *ak ^= (t >> (56 - 8 * k)) as u8; + } + ri.copy_from_slice(&block[8..]); + } + } + let mut out = a.to_vec(); + for block in &r { + out.extend_from_slice(block); + } + out +} + +// GTK KDE: dd 00 0f ac 01 , wrapped. +fn wrapped_gtk_kde(kek: &[u8; 16], key_id_byte: u8) -> Vec { + let mut kde = vec![0xDD, 22, 0x00, 0x0f, 0xac, 0x01, key_id_byte, 0x00]; + kde.extend_from_slice(>K); + aes_wrap(kek, &kde) +} + +fn message1() -> Vec { + let mut out = [0u8; 160]; + let info = KEY_INFO_VERSION2 | KEY_INFO_PAIRWISE; + let n = build_key_frame(&mut out, info, &[0, 0, 0, 0, 0, 0, 0, 1], &ANONCE, &[], &[0u8; 16]) + .unwrap(); + out[..n].to_vec() +} + +fn message3(kck: &[u8], kek: &[u8; 16], key_id_byte: u8) -> Vec { + let mut out = [0u8; 160]; + let info = KEY_INFO_VERSION2 | KEY_INFO_PAIRWISE | KEY_INFO_MIC; + let kd = wrapped_gtk_kde(kek, key_id_byte); + let n = build_key_frame(&mut out, info, &[0, 0, 0, 0, 0, 0, 0, 2], &ANONCE, &kd, kck).unwrap(); + out[..n].to_vec() +} + +fn handshake(key_id_byte: u8) -> Supplicant { + let key = pmk(b"ThisIsAPassword", b"ThisIsASSID"); + let p = ptk(&key, &AA, &SPA, &ANONCE, &SNONCE); + let mut sup = Supplicant::new(key, AA, SPA, SNONCE); + sup.step(&message1()); + assert_eq!(sup.state(), State::PtkDerived); + sup.step(&message3(&p[0..16], p[16..32].try_into().unwrap(), key_id_byte)); + assert_eq!(sup.state(), State::Connected); + assert_eq!(sup.gtk(), >K); + sup +} + +#[test] +fn the_group_key_keeps_the_index_the_ap_sent() { + assert_eq!(handshake(0x01).gtk_id(), 1); + assert_eq!(handshake(0x02).gtk_id(), 2, "the index after an AP's first rekey"); + assert_eq!(handshake(0x03).gtk_id(), 3); +} + +#[test] +fn only_the_index_bits_are_taken() { + // Bit 2 of the byte is the Tx flag, not part of the index. + assert_eq!(handshake(0x04 | 0x02).gtk_id(), 2); +} diff --git a/userland/rtl8139_proofs/Cargo.lock b/userland/rtl8139_proofs/Cargo.lock index 7d7b9a3926..2d65710987 100644 --- a/userland/rtl8139_proofs/Cargo.lock +++ b/userland/rtl8139_proofs/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "rtl8139_proofs" -version = "0.1.0" +version = "0.3.0" diff --git a/userland/rtl8139_proofs/src/rtl_tests.rs b/userland/rtl8139_proofs/src/rtl_tests.rs index d3dfdc225b..a41e72fa09 100644 --- a/userland/rtl8139_proofs/src/rtl_tests.rs +++ b/userland/rtl8139_proofs/src/rtl_tests.rs @@ -1,5 +1,5 @@ // NONOS Operating System (AGPL-3.0-or-later) -use crate::constants::dma::RX_BUF_DATA_BYTES; +use crate::constants::dma::{RX_BUF_BYTES, RX_BUF_DATA_BYTES}; use crate::constants::MAX_ETHERNET_FRAME; use crate::protocol::{decode_request, encode_response_header, Request, HDR_LEN}; use crate::ring::{copy, u16_at, u8_at}; @@ -9,25 +9,34 @@ use alloc::boxed::Box; use alloc::vec::Vec; // The device fills a 32 KiB byte ring with per-packet records: a status -// word, a raw length, then the frame, and the driver walks it by offset -// arithmetic that wraps at the data size. The primitives below are the only -// way the walk touches memory, so their property is the isolation property: -// every read lands inside the ring for every offset, the u16 assembly wraps -// correctly at the seam, and the wrapping copy fills exactly the caller's -// buffer and nothing beyond it. +// word, a raw length, then the frame. RCR.WRAP is set, so a record that +// crosses the end of the ring is written on past it into the slack the +// allocation carries (RX_BUF_BYTES), not back at the start, and the driver +// reads linearly. The primitives below are the only way the walk touches +// memory, so their property is the isolation property: no read leaves the +// allocation for any offset, the u16 assembly is little-endian across the +// seam, and the copy fills exactly the caller's buffer and nothing beyond it. fn pattern(i: usize) -> u8 { (i % 251) as u8 } -fn ring_buf() -> Box<[u8; RX_BUF_DATA_BYTES]> { - let mut b = Box::new([0u8; RX_BUF_DATA_BYTES]); +fn ring_buf() -> Box<[u8; RX_BUF_BYTES]> { + let mut b = Box::new([0u8; RX_BUF_BYTES]); for (i, byte) in b.iter_mut().enumerate() { *byte = pattern(i); } b } +fn expected(off: usize) -> u8 { + if off < RX_BUF_BYTES { + pattern(off) + } else { + 0 + } +} + fn xorshift(state: &mut u64) -> u64 { *state ^= *state << 13; *state ^= *state >> 7; @@ -36,7 +45,7 @@ fn xorshift(state: &mut u64) -> u64 { } #[test] -fn every_byte_read_lands_at_the_wrapped_offset() { +fn no_read_leaves_the_allocation() { let buf = ring_buf(); let base = buf.as_ptr() as u64; let edges = [ @@ -45,46 +54,48 @@ fn every_byte_read_lands_at_the_wrapped_offset() { RX_BUF_DATA_BYTES - 1, RX_BUF_DATA_BYTES, RX_BUF_DATA_BYTES + 1, + RX_BUF_BYTES - 1, + RX_BUF_BYTES, + RX_BUF_BYTES + 1, 7 * RX_BUF_DATA_BYTES + 13, usize::MAX / 2, usize::MAX - 1, ]; for &off in &edges { - assert_eq!(u8_at(base, off), pattern(off % RX_BUF_DATA_BYTES), "offset {off}"); + assert_eq!(u8_at(base, off), expected(off), "offset {off}"); } let mut s = 1u64; for _ in 0..200_000 { - let off = xorshift(&mut s) as usize; - assert_eq!(u8_at(base, off), pattern(off % RX_BUF_DATA_BYTES)); + let off = xorshift(&mut s) as usize % (2 * RX_BUF_BYTES); + assert_eq!(u8_at(base, off), expected(off), "offset {off}"); } } #[test] -fn u16_reads_assemble_little_endian_and_wrap_at_the_seam() { +fn u16_reads_assemble_little_endian_and_run_on_past_the_seam() { let buf = ring_buf(); let base = buf.as_ptr() as u64; assert_eq!(u16_at(base, 0), u16::from_le_bytes([pattern(0), pattern(1)])); - // The device can leave a header at the last ring byte; the high byte must - // come from the start of the ring, not from past its end. + // With WRAP set the byte after the ring's last one is in the slack, where + // the device wrote it, not at the start of the ring. let seam = RX_BUF_DATA_BYTES - 1; - assert_eq!(u16_at(base, seam), u16::from_le_bytes([pattern(seam), pattern(0)])); - assert_eq!( - u16_at(base, 3 * RX_BUF_DATA_BYTES - 1), - u16::from_le_bytes([pattern(seam), pattern(0)]) - ); + assert_eq!(u16_at(base, seam), u16::from_le_bytes([pattern(seam), pattern(seam + 1)])); + // The last byte of the allocation pairs with nothing past it. + let last = RX_BUF_BYTES - 1; + assert_eq!(u16_at(base, last), u16::from_le_bytes([pattern(last), 0])); } #[test] -fn the_wrapping_copy_fills_the_frame_from_the_wrapped_ring() { +fn a_frame_crossing_the_seam_is_read_from_the_slack() { let buf = ring_buf(); let base = buf.as_ptr() as u64; - // A frame that starts near the end of the ring and wraps through the seam. + // A frame that starts near the end of the ring and runs past it. let start = RX_BUF_DATA_BYTES - 5; let mut out = [0u8; 64]; let n = out.len(); copy(base, start, &mut out, n); for (i, b) in out.iter().enumerate() { - assert_eq!(*b, pattern((start + i) % RX_BUF_DATA_BYTES), "byte {i}"); + assert_eq!(*b, pattern(start + i), "byte {i}"); } } @@ -98,7 +109,7 @@ fn an_oversized_length_never_writes_past_the_caller_buffer() { let out_len = 32; copy(base, 7, &mut guarded[..out_len], usize::MAX / 2); for (i, b) in guarded.iter().enumerate().take(out_len) { - assert_eq!(*b, pattern((7 + i) % RX_BUF_DATA_BYTES), "byte {i}"); + assert_eq!(*b, pattern(7 + i), "byte {i}"); } for b in guarded.iter().skip(out_len) { assert_eq!(*b, 0xEE, "the copy must never write past the caller's slice"); @@ -118,7 +129,7 @@ fn the_frame_gate_constants_fit_the_ring_and_the_reply() { #[test] fn decode_never_panics_and_reads_fields_from_their_offsets() { - const MAGIC: u32 = 0x4E52_3839; // the wire tag from protocol/header.rs + const MAGIC: u32 = 0x4E4E_4554; // the NNET wire tag from protocol/header.rs for seed in 1..100_000u64 { let mut s = seed; let blen = (xorshift(&mut s) % 40) as usize; diff --git a/userland/rtl8169_proofs/src/tests/bring_up_tests.rs b/userland/rtl8169_proofs/src/tests/bring_up_tests.rs index 675ac6b809..e547173427 100644 --- a/userland/rtl8169_proofs/src/tests/bring_up_tests.rs +++ b/userland/rtl8169_proofs/src/tests/bring_up_tests.rs @@ -24,7 +24,7 @@ use nonos_libc::entropy; use super::memory::Memory; use super::model::{idr, live, resetting_part, window, FACTORY}; -use crate::constants::regs::{CMD_RX_ENABLE, ISR_ENABLED, REG_CMD, REG_IMR}; +use crate::constants::regs::{CMD_RX_ENABLE, REG_CMD, REG_IMR}; use crate::init::bring_up; /* @@ -56,5 +56,5 @@ fn the_part_is_never_enabled_while_it_still_carries_the_factory_address() { assert_eq!(d.mac, idr(&bar)); let cmd = bar.wrote8(REG_CMD); assert_eq!(cmd & 0x1C, 0x0C, "TE bit 2 and RE bit 3 on, RST bit 4 off, per the datasheet"); - assert_eq!(bar.wrote16(REG_IMR), ISR_ENABLED); + assert_eq!(bar.wrote16(REG_IMR), 0, "the driver polls: no chip interrupt is unmasked"); } diff --git a/userland/rtl8169_proofs/src/tests/memory.rs b/userland/rtl8169_proofs/src/tests/memory.rs index 4f354cf79c..e407f2a040 100644 --- a/userland/rtl8169_proofs/src/tests/memory.rs +++ b/userland/rtl8169_proofs/src/tests/memory.rs @@ -52,7 +52,6 @@ impl Memory { Driver { device_id: 1, mmio_grant: 2, - irq_grant: 3, rx_ring_grant: 4, rx_buffer_grant: 5, tx_ring_grant: 6,