// 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(); } } /* ------------------------------------------------------------------ */ /* /proc/netif */ /* ------------------------------------------------------------------ */ // // Named netif rather than net, and that is deliberate: on Linux /proc/net is a // *directory* of a dozen files, and putting a plain file there would mean // anything reaching for the familiar /proc/net/dev got ENOTDIR from a path that // looks like it exists. This is one interface's state, so it is named for what // it holds and leaves the Linux name free for the day there is a directory to // put under it. // // No snprintf in the kernel, so this appends by hand in the same style as // kernel/dev/pci.c. static int put_str(char *out, const char *s) { int n = 0; while (s[n]) { out[n] = s[n]; n++; } return n; } static int put_u(char *out, uint64 v) { char tmp[24]; int i = 0, n = 0; if (v == 0) { out[0] = '0'; return 1; } while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (char)(v % 10); v /= 10; } while (i--) out[n++] = tmp[i]; return n; } static int put_ip4(char *out, const ip4_addr_t *a) { int n = 0; n += put_u(out + n, ip4_addr1(a)); out[n++] = '.'; n += put_u(out + n, ip4_addr2(a)); out[n++] = '.'; n += put_u(out + n, ip4_addr3(a)); out[n++] = '.'; n += put_u(out + n, ip4_addr4(a)); return n; } static int put_mac(char *out, const unsigned char *m) { static const char d[] = "0123456789abcdef"; int n = 0; for (int i = 0; i < 6; i++) { if (i) out[n++] = ':'; out[n++] = d[m[i] >> 4]; out[n++] = d[m[i] & 0xF]; } return n; } // One "name value" pair per line, which is the shape /proc/schfs and // /proc/fsstat already use here -- a reader that can scan one can scan all of // them. static int put_field(char *out, const char *name) { int n = put_str(out, name); while (n < 12) out[n++] = ' '; return n; } int net_info(char *buf, int cap) { int len = 0; if (cap < 512) return 0; if (!net_started) { len += put_field(buf + len, "state"); len += put_str(buf + len, e1000_present() ? "down\n" : "no interface\n"); return len; } struct netif *nif = net_e1000_netif(); lwip_sys_lock(); len += put_field(buf + len, "interface"); buf[len++] = nif->name[0]; buf[len++] = nif->name[1]; len += put_u(buf + len, nif->num); buf[len++] = '\n'; len += put_field(buf + len, "mac"); len += put_mac(buf + len, nif->hwaddr); buf[len++] = '\n'; len += put_field(buf + len, "state"); len += put_str(buf + len, net_bound ? "up\n" : "no address\n"); len += put_field(buf + len, "address"); len += put_ip4(buf + len, netif_ip4_addr(nif)); buf[len++] = '\n'; len += put_field(buf + len, "netmask"); len += put_ip4(buf + len, netif_ip4_netmask(nif)); buf[len++] = '\n'; len += put_field(buf + len, "gateway"); len += put_ip4(buf + len, netif_ip4_gw(nif)); buf[len++] = '\n'; len += put_field(buf + len, "mtu"); len += put_u(buf + len, nif->mtu); buf[len++] = '\n'; lwip_sys_unlock(); uint64 rx, tx; net_e1000_counts(&rx, &tx); len += put_field(buf + len, "rx_frames"); len += put_u(buf + len, rx); buf[len++] = '\n'; len += put_field(buf + len, "tx_frames"); len += put_u(buf + len, tx); buf[len++] = '\n'; return len; } // Start the stack. Called from main() once processes exist. void netinit(void) { if (net_started) return; // Before the check below, not after: the lock it initialises has to be // valid on a machine with no NIC too. Nothing can reach a wait there -- // inet_new refuses while the stack is down -- but a lock whose validity // depends on that reasoning holding forever is not worth the two lines // saved. inet_init(); 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"); }