From: Tulio A M Mendes Date: Wed, 11 Feb 2026 23:33:57 +0000 (-0300) Subject: feat: E1000 NIC driver (Intel 82540EM) X-Git-Url: https://projects.tadryanom.me/sitemap.xml?a=commitdiff_plain;h=64f96ca09869611d6536fafbf4192301e5344016;p=AdrOS.git feat: E1000 NIC driver (Intel 82540EM) - include/e1000.h: register defines, TX/RX descriptor structs, public API - src/drivers/e1000.c: full E1000 driver implementation PCI BAR0 MMIO mapping (128KB at 0xC0230000), bus mastering enabled EEPROM MAC address read, TX/RX descriptor ring setup (32 entries each) DMA buffer allocation, legacy TX descriptors, interrupt handler e1000_send/e1000_recv/e1000_get_mac/e1000_link_up API - src/arch/x86/arch_platform.c: IOAPIC route IRQ 11 -> vector 43 - src/kernel/init.c: call e1000_init() after pci_init() - Tested: MAC 52:54:00:12:34:56, IRQ=11, 19/19 smoke tests pass --- diff --git a/include/e1000.h b/include/e1000.h new file mode 100644 index 0000000..0aaaee6 --- /dev/null +++ b/include/e1000.h @@ -0,0 +1,124 @@ +#ifndef E1000_H +#define E1000_H + +#include + +/* Intel 82540EM (E1000) PCI IDs */ +#define E1000_VENDOR_ID 0x8086 +#define E1000_DEVICE_ID 0x100E + +/* E1000 Register Offsets */ +#define E1000_CTRL 0x0000 /* Device Control */ +#define E1000_STATUS 0x0008 /* Device Status */ +#define E1000_EERD 0x0014 /* EEPROM Read */ +#define E1000_ICR 0x00C0 /* Interrupt Cause Read */ +#define E1000_ICS 0x00C4 /* Interrupt Cause Set */ +#define E1000_IMS 0x00C8 /* Interrupt Mask Set */ +#define E1000_IMC 0x00D0 /* Interrupt Mask Clear */ +#define E1000_RCTL 0x0100 /* Receive Control */ +#define E1000_TCTL 0x0400 /* Transmit Control */ +#define E1000_TIPG 0x0410 /* TX Inter-Packet Gap */ +#define E1000_RDBAL 0x2800 /* RX Descriptor Base Low */ +#define E1000_RDBAH 0x2804 /* RX Descriptor Base High */ +#define E1000_RDLEN 0x2808 /* RX Descriptor Length */ +#define E1000_RDH 0x2810 /* RX Descriptor Head */ +#define E1000_RDT 0x2818 /* RX Descriptor Tail */ +#define E1000_TDBAL 0x3800 /* TX Descriptor Base Low */ +#define E1000_TDBAH 0x3804 /* TX Descriptor Base High */ +#define E1000_TDLEN 0x3808 /* TX Descriptor Length */ +#define E1000_TDH 0x3810 /* TX Descriptor Head */ +#define E1000_TDT 0x3818 /* TX Descriptor Tail */ +#define E1000_MTA 0x5200 /* Multicast Table Array (128 entries) */ +#define E1000_RAL0 0x5400 /* Receive Address Low */ +#define E1000_RAH0 0x5404 /* Receive Address High */ + +/* CTRL bits */ +#define E1000_CTRL_FD (1U << 0) /* Full Duplex */ +#define E1000_CTRL_ASDE (1U << 5) /* Auto-Speed Detection Enable */ +#define E1000_CTRL_SLU (1U << 6) /* Set Link Up */ +#define E1000_CTRL_RST (1U << 26) /* Device Reset */ + +/* RCTL bits */ +#define E1000_RCTL_EN (1U << 1) /* Receiver Enable */ +#define E1000_RCTL_SBP (1U << 2) /* Store Bad Packets */ +#define E1000_RCTL_UPE (1U << 3) /* Unicast Promiscuous */ +#define E1000_RCTL_MPE (1U << 4) /* Multicast Promiscuous */ +#define E1000_RCTL_LBM (3U << 6) /* Loopback Mode */ +#define E1000_RCTL_BAM (1U << 15) /* Broadcast Accept Mode */ +#define E1000_RCTL_BSIZE_2048 (0U << 16) /* Buffer Size 2048 */ +#define E1000_RCTL_BSIZE_4096 (3U << 16 | 1U << 25) /* Buffer Size 4096 (BSEX) */ +#define E1000_RCTL_SECRC (1U << 26) /* Strip Ethernet CRC */ + +/* TCTL bits */ +#define E1000_TCTL_EN (1U << 1) /* Transmitter Enable */ +#define E1000_TCTL_PSP (1U << 3) /* Pad Short Packets */ +#define E1000_TCTL_CT_SHIFT 4 /* Collision Threshold */ +#define E1000_TCTL_COLD_SHIFT 12 /* Collision Distance */ + +/* ICR / IMS bits */ +#define E1000_ICR_TXDW (1U << 0) /* TX Descriptor Written Back */ +#define E1000_ICR_TXQE (1U << 1) /* TX Queue Empty */ +#define E1000_ICR_LSC (1U << 2) /* Link Status Change */ +#define E1000_ICR_RXDMT0 (1U << 4) /* RX Descriptor Minimum Threshold */ +#define E1000_ICR_RXO (1U << 6) /* Receiver Overrun */ +#define E1000_ICR_RXT0 (1U << 7) /* Receiver Timer Interrupt */ + +/* EERD bits */ +#define E1000_EERD_START (1U << 0) +#define E1000_EERD_DONE (1U << 4) + +/* TX command bits */ +#define E1000_TXD_CMD_EOP (1U << 0) /* End of Packet */ +#define E1000_TXD_CMD_IFCS (1U << 1) /* Insert FCS/CRC */ +#define E1000_TXD_CMD_RS (1U << 3) /* Report Status */ + +/* TX status bits */ +#define E1000_TXD_STAT_DD (1U << 0) /* Descriptor Done */ + +/* RX status bits */ +#define E1000_RXD_STAT_DD (1U << 0) /* Descriptor Done */ +#define E1000_RXD_STAT_EOP (1U << 1) /* End of Packet */ + +/* Ring sizes */ +#define E1000_NUM_TX_DESC 32 +#define E1000_NUM_RX_DESC 32 +#define E1000_RX_BUF_SIZE 2048 +#define E1000_TX_BUF_SIZE 2048 + +/* TX Descriptor (Legacy) */ +struct e1000_tx_desc { + uint64_t buffer_addr; + uint16_t length; + uint8_t cso; + uint8_t cmd; + uint8_t status; + uint8_t css; + uint16_t special; +} __attribute__((packed)); + +/* RX Descriptor */ +struct e1000_rx_desc { + uint64_t buffer_addr; + uint16_t length; + uint16_t checksum; + uint8_t status; + uint8_t errors; + uint16_t special; +} __attribute__((packed)); + +/* Initialize the E1000 NIC. Returns 0 on success, -1 on failure. */ +int e1000_init(void); + +/* Send a packet. Returns 0 on success. */ +int e1000_send(const void* data, uint16_t len); + +/* Receive a packet into buf (max buf_len bytes). Returns bytes received, or 0. */ +int e1000_recv(void* buf, uint16_t buf_len); + +/* Get the MAC address (6 bytes). */ +void e1000_get_mac(uint8_t mac[6]); + +/* Check if the NIC is initialized and link is up. */ +int e1000_link_up(void); + +#endif diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index 27f0a3f..44b3cec 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -103,6 +103,7 @@ int arch_platform_setup(const struct boot_info* bi) { * IRQ 14 (ATA primary) -> IDT vector 46 */ ioapic_route_irq(0, 32, (uint8_t)bsp_id); ioapic_route_irq(1, 33, (uint8_t)bsp_id); + ioapic_route_irq(11, 43, (uint8_t)bsp_id); /* E1000 NIC */ ioapic_route_irq(14, 46, (uint8_t)bsp_id); /* Now that IOAPIC routes are live, disable the legacy PIC. diff --git a/src/drivers/e1000.c b/src/drivers/e1000.c new file mode 100644 index 0000000..de38725 --- /dev/null +++ b/src/drivers/e1000.c @@ -0,0 +1,373 @@ +#include "e1000.h" +#include "pci.h" +#include "vmm.h" +#include "pmm.h" +#include "interrupts.h" +#include "uart_console.h" +#include "utils.h" +#include "io.h" + +#include + +/* ------------------------------------------------------------------ */ +/* Kernel VA layout for E1000 DMA buffers */ +/* 0xC0230000 .. 0xC024FFFF E1000 MMIO (128 KB = 32 pages) */ +/* 0xC0250000 TX descriptor ring (1 page) */ +/* 0xC0251000 RX descriptor ring (1 page) */ +/* 0xC0252000 .. 0xC0261FFF TX buffers (32 x 2 KB = 16 pages) */ +/* 0xC0262000 .. 0xC0271FFF RX buffers (32 x 2 KB = 16 pages) */ +/* ------------------------------------------------------------------ */ +#define E1000_MMIO_VA 0xC0230000U +#define E1000_MMIO_PAGES 32 +#define E1000_TX_DESC_VA 0xC0250000U +#define E1000_RX_DESC_VA 0xC0251000U +#define E1000_TX_BUF_VA 0xC0252000U +#define E1000_RX_BUF_VA 0xC0262000U + +static volatile uint32_t* e1000_mmio = 0; +static uint8_t e1000_mac[6]; +static int e1000_ready = 0; + +/* Physical addresses for DMA */ +static uint32_t tx_desc_phys; +static uint32_t rx_desc_phys; +static uint32_t tx_buf_phys[E1000_NUM_TX_DESC]; +static uint32_t rx_buf_phys[E1000_NUM_RX_DESC]; + +/* Ring indices */ +static volatile uint32_t tx_tail = 0; +static volatile uint32_t rx_tail = 0; + +/* Cached PCI device info */ +static uint8_t e1000_bus, e1000_slot, e1000_func; + +/* ------------------------------------------------------------------ */ +/* MMIO helpers */ +/* ------------------------------------------------------------------ */ + +static inline uint32_t e1000_read(uint32_t reg) { + return e1000_mmio[reg / 4]; +} + +static inline void e1000_write(uint32_t reg, uint32_t val) { + e1000_mmio[reg / 4] = val; +} + +/* ------------------------------------------------------------------ */ +/* EEPROM */ +/* ------------------------------------------------------------------ */ + +static uint16_t e1000_eeprom_read(uint8_t addr) { + e1000_write(E1000_EERD, ((uint32_t)addr << 8) | E1000_EERD_START); + uint32_t val; + for (int i = 0; i < 1000; i++) { + val = e1000_read(E1000_EERD); + if (val & E1000_EERD_DONE) + return (uint16_t)(val >> 16); + } + return 0; +} + +static void e1000_read_mac(void) { + uint16_t w0 = e1000_eeprom_read(0); + uint16_t w1 = e1000_eeprom_read(1); + uint16_t w2 = e1000_eeprom_read(2); + e1000_mac[0] = (uint8_t)(w0 & 0xFF); + e1000_mac[1] = (uint8_t)(w0 >> 8); + e1000_mac[2] = (uint8_t)(w1 & 0xFF); + e1000_mac[3] = (uint8_t)(w1 >> 8); + e1000_mac[4] = (uint8_t)(w2 & 0xFF); + e1000_mac[5] = (uint8_t)(w2 >> 8); +} + +/* ------------------------------------------------------------------ */ +/* DMA memory allocation */ +/* ------------------------------------------------------------------ */ + +static uint32_t alloc_dma_page(uintptr_t va) { + void* phys = pmm_alloc_page(); + if (!phys) return 0; + vmm_map_page((uint64_t)(uintptr_t)phys, (uint64_t)va, + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + memset((void*)va, 0, 4096); + return (uint32_t)(uintptr_t)phys; +} + +/* ------------------------------------------------------------------ */ +/* TX ring setup */ +/* ------------------------------------------------------------------ */ + +static int e1000_init_tx(void) { + tx_desc_phys = alloc_dma_page(E1000_TX_DESC_VA); + if (!tx_desc_phys) return -1; + + struct e1000_tx_desc* txd = (struct e1000_tx_desc*)E1000_TX_DESC_VA; + + /* Allocate TX buffers: 2 buffers per page (2048 each) */ + for (int i = 0; i < E1000_NUM_TX_DESC; i++) { + int page_idx = i / 2; + int buf_off = (i % 2) * E1000_TX_BUF_SIZE; + uintptr_t va = E1000_TX_BUF_VA + (uintptr_t)page_idx * 4096; + + if (buf_off == 0) { + /* First buffer on this page — allocate it */ + uint32_t phys = alloc_dma_page(va); + if (!phys) return -1; + tx_buf_phys[i] = phys; + } else { + tx_buf_phys[i] = tx_buf_phys[i - 1] + E1000_TX_BUF_SIZE; + } + + txd[i].buffer_addr = (uint64_t)tx_buf_phys[i]; + txd[i].cmd = 0; + txd[i].status = E1000_TXD_STAT_DD; /* Mark as done so first send works */ + } + + e1000_write(E1000_TDBAL, tx_desc_phys); + e1000_write(E1000_TDBAH, 0); + e1000_write(E1000_TDLEN, E1000_NUM_TX_DESC * (uint32_t)sizeof(struct e1000_tx_desc)); + e1000_write(E1000_TDH, 0); + e1000_write(E1000_TDT, 0); + tx_tail = 0; + + /* Enable transmitter */ + e1000_write(E1000_TCTL, E1000_TCTL_EN | E1000_TCTL_PSP | + (15U << E1000_TCTL_CT_SHIFT) | + (64U << E1000_TCTL_COLD_SHIFT)); + + /* Inter-packet gap: recommended 10, 8, 6 for copper */ + e1000_write(E1000_TIPG, 10 | (8 << 10) | (6 << 20)); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* RX ring setup */ +/* ------------------------------------------------------------------ */ + +static int e1000_init_rx(void) { + rx_desc_phys = alloc_dma_page(E1000_RX_DESC_VA); + if (!rx_desc_phys) return -1; + + struct e1000_rx_desc* rxd = (struct e1000_rx_desc*)E1000_RX_DESC_VA; + + for (int i = 0; i < E1000_NUM_RX_DESC; i++) { + int page_idx = i / 2; + int buf_off = (i % 2) * E1000_RX_BUF_SIZE; + uintptr_t va = E1000_RX_BUF_VA + (uintptr_t)page_idx * 4096; + + if (buf_off == 0) { + uint32_t phys = alloc_dma_page(va); + if (!phys) return -1; + rx_buf_phys[i] = phys; + } else { + rx_buf_phys[i] = rx_buf_phys[i - 1] + E1000_RX_BUF_SIZE; + } + + rxd[i].buffer_addr = (uint64_t)rx_buf_phys[i]; + rxd[i].status = 0; + } + + /* Set receive address (MAC filter) */ + uint32_t ral = (uint32_t)e1000_mac[0] | ((uint32_t)e1000_mac[1] << 8) | + ((uint32_t)e1000_mac[2] << 16) | ((uint32_t)e1000_mac[3] << 24); + uint32_t rah = (uint32_t)e1000_mac[4] | ((uint32_t)e1000_mac[5] << 8) | + (1U << 31); /* Address Valid */ + e1000_write(E1000_RAL0, ral); + e1000_write(E1000_RAH0, rah); + + /* Clear multicast table */ + for (int i = 0; i < 128; i++) { + e1000_write(E1000_MTA + (uint32_t)i * 4, 0); + } + + e1000_write(E1000_RDBAL, rx_desc_phys); + e1000_write(E1000_RDBAH, 0); + e1000_write(E1000_RDLEN, E1000_NUM_RX_DESC * (uint32_t)sizeof(struct e1000_rx_desc)); + e1000_write(E1000_RDH, 0); + e1000_write(E1000_RDT, E1000_NUM_RX_DESC - 1); + rx_tail = 0; + + /* Enable receiver: accept broadcast, 2048-byte buffers, strip CRC */ + e1000_write(E1000_RCTL, E1000_RCTL_EN | E1000_RCTL_BAM | + E1000_RCTL_BSIZE_2048 | E1000_RCTL_SECRC); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Interrupt handler */ +/* ------------------------------------------------------------------ */ + +static void e1000_irq_handler(struct registers* regs) { + (void)regs; + uint32_t icr = e1000_read(E1000_ICR); + (void)icr; + /* Reading ICR clears the pending interrupt bits. + * RX/TX processing is done via polling in e1000_recv/e1000_send + * for simplicity. The interrupt just wakes the system. */ +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +int e1000_init(void) { + const struct pci_device* dev = pci_find_device(E1000_VENDOR_ID, E1000_DEVICE_ID); + if (!dev) { + uart_print("[E1000] Device not found.\n"); + return -1; + } + + e1000_bus = dev->bus; + e1000_slot = dev->slot; + e1000_func = dev->func; + + /* Read BAR0 (MMIO base) */ + uint32_t bar0 = dev->bar[0]; + if (bar0 & 1) { + uart_print("[E1000] BAR0 is I/O (unsupported).\n"); + return -1; + } + uint32_t mmio_phys = bar0 & 0xFFFFFFF0U; + + /* Map E1000 MMIO region (128KB) */ + for (int i = 0; i < E1000_MMIO_PAGES; i++) { + vmm_map_page((uint64_t)(mmio_phys + (uint32_t)i * 4096), + (uint64_t)(E1000_MMIO_VA + (uint32_t)i * 4096), + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + } + e1000_mmio = (volatile uint32_t*)E1000_MMIO_VA; + + /* Enable PCI bus mastering + memory space */ + uint32_t cmd = pci_config_read(e1000_bus, e1000_slot, e1000_func, 0x04); + cmd |= (1U << 2) | (1U << 1); /* Bus Master | Memory Space */ + pci_config_write(e1000_bus, e1000_slot, e1000_func, 0x04, cmd); + + /* Reset the device */ + uint32_t ctrl = e1000_read(E1000_CTRL); + e1000_write(E1000_CTRL, ctrl | E1000_CTRL_RST); + /* Wait for reset to complete (spec says ~1us, be generous) */ + for (volatile int i = 0; i < 100000; i++) { } + + /* Disable interrupts during setup */ + e1000_write(E1000_IMC, 0xFFFFFFFF); + e1000_read(E1000_ICR); /* Clear pending */ + + /* Set link up */ + ctrl = e1000_read(E1000_CTRL); + e1000_write(E1000_CTRL, ctrl | E1000_CTRL_SLU | E1000_CTRL_ASDE); + + /* Read MAC address from EEPROM */ + e1000_read_mac(); + + uart_print("[E1000] MAC: "); + char hex[4]; + for (int i = 0; i < 6; i++) { + if (i) uart_print(":"); + hex[0] = "0123456789ABCDEF"[(e1000_mac[i] >> 4) & 0xF]; + hex[1] = "0123456789ABCDEF"[e1000_mac[i] & 0xF]; + hex[2] = '\0'; + uart_print(hex); + } + uart_print("\n"); + + /* Init TX and RX rings */ + if (e1000_init_tx() < 0) { + uart_print("[E1000] Failed to init TX ring.\n"); + return -1; + } + if (e1000_init_rx() < 0) { + uart_print("[E1000] Failed to init RX ring.\n"); + return -1; + } + + /* Register interrupt handler */ + uint8_t irq = dev->irq_line; + if (irq < 16) { + register_interrupt_handler((uint8_t)(32 + irq), e1000_irq_handler); + } + + /* Enable RX interrupts */ + e1000_write(E1000_IMS, E1000_ICR_RXT0 | E1000_ICR_LSC | + E1000_ICR_RXDMT0 | E1000_ICR_RXO); + + e1000_ready = 1; + + char buf[12]; + uart_print("[E1000] Initialized, IRQ="); + itoa(irq, buf, 10); + uart_print(buf); + uart_print(", MMIO="); + itoa_hex(mmio_phys, buf); + uart_print(buf); + uart_print("\n"); + + return 0; +} + +int e1000_send(const void* data, uint16_t len) { + if (!e1000_ready || !data || len == 0 || len > E1000_TX_BUF_SIZE) + return -1; + + struct e1000_tx_desc* txd = (struct e1000_tx_desc*)E1000_TX_DESC_VA; + uint32_t idx = tx_tail; + + /* Wait for descriptor to be available */ + int timeout = 100000; + while (!(txd[idx].status & E1000_TXD_STAT_DD) && --timeout > 0) { } + if (timeout <= 0) return -1; + + /* Copy data to TX buffer */ + uintptr_t buf_va = E1000_TX_BUF_VA + (uintptr_t)(idx / 2) * 4096 + + (uintptr_t)(idx % 2) * E1000_TX_BUF_SIZE; + memcpy((void*)buf_va, data, len); + + /* Set up descriptor */ + txd[idx].buffer_addr = (uint64_t)tx_buf_phys[idx]; + txd[idx].length = len; + txd[idx].cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS; + txd[idx].status = 0; + + /* Advance tail */ + tx_tail = (idx + 1) % E1000_NUM_TX_DESC; + e1000_write(E1000_TDT, tx_tail); + + return 0; +} + +int e1000_recv(void* buf, uint16_t buf_len) { + if (!e1000_ready || !buf || buf_len == 0) + return 0; + + struct e1000_rx_desc* rxd = (struct e1000_rx_desc*)E1000_RX_DESC_VA; + uint32_t idx = rx_tail; + + if (!(rxd[idx].status & E1000_RXD_STAT_DD)) + return 0; /* No packet available */ + + uint16_t pkt_len = rxd[idx].length; + if (pkt_len > buf_len) pkt_len = buf_len; + + /* Copy data from RX buffer */ + uintptr_t buf_va = E1000_RX_BUF_VA + (uintptr_t)(idx / 2) * 4096 + + (uintptr_t)(idx % 2) * E1000_RX_BUF_SIZE; + memcpy(buf, (const void*)buf_va, pkt_len); + + /* Reset descriptor and advance tail */ + rxd[idx].status = 0; + uint32_t old_tail = rx_tail; + rx_tail = (idx + 1) % E1000_NUM_RX_DESC; + e1000_write(E1000_RDT, old_tail); + + return (int)pkt_len; +} + +void e1000_get_mac(uint8_t mac[6]) { + for (int i = 0; i < 6; i++) mac[i] = e1000_mac[i]; +} + +int e1000_link_up(void) { + if (!e1000_ready) return 0; + return (e1000_read(E1000_STATUS) & (1U << 1)) ? 1 : 0; +} diff --git a/src/kernel/init.c b/src/kernel/init.c index 97ef659..261ae98 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -13,6 +13,7 @@ #include "diskfs.h" #include "procfs.h" #include "pci.h" +#include "e1000.h" #include "vbe.h" #include "uart_console.h" @@ -69,6 +70,7 @@ int init_start(const struct boot_info* bi) { } pci_init(); + e1000_init(); vbe_init(bi); tty_init();