// rtl816x.c - packet I/O, interrupt, and DriverEntry path. #include "rtl816x.h" const HAL_API* gHal; RTL816X_DEVICE gDevice; bool gDevicePresent = false; static __forceinline void RtlMemoryBarrier(void) { _ReadWriteBarrier(); _mm_mfence(); _ReadWriteBarrier(); } static uint8_t RtlRead8(RTL816X_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 RtlRead16(RTL816X_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 RtlRead32(RTL816X_DEVICE* r, uint32_t reg) { if (r->UseMmio) return *(volatile uint32_t*)(r->Mmio + reg); return __indword((unsigned short)(r->IoBase + reg)); } static void RtlWrite8(RTL816X_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 RtlWrite16(RTL816X_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 RtlWrite32(RTL816X_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 KSTATUS RtlReset(RTL816X_DEVICE* r) { RtlWrite8(r, RTL_REG_CHIP_CMD, RTL_CMD_RESET); for (uint32_t i = 0; i < 1000; i++) { if ((RtlRead8(r, RTL_REG_CHIP_CMD) & RTL_CMD_RESET) == 0) return STATUS_SUCCESS; if (gHal->Timer.Stall) gHal->Timer.Stall(1000); } return STATUS_TIMEOUT; } static void RtlReadMac(RTL816X_DEVICE* r) { for (uint32_t i = 0; i < 6; i++) r->NetDevice.HardwareAddress[i] = RtlRead8(r, RTL_REG_MAC0 + i); r->NetDevice.HardwareAddressLength = 6; } static KSTATUS RtlAllocateDma(RTL816X_DEVICE* r) { r->RxRingPhys = MmAllocateContiguous(RTL_RING_PAGES); r->TxRingPhys = MmAllocateContiguous(RTL_RING_PAGES); r->RxBuffersPhys = MmAllocateContiguous(RTL_RXBUF_PAGES); r->TxBuffersPhys = MmAllocateContiguous(RTL_TXBUF_PAGES); if (!r->RxRingPhys || !r->TxRingPhys || !r->RxBuffersPhys || !r->TxBuffersPhys) return STATUS_NO_MEMORY; r->RxRing = (RTL_DESC*)gHal->PhysToVirt(r->RxRingPhys); r->TxRing = (RTL_DESC*)gHal->PhysToVirt(r->TxRingPhys); r->RxBuffers = (uint8_t*)gHal->PhysToVirt(r->RxBuffersPhys); r->TxBuffers = (uint8_t*)gHal->PhysToVirt(r->TxBuffersPhys); memset(r->RxRing, 0, PAGE_SIZE * RTL_RING_PAGES); memset(r->TxRing, 0, PAGE_SIZE * RTL_RING_PAGES); memset(r->RxBuffers, 0, PAGE_SIZE * RTL_RXBUF_PAGES); memset(r->TxBuffers, 0, PAGE_SIZE * RTL_TXBUF_PAGES); DbgPrint("RTL816X: rxRing=0x%016llX txRing=0x%016llX rxBuf=0x%016llX txBuf=0x%016llX\n", (unsigned long long)r->RxRingPhys, (unsigned long long)r->TxRingPhys, (unsigned long long)r->RxBuffersPhys, (unsigned long long)r->TxBuffersPhys); return STATUS_SUCCESS; } static void RtlInitDescriptors(RTL816X_DEVICE* r) { for (uint32_t i = 0; i < RTL_RX_COUNT; i++) { PHYSICAL_ADDRESS p = r->RxBuffersPhys + (PHYSICAL_ADDRESS)i * RTL_BUF_SIZE; r->RxRing[i].addrLo = (uint32_t)p; r->RxRing[i].addrHi = (uint32_t)(p >> 32); r->RxRing[i].opts2 = 0; r->RxRing[i].opts1 = RTL_DESC_OWN | RTL_BUF_SIZE | ((i == RTL_RX_COUNT - 1) ? RTL_DESC_EOR : 0); } for (uint32_t i = 0; i < RTL_TX_COUNT; i++) { PHYSICAL_ADDRESS p = r->TxBuffersPhys + (PHYSICAL_ADDRESS)i * RTL_BUF_SIZE; r->TxRing[i].addrLo = (uint32_t)p; r->TxRing[i].addrHi = (uint32_t)(p >> 32); r->TxRing[i].opts2 = 0; r->TxRing[i].opts1 = (i == RTL_TX_COUNT - 1) ? RTL_DESC_EOR : 0; } r->RxIndex = 0; r->TxIndex = 0; RtlMemoryBarrier(); } static void RtlProgramHardware(RTL816X_DEVICE* r) { // Unlock the 9346-style config register window while programming. RtlWrite8(r, RTL_REG_CFG9346, 0xC0); // Disable interrupts while rings are being installed. RtlWrite16(r, RTL_REG_INTR_MASK, 0); RtlWrite16(r, RTL_REG_INTR_STAT, 0xFFFFu); // DMA ring base addresses. RtlWrite32(r, RTL_REG_TNPDS_LO, (uint32_t)r->TxRingPhys); RtlWrite32(r, RTL_REG_TNPDS_HI, (uint32_t)(r->TxRingPhys >> 32)); RtlWrite32(r, RTL_REG_THPDS_LO, (uint32_t)r->TxRingPhys); RtlWrite32(r, RTL_REG_THPDS_HI, (uint32_t)(r->TxRingPhys >> 32)); RtlWrite32(r, RTL_REG_RDSAR_LO, (uint32_t)r->RxRingPhys); RtlWrite32(r, RTL_REG_RDSAR_HI, (uint32_t)(r->RxRingPhys >> 32)); // Plain packet mode. Keep C+ features mostly disabled for bring-up. RtlWrite16(r, RTL_REG_CPLUS_CMD, RtlRead16(r, RTL_REG_CPLUS_CMD) & ~(uint16_t)0x00E0u); RtlWrite16(r, RTL_REG_RX_MAXSIZE, RTL_BUF_SIZE); RtlWrite32(r, RTL_REG_TX_CONFIG, RTL_TXCFG_IFG | RTL_TXCFG_DMA_UNLIMITED); RtlWrite32(r, RTL_REG_RX_CONFIG, RTL_RXCFG_APM | RTL_RXCFG_AB | RTL_RXCFG_AM | RTL_RXCFG_DMA_UNLIMITED); RtlMemoryBarrier(); // Enable receiver and transmitter. RtlWrite8(r, RTL_REG_CHIP_CMD, RTL_CMD_RX_ENABLE | RTL_CMD_TX_ENABLE); // Lock config space again and enable selected interrupts. RtlWrite8(r, RTL_REG_CFG9346, 0x00); RtlWrite16(r, RTL_REG_INTR_STAT, 0xFFFFu); RtlWrite16(r, RTL_REG_INTR_MASK, RTL_INTR_ENABLES); } static void RtlHandleRx(RTL816X_DEVICE* r) { for (uint32_t budget = 0; budget < RTL_RX_COUNT; budget++) { RTL_DESC* d = &r->RxRing[r->RxIndex]; uint32_t opts = d->opts1; if (opts & RTL_DESC_OWN) break; uint32_t keepEor = opts & RTL_DESC_EOR; uint32_t len = opts & RTL_DESC_LENGTH_MASK; bool good = ((opts & (RTL_RXDESC_RES | RTL_RXDESC_RWT | RTL_RXDESC_CRC | RTL_RXDESC_RUNT)) == 0) && ((opts & (RTL_DESC_FS | RTL_DESC_LS)) == (RTL_DESC_FS | RTL_DESC_LS)) && len >= NET_ETHERNET_HEADER_LENGTH && len <= RTL_RX_WIRE_MAX; if (good) { // Realtek reports the Ethernet FCS in the descriptor length on many // chips. Remove it when present so upper layers see a normal frame. if (len > 4) len -= 4; if (len > NET_ETHERNET_MAX_FRAME) { DbgPrint("RTL816X: dropping oversize frame len=%u\n", len); } else { NetReceiveFrame(&r->NetDevice, r->RxBuffers + r->RxIndex * RTL_BUF_SIZE, len); } } else { DbgPrint("RTL816X: rx descriptor %u error opts=0x%08x len=%u\n", r->RxIndex, opts, len); } d->opts2 = 0; RtlMemoryBarrier(); d->opts1 = RTL_DESC_OWN | RTL_BUF_SIZE | keepEor; RtlMemoryBarrier(); r->RxIndex = (r->RxIndex + 1) % RTL_RX_COUNT; } } static void RtlDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; (void)systemArgument1; (void)systemArgument2; RTL816X_DEVICE* r = (RTL816X_DEVICE*)context; uint16_t status = (uint16_t)r->PendingIsr; r->PendingIsr = 0; if (status & (RTL_ISR_ROK | RTL_ISR_RER | RTL_ISR_RXOVW | RTL_ISR_FOVW)) RtlHandleRx(r); if (status & RTL_ISR_LINKCHG) { uint8_t phy = RtlRead8(r, RTL_REG_PHY_STATUS); DbgPrint("RTL816X: link change phy=0x%02x\n", phy); } if (status & (RTL_ISR_TER | RTL_ISR_SYSERR)) DbgPrint("RTL816X: interrupt status error bits=0x%04x\n", status); } static void RtlIsr(void) { if (!gDevicePresent) return; RTL816X_DEVICE* r = &gDevice; uint16_t status = RtlRead16(r, RTL_REG_INTR_STAT); if (!status || status == 0xFFFFu) return; RtlWrite16(r, RTL_REG_INTR_STAT, status); r->PendingIsr |= status; KeInsertQueueDpc(&r->Dpc, (void*)0, (void*)0); } static KSTATUS RtlSendFrame(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; RTL816X_DEVICE* r = (RTL816X_DEVICE*)device->DriverContext; if (!r) return STATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&r->TxLock); uint32_t index = r->TxIndex; RTL_DESC* d = &r->TxRing[index]; uint32_t opts = d->opts1; if (opts & RTL_DESC_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 + index * RTL_BUF_SIZE; memcpy(buf, frame, length); if (wireLength > length) memset(buf + length, 0, wireLength - length); uint32_t eor = opts & RTL_DESC_EOR; d->opts2 = 0; RtlMemoryBarrier(); d->opts1 = RTL_DESC_OWN | RTL_DESC_FS | RTL_DESC_LS | eor | (uint32_t)wireLength; RtlMemoryBarrier(); RtlWrite8(r, RTL_REG_TX_POLL, RTL_TXPOLL_NPQ); r->TxIndex = (index + 1) % RTL_TX_COUNT; KeReleaseSpinlock(&r->TxLock, flags); return STATUS_SUCCESS; } static KSTATUS RtlInitialize(const HAL_API* hal) { gHal = hal; memset(&gDevice, 0, sizeof(gDevice)); gDevice.TxLock = (SPINLOCK)SPINLOCK_INIT; uint16_t deviceId; if (!RtlFindDevice(&gDevice.Bus, &gDevice.Dev, &gDevice.Fn, &deviceId)) { DbgPrint("RTL816X: no RTL8168/RTL8169 PCI device found; skipping\n"); return STATUS_SUCCESS; } gDevice.DeviceId = deviceId; DbgPrint("RTL816X: found Realtek PCI device 10ec:%04x at %u:%u.%u\n", deviceId, gDevice.Bus, gDevice.Dev, gDevice.Fn); KSTATUS st = RtlSelectRegisterBar(&gDevice); if (!KSUCCESS(st)) { DbgPrint("RTL816X: no usable register BAR (0x%x)\n", st); return st; } RtlEnablePci(&gDevice); gDevice.Irq = hal->Pci.GetIrqLine(gDevice.Bus, gDevice.Dev, gDevice.Fn); if (gDevice.Irq == 0xFFu) { DbgPrint("RTL816X: no legacy PCI IRQ line wired\n"); return STATUS_NOT_SUPPORTED; } st = RtlReset(&gDevice); if (!KSUCCESS(st)) { DbgPrint("RTL816X: reset timed out\n"); return st; } RtlReadMac(&gDevice); DbgPrint("RTL816X: 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 = RtlAllocateDma(&gDevice); if (!KSUCCESS(st)) return st; RtlInitDescriptors(&gDevice); KeInitializeDpc(&gDevice.Dpc, RtlDpcRoutine, &gDevice); gDevice.NetDevice.Mtu = NET_MTU_ETHERNET; gDevice.NetDevice.DriverContext = &gDevice; gDevice.NetDevice.SendFrame = RtlSendFrame; RtlProgramHardware(&gDevice); gDevicePresent = true; KeIrqRegister(gDevice.Irq, RtlIsr); st = NetRegisterDevice(&gDevice.NetDevice); if (!KSUCCESS(st)) return st; DbgPrint("RTL816X: initialized; RX/TX enabled on IRQ %u\n", gDevice.Irq); return STATUS_SUCCESS; } KSTATUS DriverEntry(const HAL_API* hal) { DbgPrint("RTL816X: driver loading...\n"); KSTATUS st = RtlInitialize(hal); if (!KSUCCESS(st)) DbgPrint("RTL816X: init failed: 0x%x\n", st); return st; }