The AF_INET blocking paths -- connect, accept, read and write -- woke on every timer tick and looked to see whether anything had changed. That is a wakeup per blocked socket per 10 ms whether or not the network did anything, and it answers late even when the data was already sitting in the buffer. What stood in the way was a lock mismatch, and it is written down in the file now because it is the reason this is not simply sleep on the socket: sleep() wants a spinlock, and the lwIP lock is a sleeplock -- it must be, since a call into lwIP can reach the driver and the driver can sleep. Nor can a waiter hold the lwIP lock while sleeping, or the network thread could never deliver the thing it is waiting for. So the wait is on a counter with a spinlock of its own. A waiter reads the counter while it still holds the lwIP lock, right after checking its condition, then releases and sleeps only if the counter has not moved. The lwIP callbacks bump it and wake the channel. No wakeup can be lost: anything that changes a condition does so after the reader's snapshot, and therefore bumps the counter after it too, and the sleeper re-checks under the spinlock before committing. One counter and one channel for all sixteen sockets rather than one each. Waking a socket with nothing to do costs a few instructions, and a per-socket channel would have to be torn down in close() against callbacks that can still arrive -- which is a use-after-free waiting to be written. tcp_sent is registered now, on both connected and accepted pcbs. It was not needed while writes woke on the clock; with this change a write blocked on a full send buffer would otherwise sleep with nobody left to wake it, because an acknowledgement is the only thing that frees space. Verified: bootcheck -Net passes, 38 [ok], no failures. It exercises the path that matters -- nettest blocks in read() until the host answers, and the host answers only after reading what the guest sent, so the wakeup comes from a callback and not from a tick that would have fired anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
269 lines
8.1 KiB
C
269 lines
8.1 KiB
C
// 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");
|
|
}
|