Files
nt-lite/code/drivers/net/rtl8139/rtl8139.c
T

328 lines
12 KiB
C

// rtl8139.c - packet I/O, interrupt, and DriverEntry path.
#include "rtl8139.h"
const HAL_API* gHal;
RTL8139_DEVICE gDevice;
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 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%016llX tx=0x%016llX\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;
}