From 6faa26d01b41062e5d32fbe994ea175f48ef65b2 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Fri, 13 Feb 2026 07:01:48 -0300 Subject: [PATCH] feat: interrupt-driven E1000 RX, non-blocking TX, root= cmdline param MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit E1000 networking overhaul — replace polling with proper interrupt-driven I/O: 1. RX interrupt-driven: - IRQ handler (e1000_irq_handler) now signals e1000_rx_sem on RXT0/RXDMT0/RXO events instead of being a no-op. - Dedicated kernel thread (e1000_rx_thread) blocks on the semaphore, drains all available packets via e1000_recv(), and delivers them to lwIP via tcpip_input(). - Latency: immediate wake on packet arrival (was 20ms polling). 2. TX non-blocking: - e1000_send() checks the DD bit immediately and returns -1 if the descriptor is not ready (was: busy-wait up to 100K iters). - lwIP's linkoutput callback returns ERR_IF on ring-full. 3. Idle loop cleanup: - net_poll() removed from kernel_main's idle loop. - net_poll() is now a no-op (kept for backward compat). - PID 0 idle loop is pure hlt — no wasted CPU cycles. 4. root= kernel command line parameter: - Syntax: root=/dev/hdX (e.g. root=/dev/hda) - Auto-detects filesystem (tries diskfs, fat, ext2 in order) - Mounts at /disk on success - Processed after ATA init, before /etc/fstab parsing - Example GRUB entry: multiboot2 /boot/adros-x86.bin root=/dev/hda quiet Files changed: - src/drivers/e1000.c: add sync.h, ksem_init/signal, non-blocking TX - include/e1000.h: export e1000_rx_sem - src/net/e1000_netif.c: rewrite with rx_thread, remove polling - src/kernel/main.c: remove net_poll() from idle loop - src/kernel/init.c: add root= auto-mount logic Build: clean, cppcheck: clean, smoke: 19/19 pass Stress: 10/10 boots without ring3 — zero panics --- include/e1000.h | 4 +++ src/drivers/e1000.c | 22 +++++++++----- src/kernel/init.c | 33 ++++++++++++++++++-- src/kernel/main.c | 2 +- src/net/e1000_netif.c | 70 ++++++++++++++++++++++++++----------------- 5 files changed, 91 insertions(+), 40 deletions(-) diff --git a/include/e1000.h b/include/e1000.h index 0aaaee6..16492ce 100644 --- a/include/e1000.h +++ b/include/e1000.h @@ -106,6 +106,10 @@ struct e1000_rx_desc { uint16_t special; } __attribute__((packed)); +/* RX semaphore — signaled by IRQ handler, waited on by RX thread */ +#include "sync.h" +extern ksem_t e1000_rx_sem; + /* Initialize the E1000 NIC. Returns 0 on success, -1 on failure. */ int e1000_init(void); diff --git a/src/drivers/e1000.c b/src/drivers/e1000.c index 551a2cd..3423917 100644 --- a/src/drivers/e1000.c +++ b/src/drivers/e1000.c @@ -7,6 +7,7 @@ #include "console.h" #include "utils.h" #include "io.h" +#include "sync.h" #include @@ -32,6 +33,9 @@ static uint32_t rx_buf_phys[E1000_NUM_RX_DESC]; static volatile uint32_t tx_tail = 0; static volatile uint32_t rx_tail = 0; +/* RX semaphore — signaled by IRQ handler, waited on by RX thread */ +ksem_t e1000_rx_sem; + /* Cached PCI device info */ static uint8_t e1000_bus, e1000_slot, e1000_func; @@ -196,10 +200,10 @@ static int e1000_init_rx(void) { 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. */ + /* Wake the RX thread if a receive event occurred */ + if (icr & (E1000_ICR_RXT0 | E1000_ICR_RXDMT0 | E1000_ICR_RXO)) { + ksem_signal(&e1000_rx_sem); + } } /* ------------------------------------------------------------------ */ @@ -244,6 +248,9 @@ int e1000_init(void) { /* Wait for reset to complete (spec says ~1us, be generous) */ for (volatile int i = 0; i < 100000; i++) { } + /* Init RX semaphore before enabling interrupts */ + ksem_init(&e1000_rx_sem, 0); + /* Disable interrupts during setup */ e1000_write(E1000_IMC, 0xFFFFFFFF); e1000_read(E1000_ICR); /* Clear pending */ @@ -295,10 +302,9 @@ int e1000_send(const void* data, uint16_t len) { 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; + /* Non-blocking: if descriptor not ready, return immediately */ + if (!(txd[idx].status & E1000_TXD_STAT_DD)) + return -1; /* Copy data to TX buffer */ uintptr_t buf_va = E1000_TX_BUF_VA + (uintptr_t)(idx / 2) * 4096 + diff --git a/src/kernel/init.c b/src/kernel/init.c index 306face..91bf14c 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -218,9 +218,36 @@ int init_start(const struct boot_info* bi) { * (primary/secondary x master/slave). */ (void)ata_pio_init(); - /* Disk-based filesystems (diskfs, FAT, ext2) are NOT auto-mounted. - * They are mounted either by /etc/fstab entries or manually via the - * kconsole 'mount' command. Parse /etc/fstab if it exists. */ + /* If root= is specified on the kernel command line, mount that device + * as the disk root filesystem. The filesystem type is auto-detected + * by trying each supported type in order. + * Example: root=/dev/hda or root=/dev/hdb */ + const char* root_dev = cmdline_get("root"); + if (root_dev) { + int drive = -1; + if (strncmp(root_dev, "/dev/", 5) == 0) + drive = ata_name_to_drive(root_dev + 5); + if (drive >= 0 && ata_pio_drive_present(drive)) { + /* Try auto-detect: diskfs, fat, ext2 */ + static const char* fstypes[] = { "diskfs", "fat", "ext2", NULL }; + int mounted = 0; + for (int i = 0; fstypes[i]; i++) { + if (init_mount_fs(fstypes[i], drive, 0, "/disk") == 0) { + kprintf("[INIT] root=%s mounted as %s on /disk\n", + root_dev, fstypes[i]); + mounted = 1; + break; + } + } + if (!mounted) + kprintf("[INIT] root=%s: no supported filesystem found\n", root_dev); + } else { + kprintf("[INIT] root=%s: device not found\n", root_dev); + } + } + + /* Disk-based filesystems can also be mounted via /etc/fstab entries + * or manually via the kconsole 'mount' command. */ init_parse_fstab(); if (!fs_root) { diff --git a/src/kernel/main.c b/src/kernel/main.c index c36d401..bdd8ad3 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -89,8 +89,8 @@ done: kprintf("Welcome to AdrOS (x86/ARM/RISC-V/MIPS)!\n"); // Infinite loop acting as Idle Task + // RX is interrupt-driven (e1000_rx_thread), no polling needed. for(;;) { - net_poll(); hal_cpu_idle(); } } diff --git a/src/net/e1000_netif.c b/src/net/e1000_netif.c index 03e0f07..5c85397 100644 --- a/src/net/e1000_netif.c +++ b/src/net/e1000_netif.c @@ -1,6 +1,9 @@ /* * lwIP netif driver for the E1000 NIC. * Bridges the E1000 hardware driver to lwIP's network interface abstraction. + * + * RX path: interrupt → e1000_rx_sem → rx_thread → e1000_recv → tcpip_input + * TX path: lwIP core → e1000_netif_output → e1000_send (non-blocking) */ #include "lwip/opt.h" #include "lwip/netif.h" @@ -15,19 +18,17 @@ #include "spinlock.h" #include "e1000.h" +#include "process.h" #include "console.h" #include "utils.h" #define E1000_NETIF_MTU 1500 -/* Temporary receive buffer (stack-allocated per poll call) */ -static uint8_t rx_tmp[2048]; - /* Forward declaration */ static err_t e1000_netif_output(struct netif* netif, struct pbuf* p); /* - * Low-level output: send a pbuf chain via E1000. + * Low-level output: send a pbuf chain via E1000 (non-blocking). */ static err_t e1000_netif_output(struct netif* netif, struct pbuf* p) { (void)netif; @@ -62,7 +63,7 @@ static err_t e1000_netif_output(struct netif* netif, struct pbuf* p) { /* * Netif init callback — called by netif_add(). */ -err_t e1000_netif_init(struct netif* netif) { +static err_t e1000_netif_init(struct netif* netif) { netif->name[0] = 'e'; netif->name[1] = 'n'; netif->output = etharp_output; @@ -76,26 +77,6 @@ err_t e1000_netif_init(struct netif* netif) { return ERR_OK; } -/* - * Poll the E1000 for received packets and feed them into lwIP. - * Must be called periodically from the main loop. - */ -void e1000_netif_poll(struct netif* netif) { - for (;;) { - int len = e1000_recv(rx_tmp, sizeof(rx_tmp)); - if (len <= 0) break; - - struct pbuf* p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL); - if (!p) break; - - pbuf_take(p, rx_tmp, (u16_t)len); - - if (netif->input(p, netif) != ERR_OK) { - pbuf_free(p); - } - } -} - /* ---- Global network state ---- */ static struct netif e1000_nif; @@ -108,6 +89,35 @@ static void net_init_done(void* arg) { tcpip_ready = 1; } +/* + * Dedicated RX thread — waits on the E1000 RX semaphore (signaled by + * the hardware interrupt handler) and drains all available packets into + * the lwIP TCP/IP stack via tcpip_input(). + */ +static uint8_t rx_tmp[2048]; + +static void e1000_rx_thread(void) { + for (;;) { + /* Block until the IRQ handler signals a receive event */ + ksem_wait(&e1000_rx_sem); + + /* Drain all available packets */ + for (;;) { + int len = e1000_recv(rx_tmp, sizeof(rx_tmp)); + if (len <= 0) break; + + struct pbuf* p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL); + if (!p) break; + + pbuf_take(p, rx_tmp, (u16_t)len); + + if (e1000_nif.input(p, &e1000_nif) != ERR_OK) { + pbuf_free(p); + } + } + } +} + void net_init(void) { if (!e1000_link_up()) { kprintf("[NET] E1000 link down, skipping lwIP init.\n"); @@ -130,14 +140,18 @@ void net_init(void) { netif_set_default(&e1000_nif); netif_set_up(&e1000_nif); + /* Start the dedicated RX thread */ + process_create_kernel(e1000_rx_thread); + net_initialized = 1; - kprintf("[NET] lwIP initialized (threaded), IP=10.0.2.15\n"); + kprintf("[NET] lwIP initialized (interrupt-driven RX), IP=10.0.2.15\n"); } void net_poll(void) { - if (!net_initialized) return; - e1000_netif_poll(&e1000_nif); + /* No-op: RX is now handled by the interrupt-driven rx_thread. + * Kept for backward compatibility — callers can safely remove. */ + (void)0; } struct netif* net_get_netif(void) { -- 2.43.0