net: an RTL8139 driver, and the e1000 stops refusing the PCIe parts

Two NICs, and unlike the RTL8111 last commit, QEMU emulates both -- so both
are tested rather than reasoned about.

**RTL8139** is its own driver and not a mode of the 8169, because it is its own
chip. No descriptor rings: the receiver is one circular buffer the chip writes
packets into back to back behind four-byte headers, and the transmitter is four
fixed slots. Putting both programming models behind one set of function
pointers is how a mode flag ends up threaded through every path.

Three details are the whole driver. CAPR runs sixteen bytes behind the true
read position -- a quirk of the part, not an off-by-one. WRAP means the chip
runs a packet past the end of the buffer rather than splitting it, so the 8 KB
buffer is allocated as 8 KB + 16 + one maximum frame; getting that wrong is a
chip writing past its allocation at line rate. And a length field of 0xFFF0
means "still writing this one", so treating it as a length copies a partial
packet and loses the alignment for everything after it.

**The e1000 now matches the PCIe 8257x parts.** Its header refused them, with
a note saying they "differ in NVM access and initialisation, and claiming a
device this driver has not been shown to initialise is worse than not claiming
it". That was right, and the second half is answerable: QEMU emulates the
82574L, so it can be *shown* to initialise.

What differs turned out to be one register. EERD moved its address field from
bits 8-15 to bits 2-15 and its done bit from 4 to 1; the rings, RCTL, TCTL and
ICR are identical. So there is a per-instance flag for the NVM layout and seven
more ids -- not a second copy of four hundred lines of ring code. The wrong
pair of constants fails silently, reading the wrong word or waiting for a bit
that never sets, and the MAC comes out as zeroes; that is why it is named in
the header rather than papered over.

Still refused: the ICH/PCH parts (82562, 82577-82579, I217-I219), which put the
PHY behind a MAC-PHY interconnect and need a different bring-up nothing here
can test.

Tested, each booted alone: rtl8139 (10ec:8139) and 82574L (8086:10d3) both bind,
read a real MAC, complete DHCP and ping 4 of 4 with no loss. The classic
82540EM does the same, which is the regression that mattered for the NVM
change. Both supported display configurations: one boot each, no traps, no
panics, socktest and memfdtest green.

Also recorded: the Chromebox CN62 carries an RTL8111, not an Intel part, so
last commit's driver covers it too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-09-24 15:30:38 +02:00
commit 0331d548c4
6 changed files with 530 additions and 8 deletions

View file

@ -0,0 +1,403 @@
// Realtek RTL8139 Fast Ethernet.
//
// The other Realtek, and a genuinely different chip from the RTL8111 next
// door despite the neighbouring name. The 8139 has no descriptor rings: the
// receiver is **one circular buffer** the chip writes packets into back to
// back, each behind a four-byte header, and the transmitter is **four fixed
// slots** used round-robin. Sharing a driver with the 8169 would mean two
// programming models behind one set of function pointers, which is how a
// driver ends up with a mode flag threaded through every path.
//
// It is here for two reasons. It is what a great many older machines have --
// and it is the one Realtek part QEMU emulates, so unlike the 8169 beside it
// every line of this has been run.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "pci.h"
#include "device.h"
#include "netdev.h"
#include "dma.h"
#include "irq.h"
// --- registers --------------------------------------------------------------
#define R_IDR0 0x00 // station address
#define R_MAR0 0x08 // multicast filter
#define R_TSD0 0x10 // four transmit status registers
#define R_TSAD0 0x20 // four transmit buffer addresses
#define R_RBSTART 0x30 // receive buffer, 32-bit physical
#define R_CR 0x37 // command
#define R_CAPR 0x38 // how far the driver has read
#define R_CBR 0x3A // how far the chip has written
#define R_IMR 0x3C
#define R_ISR 0x3E
#define R_TCR 0x40
#define R_RCR 0x44
#define R_9346CR 0x50
#define R_CONFIG1 0x52
#define R_MSR 0x58 // media status
#define CR_RST 0x10
#define CR_RE 0x08
#define CR_TE 0x04
#define CR_BUFE 0x01 // receive buffer empty
#define TSD_OWN 0x00002000 // the chip has taken the data
#define TSD_TOK 0x00008000 // and put it on the wire
#define TSD_SIZE 0x00001FFF
#define ISR_ROK 0x0001
#define ISR_RER 0x0002
#define ISR_TOK 0x0004
#define ISR_TER 0x0008
#define ISR_RXOVW 0x0010
#define ISR_LINKCHG 0x0020
#define ISR_FOVW 0x0040
#define MSR_LINKB 0x04 // link *bad* -- the sense is inverted
// Accept our own address, broadcast and multicast; no promiscuous. WRAP lets
// the chip run a packet past the end of the buffer rather than splitting it,
// which is why the allocation below is larger than the buffer length it
// declares. MXDMA and RXFTH are both "unlimited".
#define RCR_APM 0x00000002
#define RCR_AM 0x00000004
#define RCR_AB 0x00000008
#define RCR_WRAP 0x00000080
#define RCR_RBLEN_8K 0x00000000
#define RCR_UNLIMITED 0x0000E700
// 8 KB of buffer, 16 bytes the chip requires past it, and one maximum frame of
// slack because WRAP is set. Getting this last part wrong is a chip writing
// over whatever follows the allocation, at line rate.
#define RXBUF_LEN 8192
#define RXBUF_PAD 16
#define RXBUF_SLACK 1536
#define RXBUF_TOTAL (RXBUF_LEN + RXBUF_PAD + RXBUF_SLACK)
#define NTX 4
#define TXBUF 2048
#define RTL8139_MAX_NICS 2
struct rtl8139 {
volatile uint8 *mmio;
uint8 *rxbuf, *txbuf;
uint64 rxbuf_phys, txbuf_phys;
int rx_ptr; // our offset into the circular buffer
int tx_slot; // the next of the four to use
uint8 mac[6];
int ready;
uint64 rx_packets, tx_packets;
struct spinlock lock;
struct pci_dev *pdev;
struct device *ndev;
};
static struct rtl8139 nics[RTL8139_MAX_NICS];
static struct rtl8139 *
of(struct device *d)
{
return d ? (struct rtl8139 *)d->priv : 0;
}
static inline uint8 rd8 (struct rtl8139 *r, int o) { return *(volatile uint8 *)(r->mmio + o); }
static inline uint16 rd16(struct rtl8139 *r, int o) { return *(volatile uint16 *)(r->mmio + o); }
static inline uint32 rd32(struct rtl8139 *r, int o) { return *(volatile uint32 *)(r->mmio + o); }
static inline void wr8 (struct rtl8139 *r, int o, uint8 v) { *(volatile uint8 *)(r->mmio + o) = v; }
static inline void wr16(struct rtl8139 *r, int o, uint16 v) { *(volatile uint16 *)(r->mmio + o) = v; }
static inline void wr32(struct rtl8139 *r, int o, uint32 v) { *(volatile uint32 *)(r->mmio + o) = v; }
// --- the class ops ----------------------------------------------------------
static const unsigned char *
rtl8139_mac_op(struct device *d)
{
struct rtl8139 *r = of(d);
static const unsigned char zero[6];
return r ? r->mac : zero;
}
static int
rtl8139_link_up(struct device *d)
{
struct rtl8139 *r = of(d);
// LINKB is set when the link is *down*. Reading it the obvious way round is
// a driver that reports a cable as unplugged whenever it is plugged in.
return r ? (rd8(r, R_MSR) & MSR_LINKB) == 0 : 0;
}
static int
rtl8139_transmit(struct device *d, const void *frame, int len)
{
struct rtl8139 *r = of(d);
int slot;
uint32 tsd;
if (!r || !r->ready)
return -1;
if (len <= 0 || len > TXBUF)
return -1;
acquire(&r->lock);
slot = r->tx_slot;
tsd = rd32(r, R_TSD0 + slot * 4);
// A slot is ours again once the chip has taken the data (OWN) -- not once it
// has reached the wire. Waiting for TOK instead would idle three quarters of
// the transmitter for no benefit.
if (r->tx_packets >= NTX && !(tsd & TSD_OWN)) {
release(&r->lock);
return -1; // all four busy: congestion, so retry
}
// Ethernet's minimum is 60 bytes before the FCS and this chip does not pad.
// An ARP request is 42, so a driver that skips this cannot resolve anything.
int n = len < 60 ? 60 : len;
memmove(r->txbuf + (uint64)slot * TXBUF, frame, len);
if (n > len)
memset(r->txbuf + (uint64)slot * TXBUF + len, 0, n - len);
wr32(r, R_TSAD0 + slot * 4, (uint32)(r->txbuf_phys + (uint64)slot * TXBUF));
__sync_synchronize();
// Writing the size is what starts it: this clears OWN, and the chip sets OWN
// again when it has the data. The early-transmit threshold in bits 16-21 is
// left at zero, which means "start when the whole frame is in the FIFO" --
// the safe end of a trade whose other end is underruns.
wr32(r, R_TSD0 + slot * 4, (uint32)n);
r->tx_slot = (slot + 1) % NTX;
r->tx_packets++;
release(&r->lock);
return 0;
}
static int
rtl8139_receive(struct device *d, void *out, int cap)
{
struct rtl8139 *r = of(d);
uint32 hdr;
int status, len, avail;
if (!r || !r->ready)
return 0;
acquire(&r->lock);
if (rd8(r, R_CR) & CR_BUFE) { // nothing waiting
release(&r->lock);
return 0;
}
// The four-byte header the chip writes in front of every packet: status in
// the low half, length in the high half. The length counts the 4-byte CRC.
hdr = *(volatile uint32 *)(r->rxbuf + r->rx_ptr);
status = (int)(hdr & 0xFFFF);
len = (int)((hdr >> 16) & 0xFFFF);
// 0xFFF0 is the chip saying "still writing this one". Treating it as a
// length is a driver that copies a partial packet and then loses the buffer
// alignment for everything after it.
if (len == 0xFFF0 || len == 0) {
release(&r->lock);
return 0;
}
if (!(status & 0x0001) || len < 8 || len > RXBUF_LEN) {
// A bad frame. Resynchronise rather than try to skip it: the length is
// the only thing that says where the next one starts, and it is the field
// that is not to be trusted here.
wr8(r, R_CR, CR_TE); // stop the receiver
r->rx_ptr = 0;
wr32(r, R_RBSTART, (uint32)r->rxbuf_phys);
wr16(r, R_CAPR, (uint16)(0 - RXBUF_PAD));
wr8(r, R_CR, CR_RE | CR_TE);
release(&r->lock);
return 0;
}
avail = len - 4; // drop the CRC the stack does not want
if (avail > cap)
avail = cap;
// The copy may run past the end of the 8 KB window, which is exactly what
// WRAP is for and why the allocation has a frame of slack: the bytes are
// there, contiguously, so one memmove is correct.
if (avail > 0)
memmove(out, r->rxbuf + r->rx_ptr + 4, avail);
// Four-byte align the next offset, wrap it, and tell the chip how far we
// have read. CAPR is *sixteen behind* the true position -- a quirk of the
// part, not an off-by-one.
r->rx_ptr = (r->rx_ptr + len + 4 + 3) & ~3;
r->rx_ptr %= RXBUF_LEN;
wr16(r, R_CAPR, (uint16)(r->rx_ptr - RXBUF_PAD));
r->rx_packets++;
release(&r->lock);
return avail;
}
// --- interrupt --------------------------------------------------------------
static void
rtl8139_intr(void *arg)
{
struct rtl8139 *r = arg;
uint16 isr;
if (!r || !r->mmio)
return;
isr = rd16(r, R_ISR);
if (!isr)
return;
wr16(r, R_ISR, isr); // write-1-to-clear, or it re-enters
// As with every other NIC here: nothing is read from the buffer in interrupt
// context. This only ends the network thread's wait early.
if (isr & (ISR_ROK | ISR_RXOVW | ISR_FOVW))
netdev_rx_signal();
}
// --- probe ------------------------------------------------------------------
static int
rtl8139_probe(struct pci_dev *d)
{
struct rtl8139 *r = 0;
uint64 bar = 0, barlen = 0;
for (int i = 0; i < RTL8139_MAX_NICS; i++)
if (!nics[i].ready) { r = &nics[i]; break; }
if (!r)
return -1;
// The first memory BAR. BAR 0 is I/O on this part and BAR 1 is the same
// registers in memory space; using memory keeps this the same shape as
// every other driver here.
for (int i = 0; i < 6; i++)
if (d->bar[i].base && !d->bar[i].is_io) {
bar = d->bar[i].base;
barlen = d->bar[i].size;
break;
}
if (!bar) {
cprintf("rtl8139: no memory BAR; not driving this device\n");
return -1;
}
r->pdev = d;
initlock(&r->lock, "rtl8139");
pci_enable(d, PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER);
r->mmio = ioremap(bar, barlen ? barlen : 0x100);
if (!r->mmio) {
cprintf("rtl8139: cannot map registers at %p\n", (void *)bar);
return -1;
}
// Wake the chip if the firmware left it asleep, then reset.
wr8(r, R_CONFIG1, 0x00);
wr8(r, R_CR, CR_RST);
for (int i = 0; i < 10000; i++) {
microdelay(10);
if (!(rd8(r, R_CR) & CR_RST))
break;
}
if (rd8(r, R_CR) & CR_RST) {
cprintf("rtl8139: the chip did not come out of reset\n");
return -1;
}
for (int i = 0; i < 6; i++)
r->mac[i] = rd8(r, R_IDR0 + i);
r->rxbuf = dma_alloc(RXBUF_TOTAL, 8);
r->txbuf = dma_alloc(NTX * TXBUF, 8);
if (!r->rxbuf || !r->txbuf) {
cprintf("rtl8139: no DMA memory for the buffers\n");
return -1;
}
r->rxbuf_phys = dma_phys(r->rxbuf);
r->txbuf_phys = dma_phys(r->txbuf);
memset(r->rxbuf, 0, RXBUF_TOTAL);
// A 32-bit part: the buffer address register is 32 bits and there is no
// dual-address cycle to fall back on. The DMA arena is below 4 GB, so this
// holds -- but it holds for a reason rather than by luck, and if the arena
// ever moves this is where it breaks.
if (r->rxbuf_phys + RXBUF_TOTAL > 0xFFFFFFFFULL ||
r->txbuf_phys + NTX * TXBUF > 0xFFFFFFFFULL) {
cprintf("rtl8139: buffers above 4 GB; this chip cannot reach them\n");
return -1;
}
wr32(r, R_RBSTART, (uint32)r->rxbuf_phys);
r->rx_ptr = 0;
wr16(r, R_CAPR, (uint16)(0 - RXBUF_PAD));
wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB | RCR_WRAP |
RCR_RBLEN_8K | RCR_UNLIMITED);
wr32(r, R_TCR, 0x00000700); // unlimited DMA burst
wr32(r, R_MAR0, 0xFFFFFFFF); // multicast: everything; the stack filters
wr32(r, R_MAR0 + 4, 0xFFFFFFFF);
wr8(r, R_CR, CR_RE | CR_TE);
wr16(r, R_ISR, 0xFFFF);
wr16(r, R_IMR, ISR_ROK | ISR_RER | ISR_TOK | ISR_TER |
ISR_RXOVW | ISR_FOVW | ISR_LINKCHG);
if (d->irq != 0xFF && d->irq < 24)
irq_register(d->irq, rtl8139_intr, r, "rtl8139", 1);
r->ready = 1;
kinfo("rtl8139: %x:%x:%x:%x:%x:%x, %s, irq %d\n",
r->mac[0], r->mac[1], r->mac[2], r->mac[3], r->mac[4], r->mac[5],
(rd8(r, R_MSR) & MSR_LINKB) ? "link down" : "link up", d->irq);
return 0;
}
static void
rtl8139_attached(struct pci_dev *d, struct device *dev)
{
for (int i = 0; i < RTL8139_MAX_NICS; i++)
if (nics[i].ready && nics[i].pdev == d) {
dev->priv = &nics[i];
nics[i].ndev = dev;
return;
}
}
static const struct netdev_ops rtl8139_netdev_ops = {
.transmit = rtl8139_transmit,
.receive = rtl8139_receive,
.mac = rtl8139_mac_op,
.link_up = rtl8139_link_up,
};
static const struct pci_match rtl8139_match[] = {
{ 0x10EC, 0x8139, 0, 0 },
{ 0, 0, 0, 0 },
};
const struct pci_driver rtl8139_driver = {
.name = "rtl8139",
.devname = "eth",
.match = rtl8139_match,
.ops = &rtl8139_netdev_ops,
.probe = rtl8139_probe,
.attached = rtl8139_attached,
};