Measured against the host fetching the same 67 MB from Alpine's CDN at 11.5 MB/s, SchOS managed about 120 KB/s. Three causes: - The receive window reopened when data entered the socket buffer, before the reader drained it; lwIP then refused segments and the sender waited on retransmission timers. It reopens on read now. - The e1000 receive ring held 16 frames, so a full window arriving back to back lost everything past the sixteenth. 256 now. - Without window scaling the window stops at 64 KB, one window per round trip. Scaling on, 256 KB window, 512 KB socket ring (a power of two, which the free-running indices need), copied in blocks. Now 5.8 MB/s for the same file, and apk update in about two seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
118 lines
5.5 KiB
C
118 lines
5.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// lwIP configuration for SchOS.
|
|
//
|
|
// The shape of this file is one decision: NO_SYS=1.
|
|
//
|
|
// lwIP can run either as a set of threads of its own (NO_SYS=0), which needs a
|
|
// port layer of semaphores, mailboxes and thread creation and then hands you a
|
|
// blocking sockets API for free; or bare, with no threading assumptions
|
|
// (NO_SYS=1), where every call must come from one context and the blocking is
|
|
// yours to write. This kernel takes the second, for two reasons: the sys_arch
|
|
// a threaded lwIP wants is more code than the socket layer it would save, and
|
|
// the kernel already has a socket layer with its own shape (kernel/net/unix.c)
|
|
// that AF_INET has to fit into rather than replace.
|
|
//
|
|
// "One context" is enforced by a single lock rather than by literally one
|
|
// thread: kernel/net/net.c takes it around the network thread's work, and the
|
|
// socket syscalls take it around theirs. That is the entire concurrency model.
|
|
#ifndef LWIP_LWIPOPTS_H
|
|
#define LWIP_LWIPOPTS_H
|
|
|
|
#define NO_SYS 1
|
|
#define SYS_LIGHTWEIGHT_PROT 1
|
|
#define LWIP_NETCONN 0 // needs NO_SYS=0
|
|
#define LWIP_SOCKET 0 // ours lives in kernel/net/inet.c
|
|
|
|
// Memory. All of it is static: the kernel's allocator hands out whole pages
|
|
// and nothing else, so lwIP's own pools are a better fit than wrapping kalloc.
|
|
// These numbers are sized for a handful of connections, not for a server.
|
|
#define MEM_LIBC_MALLOC 0
|
|
#define MEMP_MEM_MALLOC 0
|
|
#define MEM_ALIGNMENT 4
|
|
#define MEM_SIZE (192 * 1024)
|
|
#define MEMP_NUM_PBUF 32
|
|
#define MEMP_NUM_UDP_PCB 8
|
|
#define MEMP_NUM_TCP_PCB 16
|
|
#define MEMP_NUM_TCP_PCB_LISTEN 8
|
|
#define MEMP_NUM_TCP_SEG 256 // a window held out of order
|
|
#define MEMP_NUM_ARP_QUEUE 16
|
|
#define MEMP_NUM_SYS_TIMEOUT 16
|
|
// Enough for a full receive window held out of order, plus the frames in
|
|
// flight from the driver: a window of TCP_WND below, in pool-sized pieces.
|
|
#define PBUF_POOL_SIZE 256
|
|
#define PBUF_POOL_BUFSIZE 1536 // one Ethernet frame plus room
|
|
|
|
// Protocols. IPv4 only: the driver is proven for it, the QEMU test network is
|
|
// v4, and every line of v6 here would be untested.
|
|
#define LWIP_IPV4 1
|
|
#define LWIP_IPV6 0
|
|
#define LWIP_ARP 1
|
|
#define LWIP_ETHERNET 1
|
|
#define LWIP_ICMP 1
|
|
#define LWIP_RAW 1
|
|
|
|
// A loopback interface, so 127.0.0.1 is a real destination rather than an
|
|
// address with no route. lwip_init() creates it; nothing else has to.
|
|
//
|
|
// LWIP_NETIF_LOOPBACK is the other half and is easy to miss: without it a
|
|
// packet addressed to an interface's *own* address goes out of the wire and
|
|
// never comes back. With NO_SYS=1 there is no thread to drain the loopback
|
|
// queue either, so netif_poll_all() is called from the network thread -- see
|
|
// kernel/net/net.c.
|
|
#define LWIP_HAVE_LOOPIF 1
|
|
#define LWIP_NETIF_LOOPBACK 1
|
|
#define LWIP_LOOPBACK_MAX_PBUFS 8
|
|
#define LWIP_UDP 1
|
|
#define LWIP_TCP 1
|
|
#define LWIP_DHCP 1
|
|
#define LWIP_DNS 1
|
|
#define LWIP_AUTOIP 0
|
|
#define LWIP_IGMP 0
|
|
#define LWIP_NETIF_HOSTNAME 1
|
|
|
|
// DHCP needs UDP checksums and a broadcast path; both are on. Requesting an
|
|
// address rather than hard-coding one matters because QEMU's user-mode network
|
|
// hands out 10.0.2.15 through DHCP and nothing else will route.
|
|
#define LWIP_NETIF_STATUS_CALLBACK 1
|
|
#define LWIP_NETIF_LINK_CALLBACK 1
|
|
|
|
#define TCP_MSS 1460
|
|
#define TCP_SND_BUF (8 * TCP_MSS)
|
|
// The receive window, and with it the download speed: one window per round
|
|
// trip. Eight segments held a transfer from a CDN to well under a megabyte a
|
|
// second; thirty-two, the most that fits in TCP's 16-bit window field, held
|
|
// it to 1.5 MB/s at 25 ms -- an eighth of a 100 Mbit line, measured against
|
|
// the host fetching the same file. Window scaling (RFC 7323) lifts the 64 KB
|
|
// ceiling: the field is shifted by TCP_RCV_SCALE, so 256 KB is advertised as
|
|
// 16384 << 4. Every socket's buffer in kernel/net/inet.c is larger than this,
|
|
// which is what makes the window an honest promise -- see inet_recv_cb.
|
|
#define LWIP_WND_SCALE 1
|
|
#define TCP_RCV_SCALE 4
|
|
#define TCP_WND (256 * 1024)
|
|
#define TCP_SND_QUEUELEN ((4 * TCP_SND_BUF) / TCP_MSS)
|
|
#define TCP_LISTEN_BACKLOG 1
|
|
|
|
// Checksums in software. The e1000 can offload them, but the driver does not
|
|
// ask it to, and a checksum computed by a device the stack has not been told
|
|
// about is the sort of mismatch that shows up only against a real peer.
|
|
#define CHECKSUM_GEN_IP 1
|
|
#define CHECKSUM_GEN_UDP 1
|
|
#define CHECKSUM_GEN_TCP 1
|
|
#define CHECKSUM_CHECK_IP 1
|
|
#define CHECKSUM_CHECK_UDP 1
|
|
#define CHECKSUM_CHECK_TCP 1
|
|
|
|
// Statistics are what /proc/net reports; they cost a few counters.
|
|
#define LWIP_STATS 1
|
|
#define LWIP_STATS_DISPLAY 0
|
|
|
|
// Debug output off by default -- turning LWIP_DEBUG on here makes the stack
|
|
// extremely chatty on the console, which is occasionally exactly what you want.
|
|
#define LWIP_DEBUG 0
|
|
|
|
// lwIP would otherwise provide its own errno, which would then disagree with
|
|
// the kernel's for every value a syscall returns.
|
|
#define LWIP_PROVIDE_ERRNO 1
|
|
|
|
#endif
|