add lwIP TCP/IP stack as a runtime driver, native socket syscalls, SysGetMemoryStatus replacing CatchAllDebugBucket, fix timeGetTime to return milliseconds, fix SysSleep to interpret argument as milliseconds, SysGetTick now returns microseconds, harden all syscalls against bad user pointers with CopyFromUser/CopyToUser/MmProbeUser throughout, add networking test apps, port-forward 8080 in runa.bat, split LWIP_DRIVER_CFLAGS, add settings.ini to deploy, remove ethernet.c stub

This commit is contained in:
maksw committed 2026-05-22 18:54:53 +02:00
1 parent 18b4dd0f61
commit df13b8056c
365 files changed
+143263 -1362

No files matched your search

+1
View File
@@ -4,5 +4,6 @@ ahci.sys
fat32.sys
[runtime]
lwip.sys
usb.sys
rtl8139.sys
+25
View File
@@ -0,0 +1,25 @@
# lwip.sys
`lwip.sys` is the TCP/IP stack provider driver. It owns lwIP, `tcpip_thread`,
netif attach, DNS, TCP, UDP, and the provider-side socket implementation.
The core kernel owns only:
- `NET_DEVICE` registration and Ethernet frame transport
- `NET_STACK_PROVIDER` registration
- process-owned socket handles
- syscall/user-memory boundaries
Build notes:
- Do not use lwIP CMake.
- `build.bat` compiles the sources listed in `drivers/lwip/lwip_sources.txt`.
- Include directories:
- `code/drivers/lwip/include`
- `code/third_party/lwip/src/include`
Load order:
`drivers.ini` should load `lwip.sys` before NIC drivers so the provider is
ready when the NIC registers. The kernel also supports provider-after-device
registration, but provider-first keeps boot logs cleaner.
@@ -0,0 +1 @@
#pragma pack(push, 1)
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#ifndef LWIP_ARCH_CC_H
#define LWIP_ARCH_CC_H
#include <stdint.h>
#include <stddef.h>
#include <ke_debug.h>
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1234
#endif
#ifndef BIG_ENDIAN
#define BIG_ENDIAN 4321
#endif
#ifndef BYTE_ORDER
#define BYTE_ORDER LITTLE_ENDIAN
#endif
/* MSVC/freestanding kernel-friendly printf format fallbacks. */
#define LWIP_NO_INTTYPES_H 1
#define X8_F "02x"
#define U16_F "hu"
#define S16_F "hd"
#define X16_F "hx"
#define U32_F "lu"
#define S32_F "ld"
#define X32_F "lx"
#define SZT_F "llu"
#define LWIP_NO_CTYPE_H 1
#define LWIP_TIMEVAL_PRIVATE 1
#define LWIP_PROVIDE_ERRNO 1
/* Used by SYS_LIGHTWEIGHT_PROT. Keep it wide enough for saved RFLAGS. */
typedef uintptr_t sys_prot_t;
#define LWIP_PLATFORM_DIAG(x) do { DbgPrint x; } while (0)
#define LWIP_PLATFORM_ASSERT(x) do { KePanic("lwIP assert: %s\n", (x)); } while (0)
unsigned int lwip_port_rand(void);
#define LWIP_RAND() ((uint32_t)lwip_port_rand())
/* Force lwIP's static heap/aligned buffers to be at least 8-byte aligned. */
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) \
__declspec(align(8)) uint8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
#define PACK_STRUCT_USE_INCLUDES
#endif /* LWIP_ARCH_CC_H */
@@ -0,0 +1 @@
#pragma pack(pop)
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#ifndef LWIP_ARCH_SYS_ARCH_H
#define LWIP_ARCH_SYS_ARCH_H
#include <stdint.h>
#include <lwip/opt.h>
#ifdef __cplusplus
extern "C" {
#endif
struct sys_sem;
typedef struct sys_sem* sys_sem_t;
#define SYS_SEM_NULL NULL
#define sys_sem_valid(sem) ((sem) && (*(sem) != NULL))
#define sys_sem_valid_val(sem) ((sem) != NULL)
#define sys_sem_set_invalid(sem) do { if ((sem)) { *(sem) = NULL; } } while (0)
#define sys_sem_set_invalid_val(sem) do { (sem) = NULL; } while (0)
struct sys_mutex;
typedef struct sys_mutex* sys_mutex_t;
#define sys_mutex_valid(mutex) ((mutex) && (*(mutex) != NULL))
#define sys_mutex_valid_val(mutex) ((mutex) != NULL)
#define sys_mutex_set_invalid(mutex) do { if ((mutex)) { *(mutex) = NULL; } } while (0)
struct sys_mbox;
typedef struct sys_mbox* sys_mbox_t;
#define SYS_MBOX_NULL NULL
#define sys_mbox_valid(mbox) ((mbox) && (*(mbox) != NULL))
#define sys_mbox_valid_val(mbox) ((mbox) != NULL)
#define sys_mbox_set_invalid(mbox) do { if ((mbox)) { *(mbox) = NULL; } } while (0)
#define sys_mbox_set_invalid_val(mbox) do { (mbox) = NULL; } while (0)
/*
* lwIP only treats sys_thread_t as an opaque handle. Keep it pointer-sized.
*
* Do NOT make this a by-value incomplete struct. MSVC may compile callers
* that see only the incomplete type with a hidden struct-return parameter,
* which shifts sys_thread_new(name, thread, arg, ...) so the callee receives
* thread == name and arg == thread. The first tcpip_thread then jumps into
* the literal string "tcpip" as code.
*/
typedef void* sys_thread_t;
void LwipPortSetTimerQuery(uint64_t (*query_ticks_us)(void));
#ifdef __cplusplus
}
#endif
#endif /* LWIP_ARCH_SYS_ARCH_H */
+91
View File
@@ -0,0 +1,91 @@
#pragma once
/*
* lwipopts.h - kernel port configuration for lwIP 2.2.1.
*
* This is intentionally a practical first configuration: IPv4, ARP, ICMP,
* UDP, TCP, DHCP, DNS and netconn are enabled; IPv6, PPP,
* SNMP, lwIP's POSIX socket API, and app protocols stay off.
*/
#define NO_SYS 0
#define SYS_LIGHTWEIGHT_PROT 1
#define MEM_ALIGNMENT 8
/* Keep lwIP memory self-contained. It uses its own heap/pools, not malloc(). */
#define MEM_LIBC_MALLOC 0
#define MEMP_MEM_MALLOC 0
#define MEM_SIZE (256 * 1024)
#define MEMP_OVERFLOW_CHECK 0
#define MEMP_SANITY_CHECK 0
#define PBUF_POOL_SIZE 64
#define PBUF_POOL_BUFSIZE 1536
#define MEMP_NUM_PBUF 32
#define MEMP_NUM_NETBUF 32
#define MEMP_NUM_NETCONN 32
#define MEMP_NUM_TCPIP_MSG_API 64
#define MEMP_NUM_TCPIP_MSG_INPKT 64
#define MEMP_NUM_SYS_TIMEOUT 32
#define LWIP_IPV4 1
#define LWIP_IPV6 0
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_IGMP 0
#define LWIP_RAW 1
#define LWIP_UDP 1
#define LWIP_TCP 1
#define LWIP_DHCP 1
#define LWIP_AUTOIP 0
#define LWIP_DNS 1
#define TCP_MSS 1460
#define TCP_WND (4 * TCP_MSS)
#define TCP_SND_BUF (4 * TCP_MSS)
#define TCP_SND_QUEUELEN 16
#define MEMP_NUM_TCP_PCB 16
#define MEMP_NUM_TCP_PCB_LISTEN 8
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_UDP_PCB 16
#define LWIP_NETCONN 1
#define LWIP_SOCKET 0
#define LWIP_COMPAT_SOCKETS 0
#define LWIP_POSIX_SOCKETS_IO_NAMES 0
#define LWIP_SOCKET_SELECT 0
#define LWIP_SO_RCVTIMEO 1
#define LWIP_SO_SNDTIMEO 1
#define LWIP_SO_RCVBUF 1
#define LWIP_SO_REUSE 1
#define LWIP_TCP_KEEPALIVE 1
#define TCPIP_THREAD_NAME "tcpip"
#define TCPIP_THREAD_STACKSIZE 0
#define TCPIP_THREAD_PRIO 2
#define TCPIP_MBOX_SIZE 128
#define DEFAULT_THREAD_STACKSIZE 0
#define DEFAULT_THREAD_PRIO 1
#define DEFAULT_RAW_RECVMBOX_SIZE 32
#define DEFAULT_UDP_RECVMBOX_SIZE 64
#define DEFAULT_TCP_RECVMBOX_SIZE 64
#define DEFAULT_ACCEPTMBOX_SIZE 32
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_CHECKSUM_CTRL_PER_NETIF 0
#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
#define LWIP_TIMERS 1
#define LWIP_TCPIP_TIMEOUT 1
#define LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS 1
#define LWIP_STATS 0
#define LWIP_DEBUG 0
#define LWIP_NOASSERT 0
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#ifndef LWIP_PORT_STDLIB_H
#define LWIP_PORT_STDLIB_H
/*
* Minimal freestanding stdlib shim for lwIP.
*
* lwIP 2.2.1 includes <stdlib.h> in a couple of files only for atoi(). If MSVC
* finds the UCRT header, it drags errno.h into the kernel build and then lwIP's
* own errno values collide with UCRT's. Put this header first in lwIP's include
* path so those sources stay freestanding.
*/
static __inline int atoi(const char* s) {
int sign = 1;
int value = 0;
if (!s)
return 0;
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r' || *s == '\v' || *s == '\f')
++s;
if (*s == '-') {
sign = -1;
++s;
} else if (*s == '+') {
++s;
}
while (*s >= '0' && *s <= '9') {
value = value * 10 + (*s - '0');
++s;
}
return sign * value;
}
#endif /* LWIP_PORT_STDLIB_H */
+50
View File
@@ -0,0 +1,50 @@
# Add these lwIP 2.2.1 files to the lwip.sys driver build.
# Include directories required:
# code/drivers/lwip/include
# code/third_party/lwip/src/include
# Port glue
drivers/lwip/lwip_sys_arch.c
drivers/lwip/lwip_stack.c
# lwIP core
third_party/lwip/src/core/init.c
third_party/lwip/src/core/def.c
third_party/lwip/src/core/dns.c
third_party/lwip/src/core/inet_chksum.c
third_party/lwip/src/core/ip.c
third_party/lwip/src/core/mem.c
third_party/lwip/src/core/memp.c
third_party/lwip/src/core/netif.c
third_party/lwip/src/core/pbuf.c
third_party/lwip/src/core/raw.c
third_party/lwip/src/core/stats.c
third_party/lwip/src/core/sys.c
third_party/lwip/src/core/altcp.c
third_party/lwip/src/core/altcp_alloc.c
third_party/lwip/src/core/altcp_tcp.c
third_party/lwip/src/core/tcp.c
third_party/lwip/src/core/tcp_in.c
third_party/lwip/src/core/tcp_out.c
third_party/lwip/src/core/timeouts.c
third_party/lwip/src/core/udp.c
# IPv4
third_party/lwip/src/core/ipv4/acd.c
third_party/lwip/src/core/ipv4/autoip.c
third_party/lwip/src/core/ipv4/dhcp.c
third_party/lwip/src/core/ipv4/etharp.c
third_party/lwip/src/core/ipv4/icmp.c
third_party/lwip/src/core/ipv4/igmp.c
third_party/lwip/src/core/ipv4/ip4_frag.c
third_party/lwip/src/core/ipv4/ip4.c
third_party/lwip/src/core/ipv4/ip4_addr.c
# netconn/socket API
third_party/lwip/src/api/api_lib.c
third_party/lwip/src/api/api_msg.c
third_party/lwip/src/api/err.c
third_party/lwip/src/api/netbuf.c
third_party/lwip/src/api/tcpip.c
# Ethernet netif helper
third_party/lwip/src/netif/ethernet.c
File diff suppressed because it is too large. Load diff
+363
View File
@@ -0,0 +1,363 @@
// lwip_sys_arch.c - lwIP OS abstraction layer for the kernel.
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <intrin.h>
#include <lwip/opt.h>
#include <lwip/sys.h>
#include <lwip/err.h>
#include <mm_virt.h>
#include <ob_object.h>
#include <ke_debug.h>
#include <ke_sched.h>
#include <ke_thread.h>
#include <ke_semaphore.h>
#include <ke_spinlock.h>
#include <ke_irql.h>
#define LWIP_PORT_MBOX_DEFAULT_SIZE 128u
struct sys_sem {
SEMAPHORE* Sem;
};
struct sys_mutex {
SEMAPHORE* Sem;
};
struct sys_mbox {
SPINLOCK Lock;
SEMAPHORE* Items;
void** Ring;
uint32_t Size;
uint32_t Head;
uint32_t Tail;
uint32_t Count;
uint32_t Valid;
};
static uint64_t (*gLwipQueryTicksUs)(void) = 0;
static uint32_t gRandState = 0x4b4e4554u; /* 'KNET' */
void LwipPortSetTimerQuery(uint64_t (*query_ticks_us)(void)) {
gLwipQueryTicksUs = query_ticks_us;
}
unsigned int lwip_port_rand(void) {
/* Tiny deterministic LCG. Good enough for ephemeral port randomisation. */
gRandState = gRandState * 1664525u + 1013904223u;
return gRandState;
}
void sys_init(void) {
}
u32_t sys_now(void) {
if (!gLwipQueryTicksUs)
return 0;
return (u32_t)(gLwipQueryTicksUs() / 1000u);
}
u32_t sys_jiffies(void) {
return sys_now();
}
/*
* lwIP's core/sys.c provides sys_msleep() when it is not a macro. Do not
* define it here as well, or MSVC links both sys.obj and lwip_sys_arch.obj
* with a duplicate sys_msleep symbol.
*/
/*
* First bring-up errno storage for lwIP's socket/netdb compatibility layer.
* This is global, not per-thread; the kernel socket-object wrapper should
* eventually translate lwIP errors to KSTATUS instead of exposing this.
*/
// fuckass microsoft
#ifdef errno
#undef errno
#endif
int errno;
int* __cdecl _errno(void) { return &errno; }
err_t sys_sem_new(sys_sem_t* sem, u8_t count) {
if (!sem)
return ERR_ARG;
struct sys_sem* s = (struct sys_sem*)MmAllocatePool(sizeof(*s));
if (!s)
return ERR_MEM;
memset(s, 0, sizeof(*s));
SEMAPHORE* ksem = 0;
if (!KSUCCESS(KeSemaphoreCreate(count ? 1 : 0, 0x7fffffff, &ksem))) {
MmFreePool(s);
return ERR_MEM;
}
s->Sem = ksem;
*sem = s;
return ERR_OK;
}
void sys_sem_signal(sys_sem_t* sem) {
if (sem && *sem && (*sem)->Sem)
KeSignalSemaphore((*sem)->Sem);
}
u32_t sys_arch_sem_wait(sys_sem_t* sem, u32_t timeout) {
if (!sem || !*sem || !(*sem)->Sem)
return SYS_ARCH_TIMEOUT;
u32_t start = sys_now();
if (timeout == 0) {
KSTATUS st = KeWaitSemaphore((*sem)->Sem);
return KSUCCESS(st) ? (u32_t)(sys_now() - start) : SYS_ARCH_TIMEOUT;
}
for (;;) {
if (KSUCCESS(KeTrySemaphore((*sem)->Sem)))
return (u32_t)(sys_now() - start);
if ((u32_t)(sys_now() - start) >= timeout)
return SYS_ARCH_TIMEOUT;
KeYield();
}
}
void sys_sem_free(sys_sem_t* sem) {
if (!sem || !*sem)
return;
if ((*sem)->Sem)
ObDereferenceObject(&(*sem)->Sem->Header);
MmFreePool(*sem);
*sem = 0;
}
err_t sys_mutex_new(sys_mutex_t* mutex) {
if (!mutex)
return ERR_ARG;
struct sys_mutex* m = (struct sys_mutex*)MmAllocatePool(sizeof(*m));
if (!m)
return ERR_MEM;
memset(m, 0, sizeof(*m));
SEMAPHORE* sem = 0;
if (!KSUCCESS(KeSemaphoreCreate(1, 1, &sem))) {
MmFreePool(m);
return ERR_MEM;
}
m->Sem = sem;
*mutex = m;
return ERR_OK;
}
void sys_mutex_lock(sys_mutex_t* mutex) {
if (mutex && *mutex && (*mutex)->Sem)
(void)KeWaitSemaphore((*mutex)->Sem);
}
void sys_mutex_unlock(sys_mutex_t* mutex) {
if (mutex && *mutex && (*mutex)->Sem)
KeSignalSemaphore((*mutex)->Sem);
}
void sys_mutex_free(sys_mutex_t* mutex) {
if (!mutex || !*mutex)
return;
if ((*mutex)->Sem)
ObDereferenceObject(&(*mutex)->Sem->Header);
MmFreePool(*mutex);
*mutex = 0;
}
static err_t LwipMboxTryPost(sys_mbox_t* mbox, void* msg) {
if (!mbox || !*mbox || !(*mbox)->Valid)
return ERR_ARG;
struct sys_mbox* m = *mbox;
uintptr_t flags = KeAcquireSpinlock(&m->Lock);
if (m->Count >= m->Size) {
KeReleaseSpinlock(&m->Lock, flags);
return ERR_MEM;
}
m->Ring[m->Tail] = msg;
m->Tail = (m->Tail + 1u) % m->Size;
m->Count++;
KeReleaseSpinlock(&m->Lock, flags);
KeSignalSemaphore(m->Items);
return ERR_OK;
}
err_t sys_mbox_new(sys_mbox_t* mbox, int size) {
if (!mbox)
return ERR_ARG;
uint32_t n = (size > 0) ? (uint32_t)size : LWIP_PORT_MBOX_DEFAULT_SIZE;
struct sys_mbox* m = (struct sys_mbox*)MmAllocatePool(sizeof(*m));
if (!m)
return ERR_MEM;
memset(m, 0, sizeof(*m));
m->Ring = (void**)MmAllocatePool(sizeof(void*) * n);
if (!m->Ring) {
MmFreePool(m);
return ERR_MEM;
}
memset(m->Ring, 0, sizeof(void*) * n);
SEMAPHORE* items = 0;
if (!KSUCCESS(KeSemaphoreCreate(0, (int32_t)n, &items))) {
MmFreePool(m->Ring);
MmFreePool(m);
return ERR_MEM;
}
m->Lock = (SPINLOCK)SPINLOCK_INIT;
m->Items = items;
m->Size = n;
m->Valid = 1;
*mbox = m;
return ERR_OK;
}
void sys_mbox_post(sys_mbox_t* mbox, void* msg) {
/* lwIP promises this is not called from ISR context. */
while (LwipMboxTryPost(mbox, msg) != ERR_OK)
KeYield();
}
err_t sys_mbox_trypost(sys_mbox_t* mbox, void* msg) {
return LwipMboxTryPost(mbox, msg);
}
err_t sys_mbox_trypost_fromisr(sys_mbox_t* mbox, void* msg) {
return LwipMboxTryPost(mbox, msg);
}
static u32_t LwipMboxPopAfterItem(sys_mbox_t* mbox, void** msg, u32_t waited) {
struct sys_mbox* m = *mbox;
uintptr_t flags = KeAcquireSpinlock(&m->Lock);
if (m->Count == 0) {
KeReleaseSpinlock(&m->Lock, flags);
return SYS_ARCH_TIMEOUT;
}
void* value = m->Ring[m->Head];
m->Ring[m->Head] = 0;
m->Head = (m->Head + 1u) % m->Size;
m->Count--;
KeReleaseSpinlock(&m->Lock, flags);
if (msg)
*msg = value;
return waited;
}
u32_t sys_arch_mbox_fetch(sys_mbox_t* mbox, void** msg, u32_t timeout) {
if (!mbox || !*mbox || !(*mbox)->Valid)
return SYS_ARCH_TIMEOUT;
u32_t start = sys_now();
if (timeout == 0) {
KSTATUS st = KeWaitSemaphore((*mbox)->Items);
if (!KSUCCESS(st))
return SYS_ARCH_TIMEOUT;
return LwipMboxPopAfterItem(mbox, msg, (u32_t)(sys_now() - start));
}
for (;;) {
if (KSUCCESS(KeTrySemaphore((*mbox)->Items)))
return LwipMboxPopAfterItem(mbox, msg, (u32_t)(sys_now() - start));
if ((u32_t)(sys_now() - start) >= timeout)
return SYS_ARCH_TIMEOUT;
KeYield();
}
}
u32_t sys_arch_mbox_tryfetch(sys_mbox_t* mbox, void** msg) {
if (!mbox || !*mbox || !(*mbox)->Valid)
return SYS_MBOX_EMPTY;
if (!KSUCCESS(KeTrySemaphore((*mbox)->Items)))
return SYS_MBOX_EMPTY;
return LwipMboxPopAfterItem(mbox, msg, 0);
}
void sys_mbox_free(sys_mbox_t* mbox) {
if (!mbox || !*mbox)
return;
struct sys_mbox* m = *mbox;
m->Valid = 0;
if (m->Items)
ObDereferenceObject(&m->Items->Header);
if (m->Ring)
MmFreePool(m->Ring);
MmFreePool(m);
*mbox = 0;
}
sys_thread_t sys_thread_new(const char* name, lwip_thread_fn thread, void* arg,
int stacksize, int prio) {
(void)name;
(void)stacksize;
if (!thread)
KePanic("lwIP: sys_thread_new called with null entry\n");
THREAD_PRIORITY kp = PriorityNormal;
if (prio >= 2)
kp = PriorityHigh;
else if (prio <= 0)
kp = PriorityLow;
/*
* lwIP thread entries already use the same ABI as kernel worker threads:
*
* void entry(void* arg)
*
* Do not bounce through a heap-allocated thunk. The first scaffold used a
* tiny { function, argument } heap block and a wrapper, but an early boot
* crash showed the wrapper sometimes loaded a bogus function pointer and
* jumped into lwIP's .rdata strings. Passing the lwIP entry directly keeps
* the initial thread stack simple: KeKernelThreadEntry calls exactly the
* function pointer that lwIP supplied, with exactly the argument it supplied.
*/
THREAD* t = 0;
KSTATUS st = KeCreateKernelThread((uintptr_t)thread,
(uintptr_t)arg,
kp,
&t);
if (!KSUCCESS(st))
KePanic("lwIP: failed to create kernel thread: 0x%x\n", st);
return (sys_thread_t)t;
}
sys_prot_t sys_arch_protect(void) {
uintptr_t flags = __readeflags();
_disable();
return (sys_prot_t)flags;
}
void sys_arch_unprotect(sys_prot_t pval) {
if (((uintptr_t)pval) & 0x200u)
_enable();
else
_disable();
}