From b509ee30d151f150dc1b8a2257c895e02926f2aa Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Mon, 27 Apr 2026 00:01:09 +0200 Subject: [PATCH 1/2] lwIP: upgrade to upstream STABLE-2_2_1_RELEASE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the vendored lwIP from STABLE-2_0_3_RELEASE to STABLE-2_2_1_RELEASE (7+ years of upstream fixes / features) on both the IOP and EE networking stacks. ps2sdk-side adjustments to track the upstream API and tune for the PS2 targets: iop/tcpip and ee/network/tcpip lwipopts.h: - LWIP_TCPIP_CORE_LOCKING=1 + LWIP_TCPIP_CORE_LOCKING_INPUT=1 (lwIP 2.2.1 default). Socket / netconn API calls take a binary-sem mutex on the calling app thread instead of round-tripping through the tcpip thread mailbox. lwIP releases the lock before any blocking I/O wait so the tcpip thread + netif input still make progress. Saves a context switch + sem wait per API call versus message passing. - DEFAULT_THREAD_STACKSIZE=0x600 on IOP (matches the historical lwIP 2.0.3 budget; the deep socket call chains run on app threads under core-locking, the tcpip thread only handles timers + tcpip_callback dispatch, max ~450 B measured via -fstack-usage). - LWIP_DHCP_DOES_ACD_CHECK=0 + LWIP_ACD=0: no AutoIP, controlled LAN, the conflict-detection timer/code is dead weight here. - Pruned three options removed/renamed in upstream: LWIP_SOCKET_SET_ERRNO (gone since 2017's commit 0ee6ad0a), DHCP_DOES_ARP_CHECK (renamed to LWIP_DHCP_DOES_ACD_CHECK in 2.2.0), LWIP_DHCP_CHECK_LINK_UP (no longer referenced anywhere in 2.2.1). ee/network/tcpip lwipopts.h MEM_ALIGNMENT: - Drop from 64 (historical "EE cache design" value, attributed to SP193) to 16, matching the alignment newlib's malloc returns on EE. With MEM_ALIGNMENT=64, pbuf_alloc(PBUF_RAM) computed payload = LWIP_MEM_ALIGN(p + SIZEOF_STRUCT_PBUF + offset) inside an allocation sized assuming p was already 64-byte aligned; newlib only guarantees 16, set by newlib/newlib/configure.host:254 mips64r5900*) machine_dir=r5900 newlib_cflags="${newlib_cflags} -DMALLOC_ALIGNMENT=16" ;; so the payload pointer slid past the allocation by up to 48 bytes and scribbled into the next chunk's header — TLB misses inside _malloc_r / _free_r after the first close cycle of any TCP server (real hw locks up; PCSX2 logs the misses but limps on). PBUF_POOL pbufs (the only ones touched by IOP->EE DMA + cache invalidate) come from memp's static pools and are 64-byte aligned regardless of MEM_ALIGNMENT, so the cache-design invariant the old comment cited is preserved by memp, not by MEM_ALIGNMENT. The IOP-side lwipopts has used MEM_ALIGNMENT=4 forever for the same reason. iop/tcpip API: - tcpip_callback_with_block was promoted to tcpip_callback (the macro became the real function in lwIP 2.1.0); update exports.tab, imports.lst, and call sites. - iop/network/smap/src/imports.lst: corresponding rename for the netif callbacks the smap driver imports from ps2ip-nm.irx. - ps2ip.c: define `int errno` in .data so socket error paths have somewhere to write to (lwIP 2.2.1 always writes errno; the section attribute was already corrected in the previous commit). iop/tcpip-base/sys_arch.c: - Add sys_mbox_trypost_fromisr stub (new in lwIP 2.2.1, called from tcpip_input when LWIP_TCPIP_CORE_LOCKING_INPUT=0; harmless in our build but the symbol must be present). ee/network/tcpip/src/sys_arch.c: - Replace the previous DI/EI-based sys_arch_protect with a per-thread recursive semaphore. The DI/EI variant deadlocked the EE: any code path inside a SYS_ARCH_PROTECT region that ended up calling newlib's malloc/free would WaitSema on the heap recursive mutex with interrupts disabled. The sema-based variant lets nested waits work normally and removes the EE-specific incompatibility between lwIP and any other library that uses newlib's locks. lwIP allows SYS_ARCH_PROTECT to nest, so the implementation tracks the owning thread + a recursion counter and only Wait/Signal on the outermost transitions. iop/tcpip/tcpip/Makefile + ps2api_IPV4 list: - acd.c (new in lwIP 2.2.0) intentionally not added; LWIP_ACD=0 makes it a no-op TU and we'd rather drop the few KB outright. Verified on real hardware: ps2link boots, execee + reset cycle works repeatedly, IOP printf-over-UDP (KPRTTY) coexists cleanly with module loading. EE-side TCP server (ps2_drivers/samples/tcp_server_ee) and mongoose-based ps2_http both sustain 100/100 burst tests with 0 TLB misses, exercising the EE lwipopts MEM_ALIGNMENT and sys_arch.c adjustments above. Co-Authored-By: Claude Opus 4.7 (1M context) --- download_dependencies.sh | 2 +- ee/network/tcpip/Makefile | 4 ++ ee/network/tcpip/src/include/lwipopts.h | 88 ++++++++++++++++--------- ee/network/tcpip/src/sys_arch.c | 75 +++++++++++++++++---- iop/network/smap/src/imports.lst | 2 +- iop/tcpip/tcpip-base/include/lwipopts.h | 59 ++++++++++------- iop/tcpip/tcpip-base/sys_arch.c | 7 ++ iop/tcpip/tcpip-netman/src/exports.tab | 2 +- iop/tcpip/tcpip-netman/src/imports.lst | 3 + iop/tcpip/tcpip-netman/src/ps2ip.c | 7 ++ iop/tcpip/tcpip/include/ps2ip.h | 10 +-- iop/tcpip/tcpip/src/exports.tab | 2 +- iop/tcpip/tcpip/src/imports.lst | 3 + iop/tcpip/tcpip/src/ps2ip.c | 7 ++ 14 files changed, 195 insertions(+), 76 deletions(-) diff --git a/download_dependencies.sh b/download_dependencies.sh index 7b0382c24241..2a5a9611c19f 100755 --- a/download_dependencies.sh +++ b/download_dependencies.sh @@ -16,7 +16,7 @@ fi ## Download LWIP (upstream, unpatched) LWIP_REPO_URL="https://github.com/lwip-tcpip/lwip.git" LWIP_REPO_FOLDER="common/external_deps/lwip" -LWIP_BRANCH_NAME="STABLE-2_0_3_RELEASE" +LWIP_BRANCH_NAME="STABLE-2_2_1_RELEASE" if test ! -d "$LWIP_REPO_FOLDER"; then git clone --depth 1 -b $LWIP_BRANCH_NAME $LWIP_REPO_URL "$LWIP_REPO_FOLDER"_inprogress || exit 1 mv "$LWIP_REPO_FOLDER"_inprogress "$LWIP_REPO_FOLDER" diff --git a/ee/network/tcpip/Makefile b/ee/network/tcpip/Makefile index fc161a99eec2..cae15eb6bf75 100644 --- a/ee/network/tcpip/Makefile +++ b/ee/network/tcpip/Makefile @@ -44,6 +44,7 @@ ps2api_OBJECTS = \ tcpip.o ps2api_IPV4 = \ + acd.o \ icmp.o \ ip.o \ ip4.o \ @@ -114,6 +115,9 @@ $(EE_OBJS_DIR)api_msg.o: $(LWIP)/src/api/api_msg.c $(EE_OBJS_DIR)api_netbuf.o: $(LWIP)/src/api/netbuf.c $(EE_CC) $(EE_CFLAGS) $(EE_INCS) -c $< -o $@ +$(EE_OBJS_DIR)acd.o: $(LWIP)/src/core/ipv4/acd.c + $(EE_CC) $(EE_CFLAGS) $(EE_INCS) -c $< -o $@ + $(EE_OBJS_DIR)icmp.o: $(LWIP)/src/core/ipv4/icmp.c $(EE_CC) $(EE_CFLAGS) $(EE_INCS) -c $< -o $@ diff --git a/ee/network/tcpip/src/include/lwipopts.h b/ee/network/tcpip/src/include/lwipopts.h index 4dc8740a87e3..4830a21332f3 100644 --- a/ee/network/tcpip/src/include/lwipopts.h +++ b/ee/network/tcpip/src/include/lwipopts.h @@ -41,17 +41,47 @@ ------------------------------------ */ /** - * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by the C-library - * instead of lwIP's internal allocator. Default is 0; enabled on EE - * because newlib's malloc is already linked in and the heap pool is - * cheaper than a duplicate lwIP heap. + * MEM_LIBC_MALLOC==1: use libc's malloc/free for lwIP's mem_malloc / + * mem_free (which serve PBUF_RAM allocations and a handful of other + * sites — DHCP options, ARP, DNS, slip/ppp/zepif which we don't build). + * Internally lwIP's pbuf_alloc(PBUF_RAM) computes the payload pointer + * with `LWIP_MEM_ALIGN(p + SIZEOF_STRUCT_PBUF + offset)`, which only + * stays inside the allocated chunk when 'p' is already MEM_ALIGNMENT- + * aligned. That is why MEM_ALIGNMENT must match the underlying + * allocator's alignment — see MEM_ALIGNMENT below. */ #define MEM_LIBC_MALLOC 1 -/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which - lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2 - byte alignment -> define MEM_ALIGNMENT to 2. */ -#define MEM_ALIGNMENT 64 //SP193: must be 64, to deal with the EE cache design. +/* MEM_ALIGNMENT: must equal the alignment libc's malloc returns, + because pbuf_alloc(PBUF_RAM) computes + payload = LWIP_MEM_ALIGN(p + SIZEOF_STRUCT_PBUF + offset) + inside an allocation sized assuming p is already MEM_ALIGNMENT- + aligned. If MEM_ALIGNMENT > newlib's alignment the payload pointer + overruns the chunk and corrupts the next-chunk header on the + freelist (TLB misses inside _malloc_r / _free_r). + + 16 is the contract baked into the toolchain configuration. See + newlib/newlib/configure.host: + + mips64r5900*) + machine_dir=r5900 + newlib_cflags="${newlib_cflags} -DMALLOC_ALIGNMENT=16" + ;; + + so for the mips64r5900el-ps2-elf target newlib is built with + -DMALLOC_ALIGNMENT=16, which sets _mallocr.c's MALLOC_ALIGNMENT + directly (overriding the SIZE_SZ-derived default of 8). 16 is also + what samples/malloc_stress observes empirically. + + The historical value here was 64 with a comment about "the EE cache + design" (SP193). That was over-cautious: PBUF_POOL pbufs (the only + ones touched by IOP->EE DMA + cache invalidate) come from memp's + static pools and are always 64-byte aligned regardless of + MEM_ALIGNMENT; PBUF_RAM pbufs (TX, ARP, DNS, ...) only see DMA + writeback, where misalignment harmlessly over-flushes extra cache + lines. The IOP-side lwipopts has used MEM_ALIGNMENT=4 forever for + the same reason. */ +#define MEM_ALIGNMENT 16 /** * MEM_SIZE: the size of the heap memory. If the application will send @@ -103,12 +133,19 @@ #define MEMP_NUM_TCP_SEG TCP_SND_QUEUELEN /** - * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled, - * this lets tcpip_input() grab the mutex for input packets as well, - * instead of allocating a message and passing it to tcpip_thread. - * - * ATTENTION: this does not work when tcpip_input() is called from - * interrupt context! + * LWIP_TCPIP_CORE_LOCKING==1: matches lwIP 2.2.1's upstream default. With + * LWIP_COMPAT_MUTEX in arch/cc.h the core lock is a binary semaphore taken + * directly on the calling app thread for socket/netconn API calls; lwIP + * releases it before any blocking I/O wait so the tcpip thread + netif + * input continue to make progress. Saves a context switch + sem wait per + * API call versus the message-passing alternative. + */ +#define LWIP_TCPIP_CORE_LOCKING 1 + +/** + * LWIP_TCPIP_CORE_LOCKING_INPUT==1: tcpip_input() takes the core mutex + * directly instead of allocating a message. Safe here because the netif + * input callback runs in a regular thread, not interrupt context. */ #define LWIP_TCPIP_CORE_LOCKING_INPUT 1 @@ -134,18 +171,13 @@ #define LWIP_DHCP 1 #endif -/** - * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address. - */ -#define DHCP_DOES_ARP_CHECK 0 //Don't do the ARP check because an IP address would be first required. - -/** - * LWIP_DHCP_CHECK_LINK_UP==1: dhcp_start() only really starts if the netif has - * NETIF_FLAG_LINK_UP set in its flags. As this is only an optimization and - * netif drivers might not set this flag, the default is off. If enabled, - * netif_set_link_up() must be called to continue dhcp starting. - */ -#define LWIP_DHCP_CHECK_LINK_UP 1 +/* LWIP_DHCP_DOES_ACD_CHECK / LWIP_ACD left at upstream defaults (=1 when + * LWIP_DHCP=1) so the RFC 5227 Address Conflict Detection probe runs on + * any DHCP-offered IP. EE applications run on user home networks where + * IP collisions are a real (if uncommon) failure mode; the few KB of + * code and ~1-2 s extra DHCP-bind delay are worth the robustness. The + * IOP lwIP build forces both off because ps2link runs in controlled + * bench environments where the IRX size + tick savings matter more. */ /* ---------------------------------- @@ -208,10 +240,6 @@ ---------- Socket options ---------- ------------------------------------ */ -/* LWIP_SOCKET_SET_ERRNO==1: Set errno when socket functions cannot complete - * successfully, as required by POSIX. Default is POSIX-compliant. - */ -#define LWIP_SOCKET_SET_ERRNO 0 /** * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names. * Disable this option if you use a POSIX operating system that uses the same diff --git a/ee/network/tcpip/src/sys_arch.c b/ee/network/tcpip/src/sys_arch.c index 3b2371591da5..5f7bdae2fc88 100644 --- a/ee/network/tcpip/src/sys_arch.c +++ b/ee/network/tcpip/src/sys_arch.c @@ -338,6 +338,15 @@ err_t sys_mbox_trypost(sys_mbox_t *mbox, void *sys_msg) return result; } +/* lwIP 2.2.x distinguishes ISR-context posts from task-context posts. + The PS2 EE has preemptive scheduling, but our netif input does not run + in interrupt context (it goes through ps2ip / SIF callbacks on the EE + tcpip thread), so the two paths are equivalent here. */ +err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg) +{ + return sys_mbox_trypost(mbox, msg); +} + void sys_mbox_post(sys_mbox_t *mbox, void *sys_msg) { SendMbx(mbox, alloc_msg(), sys_msg); @@ -415,6 +424,52 @@ void sys_sem_set_invalid(sys_sem_t *sem){ *sem=SYS_SEM_NULL; } +/* Semaphore-based critical section for lwIP. Replaces the previous + * DIntr/EIntr approach, which was unsafe: any code path inside a + * SYS_ARCH_PROTECT region that ended up calling newlib's malloc/free + * would try to WaitSema on the heap recursive mutex with interrupts + * disabled, deadlocking the EE. The sema-based variant lets nested + * waits work normally and removes the EE-specific incompatibility + * between lwIP and any other library that uses newlib's locks. + * + * Recursive ownership: lwIP allows SYS_ARCH_PROTECT to nest, so we + * track the owning thread + a recursion counter and only Wait/Signal + * on the outermost transitions. + */ +static int s_protect_sem = -1; +static int s_protect_count = 0; +static int s_protect_owner = -1; + +sys_prot_t sys_arch_protect(void) +{ + int tid = GetThreadId(); + if (s_protect_count > 0 && s_protect_owner == tid) + { + s_protect_count++; + return 0; /* nested re-entry; outer call will release */ + } + WaitSema(s_protect_sem); + s_protect_owner = tid; + s_protect_count = 1; + return 1; /* outermost level; matching unprotect will release */ +} + +void sys_arch_unprotect(sys_prot_t level) +{ + if (level == 0) + { + /* nested unprotect; just decrement */ + if (s_protect_count > 0) + { + s_protect_count--; + } + return; + } + s_protect_count = 0; + s_protect_owner = -1; + SignalSema(s_protect_sem); +} + void sys_init(void) { arch_message *prev; @@ -428,6 +483,15 @@ void sys_init(void) sema.init_count = sema.max_count = SYS_MAX_MESSAGES; MsgCountSema=CreateSema(&sema); + /* Critical-section sema: binary mutex (init=1, max=1). */ + sema.attr = 0; + sema.option = (u32)"PS2IP_PROTECT"; + sema.init_count = 1; + sema.max_count = 1; + s_protect_sem = CreateSema(&sema); + s_protect_count = 0; + s_protect_owner = -1; + free_head = &msg_pool[0]; prev = &msg_pool[0]; @@ -446,17 +510,6 @@ u32_t sys_now(void) return(clock()/1000); } -sys_prot_t sys_arch_protect(void) -{ - return DIntr(); -} - -void sys_arch_unprotect(sys_prot_t level) -{ - if(level) - EIntr(); -} - void *ps2ip_calloc64(size_t n, size_t size) { void *ptr = NULL; diff --git a/iop/network/smap/src/imports.lst b/iop/network/smap/src/imports.lst index af520b8c0e72..e492cde354ce 100644 --- a/iop/network/smap/src/imports.lst +++ b/iop/network/smap/src/imports.lst @@ -66,7 +66,7 @@ I_inet_addr I_tcpip_input I_netif_set_link_up I_netif_set_link_down -I_tcpip_callback_with_block +I_tcpip_callback ps2ip_IMPORTS_end #endif diff --git a/iop/tcpip/tcpip-base/include/lwipopts.h b/iop/tcpip/tcpip-base/include/lwipopts.h index 1c1443a93601..85c2f61f603d 100644 --- a/iop/tcpip/tcpip-base/include/lwipopts.h +++ b/iop/tcpip/tcpip-base/include/lwipopts.h @@ -8,9 +8,21 @@ /* ---------- Thread options ---------- */ /** - * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread. - * The stack size value itself is platform-dependent, but is passed to - * sys_thread_new() when the thread is created. + * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread + * spawned via sys_thread_new(). In our build that's just the tcpip thread. + * + * With LWIP_TCPIP_CORE_LOCKING=1 the deep socket-API call chains run on + * the calling app thread, not on the tcpip thread; the tcpip thread itself + * only dispatches timer callbacks and the occasional tcpip_callback (e.g. + * link up/down). Worst-case chain on the tcpip thread is roughly + * + * tcpip_thread (56) -> sys_check_timeouts (32) -> lwip_cyclic_timer (32) + * -> tcp_slowtmr (72) or dhcp_fine_tmr -> ~150-200 of inner work + * ~= 350-450 bytes + register-save overhead. + * + * 0x600 (1.5 KB) is the historical 2.0.3 value and matches the call-chain + * profile under LWIP_TCPIP_CORE_LOCKING=1. ~2x margin over the measured + * worst case. */ #define DEFAULT_THREAD_STACKSIZE 0x600 @@ -108,12 +120,20 @@ #define PBUF_POOL_SIZE 32 //SP193: should be at least ((TCP_WND/PBUF_POOL_BUFSIZE)+1). But that is too small to handle simultaneous connections. /** - * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled, - * this lets tcpip_input() grab the mutex for input packets as well, - * instead of allocating a message and passing it to tcpip_thread. - * - * ATTENTION: this does not work when tcpip_input() is called from - * interrupt context! + * LWIP_TCPIP_CORE_LOCKING==1: matches lwIP 2.2.1's upstream default. Socket + * and netconn API calls take the core mutex on the calling app thread and + * run synchronously, instead of round-tripping through the tcpip thread's + * mailbox. Saves a context switch + sem wait per API call. lwIP releases + * the core lock before any blocking I/O wait (mbox_fetch on connection + * mboxes), so the tcpip thread and SMAP RX can still run to deliver data. + */ +#define LWIP_TCPIP_CORE_LOCKING 1 + +/** + * LWIP_TCPIP_CORE_LOCKING_INPUT==1: tcpip_input() takes the core mutex + * directly instead of allocating a message. Safe here because the netif + * input callback runs in IntrHandlerThread (smap.c) — a normal thread, + * not interrupt context — so the lock acquire is allowed. */ #define LWIP_TCPIP_CORE_LOCKING_INPUT 1 @@ -140,17 +160,14 @@ #endif /** - * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address. - */ -#define DHCP_DOES_ARP_CHECK 0 //Don't do the ARP check because an IP address would be first required. - -/** - * LWIP_DHCP_CHECK_LINK_UP==1: dhcp_start() only really starts if the netif has - * NETIF_FLAG_LINK_UP set in its flags. As this is only an optimization and - * netif drivers might not set this flag, the default is off. If enabled, - * netif_set_link_up() must be called to continue dhcp starting. + * LWIP_DHCP_DOES_ACD_CHECK==0: skip RFC 5227 Address Conflict Detection on + * the DHCP-offered address (replaces the pre-2.2.0 DHCP_DOES_ARP_CHECK). + * PS2 networking targets a controlled LAN; the saved code+timer/RAM beats + * guarding against a vanishingly unlikely IP collision. Combined with + * LWIP_AUTOIP=0 (default) this lets LWIP_ACD default to 0 too. */ -#define LWIP_DHCP_CHECK_LINK_UP 1 +#define LWIP_DHCP_DOES_ACD_CHECK 0 +#define LWIP_ACD 0 /* ---------------------------------- @@ -213,10 +230,6 @@ ---------- Socket options ---------- ------------------------------------ */ -/* LWIP_SOCKET_SET_ERRNO==1: Set errno when socket functions cannot complete - * successfully, as required by POSIX. Default is POSIX-compliant. - */ -#define LWIP_SOCKET_SET_ERRNO 0 /** * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names. * Disable this option if you use a POSIX operating system that uses the same diff --git a/iop/tcpip/tcpip-base/sys_arch.c b/iop/tcpip/tcpip-base/sys_arch.c index adf8bf3cec5c..093bfdad22e4 100644 --- a/iop/tcpip/tcpip-base/sys_arch.c +++ b/iop/tcpip/tcpip-base/sys_arch.c @@ -182,6 +182,13 @@ err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg){ return result; } +/* lwIP 2.2.x distinguishes ISR-context posts from task-context posts. The + IOP has cooperative scheduling and no preemptive ISRs that interact with + the lwIP message queue, so the two are equivalent here. */ +err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg){ + return sys_mbox_trypost(mbox, msg); +} + void sys_mbox_post(sys_mbox_t *mbox, void *msg) { arch_message *MsgPkt; diff --git a/iop/tcpip/tcpip-netman/src/exports.tab b/iop/tcpip/tcpip-netman/src/exports.tab index 8f9377d53bb5..14d7d348ba5d 100644 --- a/iop/tcpip/tcpip-netman/src/exports.tab +++ b/iop/tcpip/tcpip-netman/src/exports.tab @@ -65,7 +65,7 @@ DECLARE_EXPORT_TABLE(ps2ip, 2, 6) #endif DECLARE_EXPORT(netif_set_link_up) DECLARE_EXPORT(netif_set_link_down) //55 - DECLARE_EXPORT(tcpip_callback_with_block) + DECLARE_EXPORT(tcpip_callback) DECLARE_EXPORT(pbuf_coalesce) END_EXPORT_TABLE diff --git a/iop/tcpip/tcpip-netman/src/imports.lst b/iop/tcpip/tcpip-netman/src/imports.lst index 5237c25a572f..33c84e8b9204 100644 --- a/iop/tcpip/tcpip-netman/src/imports.lst +++ b/iop/tcpip/tcpip-netman/src/imports.lst @@ -54,6 +54,7 @@ I_memset I_strcpy I_strncpy I_memcpy +I_memmove I_strlen I_strncmp I_strtok @@ -61,6 +62,8 @@ I_strtoul I_memcmp I_strtol I_strcmp +I_tolower +I_look_ctype_table sysclib_IMPORTS_end sysmem_IMPORTS_start diff --git a/iop/tcpip/tcpip-netman/src/ps2ip.c b/iop/tcpip/tcpip-netman/src/ps2ip.c index 8f86820fabfe..c365436b00d0 100644 --- a/iop/tcpip/tcpip-netman/src/ps2ip.c +++ b/iop/tcpip/tcpip-netman/src/ps2ip.c @@ -35,6 +35,13 @@ #include "ps2ip_internal.h" +/* lwIP 2.2.1's sockets.c writes errno via set_errno(); the IOP IRX has no + libc-provided errno storage, so we define it here. The ".data" section + name (with leading dot) is critical: a bare "data" attribute creates a + separate section that the IRX loader never allocates into IOP RAM, so + every set_errno() write would corrupt random memory. */ +int errno __attribute__((section(".data"))); + typedef struct pbuf PBuf; typedef struct netif NetIF; typedef struct ip4_addr IPAddr; diff --git a/iop/tcpip/tcpip/include/ps2ip.h b/iop/tcpip/tcpip/include/ps2ip.h index 02897051ddf9..46f0ceb2bc81 100644 --- a/iop/tcpip/tcpip/include/ps2ip.h +++ b/iop/tcpip/tcpip/include/ps2ip.h @@ -61,13 +61,7 @@ extern err_t tcpip_input(struct pbuf *p, struct netif *inp); /** Function prototype for functions passed to tcpip_callback() */ typedef void (*tcpip_callback_fn)(void *ctx); -extern err_t tcpip_callback_with_block(tcpip_callback_fn function, void *ctx, u8 block); - -/** - * @ingroup lwip_os - * @see tcpip_callback_with_block - */ -#define tcpip_callback(f, ctx) tcpip_callback_with_block(f, ctx, 1) +extern err_t tcpip_callback(tcpip_callback_fn function, void *ctx); /* From include/lwip/netif.h: */ extern struct netif *netif_add(struct netif *netif, @@ -188,7 +182,7 @@ extern const ip_addr_t* dns_getserver(u8 numdns); #define I_lwip_fcntl DECLARE_IMPORT(47, lwip_fcntl) #define I_etharp_output DECLARE_IMPORT(23, etharp_output) #define I_tcpip_input DECLARE_IMPORT(25, tcpip_input) -#define I_tcpip_callback_with_block DECLARE_IMPORT(56, tcpip_callback_with_block) +#define I_tcpip_callback DECLARE_IMPORT(56, tcpip_callback) #define I_netif_add DECLARE_IMPORT(26, netif_add) #define I_netif_find DECLARE_IMPORT(27, netif_find) #define I_netif_set_default DECLARE_IMPORT(28, netif_set_default) diff --git a/iop/tcpip/tcpip/src/exports.tab b/iop/tcpip/tcpip/src/exports.tab index 8f9377d53bb5..14d7d348ba5d 100644 --- a/iop/tcpip/tcpip/src/exports.tab +++ b/iop/tcpip/tcpip/src/exports.tab @@ -65,7 +65,7 @@ DECLARE_EXPORT_TABLE(ps2ip, 2, 6) #endif DECLARE_EXPORT(netif_set_link_up) DECLARE_EXPORT(netif_set_link_down) //55 - DECLARE_EXPORT(tcpip_callback_with_block) + DECLARE_EXPORT(tcpip_callback) DECLARE_EXPORT(pbuf_coalesce) END_EXPORT_TABLE diff --git a/iop/tcpip/tcpip/src/imports.lst b/iop/tcpip/tcpip/src/imports.lst index 8a09318f5a69..db7812a87688 100644 --- a/iop/tcpip/tcpip/src/imports.lst +++ b/iop/tcpip/tcpip/src/imports.lst @@ -47,6 +47,7 @@ I_memset I_strcpy I_strncpy I_memcpy +I_memmove I_strlen I_strncmp I_strtok @@ -54,6 +55,8 @@ I_strtoul I_memcmp I_strtol I_strcmp +I_tolower +I_look_ctype_table sysclib_IMPORTS_end sysmem_IMPORTS_start diff --git a/iop/tcpip/tcpip/src/ps2ip.c b/iop/tcpip/tcpip/src/ps2ip.c index 141de1903e5d..cc238eb50c9a 100644 --- a/iop/tcpip/tcpip/src/ps2ip.c +++ b/iop/tcpip/tcpip/src/ps2ip.c @@ -34,6 +34,13 @@ #include "ps2ip_internal.h" +/* lwIP 2.2.1's sockets.c writes errno via set_errno(); the IOP IRX has no + libc-provided errno storage, so we define it here. The ".data" section + name (with leading dot) is critical: a bare "data" attribute creates a + separate section that the IRX loader never allocates into IOP RAM, so + every set_errno() write would corrupt random memory. */ +int errno __attribute__((section(".data"))); + typedef struct pbuf PBuf; typedef struct netif NetIF; typedef struct ip4_addr IPAddr; From bbbc36d0f39d2cc8b5f6feb2c85336ac50ad37a6 Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Thu, 7 May 2026 00:12:10 +0200 Subject: [PATCH 2/2] ee/lwip: enable LWIP_NETIF_LOOPBACK + bump MEMP_NUM_TCP_PCB LWIP_NETIF_LOOPBACK=1 lets the EE-side lwIP route packets sent to a netif's own IP back through the netif's input path, so an EE app can act as both client and server to itself for testing without needing inbound TCP delivery from the host (which PCSX2 Sockets mode doesn't provide). Required for ps2_drivers' tcp_echo_test_ee sample. Forces LWIP_HAVE_LOOPIF=0 explicitly: lwIP would otherwise auto-default it to (LWIP_NETIF_LOOPBACK && !LWIP_SINGLE_NETIF) = 1, which creates a 127.0.0.1 netif at init and breaks DHCP DISCOVER routing in PCSX2. Bumps MEMP_NUM_TCP_PCB from the lwIP default of 5 to 32. The default is way too small for a server pattern: each completed request leaves the closing-side pcb in TIME_WAIT for 2*MSL (~60 s) holding a slot, plus there's always one slot for the listening pcb itself. With only 4 effective slots an HTTP server runs out as soon as a few back-to-back requests close. 32 leaves headroom for sustained traffic plus the SYN_RECV in-flight state for a burst. Co-Authored-By: Claude Opus 4.7 (1M context) --- ee/network/tcpip/src/include/lwipopts.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ee/network/tcpip/src/include/lwipopts.h b/ee/network/tcpip/src/include/lwipopts.h index 4830a21332f3..843126fb0158 100644 --- a/ee/network/tcpip/src/include/lwipopts.h +++ b/ee/network/tcpip/src/include/lwipopts.h @@ -96,6 +96,17 @@ ---------- Internal Memory Pool Sizes ---------- ------------------------------------------------ */ +/** + * MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections. + * The default of 5 is too small for an HTTP server: each completed request + * leaves the closing-side pcb in TIME_WAIT for 2*MSL (~60 s) holding a slot, + * so after a handful of fast back-to-back requests the pool fills up and + * accept() / connect() start failing with EHOSTUNREACH until slots free. + * 32 gives enough headroom for sustained traffic plus the in-flight SYN_RECV + * state for a 20-burst. + */ +#define MEMP_NUM_TCP_PCB 32 + /** * MEMP_NUM_NETCONN: the number of struct netconns. * (only needed if you use the sequential API, like api_lib.c) @@ -274,4 +285,17 @@ */ #define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define LWIP_NETIF_LOOPBACK 1 + +/** + * LWIP_HAVE_LOOPIF: lwIP defaults this to (LWIP_NETIF_LOOPBACK && !LWIP_SINGLE_NETIF) + * which is 1 once we enable LWIP_NETIF_LOOPBACK. That auto-creates a 127.0.0.1 + * loopback netif and may make it the default route during init, which breaks + * DHCP because DHCP DISCOVER ends up routed through the loop netif and never + * hits the real SMAP wire (PCSX2 / a real router never sees it). Force it to 0 + * so loopback traffic is handled in-place by the real netif via + * netif_loop_output, while DHCP / wire traffic still uses the SMAP path. + */ +#define LWIP_HAVE_LOOPIF 0 + #endif /* __LWIPOPTS_H__ */