338 lines
11 KiB
C
338 lines
11 KiB
C
// 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;
|
|
}
|