feat: e100e Ethernet driver, ramdisk reorganization, shell rewrite, web server/client, and more
This commit is contained in:
@@ -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();
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user