SchOS/kernel/net/lwip/sys_arch.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

121 lines
3.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// The lwIP port layer: everything lwIP needs from the system it runs on.
//
// With NO_SYS=1 that is a short list -- a clock, a source of randomness, and a
// lock -- because lwIP is not creating threads or mailboxes of its own. The
// lock is the interesting one.
//
// ---------------------------------------------------------------------------
// The concurrency model, in full
//
// lwIP with NO_SYS=1 is not re-entrant. It is entered from exactly two kinds of
// place here: the network thread (kernel/net/net.c), which feeds it received
// frames and runs its timers, and the socket syscalls, which call into it on
// behalf of a process. Those can run on different CPUs at the same time, so
// something has to serialise them, and lwIP's own SYS_ARCH_PROTECT hooks are
// the sanctioned place to put it.
//
// A sleeplock rather than a spinlock, because a call into lwIP can reach the
// driver and the driver can sleep. That makes this lock unusable from an
// interrupt handler -- which is why the e1000 interrupt does not feed lwIP
// directly and the network thread polls instead.
//
// It is recursive by necessity: lwIP brackets small critical sections inside
// larger ones and expects nesting to be free.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "proc.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
static struct sleeplock lwip_lock;
static struct proc *lwip_owner;
static int lwip_depth;
static int lwip_lock_ready;
void
lwip_sys_init(void)
{
initsleeplock(&lwip_lock, "lwip");
lwip_owner = 0;
lwip_depth = 0;
lwip_lock_ready = 1;
}
// Reading `lwip_owner` without holding anything is safe in the one direction it
// is asked: only the process inside the lock ever writes its own proc pointer
// there, and it holds the lock the whole time. No other process can see its own
// pointer here, so a stale read cannot produce a false "I hold it". The null
// check matters because myproc() is 0 in early boot, and 0 == 0 would otherwise
// claim the lock for a process that does not exist.
static int
lwip_lock_mine(void)
{
return lwip_owner != 0 && lwip_owner == myproc();
}
void
lwip_sys_lock(void)
{
// Before lwip_sys_init runs there is exactly one context in existence, so
// there is nothing to serialise against and the sleeplock is not yet valid.
if (!lwip_lock_ready)
return;
if (lwip_lock_mine()) {
lwip_depth++;
return;
}
acquiresleep(&lwip_lock);
lwip_owner = myproc();
lwip_depth = 1;
}
void
lwip_sys_unlock(void)
{
if (!lwip_lock_ready)
return;
if (!lwip_lock_mine())
return; // not ours; releasing would free someone else's
if (--lwip_depth > 0)
return;
lwip_owner = 0;
releasesleep(&lwip_lock);
}
// Milliseconds since boot. lwIP drives every timer off this: retransmission,
// TIME_WAIT, ARP expiry, DHCP renewal. It need not match wall time, but it must
// advance monotonically -- a clock that goes backwards makes lwIP believe every
// timer is due at once.
//
// `ticks` is the timer interrupt count at 100 Hz, so one tick is 10 ms. Taken
// without the lock: it is a single aligned word, and a torn read would cost
// lwIP one early or late timer rather than anything worse.
u32_t
sys_now(void)
{
return (u32_t)ticks * 10u;
}
// lwIP wants randomness for initial TCP sequence numbers and for the DHCP
// transaction id. Sequence numbers that are predictable are a real weakness on
// a network with anyone hostile on it; this is not a CSPRNG and does not claim
// to be, but it is seeded from a clock that an outsider cannot observe.
u32_t
lwip_rand_seed(void)
{
static u32_t s;
if (s == 0)
s = (u32_t)ticks ^ 0x9E3779B9u;
// xorshift32
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
return s;
}