]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: interrupt-driven E1000 RX, non-blocking TX, root= cmdline param
authorTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 10:01:48 +0000 (07:01 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 10:01:48 +0000 (07:01 -0300)
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
src/drivers/e1000.c
src/kernel/init.c
src/kernel/main.c
src/net/e1000_netif.c

index 0aaaee6763caa916e42d9ac854ef8b0b4637404f..16492ce074d174ad3ae25781754df0f7ce030f66 100644 (file)
@@ -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);
 
index 551a2cdad47ba46ff6ff100edb9339659e27ce46..3423917e60b8e1cd805f90d729c03bf80f0fd47a 100644 (file)
@@ -7,6 +7,7 @@
 #include "console.h"
 #include "utils.h"
 #include "io.h"
+#include "sync.h"
 
 #include <stddef.h>
 
@@ -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 +
index 306face7a03868d54dec11ac8fd248a69bbe882e..91bf14c5c05b8abd8dfcde8786b55ccb2bfd2ada 100644 (file)
@@ -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) {
index c36d401867118e4b0cb56fc0260c353f5565f6e5..bdd8ad38cb3ada328766e87ca131f3d0edbec2b7 100644 (file)
@@ -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();
     }
 }
index 03e0f074302757d5b1e323b2ef09b2df587c19c4..5c853979df6103e10b3c487a2307ee20c85cbdfc 100644 (file)
@@ -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"
 #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) {