Skip to content

Hardware Layer

PCI Enumeration (pci.rs)

Before a NIC can be used, its register base must be found by scanning the PCI configuration space. The kernel uses the standard x86 mechanism: write a configuration address to port 0xCF8, then read data from 0xCFC.

address = 0x8000_0000 | (bus << 16) | (device << 11) | (function << 8) | (offset & 0xFC)

find_io_base(vendor_id, device_id) iterates all 256 buses × 32 devices × 8 functions and returns the I/O BAR0 base (BAR0 bit 0 = 1 means I/O space). For RTL8139: vendor 0x10EC, device 0x8139. The RTL8139 is only initialised when PCI discovery finds it; the initial 0xC000 global value is not evidence of a card.

enable_bus_mastering sets bit 2 (Bus Master Enable) in PCI command register (offset 0x04) so the NIC can DMA without CPU involvement.


RTL8139 Driver (rtl8139.rs)

Initialization (rtl8139_init)

Called once by netdrv::register_driver when the global Ethernet driver process registers via syscall 0x37.

1. find_io_base()         discover I/O base from PCI BAR0
2. enable_bus_mastering() allow NIC DMA
3. CONFIG1 ← 0x00        power on
4. CMD ← 0x10            software reset; poll until bit 4 clears
5. RBSTART ← &RX_BUFFER  set RX ring base (physical == virtual, identity map)
6. CMD ← 0x0C            enable RX + TX
7. RCR ← 0xF | (1<<7) | (2<<11)   accept broadcast + multicast + runt frames; wrap mode, 32 KiB ring
8. IMR ← 0x0005          enable ROK (bit 0) and TOK (bit 2) interrupts

The RX buffer is a 32 KiB ring plus a 1 500-byte tail overrun guard (RX_BUFFER: [u8; 32768 + 16 + 1500]). The extra 16 bytes are the RTL8139 header prefix; the extra 1 500 are for frames that wrap across the ring boundary.

The driver does not register an IRQ handler. Instead, nic::peek_frame/consume_frame are polled every PIT tick and acknowledges ROK in the ISR register (+0x3E) manually.

RX Ring (receive_frame)

The RTL8139 writes incoming frames in order into RX_BUFFER starting at offset 0, wrapping around at 32 KiB. Each frame is prefixed with a 4-byte RTL8139 header:

offset +0: u16 rx_status  (bit 0 = ROK)
offset +2: u16 frame_len  (includes 4-byte CRC)
offset +4: frame data (frame_len bytes)

peek_frame checks CAPR+16 != CBR, reads the header at RX_OFFSET & 0x7FFF, and returns a borrowed slice including the attached FCS. It leaves the ring position unchanged so a receiver with no queue space can retry. consume_frame(len) advances RX_OFFSET, aligns it to four bytes, and writes RX_OFFSET - 16 to CAPR (+0x38). Invalid lengths below 14 or above 2048 advance past the bad header. The copying receive_frame helper leaves a frame queued if its destination buffer is too small.

nic.rs counts RX once on consumption, subtracting the RTL8139 FCS from the byte total. See Network Monitoring.

TX Descriptors (send_frame)

The RTL8139 has 4 TX descriptors (TSD0–TSD3 at +0x10–+0x1C, TxAddr0–TxAddr3 at +0x20–+0x2C). The driver cycles through them round-robin via TX_INDEX.

1. copy data into TX_BUFFERS[TX_INDEX]  (zero-pad to 60 bytes minimum)
2. write physical buffer address to TxAddr[TX_INDEX]  (+0x20 + idx*4)
3. write frame length to TxStatus[TX_INDEX]           (+0x10 + idx*4)
   → writing TxStatus with length < 2048 starts the DMA
4. TX_INDEX = (TX_INDEX + 1) % 4

No completion poll: the driver reuses the four descriptors round robin without checking ownership. A burst of sends can therefore reuse a descriptor before transmission finishes.

MAC Address (read_mac_addr)

The MAC is read from IDR0–IDR5 (registers +0x00–+0x05) after rtl8139_init. It is cached in SYSTEM_CONFIG so userland can query it without re-probing.


NIC Selection and Intel Driver (nic.rs, e1000.rs)

netdrv::register_driver initialises the selected backend through nic::init. RTL8139 is tried first, then Intel, then Broadcom BCM57765; no initialized card means registration fails. Routing and userland use the same interface for all backends: peek_frame, consume_frame, send_frame, MAC lookup and missed-packet counters.

The Intel driver recognises these PCI devices (vendor 0x8086):

Models Device IDs
82540EM, 82545EM 0x100e, 0x100f
82574L 0x10d3
82577LM/LC, 82578DM/DC 0x10ea, 0x10eb, 0x10ef, 0x10f0
82579LM/V 0x1502, 0x1503

It uses BAR0 memory-mapped registers below 4 GiB and identity-mapped static DMA buffers. Legacy descriptor rings contain 32 RX and 64 TX descriptors, each with a 2 KiB buffer. No NIC interrupt handler is installed: timer polling handles receive delivery and link housekeeping. A full TX ring can refuse a send.

PCH controllers have additional reset, PHY, power-management and flash NVM setup. The kernel shell's nic command exposes link state, PHY and ring diagnostics, reset, test transmission, register access and PCH NVM reads. Recognition of a model does not establish that every board's firmware configuration works.

For QEMU, replace -device rtl8139,netdev=net0 with -device e1000,netdev=net0 (82540EM) or -device e1000e,netdev=net0 (82574L) to exercise the Intel backend.


Broadcom BCM57765 (bcm57765/)

The wired BCM57765 function has primary PCI ID 14e4:16b4, class 02:00. Matching uses that ID rather than the subsystem ID. Wireless functions are excluded. BAR0 must be mapped below 4 GiB; the MacBook report's 0xa0400000 meets this requirement.

The backend wakes the PCI function into D0, masks interrupts, resets the controller, waits for firmware, retrieves the station MAC and resets the copper PHY. It advertises 10/100 Mb/s and gigabit full duplex. Standard Ethernet frames use 512-entry RX producer, RX return and TX descriptor rings, with 64 RX and 32 TX buffers of 2 KiB each. DMA storage is reserved from usable RAM before the RAM disk is placed, separately from USB DMA and user memory.

Receive buffers stay owned by the CPU until consumption. Transmit buffers are reused only after hardware completion. Frame checksums and segmentation remain in userland; VLAN headers are retained and jumbo frames are disabled. Timer polling handles status updates and starts an asynchronous PHY link check once per second. Reset and MDIO waits during registration are bounded; failed initialization disables bus mastering and reports the failure stage.

Run nic after ETH starts to see firmware status, the PHY ID, negotiated link speed, software drops and ring indices. It also refreshes /mnt/tmp/NETINFO.TXT. nic reset, raw register/PHY access and nic tx remain Intel-specific commands. Host register-model tests cover the Broadcom core, but physical BCM57765 packet traffic is not yet verified.

Initialization and register definitions are adapted from FreeBSD's bge driver and register header. Their BSD license notice ships at /mnt/iso/opt/licenses/bcm57765.txt. This product includes software developed by Bill Paul.


Serial / UART (serial.rs)

COM1 base address: 0x3F8.

Initialization

+1 ← 0x00   disable interrupts
+3 ← 0x80   enable DLAB (divisor latch access)
+0 ← 0x03   divisor low byte  → 38 400 baud  (115 200 / 3)
+1 ← 0x00   divisor high byte
+3 ← 0x03   8N1 (8 data bits, no parity, 1 stop bit)
+2 ← 0xC7   enable FIFO, clear TX+RX FIFOs, 14-byte threshold
+4 ← 0x0B   enable IRQs, assert RTS + DTR

API

Function Description
init() Configure COM1 as above
ready() → bool Check bit 0 of Line Status Register (+5); true if RX byte available
read() → u8 Read one byte from data register (+0)
write(b: u8) Spin on LSR bit 5 (TX FIFO empty), then write to +0

The UART is used as the transport for the SLIP path (see protocols.md).


Related documentation: Networking Overview, Kernel Internals, NIC Diagnostics, Build and Run.