// SPDX-License-Identifier: Apache-2.0 // // The network thread, and the one place the TCP/IP stack is started. // // lwIP with NO_SYS=1 has no threads of its own, so something has to feed it // received frames and run its timers. That something is a kernel thread rather // than the e1000 interrupt handler, and the reason is the lock: a call into // lwIP can reach the driver, the driver can sleep, and an interrupt handler // cannot. So the interrupt keeps doing what it always did -- clear the cause // register -- and this thread polls. // // Polling costs a wakeup every few milliseconds when the link is idle. The // alternative, waking this thread from the interrupt, is a worthwhile change // later; it is deliberately not the first version, because a receive path that // is woken from an interrupt is much harder to debug than one that is asked. #include "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "proc.h" #include "lwip/opt.h" #include "lwip/init.h" #include "lwip/timeouts.h" #include "lwip/netif.h" #include "lwip/dhcp.h" #include "lwip/ip4_addr.h" // Wait until the next timer tick. `ticks` advances at 100 Hz, so this is a // sleep of up to 10 ms -- and, more importantly, it is the kernel's only timed // sleep: there is no higher-resolution timer to wait on. The lwIP lock must not // be held across it, or nothing else could touch the stack while the link is // idle, which is most of the time. static void wait_tick(void) { acquire(&tickslock); uint start = ticks; while (ticks == start) sleep(&ticks, &tickslock); release(&tickslock); } static int net_started; // Set once DHCP has bound, so `ifconfig`-ish output and /proc/net have // something true to say rather than reporting 0.0.0.0 forever. static int net_bound; int net_is_up(void) { return net_bound; } static void net_thread(void) { // lwIP's own initialisation has to happen in this thread, not in main(): // it allocates from lwIP's pools and registers timers, and doing that // before there is a process context means doing it somewhere that cannot // sleep if the allocator ever needs to. lwip_sys_init(); lwip_sys_lock(); lwip_init(); if (net_e1000_attach() != 0) { lwip_sys_unlock(); cprintf("net: no e1000; the network thread has nothing to drive\n"); // Not a panic and not an exit: a kernel thread must never return, and // a machine with no NIC is an ordinary machine. for (;;) wait_tick(); } struct netif *nif = net_e1000_netif(); dhcp_start(nif); lwip_sys_unlock(); cprintf("net: lwIP %d.%d.%d up, requesting an address\n", LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, LWIP_VERSION_REVISION); int announced = 0; for (;;) { int moved; lwip_sys_lock(); moved = net_e1000_poll(); sys_check_timeouts(); // Report the address once, when it first arrives. Anyone who has spent // an evening wondering whether the guest has an address at all wants // this on the console rather than in a file they have to know about. if (!announced && !ip4_addr_isany_val(*netif_ip4_addr(nif))) { const ip4_addr_t *a = netif_ip4_addr(nif); const ip4_addr_t *m = netif_ip4_netmask(nif); const ip4_addr_t *gw = netif_ip4_gw(nif); cprintf("net: %d.%d.%d.%d/%d.%d.%d.%d via %d.%d.%d.%d\n", ip4_addr1(a), ip4_addr2(a), ip4_addr3(a), ip4_addr4(a), ip4_addr1(m), ip4_addr2(m), ip4_addr3(m), ip4_addr4(m), ip4_addr1(gw), ip4_addr2(gw), ip4_addr3(gw), ip4_addr4(gw)); announced = 1; net_bound = 1; } lwip_sys_unlock(); // Busy while there is traffic, idle otherwise. Sleeping even 1 ms with // frames waiting would cap throughput at one burst per tick. if (moved == 0) wait_tick(); } } // Start the stack. Called from main() once processes exist. void netinit(void) { if (net_started) return; if (!e1000_present()) { // Silent: a machine without an e1000 is not misconfigured, and this // runs on every boot. return; } net_started = 1; if (kthread_create(net_thread, "netd") < 0) cprintf("net: could not start the network thread\n"); }