feat: e100e Ethernet driver, ramdisk reorganization, shell rewrite, web server/client, and more
This commit is contained in:
@@ -20,6 +20,9 @@
|
||||
#include <Net/Icmp.hpp>
|
||||
#include <Net/Socket.hpp>
|
||||
#include <Net/ByteOrder.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
@@ -303,6 +306,45 @@ namespace Zenith {
|
||||
Net::Socket::Close(fd, Sched::GetCurrentPid());
|
||||
}
|
||||
|
||||
static int Sys_SendTo(int fd, const uint8_t* data, uint32_t len,
|
||||
uint32_t destIp, uint16_t destPort) {
|
||||
return Net::Socket::SendTo(fd, data, len, destIp, destPort, Sched::GetCurrentPid());
|
||||
}
|
||||
|
||||
static int Sys_RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
|
||||
uint32_t* srcIp, uint16_t* srcPort) {
|
||||
return Net::Socket::RecvFrom(fd, buf, maxLen, srcIp, srcPort, Sched::GetCurrentPid());
|
||||
}
|
||||
|
||||
static void Sys_GetNetCfg(NetCfg* out) {
|
||||
if (out == nullptr) return;
|
||||
out->ipAddress = Net::GetIpAddress();
|
||||
out->subnetMask = Net::GetSubnetMask();
|
||||
out->gateway = Net::GetGateway();
|
||||
|
||||
const uint8_t* mac = nullptr;
|
||||
if (Drivers::Net::E1000::IsInitialized()) {
|
||||
mac = Drivers::Net::E1000::GetMacAddress();
|
||||
} else if (Drivers::Net::E1000E::IsInitialized()) {
|
||||
mac = Drivers::Net::E1000E::GetMacAddress();
|
||||
}
|
||||
if (mac) {
|
||||
for (int i = 0; i < 6; i++) out->macAddress[i] = mac[i];
|
||||
} else {
|
||||
for (int i = 0; i < 6; i++) out->macAddress[i] = 0;
|
||||
}
|
||||
out->_pad[0] = 0;
|
||||
out->_pad[1] = 0;
|
||||
}
|
||||
|
||||
static int Sys_SetNetCfg(const NetCfg* in) {
|
||||
if (in == nullptr) return -1;
|
||||
Net::SetIpAddress(in->ipAddress);
|
||||
Net::SetSubnetMask(in->subnetMask);
|
||||
Net::SetGateway(in->gateway);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void Sys_Reset() {
|
||||
/*
|
||||
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
|
||||
@@ -320,6 +362,16 @@ namespace Zenith {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
// ---- File write/create ----
|
||||
|
||||
static int Sys_FWrite(int handle, const uint8_t* data, uint64_t offset, uint64_t size) {
|
||||
return Fs::Vfs::VfsWrite(handle, data, offset, size);
|
||||
}
|
||||
|
||||
static int Sys_FCreate(const char* path) {
|
||||
return Fs::Vfs::VfsCreate(path);
|
||||
}
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
|
||||
@@ -416,6 +468,24 @@ namespace Zenith {
|
||||
case SYS_CLOSESOCK:
|
||||
Sys_CloseSock((int)frame->arg1);
|
||||
return 0;
|
||||
case SYS_GETNETCFG:
|
||||
Sys_GetNetCfg((NetCfg*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_SETNETCFG:
|
||||
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
|
||||
case SYS_SENDTO:
|
||||
return (int64_t)Sys_SendTo((int)frame->arg1, (const uint8_t*)frame->arg2,
|
||||
(uint32_t)frame->arg3, (uint32_t)frame->arg4,
|
||||
(uint16_t)frame->arg5);
|
||||
case SYS_RECVFROM:
|
||||
return (int64_t)Sys_RecvFrom((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||
(uint32_t)frame->arg3, (uint32_t*)frame->arg4,
|
||||
(uint16_t*)frame->arg5);
|
||||
case SYS_FWRITE:
|
||||
return (int64_t)Sys_FWrite((int)frame->arg1, (const uint8_t*)frame->arg2,
|
||||
frame->arg3, frame->arg4);
|
||||
case SYS_FCREATE:
|
||||
return (int64_t)Sys_FCreate((const char*)frame->arg1);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -442,7 +512,7 @@ namespace Zenith {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 37 syscalls)";
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 43 syscalls)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,8 +48,15 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_SEND = 34;
|
||||
static constexpr uint64_t SYS_RECV = 35;
|
||||
static constexpr uint64_t SYS_CLOSESOCK = 36;
|
||||
static constexpr uint64_t SYS_GETNETCFG = 37;
|
||||
static constexpr uint64_t SYS_SETNETCFG = 38;
|
||||
static constexpr uint64_t SYS_SENDTO = 39;
|
||||
static constexpr uint64_t SYS_RECVFROM = 40;
|
||||
static constexpr uint64_t SYS_FWRITE = 41;
|
||||
static constexpr uint64_t SYS_FCREATE = 42;
|
||||
|
||||
static constexpr int SOCK_TCP = 1;
|
||||
static constexpr int SOCK_UDP = 2;
|
||||
|
||||
struct DateTime {
|
||||
uint16_t Year;
|
||||
@@ -75,6 +82,14 @@ namespace Zenith {
|
||||
uint32_t maxProcesses;
|
||||
};
|
||||
|
||||
struct NetCfg {
|
||||
uint32_t ipAddress; // network byte order
|
||||
uint32_t subnetMask; // network byte order
|
||||
uint32_t gateway; // network byte order
|
||||
uint8_t macAddress[6];
|
||||
uint8_t _pad[2];
|
||||
};
|
||||
|
||||
struct KeyEvent {
|
||||
uint8_t scancode;
|
||||
char ascii;
|
||||
|
||||
@@ -0,0 +1,763 @@
|
||||
/*
|
||||
* E1000E.cpp
|
||||
* Intel I217/I218/I219 (E1000E) Ethernet driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "E1000E.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Net::E1000E {
|
||||
|
||||
// Supported device entry
|
||||
struct DeviceEntry {
|
||||
uint16_t DeviceId;
|
||||
const char* Name;
|
||||
};
|
||||
|
||||
// PCI vendor ID for Intel
|
||||
static constexpr uint16_t VendorIntel = 0x8086;
|
||||
|
||||
// Device ID table for I217/I218/I219 series
|
||||
static constexpr DeviceEntry g_deviceTable[] = {
|
||||
// I217
|
||||
{ 0x153A, "I217-LM" },
|
||||
{ 0x153B, "I217-V" },
|
||||
// I218
|
||||
{ 0x155A, "I218-LM" },
|
||||
{ 0x1559, "I218-V" },
|
||||
{ 0x15A0, "I218-LM (2)" },
|
||||
{ 0x15A1, "I218-V (2)" },
|
||||
{ 0x15A2, "I218-LM (3)" },
|
||||
{ 0x15A3, "I218-V (3)" },
|
||||
// I219-LM variants
|
||||
{ 0x156F, "I219-LM" },
|
||||
{ 0x15B7, "I219-LM (2)" },
|
||||
{ 0x15BB, "I219-LM (3)" },
|
||||
{ 0x15BD, "I219-LM (4)" },
|
||||
{ 0x15DF, "I219-LM (5)" },
|
||||
{ 0x15E1, "I219-LM (6)" },
|
||||
{ 0x15E3, "I219-LM (7)" },
|
||||
{ 0x15D7, "I219-LM (8)" },
|
||||
{ 0x0D4C, "I219-LM (9)" },
|
||||
{ 0x0D4E, "I219-LM (10)" },
|
||||
{ 0x0D53, "I219-LM (11)" },
|
||||
{ 0x0D55, "I219-LM (12)" },
|
||||
{ 0x0DC5, "I219-LM (13)" },
|
||||
{ 0x0DC7, "I219-LM (14)" },
|
||||
{ 0x1A1C, "I219-LM (15)" },
|
||||
{ 0x1A1E, "I219-LM (16)" },
|
||||
// I219-V variants
|
||||
{ 0x1570, "I219-V" },
|
||||
{ 0x15B8, "I219-V (2)" },
|
||||
{ 0x15BC, "I219-V (3)" },
|
||||
{ 0x15BE, "I219-V (4)" },
|
||||
{ 0x15E0, "I219-V (5)" },
|
||||
{ 0x15E2, "I219-V (6)" },
|
||||
{ 0x15D6, "I219-V (7)" },
|
||||
{ 0x15D8, "I219-V (8)" },
|
||||
{ 0x0D4D, "I219-V (9)" },
|
||||
{ 0x0D4F, "I219-V (10)" },
|
||||
{ 0x0D54, "I219-V (11)" },
|
||||
{ 0x0DC6, "I219-V (13)" },
|
||||
{ 0x0DC8, "I219-V (14)" },
|
||||
{ 0x1A1D, "I219-V (15)" },
|
||||
{ 0x1A1F, "I219-V (16)" },
|
||||
};
|
||||
|
||||
static constexpr uint32_t g_deviceTableSize = sizeof(g_deviceTable) / sizeof(g_deviceTable[0]);
|
||||
|
||||
// PCI config space offsets
|
||||
static constexpr uint8_t PCI_REG_BAR0 = 0x10;
|
||||
static constexpr uint8_t PCI_REG_BAR1 = 0x14;
|
||||
static constexpr uint8_t PCI_REG_COMMAND = 0x04;
|
||||
static constexpr uint8_t PCI_REG_INTERRUPT = 0x3C;
|
||||
|
||||
// PCI command register bits
|
||||
static constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2);
|
||||
static constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1);
|
||||
static constexpr uint16_t PCI_CMD_INTX_DISABLE = (1 << 10);
|
||||
|
||||
// MSI configuration
|
||||
static constexpr uint8_t MSI_IRQ = 24; // IRQ slot 24 = vector 56 (first MSI slot)
|
||||
static constexpr uint32_t MSI_VECTOR = 56; // IRQ_VECTOR_BASE + MSI_IRQ
|
||||
static constexpr uint32_t MSI_ADDR_BASE = 0xFEE00000; // Local APIC message address range
|
||||
|
||||
// Driver state
|
||||
static bool g_initialized = false;
|
||||
static bool g_pollingMode = false;
|
||||
static volatile uint8_t* g_mmioBase = nullptr;
|
||||
static uint8_t g_macAddress[6] = {};
|
||||
static uint8_t g_irqLine = 0;
|
||||
|
||||
// Descriptor rings (physical addresses for DMA, virtual for CPU access)
|
||||
static RxDescriptor* g_rxDescs = nullptr;
|
||||
static TxDescriptor* g_txDescs = nullptr;
|
||||
static uint64_t g_rxDescsPhys = 0;
|
||||
static uint64_t g_txDescsPhys = 0;
|
||||
|
||||
// Packet buffers (virtual addresses)
|
||||
static uint8_t* g_rxBuffers[RX_DESC_COUNT] = {};
|
||||
static uint8_t* g_txBuffers[TX_DESC_COUNT] = {};
|
||||
static uint64_t g_rxBuffersPhys[RX_DESC_COUNT] = {};
|
||||
static uint64_t g_txBuffersPhys[TX_DESC_COUNT] = {};
|
||||
|
||||
// Current descriptor indices
|
||||
static uint32_t g_rxTail = 0;
|
||||
static uint32_t g_txTail = 0;
|
||||
|
||||
// Statistics
|
||||
static uint64_t g_rxPacketCount = 0;
|
||||
static uint64_t g_txPacketCount = 0;
|
||||
|
||||
// RX callback
|
||||
static RxCallback g_rxCallback = nullptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Register access helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void WriteReg(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_mmioBase + reg) = value;
|
||||
}
|
||||
|
||||
static uint32_t ReadReg(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_mmioBase + reg);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SW/FW semaphore (prevents conflicts with Intel Management Engine)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool AcquireSwFwSync() {
|
||||
// Step 1: Acquire the software semaphore (SWSM.SMBI)
|
||||
bool gotSmbi = false;
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
uint32_t swsm = ReadReg(REG_SWSM);
|
||||
if (!(swsm & SWSM_SMBI)) {
|
||||
WriteReg(REG_SWSM, swsm | SWSM_SMBI);
|
||||
if (ReadReg(REG_SWSM) & SWSM_SMBI) {
|
||||
gotSmbi = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotSmbi) {
|
||||
KernelLogStream(WARNING, "E1000E") << "Could not acquire SMBI, proceeding anyway";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Acquire the SW/FW semaphore (EXTCNF_CTRL.SW_OWN)
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
uint32_t extcnf = ReadReg(REG_EXTCNF_CTRL);
|
||||
if (!(extcnf & EXTCNF_CTRL_SWFLAG)) {
|
||||
WriteReg(REG_EXTCNF_CTRL, extcnf | EXTCNF_CTRL_SWFLAG);
|
||||
if (ReadReg(REG_EXTCNF_CTRL) & EXTCNF_CTRL_SWFLAG) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to acquire SWFLAG — release SMBI
|
||||
uint32_t swsm = ReadReg(REG_SWSM);
|
||||
WriteReg(REG_SWSM, swsm & ~SWSM_SMBI);
|
||||
KernelLogStream(WARNING, "E1000E") << "Could not acquire SWFLAG, proceeding anyway";
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ReleaseSwFwSync() {
|
||||
uint32_t extcnf = ReadReg(REG_EXTCNF_CTRL);
|
||||
WriteReg(REG_EXTCNF_CTRL, extcnf & ~EXTCNF_CTRL_SWFLAG);
|
||||
|
||||
uint32_t swsm = ReadReg(REG_SWSM);
|
||||
WriteReg(REG_SWSM, swsm & ~SWSM_SMBI);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PHY access via MDIC register
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint16_t PhyRead(uint32_t phyReg) {
|
||||
// PHY address = 1 for internal PHY
|
||||
uint32_t mdic = ((phyReg & 0x1F) << MDIC_REG_SHIFT)
|
||||
| (1u << MDIC_PHY_SHIFT)
|
||||
| MDIC_OP_READ;
|
||||
|
||||
WriteReg(REG_MDIC, mdic);
|
||||
|
||||
for (int i = 0; i < 200000; i++) {
|
||||
mdic = ReadReg(REG_MDIC);
|
||||
if (mdic & MDIC_READY) {
|
||||
if (mdic & MDIC_ERROR) {
|
||||
KernelLogStream(WARNING, "E1000E") << "PHY read error for reg " << base::hex << (uint64_t)phyReg;
|
||||
return 0;
|
||||
}
|
||||
return (uint16_t)(mdic & MDIC_DATA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "E1000E") << "PHY read timeout for reg " << base::hex << (uint64_t)phyReg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PhyWrite(uint32_t phyReg, uint16_t value) {
|
||||
uint32_t mdic = (uint32_t)value
|
||||
| ((phyReg & 0x1F) << MDIC_REG_SHIFT)
|
||||
| (1u << MDIC_PHY_SHIFT)
|
||||
| MDIC_OP_WRITE;
|
||||
|
||||
WriteReg(REG_MDIC, mdic);
|
||||
|
||||
for (int i = 0; i < 200000; i++) {
|
||||
mdic = ReadReg(REG_MDIC);
|
||||
if (mdic & MDIC_READY) {
|
||||
if (mdic & MDIC_ERROR) {
|
||||
KernelLogStream(WARNING, "E1000E") << "PHY write error for reg " << base::hex << (uint64_t)phyReg;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "E1000E") << "PHY write timeout for reg " << base::hex << (uint64_t)phyReg;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PHY initialization
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void InitPhy() {
|
||||
// Reset the PHY
|
||||
PhyWrite(PHY_CONTROL, PHY_CTRL_RESET);
|
||||
|
||||
// Wait for reset to complete
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
uint16_t ctrl = PhyRead(PHY_CONTROL);
|
||||
if (!(ctrl & PHY_CTRL_RESET)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Advertise 10/100/1000 Mbps capabilities
|
||||
uint16_t anar = PhyRead(PHY_AUTONEG_ADV);
|
||||
anar |= (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8); // 10/100 HD/FD
|
||||
PhyWrite(PHY_AUTONEG_ADV, anar);
|
||||
|
||||
// Advertise 1000BASE-T
|
||||
uint16_t gbcr = PhyRead(PHY_1000T_CTRL);
|
||||
gbcr |= (1 << 8) | (1 << 9); // 1000BASE-T HD/FD
|
||||
PhyWrite(PHY_1000T_CTRL, gbcr);
|
||||
|
||||
// Enable and restart auto-negotiation
|
||||
PhyWrite(PHY_CONTROL, PHY_CTRL_AUTONEG_EN | PHY_CTRL_RESTART_AN);
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "PHY initialized, auto-negotiation started";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// EEPROM access (e1000e encoding differs from e1000)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint16_t EepromRead(uint8_t address) {
|
||||
// E1000E: address shifted left by 2 (not 8), done bit at position 1 (not 4)
|
||||
WriteReg(REG_EERD, ((uint32_t)address << 2) | 1);
|
||||
|
||||
uint32_t value;
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
value = ReadReg(REG_EERD);
|
||||
if (value & (1 << 1)) {
|
||||
return (uint16_t)(value >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "E1000E") << "EEPROM read timeout for address " << base::hex << (uint64_t)address;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MAC address
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void ReadMacAddress() {
|
||||
// Try reading from RAL/RAH first
|
||||
uint32_t ral = ReadReg(REG_RAL);
|
||||
uint32_t rah = ReadReg(REG_RAH);
|
||||
|
||||
if (ral != 0) {
|
||||
g_macAddress[0] = (uint8_t)(ral);
|
||||
g_macAddress[1] = (uint8_t)(ral >> 8);
|
||||
g_macAddress[2] = (uint8_t)(ral >> 16);
|
||||
g_macAddress[3] = (uint8_t)(ral >> 24);
|
||||
g_macAddress[4] = (uint8_t)(rah);
|
||||
g_macAddress[5] = (uint8_t)(rah >> 8);
|
||||
} else {
|
||||
// Fallback: read from EEPROM
|
||||
uint16_t word0 = EepromRead(0);
|
||||
uint16_t word1 = EepromRead(1);
|
||||
uint16_t word2 = EepromRead(2);
|
||||
|
||||
g_macAddress[0] = (uint8_t)(word0);
|
||||
g_macAddress[1] = (uint8_t)(word0 >> 8);
|
||||
g_macAddress[2] = (uint8_t)(word1);
|
||||
g_macAddress[3] = (uint8_t)(word1 >> 8);
|
||||
g_macAddress[4] = (uint8_t)(word2);
|
||||
g_macAddress[5] = (uint8_t)(word2 >> 8);
|
||||
}
|
||||
|
||||
// Write MAC back to RAL/RAH to ensure the filter is set
|
||||
WriteReg(REG_RAL,
|
||||
(uint32_t)g_macAddress[0] |
|
||||
((uint32_t)g_macAddress[1] << 8) |
|
||||
((uint32_t)g_macAddress[2] << 16) |
|
||||
((uint32_t)g_macAddress[3] << 24));
|
||||
WriteReg(REG_RAH,
|
||||
(uint32_t)g_macAddress[4] |
|
||||
((uint32_t)g_macAddress[5] << 8) |
|
||||
(1u << 31)); // AV (Address Valid) bit
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Allocate page-aligned DMA buffer, returns virtual address
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint8_t* AllocateDmaBuffer(uint64_t& outPhysAddr) {
|
||||
void* virt = Memory::g_pfa->AllocateZeroed();
|
||||
outPhysAddr = Memory::SubHHDM(virt);
|
||||
return (uint8_t*)virt;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// RX setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void SetupRx() {
|
||||
uint64_t descPhys;
|
||||
g_rxDescs = (RxDescriptor*)AllocateDmaBuffer(descPhys);
|
||||
g_rxDescsPhys = descPhys;
|
||||
|
||||
for (uint32_t i = 0; i < RX_DESC_COUNT; i++) {
|
||||
g_rxBuffers[i] = AllocateDmaBuffer(g_rxBuffersPhys[i]);
|
||||
|
||||
// Allocate second page for larger buffer support
|
||||
uint64_t secondPhys;
|
||||
AllocateDmaBuffer(secondPhys);
|
||||
|
||||
g_rxDescs[i].BufferAddress = g_rxBuffersPhys[i];
|
||||
g_rxDescs[i].Status = 0;
|
||||
g_rxDescs[i].Length = 0;
|
||||
g_rxDescs[i].Checksum = 0;
|
||||
g_rxDescs[i].Errors = 0;
|
||||
g_rxDescs[i].Special = 0;
|
||||
}
|
||||
|
||||
WriteReg(REG_RDBAL, (uint32_t)(g_rxDescsPhys & 0xFFFFFFFF));
|
||||
WriteReg(REG_RDBAH, (uint32_t)(g_rxDescsPhys >> 32));
|
||||
WriteReg(REG_RDLEN, RX_DESC_COUNT * sizeof(RxDescriptor));
|
||||
WriteReg(REG_RDH, 0);
|
||||
WriteReg(REG_RDT, RX_DESC_COUNT - 1);
|
||||
|
||||
g_rxTail = RX_DESC_COUNT - 1;
|
||||
|
||||
uint32_t rctl = RCTL_EN | RCTL_BAM | RCTL_SECRC | RCTL_BSIZE_4096 | RCTL_BSEX;
|
||||
WriteReg(REG_RCTL, rctl);
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "RX ring configured: " << base::dec << (uint64_t)RX_DESC_COUNT << " descriptors";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TX setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void SetupTx() {
|
||||
uint64_t descPhys;
|
||||
g_txDescs = (TxDescriptor*)AllocateDmaBuffer(descPhys);
|
||||
g_txDescsPhys = descPhys;
|
||||
|
||||
for (uint32_t i = 0; i < TX_DESC_COUNT; i++) {
|
||||
g_txBuffers[i] = AllocateDmaBuffer(g_txBuffersPhys[i]);
|
||||
|
||||
g_txDescs[i].BufferAddress = g_txBuffersPhys[i];
|
||||
g_txDescs[i].Length = 0;
|
||||
g_txDescs[i].Command = 0;
|
||||
g_txDescs[i].Status = TXSTA_DD;
|
||||
g_txDescs[i].ChecksumOffset = 0;
|
||||
g_txDescs[i].ChecksumStart = 0;
|
||||
g_txDescs[i].Special = 0;
|
||||
}
|
||||
|
||||
WriteReg(REG_TDBAL, (uint32_t)(g_txDescsPhys & 0xFFFFFFFF));
|
||||
WriteReg(REG_TDBAH, (uint32_t)(g_txDescsPhys >> 32));
|
||||
WriteReg(REG_TDLEN, TX_DESC_COUNT * sizeof(TxDescriptor));
|
||||
WriteReg(REG_TDH, 0);
|
||||
WriteReg(REG_TDT, 0);
|
||||
|
||||
g_txTail = 0;
|
||||
|
||||
uint32_t tctl = TCTL_EN | TCTL_PSP
|
||||
| (15u << TCTL_CT_SHIFT)
|
||||
| (64u << TCTL_COLD_SHIFT);
|
||||
WriteReg(REG_TCTL, tctl);
|
||||
|
||||
WriteReg(REG_TIPG, 10 | (10 << 10) | (10 << 20));
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "TX ring configured: " << base::dec << (uint64_t)TX_DESC_COUNT << " descriptors";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MSI setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void HandleInterrupt(uint8_t irq); // forward declaration
|
||||
|
||||
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
|
||||
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
|
||||
if (cap == 0) {
|
||||
KernelLogStream(INFO, "E1000E") << "MSI capability not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "E1000E") << "MSI capability at offset " << base::hex << (uint64_t)cap;
|
||||
|
||||
// Read Message Control (cap+2)
|
||||
uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2);
|
||||
bool is64bit = (msgCtrl & (1 << 7)) != 0;
|
||||
|
||||
// Write Message Address (cap+4): BSP APIC ID 0, physical destination, fixed delivery
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE);
|
||||
|
||||
// Write Message Data (vector number, edge-triggered, fixed delivery)
|
||||
if (is64bit) {
|
||||
// 64-bit: Upper Address at cap+8, Data at cap+12
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 8, 0);
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR);
|
||||
} else {
|
||||
// 32-bit: Data at cap+8
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR);
|
||||
}
|
||||
|
||||
// Enable MSI: set bit 0 (MSI Enable), clear bits 6:4 (single message)
|
||||
msgCtrl &= ~(0x70); // Clear Multiple Message Enable (bits 6:4)
|
||||
msgCtrl |= (1 << 0); // MSI Enable
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl);
|
||||
|
||||
// Disable legacy INTx in PCI command register
|
||||
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, PCI_REG_COMMAND);
|
||||
pciCmd |= PCI_CMD_INTX_DISABLE;
|
||||
Pci::LegacyWrite16(bus, dev, func, PCI_REG_COMMAND, pciCmd);
|
||||
|
||||
// Register the interrupt handler for MSI vector
|
||||
Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt);
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR
|
||||
<< " (IRQ slot " << (uint64_t)MSI_IRQ << ")" << (is64bit ? " [64-bit]" : " [32-bit]");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Interrupt handler
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void HandleInterrupt(uint8_t irq) {
|
||||
(void)irq;
|
||||
|
||||
uint32_t icr = ReadReg(REG_ICR);
|
||||
|
||||
// Spurious IRQ guard
|
||||
if (icr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (icr & ICR_LSC) {
|
||||
uint32_t status = ReadReg(REG_STATUS);
|
||||
bool linkUp = (status & (1 << 1)) != 0;
|
||||
KernelLogStream(INFO, "E1000E") << "Link status change: " << (linkUp ? "UP" : "DOWN");
|
||||
}
|
||||
|
||||
if (icr & ICR_RXT0) {
|
||||
while (true) {
|
||||
uint32_t nextIdx = (g_rxTail + 1) % RX_DESC_COUNT;
|
||||
RxDescriptor& desc = g_rxDescs[nextIdx];
|
||||
|
||||
if (!(desc.Status & RXSTA_DD)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t length = desc.Length;
|
||||
g_rxPacketCount++;
|
||||
|
||||
if (g_rxCallback != nullptr) {
|
||||
g_rxCallback(g_rxBuffers[nextIdx], length);
|
||||
}
|
||||
|
||||
desc.Status = 0;
|
||||
desc.Length = 0;
|
||||
desc.Errors = 0;
|
||||
|
||||
g_rxTail = nextIdx;
|
||||
WriteReg(REG_RDT, g_rxTail);
|
||||
}
|
||||
}
|
||||
|
||||
if (icr & (ICR_TXDW | ICR_TXQE)) {
|
||||
// TX completion — nothing to do for now
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void Initialize() {
|
||||
KernelLogStream(INFO, "E1000E") << "Scanning for Intel e1000e NIC...";
|
||||
|
||||
auto& devices = Pci::GetDevices();
|
||||
const Pci::PciDevice* foundDev = nullptr;
|
||||
const char* foundName = nullptr;
|
||||
|
||||
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||
if (devices[i].VendorId != VendorIntel) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t j = 0; j < g_deviceTableSize; j++) {
|
||||
if (devices[i].DeviceId == g_deviceTable[j].DeviceId) {
|
||||
foundDev = &devices[i];
|
||||
foundName = g_deviceTable[j].Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundDev != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDev == nullptr) {
|
||||
KernelLogStream(WARNING, "E1000E") << "No e1000e NIC found";
|
||||
return;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "Found " << foundName << " at PCI "
|
||||
<< base::hex << (uint64_t)foundDev->Bus << ":"
|
||||
<< (uint64_t)foundDev->Device << "." << (uint64_t)foundDev->Function;
|
||||
|
||||
// Read BAR0 (MMIO base address), check for 64-bit BAR
|
||||
uint32_t bar0 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR0);
|
||||
uint64_t mmioPhys = bar0 & 0xFFFFFFF0;
|
||||
|
||||
// Check if 64-bit BAR (type field bits 2:1 == 0b10)
|
||||
if ((bar0 & 0x06) == 0x04) {
|
||||
uint32_t bar1 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR1);
|
||||
mmioPhys |= ((uint64_t)bar1 << 32);
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "E1000E") << "BAR0 physical: " << base::hex << mmioPhys;
|
||||
|
||||
// Map the MMIO region (128KB = 32 pages)
|
||||
constexpr uint64_t MmioSize = 0x20000;
|
||||
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
|
||||
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
|
||||
// Enable bus mastering and memory space in PCI command register
|
||||
uint16_t pciCmd = Pci::LegacyRead16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND);
|
||||
pciCmd |= PCI_CMD_BUS_MASTER | PCI_CMD_MEM_SPACE;
|
||||
Pci::LegacyWrite16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND, pciCmd);
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "Bus mastering enabled";
|
||||
|
||||
// Read interrupt line from PCI config (used for legacy IRQ fallback)
|
||||
g_irqLine = Pci::LegacyRead8(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_INTERRUPT);
|
||||
KernelLogStream(INFO, "E1000E") << "PCI IRQ line: " << base::dec << (uint64_t)g_irqLine;
|
||||
|
||||
// --- ICH/PCH reset sequence ---
|
||||
|
||||
// 1. Disable interrupts, flush ICR
|
||||
KernelLogStream(INFO, "E1000E") << "Disabling interrupts...";
|
||||
WriteReg(REG_IMC, 0xFFFFFFFF);
|
||||
ReadReg(REG_ICR);
|
||||
|
||||
// 2. Acquire SW/FW semaphore (non-fatal if it fails)
|
||||
KernelLogStream(INFO, "E1000E") << "Acquiring semaphore...";
|
||||
AcquireSwFwSync();
|
||||
|
||||
// 3. Reset the device
|
||||
KernelLogStream(INFO, "E1000E") << "Resetting device...";
|
||||
uint32_t ctrl = ReadReg(REG_CTRL);
|
||||
WriteReg(REG_CTRL, ctrl | CTRL_RST);
|
||||
|
||||
// Post-reset settling delay (give hardware time before polling)
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
// Poll for reset completion
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
if (!(ReadReg(REG_CTRL) & CTRL_RST)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Release semaphore
|
||||
ReleaseSwFwSync();
|
||||
|
||||
// 5. Disable interrupts again after reset
|
||||
WriteReg(REG_IMC, 0xFFFFFFFF);
|
||||
ReadReg(REG_ICR);
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "Reset complete";
|
||||
|
||||
// Set link up: SLU on, but let auto-negotiation decide speed/duplex
|
||||
ctrl = ReadReg(REG_CTRL);
|
||||
ctrl |= CTRL_SLU;
|
||||
ctrl &= ~CTRL_FRCSPD;
|
||||
ctrl &= ~CTRL_FRCDPLX;
|
||||
ctrl &= ~(1u << 3); // Clear LRST
|
||||
ctrl &= ~(1u << 31); // Clear PHY_RST
|
||||
ctrl &= ~(1u << 7); // Clear ILOS
|
||||
WriteReg(REG_CTRL, ctrl);
|
||||
|
||||
// Initialize PHY
|
||||
InitPhy();
|
||||
|
||||
// Read MAC address
|
||||
ReadMacAddress();
|
||||
|
||||
KernelLogStream(OK, "E1000E") << "MAC: "
|
||||
<< base::hex
|
||||
<< (uint64_t)g_macAddress[0] << ":"
|
||||
<< (uint64_t)g_macAddress[1] << ":"
|
||||
<< (uint64_t)g_macAddress[2] << ":"
|
||||
<< (uint64_t)g_macAddress[3] << ":"
|
||||
<< (uint64_t)g_macAddress[4] << ":"
|
||||
<< (uint64_t)g_macAddress[5];
|
||||
|
||||
// Zero out the Multicast Table Array (128 entries)
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
WriteReg(REG_MTA + (i * 4), 0);
|
||||
}
|
||||
|
||||
// Set up RX and TX descriptor rings
|
||||
SetupRx();
|
||||
SetupTx();
|
||||
|
||||
// Three-tier interrupt strategy: MSI → legacy IRQ → polling
|
||||
if (SetupMsi(foundDev->Bus, foundDev->Device, foundDev->Function)) {
|
||||
// MSI configured — enable NIC interrupt causes
|
||||
WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0);
|
||||
} else if (g_irqLine != 0xFF) {
|
||||
// Legacy IRQ fallback
|
||||
KernelLogStream(INFO, "E1000E") << "Falling back to legacy IRQ " << base::dec << (uint64_t)g_irqLine;
|
||||
Hal::RegisterIrqHandler(g_irqLine, HandleInterrupt);
|
||||
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(g_irqLine));
|
||||
WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0);
|
||||
} else {
|
||||
// Polling as last resort
|
||||
KernelLogStream(WARNING, "E1000E") << "No MSI or legacy IRQ available, using polling mode";
|
||||
g_pollingMode = true;
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
uint32_t status = ReadReg(REG_STATUS);
|
||||
bool linkUp = (status & (1 << 1)) != 0;
|
||||
KernelLogStream(OK, "E1000E") << "Initialization complete, link: " << (linkUp ? "UP" : "DOWN");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Polling: process received packets (with reentrancy guard)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool g_polling = false;
|
||||
|
||||
static void PollRx() {
|
||||
// Guard against reentrancy: RX callback can trigger ARP reply →
|
||||
// Ethernet::Send → SendPacket → PollRx(). Two concurrent callers
|
||||
// modifying g_rxTail / RDT would corrupt the descriptor ring.
|
||||
if (g_polling) {
|
||||
return;
|
||||
}
|
||||
g_polling = true;
|
||||
|
||||
while (true) {
|
||||
uint32_t nextIdx = (g_rxTail + 1) % RX_DESC_COUNT;
|
||||
RxDescriptor& desc = g_rxDescs[nextIdx];
|
||||
|
||||
if (!(desc.Status & RXSTA_DD)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t length = desc.Length;
|
||||
g_rxPacketCount++;
|
||||
|
||||
if (g_rxCallback != nullptr) {
|
||||
g_rxCallback(g_rxBuffers[nextIdx], length);
|
||||
}
|
||||
|
||||
desc.Status = 0;
|
||||
desc.Length = 0;
|
||||
desc.Errors = 0;
|
||||
|
||||
g_rxTail = nextIdx;
|
||||
WriteReg(REG_RDT, g_rxTail);
|
||||
}
|
||||
|
||||
g_polling = false;
|
||||
}
|
||||
|
||||
bool SendPacket(const uint8_t* data, uint16_t length) {
|
||||
if (!g_initialized || data == nullptr || length == 0 || length > 1518) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TxDescriptor& desc = g_txDescs[g_txTail];
|
||||
if (!(desc.Status & TXSTA_DD)) {
|
||||
KernelLogStream(WARNING, "E1000E") << "TX ring full";
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(g_txBuffers[g_txTail], data, length);
|
||||
|
||||
desc.BufferAddress = g_txBuffersPhys[g_txTail];
|
||||
desc.Length = length;
|
||||
desc.Command = TXCMD_EOP | TXCMD_IFCS | TXCMD_RS;
|
||||
desc.Status = 0;
|
||||
|
||||
g_txTail = (g_txTail + 1) % TX_DESC_COUNT;
|
||||
WriteReg(REG_TDT, g_txTail);
|
||||
|
||||
g_txPacketCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* GetMacAddress() {
|
||||
return g_macAddress;
|
||||
}
|
||||
|
||||
bool IsInitialized() {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
void SetRxCallback(RxCallback callback) {
|
||||
g_rxCallback = callback;
|
||||
}
|
||||
|
||||
void Poll() {
|
||||
if (!g_initialized) {
|
||||
return;
|
||||
}
|
||||
PollRx();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* E1000E.hpp
|
||||
* Intel I217/I218/I219 (E1000E) Ethernet driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::Net::E1000E {
|
||||
|
||||
// E1000E register offsets (memory-mapped via BAR0)
|
||||
constexpr uint32_t REG_CTRL = 0x0000; // Device Control
|
||||
constexpr uint32_t REG_STATUS = 0x0008; // Device Status
|
||||
constexpr uint32_t REG_EERD = 0x0014; // EEPROM Read
|
||||
constexpr uint32_t REG_CTRL_EXT = 0x0018; // Extended Device Control
|
||||
constexpr uint32_t REG_MDIC = 0x0020; // MDI Control (PHY access)
|
||||
constexpr uint32_t REG_ICR = 0x00C0; // Interrupt Cause Read
|
||||
constexpr uint32_t REG_IMS = 0x00D0; // Interrupt Mask Set
|
||||
constexpr uint32_t REG_IMC = 0x00D8; // Interrupt Mask Clear
|
||||
constexpr uint32_t REG_RCTL = 0x0100; // Receive Control
|
||||
constexpr uint32_t REG_TCTL = 0x0400; // Transmit Control
|
||||
constexpr uint32_t REG_TIPG = 0x0410; // Transmit IPG
|
||||
constexpr uint32_t REG_RDBAL = 0x2800; // RX Descriptor Base Low
|
||||
constexpr uint32_t REG_RDBAH = 0x2804; // RX Descriptor Base High
|
||||
constexpr uint32_t REG_RDLEN = 0x2808; // RX Descriptor Length
|
||||
constexpr uint32_t REG_RDH = 0x2810; // RX Descriptor Head
|
||||
constexpr uint32_t REG_RDT = 0x2818; // RX Descriptor Tail
|
||||
constexpr uint32_t REG_TDBAL = 0x3800; // TX Descriptor Base Low
|
||||
constexpr uint32_t REG_TDBAH = 0x3804; // TX Descriptor Base High
|
||||
constexpr uint32_t REG_TDLEN = 0x3808; // TX Descriptor Length
|
||||
constexpr uint32_t REG_TDH = 0x3810; // TX Descriptor Head
|
||||
constexpr uint32_t REG_TDT = 0x3818; // TX Descriptor Tail
|
||||
constexpr uint32_t REG_MTA = 0x5200; // Multicast Table Array (128 entries)
|
||||
constexpr uint32_t REG_RAL = 0x5400; // Receive Address Low
|
||||
constexpr uint32_t REG_RAH = 0x5404; // Receive Address High
|
||||
constexpr uint32_t REG_EXTCNF_CTRL = 0x0F00; // Extended Configuration Control
|
||||
constexpr uint32_t REG_SWSM = 0x5B50; // Software Semaphore
|
||||
constexpr uint32_t REG_FWSM = 0x5B54; // Firmware Semaphore
|
||||
|
||||
// CTRL register bits
|
||||
constexpr uint32_t CTRL_SLU = (1 << 6); // Set Link Up
|
||||
constexpr uint32_t CTRL_FRCSPD = (1 << 11); // Force Speed
|
||||
constexpr uint32_t CTRL_FRCDPLX = (1 << 12); // Force Duplex
|
||||
constexpr uint32_t CTRL_RST = (1 << 26); // Device Reset
|
||||
|
||||
// MDIC register
|
||||
constexpr uint32_t MDIC_DATA_MASK = 0x0000FFFF;
|
||||
constexpr uint32_t MDIC_REG_SHIFT = 16; // PHY register address shift
|
||||
constexpr uint32_t MDIC_PHY_SHIFT = 21; // PHY address shift
|
||||
constexpr uint32_t MDIC_OP_READ = (2u << 26);
|
||||
constexpr uint32_t MDIC_OP_WRITE = (1u << 26);
|
||||
constexpr uint32_t MDIC_READY = (1u << 28);
|
||||
constexpr uint32_t MDIC_ERROR = (1u << 30);
|
||||
|
||||
// PHY register addresses
|
||||
constexpr uint32_t PHY_CONTROL = 0x00; // PHY Control Register
|
||||
constexpr uint32_t PHY_STATUS = 0x01; // PHY Status Register
|
||||
constexpr uint32_t PHY_AUTONEG_ADV = 0x04; // Auto-Negotiation Advertisement
|
||||
constexpr uint32_t PHY_1000T_CTRL = 0x09; // 1000BASE-T Control
|
||||
|
||||
// PHY Control register bits
|
||||
constexpr uint16_t PHY_CTRL_RESET = (1 << 15);
|
||||
constexpr uint16_t PHY_CTRL_AUTONEG_EN = (1 << 12);
|
||||
constexpr uint16_t PHY_CTRL_RESTART_AN = (1 << 9);
|
||||
|
||||
// Semaphore bits
|
||||
constexpr uint32_t SWSM_SMBI = (1 << 0);
|
||||
constexpr uint32_t SWSM_SWESMBI = (1 << 1);
|
||||
constexpr uint32_t EXTCNF_CTRL_SWFLAG = (1 << 5);
|
||||
|
||||
// RCTL register bits
|
||||
constexpr uint32_t RCTL_EN = (1 << 1); // Receiver Enable
|
||||
constexpr uint32_t RCTL_SBP = (1 << 2); // Store Bad Packets
|
||||
constexpr uint32_t RCTL_UPE = (1 << 3); // Unicast Promiscuous
|
||||
constexpr uint32_t RCTL_MPE = (1 << 4); // Multicast Promiscuous
|
||||
constexpr uint32_t RCTL_BAM = (1 << 15); // Broadcast Accept Mode
|
||||
constexpr uint32_t RCTL_BSIZE_4096 = (3 << 16); // Buffer Size 4096 (with BSEX)
|
||||
constexpr uint32_t RCTL_BSEX = (1 << 25); // Buffer Size Extension
|
||||
constexpr uint32_t RCTL_SECRC = (1 << 26); // Strip Ethernet CRC
|
||||
|
||||
// TCTL register bits
|
||||
constexpr uint32_t TCTL_EN = (1 << 1); // Transmit Enable
|
||||
constexpr uint32_t TCTL_PSP = (1 << 3); // Pad Short Packets
|
||||
constexpr uint32_t TCTL_CT_SHIFT = 4; // Collision Threshold shift
|
||||
constexpr uint32_t TCTL_COLD_SHIFT = 12; // Collision Distance shift
|
||||
|
||||
// ICR (interrupt cause) bits
|
||||
constexpr uint32_t ICR_TXDW = (1 << 0); // TX Descriptor Written Back
|
||||
constexpr uint32_t ICR_TXQE = (1 << 1); // TX Queue Empty
|
||||
constexpr uint32_t ICR_LSC = (1 << 2); // Link Status Change
|
||||
constexpr uint32_t ICR_RXDMT0 = (1 << 4); // RX Descriptor Minimum Threshold
|
||||
constexpr uint32_t ICR_RXO = (1 << 6); // Receiver Overrun
|
||||
constexpr uint32_t ICR_RXT0 = (1 << 7); // Receiver Timer Interrupt
|
||||
|
||||
// TX descriptor command bits
|
||||
constexpr uint8_t TXCMD_EOP = (1 << 0); // End Of Packet
|
||||
constexpr uint8_t TXCMD_IFCS = (1 << 1); // Insert FCS/CRC
|
||||
constexpr uint8_t TXCMD_RS = (1 << 3); // Report Status
|
||||
|
||||
// TX descriptor status bits
|
||||
constexpr uint8_t TXSTA_DD = (1 << 0); // Descriptor Done
|
||||
|
||||
// RX descriptor status bits
|
||||
constexpr uint8_t RXSTA_DD = (1 << 0); // Descriptor Done
|
||||
constexpr uint8_t RXSTA_EOP = (1 << 1); // End Of Packet
|
||||
|
||||
// Descriptor ring sizes
|
||||
constexpr uint32_t RX_DESC_COUNT = 32;
|
||||
constexpr uint32_t TX_DESC_COUNT = 32;
|
||||
constexpr uint32_t PACKET_BUFFER_SIZE = 8192;
|
||||
|
||||
// RX descriptor (legacy format, 16 bytes)
|
||||
struct RxDescriptor {
|
||||
uint64_t BufferAddress;
|
||||
uint16_t Length;
|
||||
uint16_t Checksum;
|
||||
uint8_t Status;
|
||||
uint8_t Errors;
|
||||
uint16_t Special;
|
||||
} __attribute__((packed));
|
||||
|
||||
// TX descriptor (legacy format, 16 bytes)
|
||||
struct TxDescriptor {
|
||||
uint64_t BufferAddress;
|
||||
uint16_t Length;
|
||||
uint8_t ChecksumOffset;
|
||||
uint8_t Command;
|
||||
uint8_t Status;
|
||||
uint8_t ChecksumStart;
|
||||
uint16_t Special;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Initialize the E1000E driver (scans PCI for the device)
|
||||
void Initialize();
|
||||
|
||||
// Send a raw Ethernet frame
|
||||
bool SendPacket(const uint8_t* data, uint16_t length);
|
||||
|
||||
// Get the MAC address (6 bytes)
|
||||
const uint8_t* GetMacAddress();
|
||||
|
||||
// Check if the device was found and initialized
|
||||
bool IsInitialized();
|
||||
|
||||
// RX callback type: called with (packet data, length)
|
||||
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
|
||||
|
||||
// Register a callback for received packets
|
||||
void SetRxCallback(RxCallback callback);
|
||||
|
||||
// Poll for received packets (used when legacy IRQ is unavailable)
|
||||
void Poll();
|
||||
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
@@ -105,6 +106,8 @@ namespace Fs::Ramdisk {
|
||||
|
||||
entry.isDirectory = (typeFlag == '5');
|
||||
entry.size = size;
|
||||
entry.capacity = size;
|
||||
entry.heapAllocated = false;
|
||||
|
||||
// Data starts at next 512-byte block
|
||||
entry.data = ptr + 512;
|
||||
@@ -235,6 +238,110 @@ namespace Fs::Ramdisk {
|
||||
return count;
|
||||
}
|
||||
|
||||
int Write(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= fileCount) return -1;
|
||||
if (buffer == nullptr || size == 0) return 0;
|
||||
|
||||
FileEntry& entry = fileTable[handle];
|
||||
if (entry.isDirectory) return -1;
|
||||
|
||||
uint64_t endOffset = offset + size;
|
||||
|
||||
// Copy-on-write: if data points into tar memory, copy to heap
|
||||
if (!entry.heapAllocated) {
|
||||
uint64_t newCap = entry.size;
|
||||
if (endOffset > newCap) newCap = endOffset;
|
||||
if (newCap < 256) newCap = 256;
|
||||
// Round up to next power of 2 for growth
|
||||
uint64_t rounded = 256;
|
||||
while (rounded < newCap) rounded *= 2;
|
||||
newCap = rounded;
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||
if (newBuf == nullptr) return -1;
|
||||
|
||||
if (entry.data && entry.size > 0) {
|
||||
memcpy(newBuf, entry.data, entry.size);
|
||||
}
|
||||
|
||||
entry.data = newBuf;
|
||||
entry.capacity = newCap;
|
||||
entry.heapAllocated = true;
|
||||
}
|
||||
|
||||
// Grow buffer if needed
|
||||
if (endOffset > entry.capacity) {
|
||||
uint64_t newCap = entry.capacity;
|
||||
while (newCap < endOffset) newCap *= 2;
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||
if (newBuf == nullptr) return -1;
|
||||
|
||||
if (entry.data && entry.size > 0) {
|
||||
memcpy(newBuf, entry.data, entry.size);
|
||||
}
|
||||
Memory::g_heap->Free(entry.data);
|
||||
|
||||
entry.data = newBuf;
|
||||
entry.capacity = newCap;
|
||||
}
|
||||
|
||||
memcpy(entry.data + offset, buffer, size);
|
||||
|
||||
if (endOffset > entry.size) {
|
||||
entry.size = endOffset;
|
||||
}
|
||||
|
||||
return (int)size;
|
||||
}
|
||||
|
||||
int Create(const char* path) {
|
||||
if (path == nullptr) return -1;
|
||||
if (fileCount >= MaxFiles) return -1;
|
||||
|
||||
// Normalize: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
// Check if file already exists
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
if (StrEqual(fileTable[i].name, path)) {
|
||||
// File exists — truncate it
|
||||
FileEntry& entry = fileTable[i];
|
||||
if (!entry.heapAllocated) {
|
||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(256);
|
||||
if (newBuf == nullptr) return -1;
|
||||
entry.data = newBuf;
|
||||
entry.capacity = 256;
|
||||
entry.heapAllocated = true;
|
||||
}
|
||||
entry.size = 0;
|
||||
entry.isDirectory = false;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new file entry
|
||||
FileEntry& entry = fileTable[fileCount];
|
||||
|
||||
int nameLen = 0;
|
||||
while (nameLen < MaxNameLen - 1 && path[nameLen] != '\0') {
|
||||
entry.name[nameLen] = path[nameLen];
|
||||
nameLen++;
|
||||
}
|
||||
entry.name[nameLen] = '\0';
|
||||
|
||||
uint8_t* buf = (uint8_t*)Memory::g_heap->Request(256);
|
||||
if (buf == nullptr) return -1;
|
||||
|
||||
entry.data = buf;
|
||||
entry.size = 0;
|
||||
entry.capacity = 256;
|
||||
entry.isDirectory = false;
|
||||
entry.heapAllocated = true;
|
||||
|
||||
return fileCount++;
|
||||
}
|
||||
|
||||
int GetFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,17 @@ namespace Fs::Ramdisk {
|
||||
char name[MaxNameLen];
|
||||
uint8_t* data;
|
||||
uint64_t size;
|
||||
uint64_t capacity;
|
||||
bool isDirectory;
|
||||
bool heapAllocated;
|
||||
};
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize);
|
||||
|
||||
int Open(const char* path);
|
||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int Write(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int Create(const char* path);
|
||||
uint64_t GetSize(int handle);
|
||||
void Close(int handle);
|
||||
|
||||
|
||||
@@ -123,6 +123,46 @@ namespace Fs::Vfs {
|
||||
entry.inUse = false;
|
||||
}
|
||||
|
||||
int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return -1;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
if (driveTable[entry.driveNumber]->Write == nullptr) return -1;
|
||||
return driveTable[entry.driveNumber]->Write(entry.localHandle, buffer, offset, size);
|
||||
}
|
||||
|
||||
int VfsCreate(const char* path) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format for Create";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (driveTable[drive]->Create == nullptr) return -1;
|
||||
|
||||
int localHandle = driveTable[drive]->Create(localPath);
|
||||
if (localHandle < 0) return -1;
|
||||
|
||||
int globalHandle = AllocHandle();
|
||||
if (globalHandle < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "No free handles";
|
||||
return -1;
|
||||
}
|
||||
|
||||
handleTable[globalHandle].inUse = true;
|
||||
handleTable[globalHandle].driveNumber = drive;
|
||||
handleTable[globalHandle].localHandle = localHandle;
|
||||
|
||||
return globalHandle;
|
||||
}
|
||||
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Fs::Vfs {
|
||||
uint64_t (*GetSize)(int handle);
|
||||
void (*Close)(int handle);
|
||||
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
|
||||
int (*Write)(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int (*Create)(const char* path);
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
@@ -26,6 +28,8 @@ namespace Fs::Vfs {
|
||||
|
||||
int VfsOpen(const char* path);
|
||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int VfsWrite(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
int VfsCreate(const char* path);
|
||||
uint64_t VfsGetSize(int handle);
|
||||
void VfsClose(int handle);
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
// IRQ handler function type. The parameter is the IRQ number (0-23).
|
||||
// IRQ handler function type. The parameter is the IRQ number (0-47).
|
||||
using IrqHandler = void(*)(uint8_t irq);
|
||||
|
||||
// Number of IRQ lines supported (IOAPIC inputs)
|
||||
constexpr int IRQ_COUNT = 24;
|
||||
// Number of IRQ slots supported (0-23: legacy ISA via IOAPIC, 24-47: MSI)
|
||||
constexpr int IRQ_COUNT = 48;
|
||||
|
||||
// IRQ vector base: hardware IRQs start at IDT vector 32
|
||||
constexpr uint8_t IRQ_VECTOR_BASE = 32;
|
||||
@@ -29,7 +29,7 @@ namespace Hal {
|
||||
constexpr uint8_t IRQ_ATA1 = 14;
|
||||
constexpr uint8_t IRQ_ATA2 = 15;
|
||||
|
||||
// Register a handler for the given IRQ number (0-23)
|
||||
// Register a handler for the given IRQ number (0-47)
|
||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler);
|
||||
|
||||
// Install IRQ stubs into the IDT and set up the dispatch table
|
||||
|
||||
@@ -65,8 +65,8 @@ IrqCommon:
|
||||
|
||||
iretq
|
||||
|
||||
; Define stubs for IRQs 0..23 (vectors 32..55)
|
||||
; This covers all standard ISA IRQs plus some extra IOAPIC inputs
|
||||
; Define stubs for IRQs 0..47 (vectors 32..79)
|
||||
; 0-23: legacy ISA IRQs via IOAPIC, 24-47: MSI vectors
|
||||
IRQ_STUB 0
|
||||
IRQ_STUB 1
|
||||
IRQ_STUB 2
|
||||
@@ -91,6 +91,30 @@ IRQ_STUB 20
|
||||
IRQ_STUB 21
|
||||
IRQ_STUB 22
|
||||
IRQ_STUB 23
|
||||
IRQ_STUB 24
|
||||
IRQ_STUB 25
|
||||
IRQ_STUB 26
|
||||
IRQ_STUB 27
|
||||
IRQ_STUB 28
|
||||
IRQ_STUB 29
|
||||
IRQ_STUB 30
|
||||
IRQ_STUB 31
|
||||
IRQ_STUB 32
|
||||
IRQ_STUB 33
|
||||
IRQ_STUB 34
|
||||
IRQ_STUB 35
|
||||
IRQ_STUB 36
|
||||
IRQ_STUB 37
|
||||
IRQ_STUB 38
|
||||
IRQ_STUB 39
|
||||
IRQ_STUB 40
|
||||
IRQ_STUB 41
|
||||
IRQ_STUB 42
|
||||
IRQ_STUB 43
|
||||
IRQ_STUB 44
|
||||
IRQ_STUB 45
|
||||
IRQ_STUB 46
|
||||
IRQ_STUB 47
|
||||
|
||||
; Spurious interrupt handler (vector 0xFF) - do nothing, no EOI
|
||||
global IrqStubSpurious
|
||||
@@ -125,3 +149,27 @@ IrqStubTable:
|
||||
dq IrqStub21
|
||||
dq IrqStub22
|
||||
dq IrqStub23
|
||||
dq IrqStub24
|
||||
dq IrqStub25
|
||||
dq IrqStub26
|
||||
dq IrqStub27
|
||||
dq IrqStub28
|
||||
dq IrqStub29
|
||||
dq IrqStub30
|
||||
dq IrqStub31
|
||||
dq IrqStub32
|
||||
dq IrqStub33
|
||||
dq IrqStub34
|
||||
dq IrqStub35
|
||||
dq IrqStub36
|
||||
dq IrqStub37
|
||||
dq IrqStub38
|
||||
dq IrqStub39
|
||||
dq IrqStub40
|
||||
dq IrqStub41
|
||||
dq IrqStub42
|
||||
dq IrqStub43
|
||||
dq IrqStub44
|
||||
dq IrqStub45
|
||||
dq IrqStub46
|
||||
dq IrqStub47
|
||||
|
||||
+9
-2
@@ -34,6 +34,7 @@
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Drivers/PS2/Mouse.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Net/Net.hpp>
|
||||
#include <CppLib/BoxUI.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
@@ -153,6 +154,10 @@ extern "C" void kmain() {
|
||||
Drivers::PS2::Mouse::Initialize();
|
||||
|
||||
Drivers::Net::E1000::Initialize();
|
||||
if (!Drivers::Net::E1000::IsInitialized()) {
|
||||
KernelLogStream(INFO, "Init") << "E1000 not found, trying E1000E...";
|
||||
Drivers::Net::E1000E::Initialize();
|
||||
}
|
||||
Net::Initialize();
|
||||
}
|
||||
#endif
|
||||
@@ -188,7 +193,9 @@ extern "C" void kmain() {
|
||||
Fs::Ramdisk::Read,
|
||||
Fs::Ramdisk::GetSize,
|
||||
Fs::Ramdisk::Close,
|
||||
Fs::Ramdisk::ReadDir
|
||||
Fs::Ramdisk::ReadDir,
|
||||
Fs::Ramdisk::Write,
|
||||
Fs::Ramdisk::Create
|
||||
};
|
||||
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
|
||||
|
||||
@@ -198,7 +205,7 @@ extern "C" void kmain() {
|
||||
Zenith::InitializeSyscalls();
|
||||
|
||||
Sched::Initialize();
|
||||
Sched::Spawn("0:/shell.elf");
|
||||
Sched::Spawn("0:/os/init.elf");
|
||||
|
||||
// Enable preemptive scheduling via the APIC timer
|
||||
Timekeeping::EnableSchedulerTick();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Net/Ipv4.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -19,6 +20,12 @@ using namespace Kt;
|
||||
|
||||
namespace Net::Arp {
|
||||
|
||||
static const uint8_t* GetActiveNicMac() {
|
||||
if (Drivers::Net::E1000::IsInitialized())
|
||||
return Drivers::Net::E1000::GetMacAddress();
|
||||
return Drivers::Net::E1000E::GetMacAddress();
|
||||
}
|
||||
|
||||
// ARP cache entry
|
||||
struct CacheEntry {
|
||||
uint32_t Ip;
|
||||
@@ -107,7 +114,7 @@ namespace Net::Arp {
|
||||
reply.ProtocolAddrLen = 4;
|
||||
reply.Operation = Htons(OP_REPLY);
|
||||
|
||||
memcpy(reply.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
||||
memcpy(reply.SenderMac, GetActiveNicMac(), 6);
|
||||
reply.SenderIp = GetIpAddress();
|
||||
memcpy(reply.TargetMac, pkt->SenderMac, 6);
|
||||
reply.TargetIp = senderIp;
|
||||
@@ -141,7 +148,7 @@ namespace Net::Arp {
|
||||
req.ProtocolAddrLen = 4;
|
||||
req.Operation = Htons(OP_REQUEST);
|
||||
|
||||
memcpy(req.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
||||
memcpy(req.SenderMac, GetActiveNicMac(), 6);
|
||||
req.SenderIp = GetIpAddress();
|
||||
memset(req.TargetMac, 0, 6);
|
||||
req.TargetIp = targetIp;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <Net/Arp.hpp>
|
||||
#include <Net/Ipv4.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -17,6 +18,18 @@ using namespace Kt;
|
||||
|
||||
namespace Net::Ethernet {
|
||||
|
||||
static const uint8_t* GetActiveNicMac() {
|
||||
if (Drivers::Net::E1000::IsInitialized())
|
||||
return Drivers::Net::E1000::GetMacAddress();
|
||||
return Drivers::Net::E1000E::GetMacAddress();
|
||||
}
|
||||
|
||||
static bool ActiveNicSend(const uint8_t* data, uint16_t length) {
|
||||
if (Drivers::Net::E1000::IsInitialized())
|
||||
return Drivers::Net::E1000::SendPacket(data, length);
|
||||
return Drivers::Net::E1000E::SendPacket(data, length);
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
KernelLogStream(OK, "Net") << "Ethernet layer initialized";
|
||||
}
|
||||
@@ -30,14 +43,14 @@ namespace Net::Ethernet {
|
||||
Header* hdr = (Header*)frame;
|
||||
|
||||
memcpy(hdr->DestMac, destMac, 6);
|
||||
memcpy(hdr->SrcMac, Drivers::Net::E1000::GetMacAddress(), 6);
|
||||
memcpy(hdr->SrcMac, GetActiveNicMac(), 6);
|
||||
hdr->EtherType = Htons(etherType);
|
||||
|
||||
memcpy(frame + HEADER_SIZE, payload, payloadLen);
|
||||
|
||||
uint16_t totalLen = HEADER_SIZE + payloadLen;
|
||||
|
||||
return Drivers::Net::E1000::SendPacket(frame, totalLen);
|
||||
return ActiveNicSend(frame, totalLen);
|
||||
}
|
||||
|
||||
void OnFrameReceived(const uint8_t* data, uint16_t length) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <Net/Socket.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
@@ -22,8 +23,8 @@ using namespace Kt;
|
||||
namespace Net {
|
||||
|
||||
void Initialize() {
|
||||
if (!Drivers::Net::E1000::IsInitialized()) {
|
||||
KernelLogStream(WARNING, "Net") << "E1000 not initialized, skipping network stack";
|
||||
if (!Drivers::Net::E1000::IsInitialized() && !Drivers::Net::E1000E::IsInitialized()) {
|
||||
KernelLogStream(WARNING, "Net") << "No NIC initialized, skipping network stack";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +37,12 @@ namespace Net {
|
||||
Tcp::Initialize();
|
||||
Socket::Initialize();
|
||||
|
||||
// Hook E1000 RX to our Ethernet dispatcher
|
||||
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
|
||||
// Hook the active NIC's RX to our Ethernet dispatcher
|
||||
if (Drivers::Net::E1000::IsInitialized()) {
|
||||
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
|
||||
} else {
|
||||
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
|
||||
}
|
||||
|
||||
// Send a gratuitous ARP to announce ourselves on the network
|
||||
Arp::SendRequest(GetIpAddress());
|
||||
|
||||
+185
-2
@@ -1,19 +1,42 @@
|
||||
/*
|
||||
* Socket.cpp
|
||||
* Socket descriptor table wrapping kernel TCP layer
|
||||
* Socket descriptor table wrapping kernel TCP and UDP layers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Socket.hpp"
|
||||
#include <Net/Tcp.hpp>
|
||||
#include <Net/Udp.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Net::Socket {
|
||||
|
||||
// ---- UDP socket state ----
|
||||
|
||||
static constexpr uint32_t UDP_RING_SIZE = 4096;
|
||||
static constexpr int MAX_UDP_SOCKETS = 16;
|
||||
|
||||
struct UdpDgramHeader {
|
||||
uint32_t SrcIp;
|
||||
uint16_t SrcPort;
|
||||
uint16_t DataLen;
|
||||
};
|
||||
|
||||
struct UdpSocketState {
|
||||
uint8_t Ring[UDP_RING_SIZE];
|
||||
uint32_t Head;
|
||||
uint32_t Tail;
|
||||
uint32_t Count;
|
||||
uint16_t LocalPort;
|
||||
bool Active;
|
||||
};
|
||||
|
||||
static UdpSocketState g_udpSockets[MAX_UDP_SOCKETS] = {};
|
||||
static SocketEntry g_sockets[MAX_SOCKETS] = {};
|
||||
static uint16_t g_nextEphemeralPort = 49152;
|
||||
|
||||
@@ -30,15 +53,78 @@ namespace Net::Socket {
|
||||
return true;
|
||||
}
|
||||
|
||||
static UdpSocketState* AllocUdpState() {
|
||||
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
|
||||
if (!g_udpSockets[i].Active) {
|
||||
g_udpSockets[i].Active = true;
|
||||
g_udpSockets[i].Head = 0;
|
||||
g_udpSockets[i].Tail = 0;
|
||||
g_udpSockets[i].Count = 0;
|
||||
g_udpSockets[i].LocalPort = 0;
|
||||
return &g_udpSockets[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void FreeUdpState(UdpSocketState* state) {
|
||||
if (state) {
|
||||
if (state->LocalPort != 0) {
|
||||
Udp::Unbind(state->LocalPort);
|
||||
}
|
||||
state->Active = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void UdpSocketDispatcher(uint32_t srcIp, uint16_t srcPort,
|
||||
uint16_t dstPort,
|
||||
const uint8_t* data, uint16_t length) {
|
||||
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
|
||||
if (g_udpSockets[i].Active && g_udpSockets[i].LocalPort == dstPort) {
|
||||
UdpSocketState* st = &g_udpSockets[i];
|
||||
uint32_t needed = sizeof(UdpDgramHeader) + length;
|
||||
if (st->Count + needed > UDP_RING_SIZE) {
|
||||
return; // drop if buffer full
|
||||
}
|
||||
|
||||
// Enqueue header
|
||||
UdpDgramHeader hdr;
|
||||
hdr.SrcIp = srcIp;
|
||||
hdr.SrcPort = srcPort;
|
||||
hdr.DataLen = length;
|
||||
|
||||
const uint8_t* hdrBytes = (const uint8_t*)&hdr;
|
||||
for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) {
|
||||
st->Ring[st->Tail] = hdrBytes[j];
|
||||
st->Tail = (st->Tail + 1) % UDP_RING_SIZE;
|
||||
}
|
||||
|
||||
// Enqueue payload
|
||||
for (uint16_t j = 0; j < length; j++) {
|
||||
st->Ring[st->Tail] = data[j];
|
||||
st->Tail = (st->Tail + 1) % UDP_RING_SIZE;
|
||||
}
|
||||
|
||||
st->Count += needed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
|
||||
void Initialize() {
|
||||
for (int i = 0; i < MAX_SOCKETS; i++) {
|
||||
g_sockets[i].Active = false;
|
||||
}
|
||||
for (int i = 0; i < MAX_UDP_SOCKETS; i++) {
|
||||
g_udpSockets[i].Active = false;
|
||||
}
|
||||
KernelLogStream(OK, "Net") << "Socket table initialized";
|
||||
}
|
||||
|
||||
int Create(int type, int pid) {
|
||||
if (type != SOCK_TCP) return -1;
|
||||
if (type != SOCK_TCP && type != SOCK_UDP) return -1;
|
||||
|
||||
for (int i = 0; i < MAX_SOCKETS; i++) {
|
||||
if (!g_sockets[i].Active) {
|
||||
@@ -46,7 +132,18 @@ namespace Net::Socket {
|
||||
g_sockets[i].Type = type;
|
||||
g_sockets[i].OwnerPid = pid;
|
||||
g_sockets[i].TcpConn = nullptr;
|
||||
g_sockets[i].UdpState = nullptr;
|
||||
g_sockets[i].LocalPort = 0;
|
||||
|
||||
if (type == SOCK_UDP) {
|
||||
UdpSocketState* us = AllocUdpState();
|
||||
if (!us) {
|
||||
g_sockets[i].Active = false;
|
||||
return -1;
|
||||
}
|
||||
g_sockets[i].UdpState = us;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +152,7 @@ namespace Net::Socket {
|
||||
|
||||
int Connect(int fd, uint32_t ip, uint16_t port, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
||||
|
||||
uint16_t srcPort = AllocEphemeralPort();
|
||||
@@ -70,11 +168,20 @@ namespace Net::Socket {
|
||||
int Bind(int fd, uint16_t port, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
g_sockets[fd].LocalPort = port;
|
||||
|
||||
if (g_sockets[fd].Type == SOCK_UDP) {
|
||||
UdpSocketState* us = g_sockets[fd].UdpState;
|
||||
if (!us) return -1;
|
||||
us->LocalPort = port;
|
||||
if (!Udp::Bind(port, UdpSocketDispatcher)) return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Listen(int fd, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||
if (g_sockets[fd].LocalPort == 0) return -1;
|
||||
if (g_sockets[fd].TcpConn != nullptr) return -1;
|
||||
|
||||
@@ -87,6 +194,7 @@ namespace Net::Socket {
|
||||
|
||||
int Accept(int fd, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||
|
||||
Tcp::Connection* clientConn = Tcp::Accept(g_sockets[fd].TcpConn);
|
||||
@@ -99,6 +207,7 @@ namespace Net::Socket {
|
||||
g_sockets[i].Type = SOCK_TCP;
|
||||
g_sockets[i].OwnerPid = pid;
|
||||
g_sockets[i].TcpConn = clientConn;
|
||||
g_sockets[i].UdpState = nullptr;
|
||||
g_sockets[i].LocalPort = g_sockets[fd].LocalPort;
|
||||
return i;
|
||||
}
|
||||
@@ -111,22 +220,92 @@ namespace Net::Socket {
|
||||
|
||||
int Send(int fd, const uint8_t* data, uint32_t len, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||
return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len);
|
||||
}
|
||||
|
||||
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_TCP) return -1;
|
||||
if (g_sockets[fd].TcpConn == nullptr) return -1;
|
||||
return Tcp::ReceiveNonBlocking(g_sockets[fd].TcpConn, buf, (uint16_t)maxLen);
|
||||
}
|
||||
|
||||
int SendTo(int fd, const uint8_t* data, uint32_t len,
|
||||
uint32_t destIp, uint16_t destPort, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_UDP) return -1;
|
||||
|
||||
UdpSocketState* us = g_sockets[fd].UdpState;
|
||||
if (!us) return -1;
|
||||
|
||||
// Auto-bind ephemeral port if not already bound
|
||||
if (us->LocalPort == 0) {
|
||||
uint16_t ep = AllocEphemeralPort();
|
||||
us->LocalPort = ep;
|
||||
g_sockets[fd].LocalPort = ep;
|
||||
if (!Udp::Bind(ep, UdpSocketDispatcher)) return -1;
|
||||
}
|
||||
|
||||
if (!Udp::Send(destIp, us->LocalPort, destPort, data, (uint16_t)len)) {
|
||||
return -1;
|
||||
}
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
|
||||
uint32_t* srcIp, uint16_t* srcPort, int pid) {
|
||||
if (!ValidFd(fd, pid)) return -1;
|
||||
if (g_sockets[fd].Type != SOCK_UDP) return -1;
|
||||
|
||||
UdpSocketState* us = g_sockets[fd].UdpState;
|
||||
if (!us) return -1;
|
||||
|
||||
if (us->Count < sizeof(UdpDgramHeader)) {
|
||||
return -1; // no data available
|
||||
}
|
||||
|
||||
// Dequeue header
|
||||
UdpDgramHeader hdr;
|
||||
uint8_t* hdrBytes = (uint8_t*)&hdr;
|
||||
for (uint32_t j = 0; j < sizeof(UdpDgramHeader); j++) {
|
||||
hdrBytes[j] = us->Ring[us->Head];
|
||||
us->Head = (us->Head + 1) % UDP_RING_SIZE;
|
||||
}
|
||||
us->Count -= sizeof(UdpDgramHeader);
|
||||
|
||||
// Dequeue payload
|
||||
uint16_t copyLen = hdr.DataLen;
|
||||
if (copyLen > maxLen) copyLen = (uint16_t)maxLen;
|
||||
|
||||
for (uint16_t j = 0; j < copyLen; j++) {
|
||||
buf[j] = us->Ring[us->Head];
|
||||
us->Head = (us->Head + 1) % UDP_RING_SIZE;
|
||||
}
|
||||
|
||||
// Skip remaining data if buffer was too small
|
||||
for (uint16_t j = copyLen; j < hdr.DataLen; j++) {
|
||||
us->Head = (us->Head + 1) % UDP_RING_SIZE;
|
||||
}
|
||||
us->Count -= hdr.DataLen;
|
||||
|
||||
if (srcIp) *srcIp = hdr.SrcIp;
|
||||
if (srcPort) *srcPort = hdr.SrcPort;
|
||||
|
||||
return (int)copyLen;
|
||||
}
|
||||
|
||||
void Close(int fd, int pid) {
|
||||
if (!ValidFd(fd, pid)) return;
|
||||
if (g_sockets[fd].TcpConn != nullptr) {
|
||||
Tcp::Close(g_sockets[fd].TcpConn);
|
||||
g_sockets[fd].TcpConn = nullptr;
|
||||
}
|
||||
if (g_sockets[fd].UdpState != nullptr) {
|
||||
FreeUdpState(g_sockets[fd].UdpState);
|
||||
g_sockets[fd].UdpState = nullptr;
|
||||
}
|
||||
g_sockets[fd].Active = false;
|
||||
}
|
||||
|
||||
@@ -137,6 +316,10 @@ namespace Net::Socket {
|
||||
Tcp::Close(g_sockets[i].TcpConn);
|
||||
g_sockets[i].TcpConn = nullptr;
|
||||
}
|
||||
if (g_sockets[i].UdpState != nullptr) {
|
||||
FreeUdpState(g_sockets[i].UdpState);
|
||||
g_sockets[i].UdpState = nullptr;
|
||||
}
|
||||
g_sockets[i].Active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Socket.hpp
|
||||
* Socket descriptor table for userspace TCP access
|
||||
* Socket descriptor table for userspace TCP/UDP access
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -14,11 +14,14 @@ namespace Net::Socket {
|
||||
static constexpr int SOCK_UDP = 2;
|
||||
static constexpr int MAX_SOCKETS = 64;
|
||||
|
||||
struct UdpSocketState;
|
||||
|
||||
struct SocketEntry {
|
||||
bool Active;
|
||||
int Type;
|
||||
int OwnerPid;
|
||||
Tcp::Connection* TcpConn;
|
||||
UdpSocketState* UdpState;
|
||||
uint16_t LocalPort;
|
||||
};
|
||||
|
||||
@@ -45,6 +48,14 @@ namespace Net::Socket {
|
||||
// Receive data from a connected socket. Returns bytes received, 0 on close, or -1.
|
||||
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid);
|
||||
|
||||
// Send a UDP datagram to a specific destination. Returns bytes sent or -1.
|
||||
int SendTo(int fd, const uint8_t* data, uint32_t len,
|
||||
uint32_t destIp, uint16_t destPort, int pid);
|
||||
|
||||
// Receive a UDP datagram, returning source info. Returns bytes received or -1.
|
||||
int RecvFrom(int fd, uint8_t* buf, uint32_t maxLen,
|
||||
uint32_t* srcIp, uint16_t* srcPort, int pid);
|
||||
|
||||
// Close a socket.
|
||||
void Close(int fd, int pid);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Net::Udp {
|
||||
// Dispatch to bound callback
|
||||
for (uint32_t i = 0; i < MAX_BINDINGS; i++) {
|
||||
if (g_bindings[i].Active && g_bindings[i].Port == dstPort) {
|
||||
g_bindings[i].Callback(srcIp, srcPort, payload, payloadLen);
|
||||
g_bindings[i].Callback(srcIp, srcPort, dstPort, payload, payloadLen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ namespace Net::Udp {
|
||||
} __attribute__((packed));
|
||||
|
||||
// Callback type for receiving UDP data
|
||||
using RecvCallback = void(*)(uint32_t srcIp, uint16_t srcPort, const uint8_t* data, uint16_t length);
|
||||
using RecvCallback = void(*)(uint32_t srcIp, uint16_t srcPort,
|
||||
uint16_t dstPort,
|
||||
const uint8_t* data, uint16_t length);
|
||||
|
||||
// Initialize the UDP subsystem
|
||||
void Initialize();
|
||||
|
||||
@@ -223,6 +223,32 @@ namespace Pci {
|
||||
return LegacyRead8(bus, device, function, (uint8_t)offset);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PCI capability list traversal
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
uint8_t FindCapability(uint8_t bus, uint8_t device, uint8_t function, uint8_t capId) {
|
||||
// Check Status register bit 4 (Capabilities List present)
|
||||
uint16_t status = ReadConfig16(bus, device, function, RegStatus);
|
||||
if (!(status & (1 << 4))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read Capabilities Pointer (offset 0x34), mask to dword-aligned
|
||||
uint8_t offset = ReadConfig8(bus, device, function, 0x34) & 0xFC;
|
||||
|
||||
// Walk the linked list (cap_id @ +0, next_ptr @ +1)
|
||||
while (offset != 0) {
|
||||
uint8_t id = ReadConfig8(bus, device, function, offset);
|
||||
if (id == capId) {
|
||||
return offset;
|
||||
}
|
||||
offset = ReadConfig8(bus, device, function, offset + 1) & 0xFC;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PCI class code names
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -60,6 +60,13 @@ namespace Pci {
|
||||
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value);
|
||||
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
|
||||
|
||||
// PCI capability IDs
|
||||
constexpr uint8_t PCI_CAP_MSI = 0x05;
|
||||
|
||||
// Walk the PCI capability linked list for a given device.
|
||||
// Returns the config-space offset of the capability, or 0 if not found.
|
||||
uint8_t FindCapability(uint8_t bus, uint8_t device, uint8_t function, uint8_t capId);
|
||||
|
||||
// Class code name lookup
|
||||
const char* GetClassName(uint8_t classCode, uint8_t subClass);
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ namespace Sched {
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||
if (handle < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ namespace Sched {
|
||||
// Load ELF into the process's address space
|
||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||
if (entry == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -204,6 +203,13 @@ namespace Sched {
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
// Reclaim terminated process slots so they can be reused
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Terminated) {
|
||||
processTable[i].state = ProcessState::Free;
|
||||
}
|
||||
}
|
||||
|
||||
int next = -1;
|
||||
int start = (currentPid >= 0) ? currentPid + 1 : 0;
|
||||
|
||||
@@ -311,9 +317,13 @@ namespace Sched {
|
||||
}
|
||||
|
||||
bool IsAlive(int pid) {
|
||||
if (pid < 0 || pid >= MaxProcesses) return false;
|
||||
return processTable[pid].state == ProcessState::Ready
|
||||
|| processTable[pid].state == ProcessState::Running;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].pid == pid) {
|
||||
return processTable[i].state == ProcessState::Ready
|
||||
|| processTable[i].state == ProcessState::Running;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -37,10 +38,14 @@ namespace Timekeeping {
|
||||
|
||||
static bool g_schedEnabled = false;
|
||||
|
||||
// Timer IRQ handler: increment tick count and drive scheduler
|
||||
// Timer IRQ handler: increment tick count, poll NIC, and drive scheduler
|
||||
static void TimerHandler(uint8_t) {
|
||||
g_tickCount = g_tickCount + 1;
|
||||
|
||||
// In polling mode, drain the NIC's RX ring from timer context
|
||||
// (equivalent to a real NIC IRQ handler, runs with interrupts disabled)
|
||||
Drivers::Net::E1000E::Poll();
|
||||
|
||||
if (g_schedEnabled) {
|
||||
Sched::Tick();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user