// rtl8139.c - Realtek RTL8139 / QEMU rtl8139 PCI fast Ethernet driver. // // Scope for this first version: // * PCI ID 10ec:8139, legacy INTx interrupts. // * MMIO preferred, I/O BAR fallback. // * Classic RTL8139 register interface: one RX ring buffer and four TX slots. // * No checksum offload, C+ mode, VLAN, multicast hash programming, or power mgmt. // // This is intentionally a separate mini-driver from rtl816x.sys. RTL8139 and // RTL8111/8168 share a vendor but not a useful packet-DMA programming model. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RTL8139_VENDOR_REALTEK 0x10ECu #define RTL8139_DEVICE_ID 0x8139u #define PCI_REG_COMMAND 0x04u #define PCI_REG_BAR0 0x10u #define PCI_COMMAND_IO (1u << 0) #define PCI_COMMAND_MEMORY (1u << 1) #define PCI_COMMAND_MASTER (1u << 2) #define PCI_COMMAND_INTX_DISABLE (1u << 10) #define RTL8139_REG_IDR0 0x00u #define RTL8139_REG_MAR0 0x08u #define RTL8139_REG_TSD0 0x10u #define RTL8139_REG_TSAD0 0x20u #define RTL8139_REG_RBSTART 0x30u #define RTL8139_REG_CMD 0x37u #define RTL8139_REG_CAPR 0x38u #define RTL8139_REG_CBR 0x3Au #define RTL8139_REG_IMR 0x3Cu #define RTL8139_REG_ISR 0x3Eu #define RTL8139_REG_TCR 0x40u #define RTL8139_REG_RCR 0x44u #define RTL8139_REG_CONFIG1 0x52u #define RTL8139_CMD_BUFE 0x01u #define RTL8139_CMD_TE 0x04u #define RTL8139_CMD_RE 0x08u #define RTL8139_CMD_RST 0x10u #define RTL8139_ISR_ROK 0x0001u #define RTL8139_ISR_RER 0x0002u #define RTL8139_ISR_TOK 0x0004u #define RTL8139_ISR_TER 0x0008u #define RTL8139_ISR_RXOVW 0x0010u #define RTL8139_ISR_PUN 0x0020u #define RTL8139_ISR_FOVW 0x0040u #define RTL8139_ISR_LENCHG 0x2000u #define RTL8139_ISR_TIMEOUT 0x4000u #define RTL8139_ISR_SYSERR 0x8000u #define RTL8139_INTR_ENABLES (RTL8139_ISR_ROK | RTL8139_ISR_RER | RTL8139_ISR_TOK | \ RTL8139_ISR_TER | RTL8139_ISR_RXOVW | RTL8139_ISR_PUN | \ RTL8139_ISR_FOVW | RTL8139_ISR_LENCHG | RTL8139_ISR_SYSERR) #define RTL8139_RCR_AAP (1u << 0) #define RTL8139_RCR_APM (1u << 1) #define RTL8139_RCR_AM (1u << 2) #define RTL8139_RCR_AB (1u << 3) #define RTL8139_RCR_AR (1u << 4) #define RTL8139_RCR_AER (1u << 5) #define RTL8139_RCR_WRAP (1u << 7) #define RTL8139_RCR_MXDMA_UNLIMITED (7u << 8) #define RTL8139_RCR_RBLEN_8K (0u << 11) #define RTL8139_TCR_MXDMA_UNLIMITED (7u << 8) #define RTL8139_TCR_IFG_NORMAL (3u << 24) #define RTL8139_TSD_SIZE_MASK 0x00001FFFu #define RTL8139_TSD_OWN (1u << 13) #define RTL8139_TSD_TUN (1u << 14) #define RTL8139_TSD_TOK (1u << 15) #define RTL8139_TSD_TABT (1u << 30) #define RTL8139_RX_OK 0x0001u #define RTL8139_RX_BAD_BITS 0x003Eu // FAE | CRC | LONG | RUNT | ISE; MAR/PAM/BAR are valid match flags. #define RTL8139_TX_SLOTS 4u #define RTL8139_TX_BUF_SIZE 2048u #define RTL8139_RX_RING_SIZE 8192u #define RTL8139_RX_PAD 16u #define RTL8139_RX_GUARD 1500u #define RTL8139_RX_ALLOC_SIZE (RTL8139_RX_RING_SIZE + RTL8139_RX_PAD + RTL8139_RX_GUARD) #define RTL8139_RX_PAGES ((RTL8139_RX_ALLOC_SIZE + 4095u) / 4096u) #define RTL8139_TX_PAGES (((RTL8139_TX_SLOTS * RTL8139_TX_BUF_SIZE) + 4095u) / 4096u) #define RTL8139_MMIO_PAGES 1u #define RTL8139_RX_WIRE_MAX (NET_ETHERNET_MAX_FRAME + 4u) #define RTL8139_REG_TSD(slot) (RTL8139_REG_TSD0 + ((slot) * 4u)) #define RTL8139_REG_TSAD(slot) (RTL8139_REG_TSAD0 + ((slot) * 4u)) typedef enum RTL8139_BAR_KIND { Rtl8139BarNone = 0, Rtl8139BarIo, Rtl8139BarMmio, } RTL8139_BAR_KIND; typedef struct RTL8139_BAR { RTL8139_BAR_KIND Kind; uint64_t Base; uint8_t Index; } RTL8139_BAR; typedef struct RTL8139_DEVICE { uint8_t Bus; uint8_t Dev; uint8_t Fn; uint8_t Irq; bool UseMmio; volatile uint8_t* Mmio; uint16_t IoBase; PHYSICAL_ADDRESS RxPhys; PHYSICAL_ADDRESS TxPhys; uint8_t* RxBuffer; uint8_t* TxBuffers; uint8_t RxScratch[RTL8139_TX_BUF_SIZE]; uint32_t RxOffset; uint32_t TxSlot; SPINLOCK TxLock; KDPC Dpc; volatile uint16_t PendingIsr; NET_DEVICE NetDevice; } RTL8139_DEVICE; static const HAL_API* gHal; static RTL8139_DEVICE gDevice; static bool gDevicePresent = false; static __forceinline void Rtl8139MemoryBarrier(void) { _ReadWriteBarrier(); _mm_mfence(); _ReadWriteBarrier(); } static uint8_t Rtl8139Read8(RTL8139_DEVICE* r, uint32_t reg) { if (r->UseMmio) return *(volatile uint8_t*)(r->Mmio + reg); return __inbyte((unsigned short)(r->IoBase + reg)); } static uint16_t Rtl8139Read16(RTL8139_DEVICE* r, uint32_t reg) { if (r->UseMmio) return *(volatile uint16_t*)(r->Mmio + reg); return __inword((unsigned short)(r->IoBase + reg)); } static uint32_t Rtl8139Read32(RTL8139_DEVICE* r, uint32_t reg) { if (r->UseMmio) return *(volatile uint32_t*)(r->Mmio + reg); return __indword((unsigned short)(r->IoBase + reg)); } static void Rtl8139Write8(RTL8139_DEVICE* r, uint32_t reg, uint8_t value) { if (r->UseMmio) *(volatile uint8_t*)(r->Mmio + reg) = value; else __outbyte((unsigned short)(r->IoBase + reg), value); } static void Rtl8139Write16(RTL8139_DEVICE* r, uint32_t reg, uint16_t value) { if (r->UseMmio) *(volatile uint16_t*)(r->Mmio + reg) = value; else __outword((unsigned short)(r->IoBase + reg), value); } static void Rtl8139Write32(RTL8139_DEVICE* r, uint32_t reg, uint32_t value) { if (r->UseMmio) *(volatile uint32_t*)(r->Mmio + reg) = value; else __outdword((unsigned short)(r->IoBase + reg), value); } static bool Rtl8139ReadBar(uint8_t bus, uint8_t dev, uint8_t fn, uint8_t index, RTL8139_BAR* out) { uint8_t reg = (uint8_t)(PCI_REG_BAR0 + index * 4u); uint32_t lo = gHal->Pci.Read32(bus, dev, fn, reg); out->Kind = Rtl8139BarNone; out->Base = 0; out->Index = index; if (lo == 0 || lo == 0xFFFFFFFFu) return false; if (lo & 1u) { uint32_t base = lo & ~0x3u; if (!base) return false; out->Kind = Rtl8139BarIo; out->Base = base; return true; } uint64_t base = (uint64_t)(lo & ~0xFu); uint32_t type = (lo >> 1) & 0x3u; if (type == 0x2u && index < 5) { uint32_t hi = gHal->Pci.Read32(bus, dev, fn, (uint8_t)(reg + 4u)); base |= ((uint64_t)hi << 32); } if (!base) return false; out->Kind = Rtl8139BarMmio; out->Base = base; return true; } static KSTATUS Rtl8139MapMmio(uint64_t physBase, volatile uint8_t** outMmio) { uint64_t pageBase = physBase & PAGE_MASK; uint64_t pageOff = physBase - pageBase; for (uint32_t i = 0; i < RTL8139_MMIO_PAGES; i++) { PHYSICAL_ADDRESS phys = pageBase + (uint64_t)i * PAGE_SIZE; uintptr_t virt = (uintptr_t)gHal->PhysToVirt(phys); KSTATUS st = MmMapPage(virt, phys, PAGE_PRESENT | PAGE_WRITE | PAGE_NOCACHE); if (st == STATUS_ALREADY_EXISTS) st = MmSetPageProtect(virt, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_NOCACHE); if (!KSUCCESS(st)) return st; } *outMmio = (volatile uint8_t*)((uintptr_t)gHal->PhysToVirt(pageBase) + pageOff); return STATUS_SUCCESS; } static KSTATUS Rtl8139SelectRegisterBar(RTL8139_DEVICE* r) { RTL8139_BAR best = { Rtl8139BarNone, 0, 0 }; RTL8139_BAR fallback = { Rtl8139BarNone, 0, 0 }; for (uint8_t i = 0; i < 6; i++) { RTL8139_BAR bar; if (!Rtl8139ReadBar(r->Bus, r->Dev, r->Fn, i, &bar)) continue; if (bar.Kind == Rtl8139BarMmio) { best = bar; break; } if (bar.Kind == Rtl8139BarIo && fallback.Kind == Rtl8139BarNone) fallback = bar; if (bar.Kind == Rtl8139BarMmio) { uint32_t lo = gHal->Pci.Read32(r->Bus, r->Dev, r->Fn, (uint8_t)(PCI_REG_BAR0 + i * 4u)); if (((lo >> 1) & 0x3u) == 0x2u) i++; } } if (best.Kind == Rtl8139BarNone) best = fallback; if (best.Kind == Rtl8139BarMmio) { KSTATUS st = Rtl8139MapMmio(best.Base, &r->Mmio); if (!KSUCCESS(st)) return st; r->UseMmio = true; DbgPrint("RTL8139: BAR%u MMIO base=0x%llx virt=%p\n", best.Index, (unsigned long long)best.Base, r->Mmio); return STATUS_SUCCESS; } if (best.Kind == Rtl8139BarIo) { if (best.Base > 0xFFFFu) return STATUS_NOT_SUPPORTED; r->IoBase = (uint16_t)best.Base; r->UseMmio = false; DbgPrint("RTL8139: BAR%u I/O base=0x%04x\n", best.Index, r->IoBase); return STATUS_SUCCESS; } return STATUS_NOT_FOUND; } static void Rtl8139EnablePci(RTL8139_DEVICE* r) { uint32_t cmd = gHal->Pci.Read32(r->Bus, r->Dev, r->Fn, PCI_REG_COMMAND); cmd |= PCI_COMMAND_MASTER; if (r->UseMmio) cmd |= PCI_COMMAND_MEMORY; else cmd |= PCI_COMMAND_IO; cmd &= ~PCI_COMMAND_INTX_DISABLE; gHal->Pci.Write32(r->Bus, r->Dev, r->Fn, PCI_REG_COMMAND, cmd); } static KSTATUS Rtl8139Reset(RTL8139_DEVICE* r) { Rtl8139Write8(r, RTL8139_REG_CMD, RTL8139_CMD_RST); for (uint32_t i = 0; i < 1000; i++) { if ((Rtl8139Read8(r, RTL8139_REG_CMD) & RTL8139_CMD_RST) == 0) return STATUS_SUCCESS; if (gHal->Timer.Stall) gHal->Timer.Stall(1000); } return STATUS_TIMEOUT; } static void Rtl8139ReadMac(RTL8139_DEVICE* r) { for (uint32_t i = 0; i < 6; i++) r->NetDevice.HardwareAddress[i] = Rtl8139Read8(r, RTL8139_REG_IDR0 + i); r->NetDevice.HardwareAddressLength = 6; } static KSTATUS Rtl8139AllocateDma(RTL8139_DEVICE* r) { r->RxPhys = MmAllocateContiguous(RTL8139_RX_PAGES); r->TxPhys = MmAllocateContiguous(RTL8139_TX_PAGES); if (!r->RxPhys || !r->TxPhys) return STATUS_NO_MEMORY; // RTL8139 has 32-bit DMA address registers. This is fine for typical QEMU // and low-memory hobby OS configs, but make failure explicit. if ((r->RxPhys + RTL8139_RX_ALLOC_SIZE - 1) > 0xFFFFFFFFULL || (r->TxPhys + RTL8139_TX_SLOTS * RTL8139_TX_BUF_SIZE - 1) > 0xFFFFFFFFULL) { DbgPrint("RTL8139: DMA allocation above 4GiB is not supported by this NIC\n"); return STATUS_NOT_SUPPORTED; } r->RxBuffer = (uint8_t*)gHal->PhysToVirt(r->RxPhys); r->TxBuffers = (uint8_t*)gHal->PhysToVirt(r->TxPhys); memset(r->RxBuffer, 0, PAGE_SIZE * RTL8139_RX_PAGES); memset(r->TxBuffers, 0, PAGE_SIZE * RTL8139_TX_PAGES); DbgPrint("RTL8139: rx=0x%llx tx=0x%llx\n", (unsigned long long)r->RxPhys, (unsigned long long)r->TxPhys); return STATUS_SUCCESS; } static uint16_t Rtl8139RxRead16(RTL8139_DEVICE* r, uint32_t offset) { offset %= RTL8139_RX_RING_SIZE; uint16_t lo = r->RxBuffer[offset]; uint16_t hi = r->RxBuffer[(offset + 1u) % RTL8139_RX_RING_SIZE]; return (uint16_t)(lo | (hi << 8)); } static void Rtl8139RxCopy(RTL8139_DEVICE* r, uint32_t offset, void* dst, uint32_t length) { uint8_t* out = (uint8_t*)dst; offset %= RTL8139_RX_RING_SIZE; uint32_t first = RTL8139_RX_RING_SIZE - offset; if (first > length) first = length; memcpy(out, r->RxBuffer + offset, first); if (first < length) memcpy(out + first, r->RxBuffer, length - first); } static void Rtl8139ProgramHardware(RTL8139_DEVICE* r) { // Wake the device and clear stale interrupts before installing buffers. Rtl8139Write8(r, RTL8139_REG_CONFIG1, 0x00); Rtl8139Write16(r, RTL8139_REG_IMR, 0); Rtl8139Write16(r, RTL8139_REG_ISR, 0xFFFFu); Rtl8139Write32(r, RTL8139_REG_RBSTART, (uint32_t)r->RxPhys); r->RxOffset = 0; Rtl8139Write16(r, RTL8139_REG_CAPR, 0xFFF0u); // CAPR is rxOffset - 16. for (uint32_t i = 0; i < RTL8139_TX_SLOTS; i++) { PHYSICAL_ADDRESS p = r->TxPhys + (PHYSICAL_ADDRESS)i * RTL8139_TX_BUF_SIZE; Rtl8139Write32(r, RTL8139_REG_TSAD(i), (uint32_t)p); } Rtl8139Write32(r, RTL8139_REG_TCR, RTL8139_TCR_IFG_NORMAL | RTL8139_TCR_MXDMA_UNLIMITED); Rtl8139Write32(r, RTL8139_REG_RCR, RTL8139_RCR_APM | RTL8139_RCR_AB | RTL8139_RCR_AM | RTL8139_RCR_WRAP | RTL8139_RCR_RBLEN_8K | RTL8139_RCR_MXDMA_UNLIMITED); Rtl8139MemoryBarrier(); Rtl8139Write8(r, RTL8139_REG_CMD, RTL8139_CMD_RE | RTL8139_CMD_TE); Rtl8139Write16(r, RTL8139_REG_ISR, 0xFFFFu); Rtl8139Write16(r, RTL8139_REG_IMR, RTL8139_INTR_ENABLES); } static void Rtl8139HandleRx(RTL8139_DEVICE* r) { for (uint32_t budget = 0; budget < 64; budget++) { uint8_t cmd = Rtl8139Read8(r, RTL8139_REG_CMD); if (cmd & RTL8139_CMD_BUFE) break; uint32_t off = r->RxOffset % RTL8139_RX_RING_SIZE; uint16_t status = Rtl8139RxRead16(r, off); uint16_t packetLength = Rtl8139RxRead16(r, off + 2u); if (packetLength < 4 || packetLength > RTL8139_RX_WIRE_MAX) { DbgPrint("RTL8139: bad rx length %u status=0x%04x, resetting rx pointer\n", packetLength, status); r->RxOffset = Rtl8139Read16(r, RTL8139_REG_CBR) % RTL8139_RX_RING_SIZE; Rtl8139Write16(r, RTL8139_REG_CAPR, (uint16_t)(r->RxOffset - 16u)); break; } uint32_t frameLength = packetLength; if (frameLength > 4) frameLength -= 4; // Strip Ethernet FCS before upper-layer handoff. bool goodFrame = (status & RTL8139_RX_OK) && !(status & RTL8139_RX_BAD_BITS) && frameLength >= NET_ETHERNET_HEADER_LENGTH && frameLength <= NET_ETHERNET_MAX_FRAME; if (goodFrame) { if ((off + 4u + frameLength) <= RTL8139_RX_RING_SIZE) { NetReceiveFrame(&r->NetDevice, r->RxBuffer + off + 4u, frameLength); } else { Rtl8139RxCopy(r, off + 4u, r->RxScratch, frameLength); NetReceiveFrame(&r->NetDevice, r->RxScratch, frameLength); } } else { DbgPrint("RTL8139: rx error status=0x%04x len=%u\n", status, packetLength); } r->RxOffset = (r->RxOffset + 4u + packetLength + 3u) & ~3u; r->RxOffset %= RTL8139_RX_RING_SIZE; Rtl8139MemoryBarrier(); Rtl8139Write16(r, RTL8139_REG_CAPR, (uint16_t)(r->RxOffset - 16u)); } } static void Rtl8139DpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; (void)systemArgument1; (void)systemArgument2; RTL8139_DEVICE* r = (RTL8139_DEVICE*)context; uint16_t status = (uint16_t)r->PendingIsr; r->PendingIsr = 0; if (status & (RTL8139_ISR_ROK | RTL8139_ISR_RER | RTL8139_ISR_RXOVW | RTL8139_ISR_FOVW)) Rtl8139HandleRx(r); if (status & (RTL8139_ISR_PUN | RTL8139_ISR_LENCHG)) DbgPrint("RTL8139: link/status change isr=0x%04x\n", status); if (status & (RTL8139_ISR_RER | RTL8139_ISR_TER | RTL8139_ISR_RXOVW | RTL8139_ISR_FOVW | RTL8139_ISR_SYSERR)) DbgPrint("RTL8139: interrupt error bits=0x%04x\n", status); } static void Rtl8139Isr(void) { if (!gDevicePresent) return; RTL8139_DEVICE* r = &gDevice; uint16_t status = Rtl8139Read16(r, RTL8139_REG_ISR); status &= RTL8139_INTR_ENABLES; if (!status || status == 0xFFFFu) return; Rtl8139Write16(r, RTL8139_REG_ISR, status); r->PendingIsr |= status; KeInsertQueueDpc(&r->Dpc, (void*)0, (void*)0); } static KSTATUS Rtl8139SendFrame(NET_DEVICE* device, const void* frame, size_t length) { if (!device || !frame) return STATUS_INVALID_PARAMETER; if (length > NET_ETHERNET_MAX_FRAME) return STATUS_BUFFER_TOO_SMALL; if (length < NET_ETHERNET_HEADER_LENGTH) return STATUS_INVALID_PARAMETER; RTL8139_DEVICE* r = (RTL8139_DEVICE*)device->DriverContext; if (!r) return STATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&r->TxLock); uint32_t slot = r->TxSlot; uint32_t tsd = Rtl8139Read32(r, RTL8139_REG_TSD(slot)); // RTL8139 TSD.OWN is 1 when the descriptor is idle/free. // Writing a command with OWN cleared hands the slot to the NIC. if (!(tsd & RTL8139_TSD_OWN)) { KeReleaseSpinlock(&r->TxLock, flags); return STATUS_QUEUE_FULL; } size_t wireLength = length < NET_ETHERNET_MIN_FRAME ? NET_ETHERNET_MIN_FRAME : length; uint8_t* buf = r->TxBuffers + slot * RTL8139_TX_BUF_SIZE; memcpy(buf, frame, length); if (wireLength > length) memset(buf + length, 0, wireLength - length); Rtl8139MemoryBarrier(); Rtl8139Write32(r, RTL8139_REG_TSD(slot), (uint32_t)wireLength & RTL8139_TSD_SIZE_MASK); r->TxSlot = (slot + 1u) % RTL8139_TX_SLOTS; KeReleaseSpinlock(&r->TxLock, flags); return STATUS_SUCCESS; } static KSTATUS Rtl8139Initialize(const HAL_API* hal) { gHal = hal; memset(&gDevice, 0, sizeof(gDevice)); gDevice.TxLock = (SPINLOCK)SPINLOCK_INIT; if (!gHal->Pci.FindDevice(RTL8139_VENDOR_REALTEK, RTL8139_DEVICE_ID, &gDevice.Bus, &gDevice.Dev, &gDevice.Fn)) { DbgPrint("RTL8139: no PCI device 10ec:8139 found; skipping\n"); return STATUS_SUCCESS; } DbgPrint("RTL8139: found Realtek PCI device 10ec:8139 at %u:%u.%u\n", gDevice.Bus, gDevice.Dev, gDevice.Fn); KSTATUS st = Rtl8139SelectRegisterBar(&gDevice); if (!KSUCCESS(st)) { DbgPrint("RTL8139: no usable register BAR (0x%x)\n", st); return st; } Rtl8139EnablePci(&gDevice); gDevice.Irq = hal->Pci.GetIrqLine(gDevice.Bus, gDevice.Dev, gDevice.Fn); if (gDevice.Irq == 0xFFu) { DbgPrint("RTL8139: no legacy PCI IRQ line wired\n"); return STATUS_NOT_SUPPORTED; } // Wake, reset, then read the station address. Rtl8139Write8(&gDevice, RTL8139_REG_CONFIG1, 0x00); st = Rtl8139Reset(&gDevice); if (!KSUCCESS(st)) { DbgPrint("RTL8139: reset timed out\n"); return st; } Rtl8139ReadMac(&gDevice); DbgPrint("RTL8139: MAC %02x:%02x:%02x:%02x:%02x:%02x irq=%u\n", gDevice.NetDevice.HardwareAddress[0], gDevice.NetDevice.HardwareAddress[1], gDevice.NetDevice.HardwareAddress[2], gDevice.NetDevice.HardwareAddress[3], gDevice.NetDevice.HardwareAddress[4], gDevice.NetDevice.HardwareAddress[5], gDevice.Irq); st = Rtl8139AllocateDma(&gDevice); if (!KSUCCESS(st)) return st; KeInitializeDpc(&gDevice.Dpc, Rtl8139DpcRoutine, &gDevice); gDevice.NetDevice.Mtu = NET_MTU_ETHERNET; gDevice.NetDevice.DriverContext = &gDevice; gDevice.NetDevice.SendFrame = Rtl8139SendFrame; Rtl8139ProgramHardware(&gDevice); gDevicePresent = true; KeIrqRegister(gDevice.Irq, Rtl8139Isr); st = NetRegisterDevice(&gDevice.NetDevice); if (!KSUCCESS(st)) return st; DbgPrint("RTL8139: initialized; RX/TX enabled on IRQ %u\n", gDevice.Irq); return STATUS_SUCCESS; } KSTATUS DriverEntry(const HAL_API* hal) { DbgPrint("RTL8139: driver loading...\n"); KSTATUS st = Rtl8139Initialize(hal); if (!KSUCCESS(st)) DbgPrint("RTL8139: init failed: 0x%x\n", st); return st; }