From 0d7f71b6938fd49db297c258be7462795a9dc549 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Wed, 20 May 2026 22:21:53 +0200 Subject: [PATCH] add CatchAllDebugBucket as a syscall for debugging, add a basic network card plus ethernet driver (wip) --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes build.bat | 11 +- code/drivers/drivers.ini | 15 +- code/drivers/rtl8139/rtl8139.c | 571 ++++++++++++++++++++++++++++ code/drivers/rtl816x/rtl816x.c | 611 ++++++++++++++++++++++++++++++ code/kernel/ethernet.c | 620 +++++++++++++++++++++++++++++++ code/kernel/ethernet.h | 26 ++ code/kernel/ke_irq.c | 32 +- code/kernel/ke_syscall.c | 12 +- code/kernel/ke_syscall.h | 2 +- code/kernel/kmain.c | 69 +++- code/kernel/net_device.c | 175 +++++++++ code/kernel/net_device.h | 57 +++ code/kernel/syscall_nr.h | 3 +- code/shell_native/shell_native.c | 54 +++ code/syscall/syscall.h | 2 + code/syscall/syscall_nr.inc | 3 +- code/syscall/syscall_stubs.asm | 2 + run.bat | 2 +- runa.bat | 2 +- runad.bat | 2 +- rund.bat | 2 +- 22 files changed, 2248 insertions(+), 25 deletions(-) create mode 100644 code/drivers/rtl8139/rtl8139.c create mode 100644 code/drivers/rtl816x/rtl816x.c create mode 100644 code/kernel/ethernet.c create mode 100644 code/kernel/ethernet.h create mode 100644 code/kernel/net_device.c create mode 100644 code/kernel/net_device.h diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index cd4f15c0aaa85cbbd9f2b3f847485bee292350ac..1001423ae96e8e18bf464cd928a5ba2c9e922f9d 100644 GIT binary patch delta 558 zcmZXQJ5Rz;6o7l6tw1o`krkqCp@OiYVyRDjkl4Y&37A|o#>BzM(8Qz+PUZ}5COSBi zFhCp}{0A^NF*rCIqWl1R?-Al-`tE7Je7&cxn{eGk>&d`pOT#y#FE~hj2#(S(ob91` z5BzZ1&2+jMIIdvDu{(zi=ttV`M~@0Dwy&Kl21|sZk5~+%cvsV-suYldk}N5bBw^Ju zAi^JmiX~?rt6>>Jd=9BU74=)`)lH$ue3sR5LA+M@2w0od@zs#An>m9ZX0!IMrf>M4 z$J~tJ?{*F{o|qQirs7z)G`dJwWcVE?sX>ZN4I61knBz?-$F~v5QTjq853rI=B6H@Z zCo#UwBset5A{<#-v7Mjp0Gy}VbuFU;P1|VE_OC delta 133 zcmZo@P;6*W+^|n$vXP3~TTE6WBlnm{nR=pr|lUvjF-6D@9kvTes3qU hQdoPzex~gO` +#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; +} diff --git a/code/drivers/rtl816x/rtl816x.c b/code/drivers/rtl816x/rtl816x.c new file mode 100644 index 0000000..26865bb --- /dev/null +++ b/code/drivers/rtl816x/rtl816x.c @@ -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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/code/kernel/ethernet.c b/code/kernel/ethernet.c new file mode 100644 index 0000000..f462aed --- /dev/null +++ b/code/kernel/ethernet.c @@ -0,0 +1,620 @@ +// ethernet.c - tiny pre-lwIP Ethernet/ARP/IPv4/ICMP shim. +// +// Goals: +// * prove NIC RX/TX works before lwIP is integrated; +// * reply to ARP for a configured static IPv4 address; +// * reply to ICMP echo requests for that address; +// * provide a tiny NetPingIpv4() smoke-test path; +// * auto-configure QEMU's default user-mode network only when the MAC looks +// like the QEMU OUI (52:54:00), so real hardware is not assigned a bogus IP. +// +// This is not intended to grow into a full stack. When lwIP arrives, keep the +// NET_DEVICE send/receive boundary and replace this protocol code with lwIP's +// netif input path. + +#include +#include +#include +#include + +#include +#include +#include + +#define NET_MAX_ETH_STATES 8 +#define NET_ARP_CACHE_SIZE 8 + +#define ETH_ADDR_LEN NET_ETHERNET_ADDRESS_LENGTH +#define ETH_HDR_LEN NET_ETHERNET_HEADER_LENGTH +#define ETH_TYPE_IPV4 0x0800u +#define ETH_TYPE_ARP 0x0806u + +#define ARP_HTYPE_ETHERNET 0x0001u +#define ARP_PTYPE_IPV4 0x0800u +#define ARP_OPER_REQUEST 0x0001u +#define ARP_OPER_REPLY 0x0002u +#define ARP_PACKET_LEN 28u + +#define IPV4_PROTO_ICMP 1u +#define ICMP_ECHO_REPLY 0u +#define ICMP_ECHO_REQUEST 8u + +#define NET_ETH_MAX_FRAME NET_ETHERNET_MAX_FRAME +#define NET_LOG_LIMIT 16u + +static const uint8_t gBroadcastMac[ETH_ADDR_LEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +typedef struct NET_ARP_ENTRY { + bool Valid; + uint32_t Ip; + uint8_t HardwareAddress[ETH_ADDR_LEN]; +} NET_ARP_ENTRY; + +typedef struct NET_ETH_STATE { + bool Present; + bool Ipv4Configured; + bool GatewayResolved; + uint32_t Address; + uint32_t Netmask; + uint32_t Gateway; + uint8_t GatewayHardwareAddress[ETH_ADDR_LEN]; + NET_ARP_ENTRY ArpCache[NET_ARP_CACHE_SIZE]; + uint32_t ArpNextReplace; + bool PendingPing; + uint32_t PendingPingTarget; + uint32_t PendingPingNextHop; + uint32_t RxFrames; + uint32_t TxFrames; + uint16_t IcmpEchoSequence; + uint32_t ArpFrames; + uint32_t Ipv4Frames; + uint32_t IcmpFrames; +} NET_ETH_STATE; + +static NET_ETH_STATE gEthStates[NET_MAX_ETH_STATES]; +static SPINLOCK gEthLock = SPINLOCK_INIT; + +static uint16_t NetLoadBe16(const uint8_t* p) { + return (uint16_t)(((uint16_t)p[0] << 8) | p[1]); +} + +static uint32_t NetLoadBe32(const uint8_t* p) { + return ((uint32_t)p[0] << 24) | + ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | + (uint32_t)p[3]; +} + +static void NetStoreBe16(uint8_t* p, uint16_t v) { + p[0] = (uint8_t)(v >> 8); + p[1] = (uint8_t)v; +} + +static void NetStoreBe32(uint8_t* p, uint32_t v) { + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void NetPrintIpv4(uint32_t ip) { + DbgPrint("%u.%u.%u.%u", + (unsigned)((ip >> 24) & 0xFFu), + (unsigned)((ip >> 16) & 0xFFu), + (unsigned)((ip >> 8) & 0xFFu), + (unsigned)(ip & 0xFFu)); +} + +static void NetPrintMac(const uint8_t* mac) { + DbgPrint("%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +static bool NetHasEthernetAddress(const NET_DEVICE* device) { + return device && device->HardwareAddressLength == ETH_ADDR_LEN; +} + +static bool NetIsQemuMac(const NET_DEVICE* device) { + return NetHasEthernetAddress(device) && + device->HardwareAddress[0] == 0x52 && + device->HardwareAddress[1] == 0x54 && + device->HardwareAddress[2] == 0x00; +} + +static bool NetIsBroadcastMac(const uint8_t* mac) { + return mac && memcmp(mac, gBroadcastMac, ETH_ADDR_LEN) == 0; +} + +static bool NetIsMulticastMac(const uint8_t* mac) { + return mac && (mac[0] & 0x01u) != 0; +} + +static bool NetEthernetFrameIsForDevice(const NET_DEVICE* device, const uint8_t* frame) { + if (!NetHasEthernetAddress(device) || !frame) + return false; + return memcmp(frame, device->HardwareAddress, ETH_ADDR_LEN) == 0 || + NetIsBroadcastMac(frame) || + NetIsMulticastMac(frame); +} + +static bool NetIpv4SameSubnet(uint32_t a, uint32_t b, uint32_t mask) { + return ((a ^ b) & mask) == 0; +} + +static NET_ETH_STATE* NetGetEthState(NET_DEVICE* device) { + if (!device || device->Index >= NET_MAX_ETH_STATES) + return (NET_ETH_STATE*)0; + return &gEthStates[device->Index]; +} + +static uint16_t NetChecksum(const uint8_t* data, size_t length) { + uint32_t sum = 0; + + while (length >= 2) { + sum += NetLoadBe16(data); + data += 2; + length -= 2; + } + + if (length) + sum += (uint16_t)((uint16_t)data[0] << 8); + + while (sum >> 16) + sum = (sum & 0xFFFFu) + (sum >> 16); + + return (uint16_t)~sum; +} + +static void NetBuildEthernetHeader(uint8_t* frame, + const uint8_t* dst, + const uint8_t* src, + uint16_t etherType) { + memcpy(frame, dst, ETH_ADDR_LEN); + memcpy(frame + ETH_ADDR_LEN, src, ETH_ADDR_LEN); + NetStoreBe16(frame + 12, etherType); +} + +static bool NetArpLookup(NET_ETH_STATE* state, uint32_t ip, uint8_t* macOut) { + if (!state) + return false; + + for (unsigned i = 0; i < NET_ARP_CACHE_SIZE; ++i) { + if (state->ArpCache[i].Valid && state->ArpCache[i].Ip == ip) { + if (macOut) + memcpy(macOut, state->ArpCache[i].HardwareAddress, ETH_ADDR_LEN); + return true; + } + } + + return false; +} + +static void NetArpRemember(NET_ETH_STATE* state, uint32_t ip, const uint8_t* mac) { + if (!state || !mac || ip == 0) + return; + + for (unsigned i = 0; i < NET_ARP_CACHE_SIZE; ++i) { + if (state->ArpCache[i].Valid && state->ArpCache[i].Ip == ip) { + memcpy(state->ArpCache[i].HardwareAddress, mac, ETH_ADDR_LEN); + return; + } + } + + unsigned slot = state->ArpNextReplace % NET_ARP_CACHE_SIZE; + state->ArpNextReplace = (state->ArpNextReplace + 1) % NET_ARP_CACHE_SIZE; + state->ArpCache[slot].Valid = true; + state->ArpCache[slot].Ip = ip; + memcpy(state->ArpCache[slot].HardwareAddress, mac, ETH_ADDR_LEN); +} + +static KSTATUS NetSendArp(NET_DEVICE* device, + uint16_t operation, + const uint8_t* targetMac, + uint32_t targetIp) { + NET_ETH_STATE* state = NetGetEthState(device); + if (!state || !state->Ipv4Configured) + return STATUS_NOT_FOUND; + if (!NetHasEthernetAddress(device)) + return STATUS_INVALID_PARAMETER; + if (operation == ARP_OPER_REPLY && !targetMac) + return STATUS_INVALID_PARAMETER; + + uint8_t frame[ETH_HDR_LEN + ARP_PACKET_LEN]; + const uint8_t* ethDst = (operation == ARP_OPER_REQUEST) ? gBroadcastMac : targetMac; + NetBuildEthernetHeader(frame, ethDst, device->HardwareAddress, ETH_TYPE_ARP); + + uint8_t* arp = frame + ETH_HDR_LEN; + NetStoreBe16(arp + 0, ARP_HTYPE_ETHERNET); + NetStoreBe16(arp + 2, ARP_PTYPE_IPV4); + arp[4] = ETH_ADDR_LEN; + arp[5] = 4; + NetStoreBe16(arp + 6, operation); + memcpy(arp + 8, device->HardwareAddress, ETH_ADDR_LEN); + NetStoreBe32(arp + 14, state->Address); + + if (operation == ARP_OPER_REQUEST) + memset(arp + 18, 0, ETH_ADDR_LEN); + else + memcpy(arp + 18, targetMac, ETH_ADDR_LEN); + NetStoreBe32(arp + 24, targetIp); + + KSTATUS st = NetSendFrame(device->Index, frame, sizeof(frame)); + if (KSUCCESS(st)) + state->TxFrames++; + return st; +} + +static void NetSendArpRequest(NET_DEVICE* device, uint32_t targetIp) { + DbgPrint("NET: %s tx ARP who-has ", device->Name); + NetPrintIpv4(targetIp); + DbgPrint("\n"); + + KSTATUS st = NetSendArp(device, ARP_OPER_REQUEST, gBroadcastMac, targetIp); + if (!KSUCCESS(st)) + DbgPrint("NET: %s ARP probe failed: 0x%x\n", device->Name, st); +} + +static KSTATUS NetSendIcmpEchoRequestResolved(NET_DEVICE* device, + NET_ETH_STATE* state, + uint32_t targetIp, + const uint8_t* nextHopMac) { + if (!device || !state || !state->Ipv4Configured || !nextHopMac) + return STATUS_INVALID_PARAMETER; + + enum { + IPV4_HEADER_LEN = 20, + ICMP_HEADER_LEN = 8, + ICMP_PAYLOAD_LEN = 32, + ICMP_TOTAL_LEN = ICMP_HEADER_LEN + ICMP_PAYLOAD_LEN, + IPV4_TOTAL_LEN = IPV4_HEADER_LEN + ICMP_TOTAL_LEN, + FRAME_LEN = ETH_HDR_LEN + IPV4_TOTAL_LEN + }; + + uint8_t frame[FRAME_LEN]; + NetBuildEthernetHeader(frame, + nextHopMac, + device->HardwareAddress, + ETH_TYPE_IPV4); + + uint8_t* ip = frame + ETH_HDR_LEN; + memset(ip, 0, IPV4_TOTAL_LEN); + ip[0] = 0x45; // IPv4, 20-byte header. + ip[1] = 0; // DSCP/ECN. + NetStoreBe16(ip + 2, IPV4_TOTAL_LEN); + NetStoreBe16(ip + 4, state->IcmpEchoSequence); + NetStoreBe16(ip + 6, 0x4000); // Don't fragment. + ip[8] = 64; // TTL. + ip[9] = IPV4_PROTO_ICMP; + NetStoreBe32(ip + 12, state->Address); + NetStoreBe32(ip + 16, targetIp); + NetStoreBe16(ip + 10, NetChecksum(ip, IPV4_HEADER_LEN)); + + uint8_t* icmp = ip + IPV4_HEADER_LEN; + icmp[0] = ICMP_ECHO_REQUEST; + icmp[1] = 0; + NetStoreBe16(icmp + 4, 0x4b4e); // temporary pre-lwIP ping ID: 'KN'. + NetStoreBe16(icmp + 6, state->IcmpEchoSequence); + for (unsigned i = 0; i < ICMP_PAYLOAD_LEN; ++i) + icmp[ICMP_HEADER_LEN + i] = (uint8_t)('A' + (i % 26)); + NetStoreBe16(icmp + 2, NetChecksum(icmp, ICMP_TOTAL_LEN)); + + DbgPrint("NET: %s tx ICMP echo request to ", device->Name); + NetPrintIpv4(targetIp); + DbgPrint(" seq=%u\n", (unsigned)state->IcmpEchoSequence); + + KSTATUS st = NetSendFrame(device->Index, frame, FRAME_LEN); + if (KSUCCESS(st)) { + state->TxFrames++; + state->IcmpEchoSequence++; + } else { + DbgPrint("NET: %s ICMP echo request failed: 0x%x\n", device->Name, st); + } + + return st; +} + +static KSTATUS NetPingIpv4OnDevice(NET_DEVICE* device, + NET_ETH_STATE* state, + uint32_t targetIp) { + if (!device || !state || !state->Ipv4Configured || targetIp == 0) + return STATUS_INVALID_PARAMETER; + + uint32_t nextHop = targetIp; + if (!NetIpv4SameSubnet(state->Address, targetIp, state->Netmask)) { + if (state->Gateway == 0) + return STATUS_NOT_FOUND; + nextHop = state->Gateway; + } + + uint8_t nextHopMac[ETH_ADDR_LEN]; + if (NetArpLookup(state, nextHop, nextHopMac)) + return NetSendIcmpEchoRequestResolved(device, state, targetIp, nextHopMac); + + state->PendingPing = true; + state->PendingPingTarget = targetIp; + state->PendingPingNextHop = nextHop; + + DbgPrint("NET: %s ping ", device->Name); + NetPrintIpv4(targetIp); + DbgPrint(" waiting for ARP "); + NetPrintIpv4(nextHop); + DbgPrint("\n"); + + NetSendArpRequest(device, nextHop); + return STATUS_PENDING; +} + +static void NetHandleArp(NET_DEVICE* device, NET_ETH_STATE* state, + const uint8_t* frame, size_t length) { + if (length < ETH_HDR_LEN + ARP_PACKET_LEN) + return; + + const uint8_t* arp = frame + ETH_HDR_LEN; + if (NetLoadBe16(arp + 0) != ARP_HTYPE_ETHERNET || + NetLoadBe16(arp + 2) != ARP_PTYPE_IPV4 || + arp[4] != ETH_ADDR_LEN || arp[5] != 4) + return; + + uint16_t op = NetLoadBe16(arp + 6); + const uint8_t* senderMac = arp + 8; + uint32_t senderIp = NetLoadBe32(arp + 14); + uint32_t targetIp = NetLoadBe32(arp + 24); + state->ArpFrames++; + + if (state->ArpFrames <= NET_LOG_LIMIT) { + DbgPrint("NET: %s rx ARP %s ", + device->Name, + op == ARP_OPER_REQUEST ? "request" : + op == ARP_OPER_REPLY ? "reply" : "op"); + NetPrintIpv4(senderIp); + DbgPrint(" "); + NetPrintMac(senderMac); + DbgPrint(" -> "); + NetPrintIpv4(targetIp); + DbgPrint("\n"); + } + + if ((op == ARP_OPER_REQUEST || op == ARP_OPER_REPLY) && senderIp != 0) + NetArpRemember(state, senderIp, senderMac); + + if (op == ARP_OPER_REPLY && state->Ipv4Configured && senderIp == state->Gateway) { + memcpy(state->GatewayHardwareAddress, senderMac, ETH_ADDR_LEN); + state->GatewayResolved = true; + DbgPrint("NET: %s gateway resolved ", device->Name); + NetPrintIpv4(senderIp); + DbgPrint(" = "); + NetPrintMac(senderMac); + DbgPrint("\n"); + } + + if (op == ARP_OPER_REPLY && state->PendingPing && senderIp == state->PendingPingNextHop) { + uint32_t pingTarget = state->PendingPingTarget; + state->PendingPing = false; + (void)NetSendIcmpEchoRequestResolved(device, state, pingTarget, senderMac); + } + + if (op == ARP_OPER_REQUEST && state->Ipv4Configured && targetIp == state->Address) { + KSTATUS st = NetSendArp(device, ARP_OPER_REPLY, senderMac, senderIp); + if (KSUCCESS(st)) { + DbgPrint("NET: %s tx ARP reply for ", device->Name); + NetPrintIpv4(state->Address); + DbgPrint("\n"); + } else { + DbgPrint("NET: %s ARP reply failed: 0x%x\n", device->Name, st); + } + } +} + +static void NetHandleIcmpEcho(NET_DEVICE* device, NET_ETH_STATE* state, + const uint8_t* frame, size_t length, + const uint8_t* ip, size_t ipHeaderLength, + size_t totalLength) { + (void)length; + + if (totalLength < ipHeaderLength + 8) + return; + + const uint8_t* icmp = ip + ipHeaderLength; + size_t icmpLength = totalLength - ipHeaderLength; + if (NetChecksum(icmp, icmpLength) != 0) + return; + if (icmp[0] != ICMP_ECHO_REQUEST || icmp[1] != 0) + return; + + if (ETH_HDR_LEN + totalLength > NET_ETH_MAX_FRAME) + return; + + uint8_t out[NET_ETH_MAX_FRAME]; + NetBuildEthernetHeader(out, frame + 6, device->HardwareAddress, ETH_TYPE_IPV4); + memcpy(out + ETH_HDR_LEN, ip, totalLength); + + uint8_t* outIp = out + ETH_HDR_LEN; + uint8_t* outIcmp = outIp + ipHeaderLength; + + // Swap IPv4 endpoints and rebuild the IPv4 header checksum. + memcpy(outIp + 12, ip + 16, 4); + memcpy(outIp + 16, ip + 12, 4); + outIp[8] = 64; + outIp[10] = 0; + outIp[11] = 0; + NetStoreBe16(outIp + 10, NetChecksum(outIp, ipHeaderLength)); + + // Convert echo request to echo reply and rebuild the ICMP checksum. + outIcmp[0] = ICMP_ECHO_REPLY; + outIcmp[2] = 0; + outIcmp[3] = 0; + NetStoreBe16(outIcmp + 2, NetChecksum(outIcmp, icmpLength)); + + KSTATUS st = NetSendFrame(device->Index, out, ETH_HDR_LEN + totalLength); + if (KSUCCESS(st)) { + state->TxFrames++; + if (state->IcmpFrames <= NET_LOG_LIMIT) { + DbgPrint("NET: %s tx ICMP echo reply to ", device->Name); + NetPrintIpv4(NetLoadBe32(ip + 12)); + DbgPrint("\n"); + } + } else { + DbgPrint("NET: %s ICMP reply failed: 0x%x\n", device->Name, st); + } +} + +static void NetHandleIpv4(NET_DEVICE* device, NET_ETH_STATE* state, + const uint8_t* frame, size_t length) { + if (!state->Ipv4Configured || length < ETH_HDR_LEN + 20) + return; + + const uint8_t* ip = frame + ETH_HDR_LEN; + uint8_t version = (uint8_t)(ip[0] >> 4); + size_t ipHeaderLength = (size_t)(ip[0] & 0x0Fu) * 4u; + if (version != 4 || ipHeaderLength < 20 || length < ETH_HDR_LEN + ipHeaderLength) + return; + + size_t totalLength = NetLoadBe16(ip + 2); + if (totalLength < ipHeaderLength || length < ETH_HDR_LEN + totalLength) + return; + + if (NetChecksum(ip, ipHeaderLength) != 0) + return; + + uint16_t fragment = NetLoadBe16(ip + 6); + if (fragment & 0x3FFFu) + return; + + uint32_t dstIp = NetLoadBe32(ip + 16); + if (dstIp != state->Address) + return; + + state->Ipv4Frames++; + if (ip[9] == IPV4_PROTO_ICMP) { + if (totalLength < ipHeaderLength + 8) + return; + + const uint8_t* icmp = ip + ipHeaderLength; + size_t icmpLength = totalLength - ipHeaderLength; + if (NetChecksum(icmp, icmpLength) != 0) + return; + + state->IcmpFrames++; + + if (icmp[0] == ICMP_ECHO_REPLY && icmp[1] == 0) { + DbgPrint("NET: %s rx ICMP echo reply from ", device->Name); + NetPrintIpv4(NetLoadBe32(ip + 12)); + DbgPrint(" seq=%u\n", (unsigned)NetLoadBe16(icmp + 6)); + return; + } + + if (state->IcmpFrames <= NET_LOG_LIMIT) { + DbgPrint("NET: %s rx ICMP type=%u code=%u from ", + device->Name, + (unsigned)icmp[0], + (unsigned)icmp[1]); + NetPrintIpv4(NetLoadBe32(ip + 12)); + DbgPrint("\n"); + } + + NetHandleIcmpEcho(device, state, frame, length, ip, ipHeaderLength, totalLength); + } +} + +__declspec(dllexport) KSTATUS NetConfigureIpv4Static(uint32_t deviceIndex, + uint32_t address, + uint32_t netmask, + uint32_t gateway) { + if (deviceIndex >= NET_MAX_ETH_STATES || address == 0) + return STATUS_INVALID_PARAMETER; + + NET_DEVICE* device = NetGetDevice(deviceIndex); + if (!device) + return STATUS_NOT_FOUND; + if (!NetHasEthernetAddress(device)) + return STATUS_NOT_SUPPORTED; + + uintptr_t flags = KeAcquireSpinlock(&gEthLock); + NET_ETH_STATE* state = &gEthStates[deviceIndex]; + state->Ipv4Configured = true; + state->GatewayResolved = false; + state->PendingPing = false; + state->IcmpEchoSequence = 1; + state->Address = address; + state->Netmask = netmask; + state->Gateway = gateway; + state->ArpNextReplace = 0; + memset(state->GatewayHardwareAddress, 0, sizeof(state->GatewayHardwareAddress)); + memset(state->ArpCache, 0, sizeof(state->ArpCache)); + KeReleaseSpinlock(&gEthLock, flags); + + DbgPrint("NET: eth%u IPv4 ", deviceIndex); + NetPrintIpv4(address); + DbgPrint(" mask "); + NetPrintIpv4(netmask); + DbgPrint(" gw "); + NetPrintIpv4(gateway); + DbgPrint("\n"); + return STATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS NetPingIpv4(uint32_t deviceIndex, uint32_t targetIp) { + NET_DEVICE* device = NetGetDevice(deviceIndex); + if (!device || deviceIndex >= NET_MAX_ETH_STATES) + return STATUS_NOT_FOUND; + + NET_ETH_STATE* state = &gEthStates[deviceIndex]; + if (!state->Present || !state->Ipv4Configured) + return STATUS_NOT_FOUND; + + return NetPingIpv4OnDevice(device, state, targetIp); +} + +void NetEthernetAttachDevice(NET_DEVICE* device) { + if (!device || device->Index >= NET_MAX_ETH_STATES) + return; + if (!NetHasEthernetAddress(device)) { + DbgPrint("NET: %s has unsupported hardware address length %u; Ethernet shim disabled\n", + device->Name, (unsigned)device->HardwareAddressLength); + return; + } + + uintptr_t flags = KeAcquireSpinlock(&gEthLock); + NET_ETH_STATE* state = &gEthStates[device->Index]; + memset(state, 0, sizeof(*state)); + state->Present = true; + KeReleaseSpinlock(&gEthLock, flags); + + if (NetIsQemuMac(device)) { + NetConfigureIpv4Static(device->Index, + NET_IPV4_ADDR(10, 0, 2, 15), + NET_IPV4_ADDR(255, 255, 255, 0), + NET_IPV4_ADDR(10, 0, 2, 2)); + state = &gEthStates[device->Index]; + (void)NetPingIpv4OnDevice(device, state, state->Gateway); + } else { + DbgPrint("NET: %s has no IPv4 address yet; ARP/ICMP replies disabled\n", + device->Name); + } +} + +void NetEthernetReceive(NET_DEVICE* device, const void* frame, size_t length) { + NET_ETH_STATE* state = NetGetEthState(device); + if (!state || !state->Present || !frame || length < ETH_HDR_LEN) + return; + + const uint8_t* b = (const uint8_t*)frame; + if (!NetEthernetFrameIsForDevice(device, b)) + return; + + uint16_t etherType = NetLoadBe16(b + 12); + state->RxFrames++; + + switch (etherType) { + case ETH_TYPE_ARP: + NetHandleArp(device, state, b, length); + break; + case ETH_TYPE_IPV4: + NetHandleIpv4(device, state, b, length); + break; + default: + break; + } +} diff --git a/code/kernel/ethernet.h b/code/kernel/ethernet.h new file mode 100644 index 0000000..7677434 --- /dev/null +++ b/code/kernel/ethernet.h @@ -0,0 +1,26 @@ +#pragma once +// ethernet.h - tiny pre-lwIP Ethernet/ARP/ICMP shim. +// +// This is deliberately small: it gives NIC drivers something useful to feed +// while lwIP is not wired up yet. Keep the public surface generic so the lwIP +// netif glue can reuse NetSendFrame() and bypass this file later. + +#include +#include +#include +#include + +#define NET_IPV4_ADDR(a, b, c, d) \ + ((((uint32_t)(a) & 0xFFu) << 24) | \ + (((uint32_t)(b) & 0xFFu) << 16) | \ + (((uint32_t)(c) & 0xFFu) << 8) | \ + ((uint32_t)(d) & 0xFFu)) + +__declspec(dllexport) KSTATUS NetConfigureIpv4Static(uint32_t deviceIndex, + uint32_t address, + uint32_t netmask, + uint32_t gateway); +__declspec(dllexport) KSTATUS NetPingIpv4(uint32_t deviceIndex, uint32_t targetIp); + +void NetEthernetAttachDevice(NET_DEVICE* device); +void NetEthernetReceive(NET_DEVICE* device, const void* frame, size_t length); diff --git a/code/kernel/ke_irq.c b/code/kernel/ke_irq.c index d7b1d04..cb503ad 100644 --- a/code/kernel/ke_irq.c +++ b/code/kernel/ke_irq.c @@ -4,10 +4,17 @@ #include #include #include +#include #define MAX_IRQS 24 +#define MAX_IRQ_HANDLERS 8 -static void (*gIrqHandlers[MAX_IRQS])(void); +typedef struct IRQ_HANDLER_LIST { + void (*Handlers[MAX_IRQ_HANDLERS])(void); + uint8_t Count; +} IRQ_HANDLER_LIST; + +static IRQ_HANDLER_LIST gIrqHandlers[MAX_IRQS]; static const HAL_API *gHal; static volatile uint32_t gInterruptDepth = 0; @@ -24,7 +31,19 @@ void KeIrqInitialize(const HAL_API *hal) { __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { if (irq >= MAX_IRQS || !handler) return; - gIrqHandlers[irq] = handler; + + IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; + for (uint8_t i = 0; i < list->Count; i++) { + if (list->Handlers[i] == handler) + return; + } + + if (list->Count >= MAX_IRQ_HANDLERS) { + DbgPrint("KeIrqRegister: IRQ %u handler table full\n", irq); + return; + } + + list->Handlers[list->Count++] = handler; gHal->Intc.RouteIrq(irq); } @@ -40,8 +59,13 @@ void KeIrqDispatch(uint8_t irq) { KIRQL oldIrql = KeRaiseIrql(targetIrql); - if (irq < MAX_IRQS && gIrqHandlers[irq]) - gIrqHandlers[irq](); + if (irq < MAX_IRQS) { + IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; + for (uint8_t i = 0; i < list->Count; i++) { + if (list->Handlers[i]) + list->Handlers[i](); + } + } // Handlers are no longer allowed to context-switch directly. Timer IRQs // only request rescheduling; KiExitInterrupt performs the switch after this diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index b71321b..67eb5bf 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -53,6 +53,7 @@ // HAL pointer - set once at boot by KeSyscallSetHal() // --------------------------------------------------------------------------- static const HAL_API* gHal; +static uint64_t (*gCatchAllDebugBucket)(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5); typedef struct SYS_IO_STATUS_BLOCK_K { volatile uint32_t Status; @@ -84,7 +85,10 @@ typedef struct SYS_USER_ASYNC_APC_CONTEXT { void* UserContext; } SYS_USER_ASYNC_APC_CONTEXT; -void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; } +void KeSyscallInit(const HAL_API* hal, uint64_t (*CatchAllDebugBucket)(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5)) { + gHal = hal; + gCatchAllDebugBucket = CatchAllDebugBucket; +} // --------------------------------------------------------------------------- // Helper: look up a handle and type-check it in one call. @@ -1494,6 +1498,11 @@ static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2, return IoRemoveDir(path); } +static uint64_t SyscallCatchAllDebugBucket(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + return gCatchAllDebugBucket(a0, a1, a2, a3, a4, a5); +} + // =========================================================================== // Dispatch table - index must match SYS_* in syscall_nr.h exactly. // =========================================================================== @@ -1576,6 +1585,7 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { /* 73 SYS_GET_TICK */ SyscallGetTick, /* 74 SYS_DEBUG_PRINT */ SyscallDebugPrint, /* 75 SYS_QUERY_TRAP_FRAME */ SyscallQueryTrapFrame, + /* 76 SYS_CATCHALLDEBUGBUCKET */ SyscallCatchAllDebugBucket }; // --------------------------------------------------------------------------- diff --git a/code/kernel/ke_syscall.h b/code/kernel/ke_syscall.h index 0f2b553..f271644 100644 --- a/code/kernel/ke_syscall.h +++ b/code/kernel/ke_syscall.h @@ -4,7 +4,7 @@ #include // Set the HAL API pointer for syscalls -void KeSyscallSetHal(const HAL_API* hal); +void KeSyscallInit(const HAL_API* hal, uint64_t (*CatchAllDebugBucket)(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5)); // Syscall dispatcher, called from assembly entry point uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index eb95b75..9a728f5 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -401,10 +402,15 @@ void SystemInitThread(void* arg) { args->hal->Screen.Clear(0x000000); // Print memory status now that the system is fully alive - DbgPrint("Memory Status:\n"); - DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f)); - DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f)); - DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f)); + DbgPrint( + "Memory Status:\n" + " -> Free (PMM): %.2f MB\n" + " -> Kernel Pool: %.2f MB\n" + " -> User Heap: %.2f MB\n", + (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f), + (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f), + (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f) + ); if (args->bootInfo->Debug & 2) KiRunKernelSelfTests(args->hal); @@ -432,6 +438,59 @@ void SystemInitThread(void* arg) { MmFreePool(args); } +// for debug! for science! +uint64_t KeCatchAllDebugBucket(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { + if (a0 == 0) { + DbgPrint( + "Memory Status:\n" + " -> Free (PMM): %.2f MB\n" + " -> Kernel Pool: %.2f MB\n" + " -> User Heap: %.2f MB\n", + (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f), + (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f), + (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f) + ); + return 0; + } + + if (a0 == 1) { + uint32_t deviceIndex = (uint32_t)a1; + uint32_t targetIp; + + // Usage from shell debug mode: + // 1,0,10,0,2,2 ping 10.0.2.2 on eth0 + // 1,0,167772674,0,0,0 ping packed IPv4 0x0A000202 on eth0 + // 1,0,0,0,0,0 ping the default QEMU gateway 10.0.2.2 + if (a2 == 0 && a3 == 0 && a4 == 0 && a5 == 0) { + targetIp = NET_IPV4_ADDR(10, 0, 2, 2); + } else if (a3 == 0 && a4 == 0 && a5 == 0 && a2 > 0xFFu) { + targetIp = (uint32_t)a2; + } else { + if (a2 > 0xFFu || a3 > 0xFFu || a4 > 0xFFu || a5 > 0xFFu) { + DbgPrint("DebugBucket[1]: invalid IPv4 octet(s): %llu.%llu.%llu.%llu\n", + a2, a3, a4, a5); + return STATUS_INVALID_PARAMETER; + } + targetIp = NET_IPV4_ADDR((uint8_t)a2, (uint8_t)a3, + (uint8_t)a4, (uint8_t)a5); + } + + DbgPrint("DebugBucket[1]: ping eth%u %u.%u.%u.%u\n", + deviceIndex, + (targetIp >> 24) & 0xFFu, + (targetIp >> 16) & 0xFFu, + (targetIp >> 8) & 0xFFu, + targetIp & 0xFFu); + + KSTATUS status = NetPingIpv4(deviceIndex, targetIp); + DbgPrint("DebugBucket[1]: NetPingIpv4 returned 0x%X\n", status); + return status; + } + + DbgPrint("DebugBucket: unknown command %llu\n", a0); + return STATUS_INVALID_PARAMETER; +} + // --------------------------------------------------------------------------- // Main Kernel Entry // --------------------------------------------------------------------------- @@ -495,7 +554,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) { HalRegisterScheduler(KeSchedulerTick); - KeSyscallSetHal(hal); + KeSyscallInit(hal, KeCatchAllDebugBucket); // // Dynamic Linking & Driver Execution diff --git a/code/kernel/net_device.c b/code/kernel/net_device.c new file mode 100644 index 0000000..e674cb9 --- /dev/null +++ b/code/kernel/net_device.c @@ -0,0 +1,175 @@ +// net_device.c - minimal network-device registry and RX handoff. +#include +#include +#include +#include +#include +#include +#include +#include + +#define NET_MAX_DEVICES 8u +#define NET_RX_LOG_LIMIT 16u + +static NET_DEVICE* gNetDevices[NET_MAX_DEVICES]; +static uint32_t gNetDeviceCount = 0; +static SPINLOCK gNetLock = SPINLOCK_INIT; + +static void NetMakeDefaultName(NET_DEVICE* device, uint32_t index) { + if (!device || device->Name[0]) + return; + + // The registry is currently capped at eight devices, so ethN is enough and + // keeps the name stable for early boot diagnostics. + device->Name[0] = 'e'; + device->Name[1] = 't'; + device->Name[2] = 'h'; + device->Name[3] = (char)('0' + (index % 10u)); + device->Name[4] = '\0'; +} + +static bool NetIsDeviceRegisteredLocked(const NET_DEVICE* device) { + for (uint32_t i = 0; i < gNetDeviceCount; ++i) { + if (gNetDevices[i] == device) + return true; + } + return false; +} + +static bool NetDeviceHasEthernetAddress(const NET_DEVICE* device) { + return device && device->HardwareAddressLength == NET_ETHERNET_ADDRESS_LENGTH; +} + +static void NetLogHardwareAddress(const NET_DEVICE* device) { + if (NetDeviceHasEthernetAddress(device)) { + DbgPrint("%02x:%02x:%02x:%02x:%02x:%02x", + device->HardwareAddress[0], device->HardwareAddress[1], + device->HardwareAddress[2], device->HardwareAddress[3], + device->HardwareAddress[4], device->HardwareAddress[5]); + } else { + DbgPrint("hwlen=%u", (unsigned)device->HardwareAddressLength); + } +} + +__declspec(dllexport) KSTATUS NetRegisterDevice(NET_DEVICE* device) { + if (!device || !device->SendFrame) + return STATUS_INVALID_PARAMETER; + if (device->HardwareAddressLength > NET_HWADDR_MAX) + return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + if (NetIsDeviceRegisteredLocked(device)) { + KeReleaseSpinlock(&gNetLock, flags); + return STATUS_ALREADY_EXISTS; + } + if (gNetDeviceCount >= NET_MAX_DEVICES) { + KeReleaseSpinlock(&gNetLock, flags); + return STATUS_QUEUE_FULL; + } + + uint32_t index = gNetDeviceCount; + device->Index = index; + if (device->Mtu == 0) + device->Mtu = NET_MTU_ETHERNET; + memset(&device->Stats, 0, sizeof(device->Stats)); + NetMakeDefaultName(device, index); + gNetDevices[index] = device; + gNetDeviceCount++; + KeReleaseSpinlock(&gNetLock, flags); + + DbgPrint("NET: registered %s index=%u mac=", device->Name, device->Index); + NetLogHardwareAddress(device); + DbgPrint(" mtu=%u\n", device->Mtu); + + NetEthernetAttachDevice(device); + return STATUS_SUCCESS; +} + +__declspec(dllexport) uint32_t NetGetDeviceCount(void) { + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + uint32_t count = gNetDeviceCount; + KeReleaseSpinlock(&gNetLock, flags); + return count; +} + +__declspec(dllexport) NET_DEVICE* NetGetDevice(uint32_t index) { + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + NET_DEVICE* device = (index < gNetDeviceCount) ? gNetDevices[index] : (NET_DEVICE*)0; + KeReleaseSpinlock(&gNetLock, flags); + return device; +} + +__declspec(dllexport) KSTATUS NetGetDeviceStats(uint32_t index, + NET_DEVICE_STATS* stats) { + if (!stats) + return STATUS_INVALID_PARAMETER; + + NET_DEVICE* device = NetGetDevice(index); + if (!device) + return STATUS_NOT_FOUND; + + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + memcpy(stats, &device->Stats, sizeof(*stats)); + KeReleaseSpinlock(&gNetLock, flags); + return STATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS NetSendFrame(uint32_t index, + const void* frame, + size_t length) { + NET_DEVICE* device = NetGetDevice(index); + if (!device || !device->SendFrame) + return STATUS_NOT_FOUND; + if (!frame || length < NET_ETHERNET_HEADER_LENGTH) + return STATUS_INVALID_PARAMETER; + if (length > (size_t)device->Mtu + NET_ETHERNET_HEADER_LENGTH) + return STATUS_BUFFER_TOO_SMALL; + + KSTATUS st = device->SendFrame(device, frame, length); + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + if (KSUCCESS(st)) { + device->Stats.TxPackets++; + device->Stats.TxBytes += length; + } else { + device->Stats.TxErrors++; + } + KeReleaseSpinlock(&gNetLock, flags); + return st; +} + +__declspec(dllexport) void NetReceiveFrame(NET_DEVICE* device, + const void* frame, + size_t length) { + static volatile uint32_t sLoggedFrames = 0; + + if (!device || !frame || length < NET_ETHERNET_HEADER_LENGTH) { + if (device) { + uintptr_t dropFlags = KeAcquireSpinlock(&gNetLock); + device->Stats.RxDrops++; + KeReleaseSpinlock(&gNetLock, dropFlags); + } + return; + } + + uintptr_t flags = KeAcquireSpinlock(&gNetLock); + device->Stats.RxPackets++; + device->Stats.RxBytes += length; + KeReleaseSpinlock(&gNetLock, flags); + + // Temporary observability until lwIP is attached. Avoid spamming the + // console on busy LANs. Log before protocol handling so RX/TX ordering is + // easier to read when an interrupt fires during a send path. + if (sLoggedFrames < NET_RX_LOG_LIMIT) { + const uint8_t* b = (const uint8_t*)frame; + uint16_t etherType = ((uint16_t)b[12] << 8) | b[13]; + DbgPrint("NET: %s rx len=%u ethertype=0x%04x dst=%02x:%02x:%02x:%02x:%02x:%02x src=%02x:%02x:%02x:%02x:%02x:%02x\n", + device->Name, + (unsigned)length, + etherType, + b[0], b[1], b[2], b[3], b[4], b[5], + b[6], b[7], b[8], b[9], b[10], b[11]); + sLoggedFrames++; + } + + NetEthernetReceive(device, frame, length); +} diff --git a/code/kernel/net_device.h b/code/kernel/net_device.h new file mode 100644 index 0000000..1ec737e --- /dev/null +++ b/code/kernel/net_device.h @@ -0,0 +1,57 @@ +#pragma once +// net_device.h - tiny kernel network-device registry. +// +// This layer is intentionally below the future socket/IP stack. NIC drivers +// register a NET_DEVICE and feed complete Ethernet frames into NetReceiveFrame(). +// Protocol code can later subscribe above this layer or bind a real stack. + +#include +#include +#include + +#define NET_DEVICE_NAME_MAX 16u +#define NET_HWADDR_MAX 16u +#define NET_MTU_ETHERNET 1500u +#define NET_ETHERNET_ADDRESS_LENGTH 6u +#define NET_ETHERNET_HEADER_LENGTH 14u +#define NET_ETHERNET_MIN_FRAME 60u +#define NET_ETHERNET_MAX_FRAME (NET_ETHERNET_HEADER_LENGTH + NET_MTU_ETHERNET) + +struct NET_DEVICE; +typedef struct NET_DEVICE NET_DEVICE; + +typedef KSTATUS (*NET_SEND_FRAME)(NET_DEVICE* device, + const void* frame, + size_t length); + +typedef struct NET_DEVICE_STATS { + uint64_t TxPackets; + uint64_t TxBytes; + uint64_t TxErrors; + uint64_t RxPackets; + uint64_t RxBytes; + uint64_t RxDrops; +} NET_DEVICE_STATS; + +struct NET_DEVICE { + char Name[NET_DEVICE_NAME_MAX]; + uint32_t Index; + uint32_t Mtu; + uint8_t HardwareAddressLength; + uint8_t HardwareAddress[NET_HWADDR_MAX]; + void* DriverContext; + NET_SEND_FRAME SendFrame; + NET_DEVICE_STATS Stats; +}; + +__declspec(dllexport) KSTATUS NetRegisterDevice(NET_DEVICE* device); +__declspec(dllexport) uint32_t NetGetDeviceCount(void); +__declspec(dllexport) NET_DEVICE* NetGetDevice(uint32_t index); +__declspec(dllexport) KSTATUS NetGetDeviceStats(uint32_t index, + NET_DEVICE_STATS* stats); +__declspec(dllexport) KSTATUS NetSendFrame(uint32_t index, + const void* frame, + size_t length); +__declspec(dllexport) void NetReceiveFrame(NET_DEVICE* device, + const void* frame, + size_t length); diff --git a/code/kernel/syscall_nr.h b/code/kernel/syscall_nr.h index 8b1c0de..3773f58 100644 --- a/code/kernel/syscall_nr.h +++ b/code/kernel/syscall_nr.h @@ -81,5 +81,6 @@ #define SYS_GET_TICK 72 #define SYS_DEBUG_PRINT 73 #define SYS_QUERY_TRAP_FRAME 74 +#define SYS_CATCHALLDEBUGBUCKET 75 -#define SYS_MAX 75 +#define SYS_MAX 76 diff --git a/code/shell_native/shell_native.c b/code/shell_native/shell_native.c index 77db322..35e9af6 100644 --- a/code/shell_native/shell_native.c +++ b/code/shell_native/shell_native.c @@ -24,6 +24,32 @@ static void printInt(int32_t n) { else { printUInt((uint32_t)n); } } +static uint64_t parseUInt64(const char* s) { + uint64_t v = 0; + while (*s == ' ') s++; + while (*s >= '0' && *s <= '9') v = v * 10 + (uint64_t)(*s++ - '0'); + return v; +} + +// Parse up to 6 comma-separated uint64 values. +// Returns the number of values actually parsed. +static int parseArgs(const char* line, uint64_t args[6]) { + int count = 0; + while (*line && count < 6) { + args[count++] = parseUInt64(line); + while (*line && *line != ',') line++; // skip to next comma + if (*line == ',') line++; // consume comma + } + return count; +} + +static void printUInt64(uint64_t n) { + if (n == 0) { print("0"); return; } + char buf[20]; int i = 0; + while (n > 0) { buf[i++] = (char)('0' + (int)(n % 10)); n /= 10; } + while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); } +} + // --------------------------------------------------------------------------- // Line editor // --------------------------------------------------------------------------- @@ -54,6 +80,7 @@ static char* trim(char* s) { static void cmd_help() { print("Available commands:\n" " help - Show this list\n" + " debug - Enter debug bucket mode\n" " dir [path] - List directory contents\n" " cd - Change directory\n" " type - Display file contents\n" @@ -63,6 +90,32 @@ static void cmd_help() { " exit - Close the shell\n"); } +static void cmd_debug(void) { + print("Entered debug mode. Type args as: a0,a1,a2,a3,a4,a5\n"); + print("Type 'exit' to leave.\n"); + + char line[MAX_CMD]; + while (true) { + print("debug> "); + readLine(line, MAX_CMD); + char* t = trim(line); + + if (strcmp(t, "exit") == 0) { + print("Exit debug.\n"); + break; + } + if (*t == '\0') continue; + + uint64_t args[6] = {0, 0, 0, 0, 0, 0}; + parseArgs(t, args); + + uint64_t ret = SysCatchAllDebugBucket(args[0], args[1], args[2], + args[3], args[4], args[5]); + printUInt64(ret); + print("\n"); + } +} + static void cmd_write(const char* args) { char filename[MAX_CMD]; int i = 0; while (args[i] != ' ' && args[i] != '\0' && i < MAX_CMD - 1) { @@ -150,6 +203,7 @@ static int runCommand(const char* cmd) { if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } if (strcmp(cmd, "clear") == 0) { SysScreenClear(0); return 0; } if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; } + if (strcmp(cmd, "debug") == 0) { cmd_debug(); return 0; } if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } if (strncmp(cmd, "cd ", 3) == 0) { if (SysSetCwd(cmd + 3) != 0) print("cd: failed\n"); return 0; } diff --git a/code/syscall/syscall.h b/code/syscall/syscall.h index 843fd88..2dae8e9 100644 --- a/code/syscall/syscall.h +++ b/code/syscall/syscall.h @@ -233,3 +233,5 @@ void SysMouseGetPos(int* x, int* y); uint64_t SysGetTick(void); uint32_t SysDebugPrint(const char* str); uint32_t SysQueryTrapFrame(SYS_TRAP_FRAME_INFO* info); + +uint64_t SysCatchAllDebugBucket(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5); diff --git a/code/syscall/syscall_nr.inc b/code/syscall/syscall_nr.inc index 2110169..273a52c 100644 --- a/code/syscall/syscall_nr.inc +++ b/code/syscall/syscall_nr.inc @@ -75,5 +75,6 @@ SYS_MOUSE_GET_POS EQU 71 SYS_GET_TICK EQU 72 SYS_DEBUG_PRINT EQU 73 SYS_QUERY_TRAP_FRAME EQU 74 +SYS_CATCHALLDEBUGBUCKET EQU 75 -SYS_MAX EQU 75 +SYS_MAX EQU 76 diff --git a/code/syscall/syscall_stubs.asm b/code/syscall/syscall_stubs.asm index dd2a9bf..4fdcb61 100644 --- a/code/syscall/syscall_stubs.asm +++ b/code/syscall/syscall_stubs.asm @@ -197,4 +197,6 @@ SYSCALL_STUB2 GetTick, SYS_GET_TICK SYSCALL_STUB2 DebugPrint, SYS_DEBUG_PRINT SYSCALL_STUB2 QueryTrapFrame, SYS_QUERY_TRAP_FRAME +SYSCALL_STUB6 CatchAllDebugBucket, SYS_CATCHALLDEBUGBUCKET + END diff --git a/run.bat b/run.bat index f62681b..0d7d150 100644 --- a/run.bat +++ b/run.bat @@ -1 +1 @@ -@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M.fd -drive format=vpc,file=disk.vhd -serial stdio +@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M.fd -drive format=vpc,file=disk.vhd -serial stdio -netdev user,id=net0 -device rtl8139,netdev=net0 diff --git a/runa.bat b/runa.bat index 5f0ff36..9c826db 100644 --- a/runa.bat +++ b/runa.bat @@ -1 +1 @@ -@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M_d.fd -device ahci,id=ahci -drive format=vpc,file=disk.vhd,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 -serial stdio -m 256M +@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M_d.fd -device ahci,id=ahci -drive format=vpc,file=disk.vhd,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 -serial stdio -m 256M -netdev user,id=net0 -device rtl8139,netdev=net0 diff --git a/runad.bat b/runad.bat index cf5eb53..f3bf91c 100644 --- a/runad.bat +++ b/runad.bat @@ -1 +1 @@ -@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M_d.fd -device ahci,id=ahci -drive format=vpc,file=disk.vhd,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 -m 4G -serial stdio -d in_asm,cpu_reset,int -D qemu.log -no-reboot -no-shutdown +@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M_d.fd -device ahci,id=ahci -drive format=vpc,file=disk.vhd,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 -m 4G -serial stdio -d in_asm,cpu_reset,int -D qemu.log -no-reboot -no-shutdown -netdev user,id=net0 -device rtl8139,netdev=net0 diff --git a/rund.bat b/rund.bat index 2ff8e3b..f8d797c 100644 --- a/rund.bat +++ b/rund.bat @@ -1 +1 @@ -@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M.fd -drive format=vpc,file=disk.vhd -m 4G -serial stdio -d in_asm,cpu_reset,int -D qemu.log -no-reboot -no-shutdown +@start qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=OVMF\OVMF_CODE_4M.fd -drive if=pflash,format=raw,file=OVMF\OVMF_VARS_4M.fd -drive format=vpc,file=disk.vhd -m 4G -serial stdio -d in_asm,cpu_reset,int -D qemu.log -no-reboot -no-shutdown -netdev user,id=net0 -device rtl8139,netdev=net0