SchOS/kernel/net/lwip/netif_eth.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

261 lines
8.2 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/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 "types.h"
#include "defs.h"
#include "param.h"
#include "device.h"
#include "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;
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;
}