add CatchAllDebugBucket as a syscall for debugging, add a basic network card plus ethernet driver (wip)
This commit is contained in:
1 parent
bf34d63ea3
commit
0d7f71b693
22 files changed
+2248
-25
No files matched your search
@@ -1,7 +1,8 @@
|
||||
[boot]
|
||||
ide.sys
|
||||
ahci.sys
|
||||
fat32.sys
|
||||
|
||||
[runtime]
|
||||
usb.sys
|
||||
[boot]
|
||||
ide.sys
|
||||
ahci.sys
|
||||
fat32.sys
|
||||
|
||||
[runtime]
|
||||
usb.sys
|
||||
rtl8139.sys
|
||||
@@ -0,0 +1,571 @@
|
||||
// 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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
#include <hal_api.h>
|
||||
#include <ke_status.h>
|
||||
#include <ke_debug.h>
|
||||
#include <ke_irq.h>
|
||||
#include <ke_dpc.h>
|
||||
#include <ke_spinlock.h>
|
||||
#include <mm_phys.h>
|
||||
#include <mm_virt.h>
|
||||
#include <net_device.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
// rtl816x.c - first-pass Realtek RTL8111/8168/8411 PCIe Gigabit NIC driver.
|
||||
//
|
||||
// Scope for this version:
|
||||
// * RTL8168/RTL8169-compatible PCI IDs, legacy INTx interrupt delivery.
|
||||
// * MMIO preferred, I/O BAR fallback.
|
||||
// * Descriptor-mode RX/TX with plain Ethernet frames.
|
||||
// * No checksum offload, TSO, VLAN, jumbo frames, MSI/MSI-X, or EEE.
|
||||
//
|
||||
// The driver registers a NET_DEVICE so a later ARP/IPv4/ICMP stack can attach
|
||||
// without knowing Realtek register details.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
|
||||
#include <hal_api.h>
|
||||
#include <ke_status.h>
|
||||
#include <ke_debug.h>
|
||||
#include <ke_irq.h>
|
||||
#include <ke_dpc.h>
|
||||
#include <ke_spinlock.h>
|
||||
#include <mm_phys.h>
|
||||
#include <mm_virt.h>
|
||||
#include <net_device.h>
|
||||
|
||||
#define RTL_VENDOR_REALTEK 0x10ECu
|
||||
#define RTL_DEVICE_8168 0x8168u
|
||||
#define RTL_DEVICE_8169 0x8169u
|
||||
|
||||
#define PCI_REG_COMMAND 0x04u
|
||||
#define PCI_REG_BAR0 0x10u
|
||||
#define PCI_REG_IRQ 0x3Cu
|
||||
#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 RTL_REG_MAC0 0x00u
|
||||
#define RTL_REG_MAR0 0x08u
|
||||
#define RTL_REG_TNPDS_LO 0x20u
|
||||
#define RTL_REG_TNPDS_HI 0x24u
|
||||
#define RTL_REG_THPDS_LO 0x28u
|
||||
#define RTL_REG_THPDS_HI 0x2Cu
|
||||
#define RTL_REG_CHIP_CMD 0x37u
|
||||
#define RTL_REG_TX_POLL 0x38u
|
||||
#define RTL_REG_INTR_MASK 0x3Cu
|
||||
#define RTL_REG_INTR_STAT 0x3Eu
|
||||
#define RTL_REG_TX_CONFIG 0x40u
|
||||
#define RTL_REG_RX_CONFIG 0x44u
|
||||
#define RTL_REG_CFG9346 0x50u
|
||||
#define RTL_REG_CONFIG1 0x52u
|
||||
#define RTL_REG_PHY_STATUS 0x6Cu
|
||||
#define RTL_REG_RX_MAXSIZE 0xDAu
|
||||
#define RTL_REG_CPLUS_CMD 0xE0u
|
||||
#define RTL_REG_RDSAR_LO 0xE4u
|
||||
#define RTL_REG_RDSAR_HI 0xE8u
|
||||
|
||||
#define RTL_CMD_RESET 0x10u
|
||||
#define RTL_CMD_RX_ENABLE 0x08u
|
||||
#define RTL_CMD_TX_ENABLE 0x04u
|
||||
|
||||
#define RTL_TXPOLL_NPQ 0x40u
|
||||
|
||||
#define RTL_ISR_ROK 0x0001u
|
||||
#define RTL_ISR_RER 0x0002u
|
||||
#define RTL_ISR_TOK 0x0004u
|
||||
#define RTL_ISR_TER 0x0008u
|
||||
#define RTL_ISR_RXOVW 0x0010u
|
||||
#define RTL_ISR_LINKCHG 0x0020u
|
||||
#define RTL_ISR_FOVW 0x0040u
|
||||
#define RTL_ISR_TDU 0x0080u
|
||||
#define RTL_ISR_SWINT 0x0100u
|
||||
#define RTL_ISR_TIMEOUT 0x4000u
|
||||
#define RTL_ISR_SYSERR 0x8000u
|
||||
|
||||
#define RTL_INTR_ENABLES (RTL_ISR_ROK | RTL_ISR_RER | RTL_ISR_TOK | RTL_ISR_TER | \
|
||||
RTL_ISR_RXOVW | RTL_ISR_LINKCHG | RTL_ISR_FOVW | \
|
||||
RTL_ISR_TDU | RTL_ISR_SYSERR)
|
||||
|
||||
#define RTL_RXCFG_AAP (1u << 0) // accept all physical packets/promiscuous
|
||||
#define RTL_RXCFG_APM (1u << 1) // accept physical match
|
||||
#define RTL_RXCFG_AM (1u << 2) // accept multicast
|
||||
#define RTL_RXCFG_AB (1u << 3) // accept broadcast
|
||||
#define RTL_RXCFG_AR (1u << 4) // accept runt
|
||||
#define RTL_RXCFG_AER (1u << 5) // accept errored
|
||||
#define RTL_RXCFG_DMA_UNLIMITED (7u << 8)
|
||||
|
||||
#define RTL_TXCFG_DMA_UNLIMITED (7u << 8)
|
||||
#define RTL_TXCFG_IFG (3u << 24)
|
||||
|
||||
#define RTL_DESC_OWN (1u << 31)
|
||||
#define RTL_DESC_EOR (1u << 30)
|
||||
#define RTL_DESC_FS (1u << 29)
|
||||
#define RTL_DESC_LS (1u << 28)
|
||||
#define RTL_RXDESC_RES (1u << 21)
|
||||
#define RTL_RXDESC_RWT (1u << 22)
|
||||
#define RTL_RXDESC_CRC (1u << 19)
|
||||
#define RTL_RXDESC_RUNT (1u << 20)
|
||||
#define RTL_DESC_LENGTH_MASK 0x00003FFFu
|
||||
|
||||
#define RTL_RX_COUNT 64u
|
||||
#define RTL_TX_COUNT 64u
|
||||
#define RTL_BUF_SIZE 2048u
|
||||
#define RTL_RX_WIRE_MAX (NET_ETHERNET_MAX_FRAME + 4u)
|
||||
#define RTL_RING_PAGES 1u
|
||||
#define RTL_RXBUF_PAGES ((RTL_RX_COUNT * RTL_BUF_SIZE + 4095u) / 4096u)
|
||||
#define RTL_TXBUF_PAGES ((RTL_TX_COUNT * RTL_BUF_SIZE + 4095u) / 4096u)
|
||||
#define RTL_MMIO_PAGES 1u
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct RTL_DESC {
|
||||
volatile uint32_t opts1;
|
||||
volatile uint32_t opts2;
|
||||
volatile uint32_t addrLo;
|
||||
volatile uint32_t addrHi;
|
||||
} RTL_DESC;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef enum RTL_BAR_KIND {
|
||||
RtlBarNone = 0,
|
||||
RtlBarIo,
|
||||
RtlBarMmio,
|
||||
} RTL_BAR_KIND;
|
||||
|
||||
typedef struct RTL_BAR {
|
||||
RTL_BAR_KIND Kind;
|
||||
uint64_t Base;
|
||||
uint8_t Index;
|
||||
} RTL_BAR;
|
||||
|
||||
typedef struct RTL816X_DEVICE {
|
||||
uint8_t Bus;
|
||||
uint8_t Dev;
|
||||
uint8_t Fn;
|
||||
uint8_t Irq;
|
||||
uint16_t DeviceId;
|
||||
|
||||
bool UseMmio;
|
||||
volatile uint8_t* Mmio;
|
||||
uint16_t IoBase;
|
||||
|
||||
RTL_DESC* RxRing;
|
||||
RTL_DESC* TxRing;
|
||||
PHYSICAL_ADDRESS RxRingPhys;
|
||||
PHYSICAL_ADDRESS TxRingPhys;
|
||||
|
||||
uint8_t* RxBuffers;
|
||||
uint8_t* TxBuffers;
|
||||
PHYSICAL_ADDRESS RxBuffersPhys;
|
||||
PHYSICAL_ADDRESS TxBuffersPhys;
|
||||
|
||||
uint32_t RxIndex;
|
||||
uint32_t TxIndex;
|
||||
SPINLOCK TxLock;
|
||||
KDPC Dpc;
|
||||
volatile uint16_t PendingIsr;
|
||||
|
||||
NET_DEVICE NetDevice;
|
||||
} RTL816X_DEVICE;
|
||||
|
||||
static const HAL_API* gHal;
|
||||
static RTL816X_DEVICE gDevice;
|
||||
static 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 bool RtlFindDevice(uint8_t* outBus, uint8_t* outDev, uint8_t* outFn, uint16_t* outDeviceId) {
|
||||
if (gHal->Pci.FindDevice(RTL_VENDOR_REALTEK, RTL_DEVICE_8168, outBus, outDev, outFn)) {
|
||||
*outDeviceId = RTL_DEVICE_8168;
|
||||
return true;
|
||||
}
|
||||
if (gHal->Pci.FindDevice(RTL_VENDOR_REALTEK, RTL_DEVICE_8169, outBus, outDev, outFn)) {
|
||||
*outDeviceId = RTL_DEVICE_8169;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool RtlReadBar(uint8_t bus, uint8_t dev, uint8_t fn, uint8_t index, RTL_BAR* out) {
|
||||
uint8_t reg = (uint8_t)(PCI_REG_BAR0 + index * 4u);
|
||||
uint32_t lo = gHal->Pci.Read32(bus, dev, fn, reg);
|
||||
|
||||
out->Kind = RtlBarNone;
|
||||
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 = RtlBarIo;
|
||||
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 = RtlBarMmio;
|
||||
out->Base = base;
|
||||
return true;
|
||||
}
|
||||
|
||||
static KSTATUS RtlMapMmio(uint64_t physBase, volatile uint8_t** outMmio) {
|
||||
uint64_t pageBase = physBase & PAGE_MASK;
|
||||
uint64_t pageOff = physBase - pageBase;
|
||||
|
||||
for (uint32_t i = 0; i < RTL_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 RtlSelectRegisterBar(RTL816X_DEVICE* r) {
|
||||
RTL_BAR best = { RtlBarNone, 0, 0 };
|
||||
RTL_BAR fallback = { RtlBarNone, 0, 0 };
|
||||
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
RTL_BAR bar;
|
||||
if (!RtlReadBar(r->Bus, r->Dev, r->Fn, i, &bar))
|
||||
continue;
|
||||
|
||||
if (bar.Kind == RtlBarMmio) {
|
||||
best = bar;
|
||||
break;
|
||||
}
|
||||
if (bar.Kind == RtlBarIo && fallback.Kind == RtlBarNone)
|
||||
fallback = bar;
|
||||
|
||||
// Skip the high dword of 64-bit memory BARs.
|
||||
if (bar.Kind == RtlBarMmio) {
|
||||
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 == RtlBarNone)
|
||||
best = fallback;
|
||||
|
||||
if (best.Kind == RtlBarMmio) {
|
||||
KSTATUS st = RtlMapMmio(best.Base, &r->Mmio);
|
||||
if (!KSUCCESS(st))
|
||||
return st;
|
||||
r->UseMmio = true;
|
||||
DbgPrint("RTL816X: BAR%u MMIO base=0x%llx virt=%p\n",
|
||||
best.Index, (unsigned long long)best.Base, r->Mmio);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (best.Kind == RtlBarIo) {
|
||||
if (best.Base > 0xFFFFu)
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
r->IoBase = (uint16_t)best.Base;
|
||||
r->UseMmio = false;
|
||||
DbgPrint("RTL816X: BAR%u I/O base=0x%04x\n", best.Index, r->IoBase);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
static void RtlEnablePci(RTL816X_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 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%llx txRing=0x%llx rxBuf=0x%llx txBuf=0x%llx\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;
|
||||
}
|
||||
Reference in new issue
Block a user