With the native r8169 out of the registry, the TUF A15 boots with no network device; netinit() saw none and left the stack off, and net_attach_late() then refused every interface that came later -- its comment assumed netinit() was still to run. Linux's r8169 under LDLS got as far as registering ldls0 and was turned away. A late interface now starts the stack, whose first act is to attach every device there is; net_eth_attach() refuses a device it already has, for the window in which both could offer it. LDLS says which refusal it got: EBUSY is another process, anything else is the kernel. Tested in QEMU with -nic none and netecho: no interface at boot, then ldls0 with a DHCP lease and ping answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
291 lines
9.4 KiB
C
291 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// lwIP network interfaces over the netdev class.
|
|
//
|
|
// The seam, and it is deliberately driver-agnostic: everything about IP, ARP
|
|
// and TCP lives in lwIP, everything about descriptors and registers lives in a
|
|
// driver, and this file knows only that a network device can transmit a frame,
|
|
// receive a frame and state its MAC address. It was `netif_e1000.c` and called
|
|
// `e1000_transmit` directly, which meant the second NIC driver would have
|
|
// arrived with a second near-identical copy of this file. See include/dev/netdev.h.
|
|
//
|
|
// One netif per bound interface, up to NETIF_MAX. Two NICs is an ordinary
|
|
// machine -- a laptop with Ethernet and the virtual one QEMU adds is already
|
|
// two -- and the limit exists only because a fixed array is simpler than
|
|
// allocation for something enumerated once at boot.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "dev/device.h"
|
|
#include "dev/netdev.h"
|
|
|
|
#include "lwip/opt.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/pbuf.h"
|
|
#include "lwip/etharp.h"
|
|
#include "lwip/ethip6.h"
|
|
#include "netif/ethernet.h"
|
|
|
|
#define ETH_MTU 1500
|
|
#define ETH_FRAMESZ 1518 // 1500 payload + 14 header + 4 FCS allowance
|
|
|
|
struct eth_if {
|
|
struct netif netif;
|
|
struct device *dev;
|
|
// Frames in and out, for /proc/netif. Counted at this seam rather than in
|
|
// the driver on purpose: a frame the driver received and this file dropped
|
|
// never reached the stack, and a counter one layer down would say it did.
|
|
uint64 rx_frames, tx_frames;
|
|
int used;
|
|
};
|
|
|
|
static struct eth_if ifs[NETIF_MAX];
|
|
static int nifs;
|
|
|
|
// One staging buffer each, not one per call: a frame is 1518 bytes and the
|
|
// kernel stack is 4096 in total, so a local of this size on a path that is
|
|
// several frames deep is exactly the overflow the kstack canary exists to
|
|
// catch. Safe as a single static because every caller holds the lwIP lock --
|
|
// which is also why one pair serves every interface.
|
|
static char txframe[ETH_FRAMESZ];
|
|
static char rxframe[ETH_FRAMESZ];
|
|
|
|
// Send one packet. lwIP hands over a pbuf chain; a driver wants one flat
|
|
// buffer, so this walks the chain into txframe.
|
|
static err_t
|
|
eth_linkoutput(struct netif *netif, struct pbuf *p)
|
|
{
|
|
struct eth_if *e = netif->state;
|
|
|
|
if (p->tot_len > sizeof(txframe))
|
|
return ERR_IF;
|
|
|
|
u16_t off = 0;
|
|
for (struct pbuf *q = p; q != NULL; q = q->next) {
|
|
if (off + q->len > sizeof(txframe))
|
|
return ERR_IF;
|
|
memmove(txframe + off, q->payload, q->len);
|
|
off += q->len;
|
|
}
|
|
|
|
// A full transmit ring is congestion, not failure. Reporting ERR_IF would
|
|
// make lwIP drop the segment and wait for a retransmission timer;
|
|
// ERR_WOULDBLOCK lets it try again, which for TCP is the difference between
|
|
// a stall of milliseconds and one of seconds.
|
|
if (netdev_transmit(e->dev, txframe, off) != 0)
|
|
return ERR_WOULDBLOCK;
|
|
|
|
e->tx_frames++;
|
|
return ERR_OK;
|
|
}
|
|
|
|
// Bring one interface up. Called by netif_add, once per device.
|
|
static err_t
|
|
eth_netif_init(struct netif *netif)
|
|
{
|
|
struct eth_if *e = netif->state;
|
|
const unsigned char *mac = netdev_mac(e->dev);
|
|
|
|
netif->name[0] = 'e';
|
|
netif->name[1] = 'n';
|
|
netif->output = etharp_output; // IP -> ARP -> linkoutput
|
|
netif->linkoutput = eth_linkoutput;
|
|
netif->mtu = ETH_MTU;
|
|
netif->hwaddr_len = 6;
|
|
for (int i = 0; i < 6; i++)
|
|
netif->hwaddr[i] = mac ? mac[i] : 0;
|
|
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
|
|
return ERR_OK;
|
|
}
|
|
|
|
// Attach one network device. Returns its interface index, or -1.
|
|
//
|
|
// No address: DHCP fills that in, and an interface that came up with a
|
|
// hardcoded one would be wrong on every network but the one it was written on.
|
|
int
|
|
net_eth_attach(struct device *dev)
|
|
{
|
|
ip4_addr_t any;
|
|
|
|
if (nifs >= NETIF_MAX || dev == 0)
|
|
return -1;
|
|
// Once only: a device that appears while netd is starting can be offered
|
|
// both by netd's boot-time walk and by net_attach_late.
|
|
for (int i = 0; i < nifs; i++)
|
|
if (ifs[i].used && ifs[i].dev == dev)
|
|
return -1;
|
|
|
|
struct eth_if *e = &ifs[nifs];
|
|
e->dev = dev;
|
|
e->used = 1;
|
|
|
|
IP4_ADDR(&any, 0, 0, 0, 0);
|
|
if (netif_add(&e->netif, &any, &any, &any, e,
|
|
eth_netif_init, netif_input) == NULL) {
|
|
e->used = 0;
|
|
return -1;
|
|
}
|
|
|
|
// The first one is the default route. With one interface that is the only
|
|
// possible answer; with two it is a policy decision that belongs to a
|
|
// routing table this kernel does not have yet, and picking the first is at
|
|
// least a rule rather than an accident of enumeration order.
|
|
if (nifs == 0)
|
|
netif_set_default(&e->netif);
|
|
|
|
netif_set_up(&e->netif);
|
|
if (netdev_link_up(dev))
|
|
netif_set_link_up(&e->netif);
|
|
else
|
|
netif_set_link_down(&e->netif);
|
|
|
|
return nifs++;
|
|
}
|
|
|
|
// An interface whose device has gone away.
|
|
//
|
|
// The missing half of net_eth_attach, and it went missing because nothing had
|
|
// ever taken a device back. /dev/pci does: the kernel's e1000 driver hands the
|
|
// NIC to a process, and until this existed the interface stayed in lwIP --
|
|
// up, holding an address, holding the route to its subnet, and attached to a
|
|
// driver that now refuses every frame. Packets went to it and stopped, which
|
|
// looks exactly like a driver that is not working.
|
|
//
|
|
// The slot is left occupied rather than compacted. Interface indices are
|
|
// visible in /proc/netif and in the names the stack has already handed out,
|
|
// and renumbering them because a device left would be worse than a gap.
|
|
int
|
|
net_eth_detach(struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nifs; i++) {
|
|
if (!ifs[i].used || ifs[i].dev != dev)
|
|
continue;
|
|
|
|
// Down first, then out. netif_remove() does stop DHCP and send the
|
|
// release, but a netif removed while still up leaves lwIP's default
|
|
// route pointing at freed storage if this was interface 0.
|
|
netif_set_link_down(&ifs[i].netif);
|
|
netif_set_down(&ifs[i].netif);
|
|
if (netif_default == &ifs[i].netif)
|
|
netif_set_default(NULL);
|
|
netif_remove(&ifs[i].netif);
|
|
|
|
ifs[i].used = 0;
|
|
ifs[i].dev = 0;
|
|
|
|
// Something else becomes the default, if anything can. A machine with
|
|
// a second interface should not lose its route because the first one
|
|
// was handed to a driver in a process.
|
|
if (netif_default == NULL) {
|
|
int j;
|
|
for (j = 0; j < nifs; j++)
|
|
if (ifs[j].used) { netif_set_default(&ifs[j].netif); break; }
|
|
}
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
net_eth_count(void)
|
|
{
|
|
return nifs;
|
|
}
|
|
|
|
struct netif *
|
|
net_eth_netif(int i)
|
|
{
|
|
if (i < 0 || i >= nifs)
|
|
return 0;
|
|
return &ifs[i].netif;
|
|
}
|
|
|
|
struct device *
|
|
net_eth_device(int i)
|
|
{
|
|
if (i < 0 || i >= nifs)
|
|
return 0;
|
|
return ifs[i].dev;
|
|
}
|
|
|
|
void
|
|
net_eth_counts(int i, uint64 *rx, uint64 *tx)
|
|
{
|
|
if (i < 0 || i >= nifs) {
|
|
*rx = *tx = 0;
|
|
return;
|
|
}
|
|
*rx = ifs[i].rx_frames;
|
|
*tx = ifs[i].tx_frames;
|
|
}
|
|
|
|
// Take whatever the drivers have and push it into lwIP. Returns how many
|
|
// frames moved across all interfaces, so the caller can tell a busy poll from
|
|
// an idle one.
|
|
//
|
|
// Bounded per interface rather than draining: a receive loop with no limit
|
|
// starves everything else on a busy network, and the caller comes straight
|
|
// back anyway.
|
|
int
|
|
net_eth_poll(void)
|
|
{
|
|
int moved = 0;
|
|
|
|
for (int k = 0; k < nifs; k++) {
|
|
struct eth_if *e = &ifs[k];
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
int len = netdev_receive(e->dev, rxframe, sizeof(rxframe));
|
|
if (len <= 0)
|
|
break;
|
|
|
|
// PBUF_POOL, so this comes from lwIP's own pool rather than a heap
|
|
// it does not have. A null return means the pool is exhausted,
|
|
// which is a dropped packet -- entirely normal under load, and what
|
|
// the protocol above is designed to survive.
|
|
struct pbuf *p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL);
|
|
if (p == NULL)
|
|
break;
|
|
|
|
pbuf_take(p, rxframe, (u16_t)len);
|
|
|
|
// netif->input is netif_input, set at netif_add: it dispatches on
|
|
// the Ethernet type. Ownership of the pbuf passes with the call,
|
|
// including when it fails, so there is nothing to free here.
|
|
if (e->netif.input(p, &e->netif) != ERR_OK)
|
|
pbuf_free(p);
|
|
|
|
e->rx_frames++;
|
|
moved++;
|
|
}
|
|
}
|
|
return moved;
|
|
}
|
|
|
|
// Follow each device's link into lwIP.
|
|
//
|
|
// The link was read once, at attach, and never again. Under QEMU it is up
|
|
// before the driver asks; on a real network autonegotiation takes two or
|
|
// three seconds, so attach saw it down and the interface stayed down for
|
|
// good -- no DHCP, no traffic, the cable plugged in and nothing to show for
|
|
// it. Bringing the link up also restarts DHCP (lwIP does that itself).
|
|
void
|
|
net_eth_link_check(void)
|
|
{
|
|
for (int k = 0; k < nifs; k++) {
|
|
struct eth_if *e = &ifs[k];
|
|
if (!e->used)
|
|
continue;
|
|
int up = netdev_link_up(e->dev) != 0;
|
|
if (up == (netif_is_link_up(&e->netif) != 0))
|
|
continue;
|
|
if (up)
|
|
netif_set_link_up(&e->netif);
|
|
else
|
|
netif_set_link_down(&e->netif);
|
|
klog("net: %s link %s\n", e->dev->name, up ? "up" : "down");
|
|
}
|
|
}
|