diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b299a65c..1495ad1a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -651,6 +651,20 @@ jobs: continue-on-error: true run: bash scripts/check-s1-lwip.sh + # Runtime gate: cross-compile libsrt for bare-metal arm-none-eabi + # (CMAKE_SYSTEM_NAME=Generic -> ::select path, pthread backend, logging + + # encryption OFF), link it with the FreeRTOS+lwIP substrate, and run a + # srt_startup+create_socket+close+cleanup boot smoke under QEMU mps2-an386. + # Same phase-in (continue-on-error) rationale as S0/S1. No new apt packages + # -- cmake is already on the runner (the host libsrt builds use it); reuses + # gcc-arm-none-eabi + libstdc++-arm-none-eabi-newlib + qemu-system-arm. + # build.sh applies our bare-metal portability patches to the pinned + # vendor/srt submodule at build time (pointer stays pristine). Promote to + # hard-gate after a few green apt runs. + - name: QEMU runtime gate (S2 libsrt boot smoke) + continue-on-error: true + run: bash scripts/check-s2-libsrt.sh + fuzz-smoke: name: fuzz smoke (nightly, compile only) runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ea1528ec0..d1125afba 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,13 @@ embedded/s1-lwip/firmware.elf embedded/s1-lwip/*.elf embedded/s1-lwip/*.o +# Embedded S2 libsrt cross-compile + boot smoke build artifacts +embedded/s2-libsrt/firmware.elf +embedded/s2-libsrt/*.elf +embedded/s2-libsrt/*.o +embedded/s2-libsrt/srt-build/ +embedded/s2-libsrt/srt-install/ + # Rust analyzer .rust-analyzer/ diff --git a/embedded/s2-libsrt/FreeRTOSConfig.h b/embedded/s2-libsrt/FreeRTOSConfig.h new file mode 100644 index 000000000..b3ef9e290 --- /dev/null +++ b/embedded/s2-libsrt/FreeRTOSConfig.h @@ -0,0 +1,89 @@ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +/* Minimal FreeRTOS V11.1.0 config for the Cortex-M4F (ARM_CM4F) port on + QEMU mps2-an386. C++ exceptions want generous task stacks, so stacks are + sized at task-create time; the heap is large to leave room for libstdc++. */ + +#define configUSE_PREEMPTION 1 +#define configUSE_TIME_SLICING 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configCPU_CLOCK_HZ (25000000) /* QEMU mps2 ~25 MHz */ +#define configTICK_RATE_HZ (1000) +#define configMAX_PRIORITIES (5) +#define configMINIMAL_STACK_SIZE (256) /* words */ +#define configSTACK_DEPTH_TYPE uint32_t /* lwIP sys_thread_new byte→word conversion */ +#define configTOTAL_HEAP_SIZE (192 * 1024) +#define configMAX_TASK_NAME_LEN (16) +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configQUEUE_REGISTRY_SIZE 0 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 + +/* Hooks */ +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configUSE_MALLOC_FAILED_HOOK 1 +#define configCHECK_FOR_STACK_OVERFLOW 2 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Software timers */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (3) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH (256) + +/* Thread-local storage — S0-critical: Task 4 stores per-task __cxa_eh_globals + in a TLS pointer slot so concurrent exceptions don't clobber each other. */ +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1 + +/* Allocation. FreeRTOS-Plus-POSIX creates pthread join mutex/barrier via the + xSemaphoreCreate*Static APIs, so static allocation must be on. Let the kernel + supply the idle/timer task static memory (configKERNEL_PROVIDED_STATIC_MEMORY) + so we don't hand-roll vApplicationGet{Idle,Timer}TaskMemory. */ +#define configSUPPORT_STATIC_ALLOCATION 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configKERNEL_PROVIDED_STATIC_MEMORY 1 + +/* FreeRTOS-Plus-POSIX needs these: it stashes the pthread object in the task's + application tag (vTaskSetApplicationTaskTag), and uses the POSIX errno field + in the TCB for its return-error reporting. */ +#define configUSE_APPLICATION_TASK_TAG 1 +#define configUSE_POSIX_ERRNO 1 +#define configUSE_COUNTING_SEMAPHORES 1 + +/* Cortex-M interrupt priority configuration */ +#define configPRIO_BITS 3 +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY (7) +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY (5) +#define configKERNEL_INTERRUPT_PRIORITY \ + (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) +#define configMAX_SYSCALL_INTERRUPT_PRIORITY \ + (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +/* Optional API */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +/* FreeRTOS-Plus-POSIX uses these in pthread_detach/join/cond/cancel paths. */ +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_xSemaphoreGetMutexHolder 1 + +#define configASSERT(x) if ((x) == 0) { taskDISABLE_INTERRUPTS(); for(;;); } + +/* Map the FreeRTOS port handlers onto the names our vector table uses. The + ARM_CM4F port already exports vPortSVCHandler/xPortPendSVHandler/ + xPortSysTickHandler, and startup.c references those directly, so no remap + is needed — but if a CMSIS-style build expected SVC_Handler etc., this is + where the #define would go. */ + +#endif /* FREERTOS_CONFIG_H */ diff --git a/embedded/s2-libsrt/FreeRTOS_POSIX_config.h b/embedded/s2-libsrt/FreeRTOS_POSIX_config.h new file mode 100644 index 000000000..009865cd5 --- /dev/null +++ b/embedded/s2-libsrt/FreeRTOS_POSIX_config.h @@ -0,0 +1,14 @@ +/* FreeRTOS+POSIX knob overrides for the S0 C++ exceptions gate. + * + * The wrapper pulls all posixconfig* defaults from + * FreeRTOS_POSIX_portable_default.h; the only port-specific deviations we need + * (the newlib typedef-collision suppressions) live in FreeRTOS_POSIX_portable.h. + * This file exists as the documented override point — currently the defaults + * suffice, so it is intentionally empty of overrides. */ + +#ifndef _FREERTOS_POSIX_CONFIG_H_ +#define _FREERTOS_POSIX_CONFIG_H_ + +/* (no overrides — defaults from FreeRTOS_POSIX_portable_default.h are fine) */ + +#endif /* _FREERTOS_POSIX_CONFIG_H_ */ diff --git a/embedded/s2-libsrt/FreeRTOS_POSIX_portable.h b/embedded/s2-libsrt/FreeRTOS_POSIX_portable.h new file mode 100644 index 000000000..b1020da5a --- /dev/null +++ b/embedded/s2-libsrt/FreeRTOS_POSIX_portable.h @@ -0,0 +1,36 @@ +/* Port-specific FreeRTOS+POSIX configuration for the S0 C++ exceptions gate + * (arm-none-eabi-gcc / newlib, Cortex-M4F, QEMU mps2-an386). + * + * FreeRTOS_POSIX.h includes this header first. Its job is to suppress the + * FreeRTOS+POSIX typedefs that the arm-none-eabi newlib headers ALSO define, + * so the two don't collide. Modeled on the vendored STM32+GNU port header + * (portable/st/stm32h745zi_nucleo/FreeRTOS_POSIX_portable.h) — same newlib + * lineage, same collisions. */ + +#ifndef _FREERTOS_POSIX_PORTABLE_H_ +#define _FREERTOS_POSIX_PORTABLE_H_ + +/* newlib already defines these via / . */ +#define posixconfigENABLE_TIMESPEC 0 +#include + +#define posixconfigENABLE_ITIMERSPEC 0 +#include + +/* newlib already provides mode_t and clockid_t. */ +#define posixconfigENABLE_MODE_T 0 +#define posixconfigENABLE_CLOCKID_T 0 + +/* Suppress newlib timer_t so it doesn't fight the FreeRTOS+POSIX one. */ +#define __timer_t_defined 1 + +/* Block newlib's definitions (which differ from the + * FreeRTOS+POSIX pthread_* typedefs) before pulling . */ +#define _SYS__PTHREADTYPES_H_ +#include + +/* Use newlib's sched_param/sched_yield/etc. rather than the POSIX-layer copy. */ +#define posixconfigENABLE_SCHED_PARAM 0 +#include + +#endif /* _FREERTOS_POSIX_PORTABLE_H_ */ diff --git a/embedded/s2-libsrt/arch/cc.h b/embedded/s2-libsrt/arch/cc.h new file mode 100644 index 000000000..24f03e679 --- /dev/null +++ b/embedded/s2-libsrt/arch/cc.h @@ -0,0 +1,17 @@ +/* lwIP arch shim for arm-none-eabi + newlib. lwIP pulls fixed-width types and + * printf formatters from the C library (LWIP_NO_STDINT_H / LWIP_NO_INTTYPES_H + * default 0 → /); we only need the diagnostic/abort + * hooks and a randomness source. */ +#ifndef LWIP_ARCH_CC_H +#define LWIP_ARCH_CC_H + +#include +#include +/* LWIP_TIMEVAL_PRIVATE=0 (lwipopts.h) → every lwIP TU must see the system + * struct timeval; lwIP's docs require including from cc.h. */ +#include + +#define LWIP_PLATFORM_DIAG(x) do { printf x; } while (0) +#define LWIP_PLATFORM_ASSERT(x) do { printf("LWIP ASSERT: %s\n", x); abort(); } while (0) + +#endif /* LWIP_ARCH_CC_H */ diff --git a/embedded/s2-libsrt/arch/sys_arch.h b/embedded/s2-libsrt/arch/sys_arch.h new file mode 100644 index 000000000..b31dbb84f --- /dev/null +++ b/embedded/s2-libsrt/arch/sys_arch.h @@ -0,0 +1,20 @@ +#ifndef LWIP_ARCH_SYS_ARCH_H +#define LWIP_ARCH_SYS_ARCH_H + +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" + +#define SYS_MBOX_NULL NULL +#define SYS_SEM_NULL NULL +#define SYS_ARCH_TIMEOUT 0xffffffffUL +#define SYS_MBOX_EMPTY SYS_ARCH_TIMEOUT + +typedef UBaseType_t sys_prot_t; +typedef struct { SemaphoreHandle_t sem; } sys_sem_t; +typedef struct { SemaphoreHandle_t mtx; } sys_mutex_t; +typedef struct { QueueHandle_t mbox; } sys_mbox_t; +typedef TaskHandle_t sys_thread_t; + +#endif /* LWIP_ARCH_SYS_ARCH_H */ diff --git a/embedded/s2-libsrt/arm-none-eabi.cmake b/embedded/s2-libsrt/arm-none-eabi.cmake new file mode 100644 index 000000000..111391f93 --- /dev/null +++ b/embedded/s2-libsrt/arm-none-eabi.cmake @@ -0,0 +1,42 @@ +# Bare-metal arm-none-eabi toolchain for cross-building libsrt against the S2 +# FreeRTOS+lwIP substrate. CMAKE_SYSTEM_NAME=Generic (NOT Linux) so libsrt's +# POSIX var is false and it compiles the portable ::select() path, not epoll. +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR arm) +set(CMAKE_C_COMPILER arm-none-eabi-gcc) +set(CMAKE_CXX_COMPILER arm-none-eabi-g++) + +# CMake's compiler sanity probe must build a static lib, not a semihosted exe +# (no _exit/OS at configure time). +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +get_filename_component(_S2 "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE) +set(_ROOT "${_S2}/../..") +set(_K "${_ROOT}/vendor/freertos-kernel") +set(_P "${_ROOT}/vendor/freertos-plus-posix") +set(_L "${_ROOT}/vendor/lwip") + +set(_ARCH "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16") +# -D__GNU__: selects libsrt utilities.h's endian branch +# (__linux__||__CYGWIN__||__GNU__||__GLIBC__) — the only non-Linux option that +# doesn't also pull epoll — so it #includes our posix-shims/endian.h instead of +# hitting its unconditional `#error Endian: platform not supported`. +# -DSRT_NO_PTHREAD_CANCEL: FreeRTOS-Plus-POSIX has no pthread_cancel; a one-line +# vendor/srt patch guards its single use (an IPE error path) on this define. +set(_DEFS "-D__GNU__=1 -DSRT_NO_PTHREAD_CANCEL -include ${CMAKE_CURRENT_LIST_DIR}/posix-shims/s2_prefix.h") +# Include order matters: posix-shims FIRST (so / route +# to FreeRTOS-Plus-POSIX/lwIP), then the substrate dir (lwipopts.h, arch/cc.h), +# then FreeRTOS + lwIP. lwIP's compat/posix supplies /. +set(_INC "-I${_S2}/posix-shims -I${_S2} \ + -I${_K}/include -I${_K}/portable/GCC/ARM_CM4F \ + -I${_P}/include -I${_P}/include/private \ + -I${_P}/FreeRTOS-Plus-POSIX/include -I${_P}/FreeRTOS-Plus-POSIX/include/portable \ + -I${_L}/src/include -I${_L}/src/include/compat/posix") + +set(CMAKE_C_FLAGS "${_ARCH} ${_DEFS} ${_INC}" CACHE STRING "") +set(CMAKE_CXX_FLAGS "${_ARCH} ${_DEFS} ${_INC}" CACHE STRING "") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/embedded/s2-libsrt/atomic64_stub.c b/embedded/s2-libsrt/atomic64_stub.c new file mode 100644 index 000000000..d0a11bae5 --- /dev/null +++ b/embedded/s2-libsrt/atomic64_stub.c @@ -0,0 +1,84 @@ +/* 64-bit atomic libcalls for single-core Cortex-M4. + * + * libsrt uses std::atomic/ (bandwidth/seqno counters). The + * Cortex-M4 has no native 64-bit atomic instructions, so GCC lowers these to + * __atomic_*_8 libcalls — normally satisfied by libatomic, which the bare-metal + * arm-none-eabi multilib for cortex-m4f does NOT ship. On a single core, a + * 64-bit atomic is just a critical section: mask interrupts (PRIMASK), do the + * op, restore. No SMP, so this is correct here. */ +#include +#include + +static inline uint32_t irq_save(void) +{ + uint32_t primask; + __asm volatile("mrs %0, primask\n\tcpsid i" : "=r"(primask) :: "memory"); + return primask; +} +static inline void irq_restore(uint32_t primask) +{ + __asm volatile("msr primask, %0" :: "r"(primask) : "memory"); +} + +uint64_t __atomic_load_8(const volatile void* ptr, int memorder) +{ + (void)memorder; + uint32_t s = irq_save(); + uint64_t v = *(const volatile uint64_t*)ptr; + irq_restore(s); + return v; +} + +void __atomic_store_8(volatile void* ptr, uint64_t val, int memorder) +{ + (void)memorder; + uint32_t s = irq_save(); + *(volatile uint64_t*)ptr = val; + irq_restore(s); +} + +uint64_t __atomic_exchange_8(volatile void* ptr, uint64_t val, int memorder) +{ + (void)memorder; + uint32_t s = irq_save(); + uint64_t old = *(volatile uint64_t*)ptr; + *(volatile uint64_t*)ptr = val; + irq_restore(s); + return old; +} + +uint64_t __atomic_fetch_add_8(volatile void* ptr, uint64_t val, int memorder) +{ + (void)memorder; + uint32_t s = irq_save(); + uint64_t old = *(volatile uint64_t*)ptr; + *(volatile uint64_t*)ptr = old + val; + irq_restore(s); + return old; +} + +uint64_t __atomic_fetch_sub_8(volatile void* ptr, uint64_t val, int memorder) +{ + (void)memorder; + uint32_t s = irq_save(); + uint64_t old = *(volatile uint64_t*)ptr; + *(volatile uint64_t*)ptr = old - val; + irq_restore(s); + return old; +} + +int __atomic_compare_exchange_8(volatile void* ptr, void* expected, + uint64_t desired, int weak, + int success, int failure) +{ + (void)weak; (void)success; (void)failure; + uint32_t s = irq_save(); + uint64_t cur = *(volatile uint64_t*)ptr; + int ok = (cur == *(uint64_t*)expected); + if (ok) + *(volatile uint64_t*)ptr = desired; + else + *(uint64_t*)expected = cur; + irq_restore(s); + return ok; +} diff --git a/embedded/s2-libsrt/build.sh b/embedded/s2-libsrt/build.sh new file mode 100755 index 000000000..efa707fc4 --- /dev/null +++ b/embedded/s2-libsrt/build.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" +ROOT=$(cd ../.. && pwd) +K=$ROOT/vendor/freertos-kernel +P=$ROOT/vendor/freertos-plus-posix +L=$ROOT/vendor/lwip + +ARCH="-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16" +OPT="-Os -ffunction-sections -fdata-sections -g" + +# posix-shims FIRST (so // route to +# FreeRTOS-Plus-POSIX / lwIP), then -I. (lwipopts.h, arch/, our config headers +# win), then FreeRTOS + lwIP. lwIP's compat/posix supplies etc. +INC="-Iposix-shims -I. -I$K/include -I$K/portable/GCC/ARM_CM4F \ + -I$P/include -I$P/include/private \ + -I$P/FreeRTOS-Plus-POSIX/include -I$P/FreeRTOS-Plus-POSIX/include/portable \ + -I$L/src/include -I$L/src/include/compat/posix" + +CC=arm-none-eabi-gcc +CXX=arm-none-eabi-g++ + +SRT=$ROOT/vendor/srt +SRT_BUILD=srt-build +SRT_INSTALL=$(pwd)/srt-install + +# Apply our bare-metal portability patches to the pinned vendor/srt submodule. +# The submodule stays pristine in git (pointer unchanged); the patches live in +# our tree so CI's clean recursive checkout gets them too. Idempotent: skip any +# patch that already reverse-applies cleanly (i.e. is already in place). +for p in "$(pwd)"/patches/*.patch; do + [ -e "$p" ] || continue + if git -C "$SRT" apply --reverse --check "$p" 2>/dev/null; then + echo "patch already applied: $(basename "$p")" + else + echo "applying patch: $(basename "$p")" + git -C "$SRT" apply "$p" + fi +done + +# Cross-build libsrt.a for the target (CMAKE_SYSTEM_NAME=Generic → ::select() +# path; pthread backend; logging/encryption OFF; mirrors srt-sys/build.rs opts). +if [ ! -f "$SRT_INSTALL/lib/libsrt.a" ] || [ "${S2_REBUILD_SRT:-0}" = "1" ]; then + rm -rf "$SRT_BUILD" "$SRT_INSTALL" + # -DGNU=ON: libsrt's system-detection block (CMakeLists.txt ~L829) FATALs on + # any CMAKE_SYSTEM_NAME it doesn't recognize, and the `elseif(GNU)` branch + # checks a `GNU` var that libsrt actually renamed to `GNU_OS` (L47 comment) — + # so even Generic/GNU systems hit the `else()` FATAL. Forcing GNU=ON selects + # that branch, which only `add_definitions(-DGNU=1)` (unused in any C/C++ + # source — verified) and, unlike the LINUX/BSD branches, does NOT pull epoll + # or kqueue. With LINUX undefined, epoll.cpp + platform_sys.h compile the + # portable ::select() path against lwIP (the whole point of S2). + cmake -S "$SRT" -B "$SRT_BUILD" \ + -DCMAKE_TOOLCHAIN_FILE="$(pwd)/arm-none-eabi.cmake" \ + -DCMAKE_INSTALL_PREFIX="$SRT_INSTALL" \ + -DGNU=ON \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DENABLE_APPS=OFF -DENABLE_SHARED=OFF -DENABLE_STATIC=ON \ + -DENABLE_UNITTESTS=OFF -DENABLE_TESTING=OFF -DENABLE_BONDING=OFF \ + -DENABLE_HEAVY_LOGGING=OFF -DENABLE_LOGGING=OFF -DENABLE_ENCRYPTION=OFF \ + -DENABLE_STDCXX_SYNC=OFF -DENABLE_MONOTONIC_CLOCK=OFF + cmake --build "$SRT_BUILD" --target install -j"$(nproc)" +fi +SRT_LIB=$(echo "$SRT_INSTALL"/lib*/libsrt.a) # lib or lib64 +arm-none-eabi-size "$SRT_LIB" | tail -1 + +# main.cpp includes from the install tree. +INC="$INC -I$SRT_INSTALL/include" + +rm -f *.o + +PS=$P/FreeRTOS-Plus-POSIX/source +# Substrate (FreeRTOS + lwIP + startup/clock) — compiled WITHOUT the posix-shim +# env, exactly as in S1 (these don't include srt or our pthread.h shim). +C_SRC="startup.c clock_shim.c syscalls_stub.c atomic64_stub.c $K/tasks.c $K/list.c $K/queue.c $K/timers.c $K/event_groups.c \ + $K/portable/GCC/ARM_CM4F/port.c $K/portable/MemMang/heap_4.c \ + $PS/FreeRTOS_POSIX_pthread.c $PS/FreeRTOS_POSIX_pthread_mutex.c \ + $PS/FreeRTOS_POSIX_pthread_cond.c $PS/FreeRTOS_POSIX_pthread_barrier.c \ + $PS/FreeRTOS_POSIX_clock.c $PS/FreeRTOS_POSIX_utils.c \ + $PS/FreeRTOS_POSIX_sched.c $PS/FreeRTOS_POSIX_unistd.c \ + $PS/FreeRTOS_POSIX_timer.c $PS/FreeRTOS_POSIX_semaphore.c" + +# lwIP core (UDP/IPv4 + IPv6 for libsrt's sockaddr_in6/ip6 types) + sequential/ +# socket API + our hand-written sys_arch port. Basenames are unique across dirs. +LWIP_SRC="sys_arch.c $L/src/core/*.c $L/src/core/ipv4/*.c $L/src/core/ipv6/*.c $L/src/api/*.c" + +for f in $C_SRC $LWIP_SRC; do + $CC $ARCH $OPT $INC -std=gnu11 -c "$f" -o "$(basename "${f%.c}").o" +done + +# App sources (our pthread_key TSD shim + main.cpp) include the posix-shim +# headers / srt.h, so they need the SAME compat env libsrt was built with: +# the s2_prefix.h force-include (newlib type suppressions), -D__GNU__ (endian +# branch), and -DSRT_NO_PTHREAD_CANCEL (matches the vendor/srt patch). +SHIM_DEFS="-include posix-shims/s2_prefix.h -D__GNU__=1 -DSRT_NO_PTHREAD_CANCEL" +$CC $ARCH $OPT $INC $SHIM_DEFS -std=gnu11 -c pthread_key_shim.c -o pthread_key_shim.o + +CXX_SRC="main.cpp ${S2_EXTRA_SRC:-}" +for f in $CXX_SRC; do + $CXX $ARCH $OPT $INC $SHIM_DEFS -std=gnu++11 -fexceptions -c "$f" -o "$(basename "${f%.cpp}").o" +done + +# --wrap=clock_gettime: redirect every clock_gettime reference to the hi-res +# __wrap_clock_gettime in clock_shim.c (see that file's header for why). +# Link libsrt.a (after our .o, before libstdc++). --start-group wraps libsrt + +# libstdc++ + libc so their cross-references resolve regardless of order. +$CXX $ARCH $OPT \ + --specs=rdimon.specs -T mps2_an386.ld -Wl,--gc-sections \ + -Wl,--wrap=clock_gettime \ + -Wl,--start-group *.o "$SRT_LIB" -lstdc++ -Wl,--end-group \ + -o firmware.elf + +arm-none-eabi-size firmware.elf diff --git a/embedded/s2-libsrt/clock_shim.c b/embedded/s2-libsrt/clock_shim.c new file mode 100644 index 000000000..4ffc8fcee --- /dev/null +++ b/embedded/s2-libsrt/clock_shim.c @@ -0,0 +1,59 @@ +/* Hi-res clock_gettime(CLOCK_MONOTONIC) for the S1 harness (R3). + * + * FreeRTOS-Plus-POSIX's FreeRTOS_POSIX_clock.c provides a tick-resolution + * (~1 ms) clock_gettime AND several other TUs (pthread_cond/mutex, timer, + * semaphore, mqueue) call it — so we can't just redefine the symbol (two strong + * defs = multiple-definition) nor drop that TU (it also owns nanosleep/ + * clock_nanosleep/clock_getres). Instead the link uses `-Wl,--wrap=clock_gettime`: + * every reference to clock_gettime (ours + the POSIX TUs' + std::chrono later) + * resolves to __wrap_clock_gettime below, while the wrapper's own def survives + * only as the now-unreferenced __real_clock_gettime. This is the S1 analog of + * S0's strong-symbol override for __cxa_get_globals — same goal (our hi-res def + * wins the link), cleaner mechanism for an inter-TU symbol. + * + * It subdivides each tick with the SysTick current-value register. + * + * ns = tick * 1e6 + ((reload - SYST_CVR) * 1e9) / configCPU_CLOCK_HZ + * + * With configCPU_CLOCK_HZ = 25 MHz and configTICK_RATE_HZ = 1000 the reload is + * 25000, so the CVR subdivides 1 ms to ~40 ns. SysTick is modeled by QEMU + * (FreeRTOS drives it). A re-read guards against a tick rollover landing + * between the tick read and the CVR read. + */ +#include +#include +#include "FreeRTOS.h" +#include "task.h" + +#define SYST_RVR (*(volatile uint32_t *)0xE000E014u) /* reload value */ +#define SYST_CVR (*(volatile uint32_t *)0xE000E018u) /* current value */ + +int __wrap_clock_gettime(clockid_t clock_id, struct timespec *tp) { + (void)clock_id; + if (tp == 0) return -1; + + uint32_t reload = SYST_RVR + 1u; /* counts per tick (25000) */ + + /* Lock-free two-sample read: pair a SysTick CVR sample with a tick value + * proven stable across it. A SysTick wrap fires the (enabled) tick ISR + * essentially atomically, so if the tick read on either side of the CVR + * sample agree, that CVR belongs to that tick; if they differ, a wrap + * happened and the SECOND pair (taken after the ISR bumped the tick) is the + * consistent one. This is monotonic across calls (a wrap advances the tick + * before the CVR resets), unlike a tick/CSR/CVR three-read sequence where a + * wrap landing mid-sequence makes the timestamp jump backward. */ + uint32_t m0 = (uint32_t)xTaskGetTickCount(); + uint32_t v0 = SYST_CVR; + uint32_t m1 = (uint32_t)xTaskGetTickCount(); + uint32_t v1 = SYST_CVR; + + uint32_t tick, into_tick; + if (m0 == m1) { tick = m0; into_tick = reload - v0; } + else { tick = m1; into_tick = reload - v1; } + + uint64_t ns = (uint64_t)tick * 1000000ull + + ((uint64_t)into_tick * 1000000000ull) / configCPU_CLOCK_HZ; + tp->tv_sec = (time_t)(ns / 1000000000ull); + tp->tv_nsec = (long)(ns % 1000000000ull); + return 0; +} diff --git a/embedded/s2-libsrt/lwipopts.h b/embedded/s2-libsrt/lwipopts.h new file mode 100644 index 000000000..1563a3b83 --- /dev/null +++ b/embedded/s2-libsrt/lwipopts.h @@ -0,0 +1,93 @@ +/* lwIP config for the S2 harness: a single device, loopback netif only, UDP + + * BSD sockets + select(). No NIC, no ARP/ethernet, no TCP. IPv6 is ENABLED (see + * LWIP_IPV6 below) so libsrt's sockaddr_in6/ip6_addr_t types resolve, but only + * IPv4 loopback traffic actually flows. The minimum surface libsrt's + * channel.cpp (sendto/recvfrom) + CEPoll (select) exercise. */ +#ifndef LWIP_LWIPOPTS_H +#define LWIP_LWIPOPTS_H + +#include /* rand() for LWIP_RAND */ + +#define NO_SYS 0 /* full OS mode: sockets + tcpip thread */ +#define SYS_LIGHTWEIGHT_PROT 1 +#define LWIP_TIMERS 1 +#define LWIP_RAND() ((u32_t)rand()) + +/* Protocols: UDP over IPv4 only. */ +#define LWIP_IPV4 1 +/* S2: libsrt references sockaddr_in6 / ip6_addr_t / IN6_IS_ADDR_* / IPV6_* + * unconditionally (it's IPv6-capable). lwIP only defines them under LWIP_IPV6. + * Enable it so libsrt compiles against real lwIP IPv6 types (the boot smoke and + * the S3 SRT loopback still run over IPv4 — no IPv6 traffic). Adds the + * core/ipv6/*.c glob to build.sh. */ +#define LWIP_IPV6 1 +#define LWIP_UDP 1 +#define LWIP_TCP 0 +#define LWIP_RAW 0 +#define LWIP_DHCP 0 +/* S2: libsrt's channel.cpp calls ::getaddrinfo(NULL,"0",...) to resolve the + * wildcard bind address, and netinet_any.h needs `struct addrinfo`. lwIP only + * exposes getaddrinfo + struct addrinfo (lwip/netdb.h) under LWIP_DNS. dns.c + + * api/netdb.c are already in the source glob; no actual DNS runs in the smoke. */ +#define LWIP_DNS 1 +#define LWIP_ARP 0 +#define LWIP_ETHERNET 0 +#define LWIP_IGMP 0 +#define LWIP_ICMP 1 + +/* Loopback netif (127.0.0.1) — the only interface; no driver. */ +#define LWIP_HAVE_LOOPIF 1 +#define LWIP_NETIF_LOOPBACK 1 +#define LWIP_NETIF_LOOPBACK_MULTITHREADING 1 +#define LWIP_LOOPBACK_MAX_PBUFS 8 + +/* Sequential / socket API. */ +#define LWIP_NETCONN 1 +#define LWIP_SOCKET 1 +#define LWIP_SOCKET_SELECT 1 +/* S2: libsrt's channel.cpp/epoll.cpp call UNPREFIXED BSD names (socket/bind/ + * select/...) and the UDT namespace declares its OWN socket()/bind()/sendmsg() + * API. Mode 2 exports the bare names as REAL functions (`#define lwip_bind bind` + * + real `int bind(...)`), NOT function-like macros — so they satisfy libsrt's + * ::socket()/::bind() calls at link time WITHOUT clobbering UDT::bind or + * std::bind (mode 1's macros break both; S1 used lwip_* explicitly with =0). */ +#define LWIP_COMPAT_SOCKETS 2 +#define LWIP_POSIX_SOCKETS_IO_NAMES 0 +#define LWIP_NETCONN_SEM_PER_THREAD 0 + +/* Memory — generous; this is an H7-class budget and the traffic is one 564B + * datagram on loopback. */ +#define MEM_ALIGNMENT 4 +#define MEM_SIZE (64 * 1024) +#define MEMP_NUM_PBUF 32 +#define MEMP_NUM_UDP_PCB 8 +#define MEMP_NUM_NETCONN 8 +#define MEMP_NUM_NETBUF 8 +#define MEMP_NUM_TCPIP_MSG_API 16 +#define MEMP_NUM_TCPIP_MSG_INPKT 16 +#define PBUF_POOL_SIZE 16 + +/* Threading: the tcpip thread + socket mboxes (sized via our sys_arch). */ +#define TCPIP_THREAD_NAME "tcpip" +#define TCPIP_THREAD_STACKSIZE 2048 +#define TCPIP_THREAD_PRIO 4 +#define TCPIP_MBOX_SIZE 16 +#define DEFAULT_UDP_RECVMBOX_SIZE 16 +#define DEFAULT_ACCEPTMBOX_SIZE 8 +#define DEFAULT_THREAD_STACKSIZE 2048 + +/* Stats/debug off for footprint. */ +#define LWIP_STATS 0 +#define LWIP_NETIF_API 1 +#define LWIP_DEBUG 0 + +/* errno: use newlib's. */ +#define LWIP_PROVIDE_ERRNO 0 +#include + +/* Use the system struct timeval (from ) rather than lwIP's private + * one — once main.cpp also pulls the FreeRTOS-Plus-POSIX/newlib headers, lwIP's + * own definition collides (redefinition of 'struct timeval'). */ +#define LWIP_TIMEVAL_PRIVATE 0 + +#endif /* LWIP_LWIPOPTS_H */ diff --git a/embedded/s2-libsrt/main.cpp b/embedded/s2-libsrt/main.cpp new file mode 100644 index 000000000..8417df150 --- /dev/null +++ b/embedded/s2-libsrt/main.cpp @@ -0,0 +1,53 @@ +// S2 — boot smoke: cross-compiled libsrt initializes on the FreeRTOS+lwIP +// substrate. srt_startup spawns the SRT:GC garbage-collector thread as a +// FreeRTOS-Plus-POSIX pthread; srt_create_socket allocates a CUDTSocket; +// srt_cleanup joins the GC thread. No bind/connect — pure runtime init/teardown +// (the data plane is S3). lwIP is linked so libsrt's socket-call symbols +// resolve (the R4 deliverable), but no datagram flows. +#include +#include +// srt.h pulls C++ stdlib headers and carries its own `extern "C"` guards, so it +// must be included at C++ linkage (NOT inside an extern "C" block). +#include +extern "C" { +#include "FreeRTOS.h" +#include "task.h" +} +// _exit (semihosting, from rdimon.specs) is declared via srt.h's . + +static void boot_task(void*) { + int fail = 0; + const char* where = ""; + + if (srt_startup() < 0) { fail = 1; where = "startup"; } + SRTSOCKET s = SRT_INVALID_SOCK; + if (!fail) { + s = srt_create_socket(); + if (s == SRT_INVALID_SOCK) { fail = 1; where = "create_socket"; } + } + // Behavioral check: a freshly created socket must report SRTS_INIT. This + // proves libsrt's runtime tracks real per-socket state (the gate isn't + // vacuous), and is the RED-proof hook: flip SRTS_INIT to another state and + // the gate must FAIL. (Note: libsrt lazily constructs its global singleton + // at static init, so srt_create_socket succeeds even before srt_startup — + // hence a state check, not a create-before-startup check, is the real RED.) + if (!fail && srt_getsockstate(s) != SRTS_INIT) { fail = 1; where = "sockstate"; } + if (!fail && srt_close(s) == SRT_ERROR) { fail = 1; where = "close"; } + if (!fail && srt_cleanup() == SRT_ERROR) { fail = 1; where = "cleanup"; } + + if (fail) printf("FAIL[s2_libsrt]: %s: %s\n", where, srt_getlasterror_str()); + else printf("PASS: s2_libsrt (startup+socket+cleanup)\n"); + fflush(stdout); + _exit(fail ? 1 : 0); +} + +int main() { + // Generous stack: srt_startup + the GC thread exercise the C++ unwinder + // (S0 finding: libsrt threads need >=4 KiB; the bootstrap task more). + xTaskCreate(boot_task, "boot", 4096, nullptr, 2, nullptr); + vTaskStartScheduler(); + for (;;) {} +} + +extern "C" void vApplicationMallocFailedHook(void) { printf("FAIL[malloc]\n"); _exit(1); } +extern "C" void vApplicationStackOverflowHook(TaskHandle_t, char*) { printf("FAIL[stack]\n"); _exit(1); } diff --git a/embedded/s2-libsrt/mps2_an386.ld b/embedded/s2-libsrt/mps2_an386.ld new file mode 100644 index 000000000..91c23f50b --- /dev/null +++ b/embedded/s2-libsrt/mps2_an386.ld @@ -0,0 +1,79 @@ +ENTRY(Reset_Handler) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4M + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 4M +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); /* top of RAM (full-descending) */ +_heap_end = ORIGIN(RAM) + LENGTH(RAM) - 0x4000; /* leave 16 KiB for the MSP stack */ + +SECTIONS +{ + .isr_vector : { KEEP(*(.isr_vector)) } > FLASH + + /* .init/.fini sequenced so crti.o prologue precedes the body and crtn.o + epilogue follows — giving _init/_fini a proper return path. */ + .init : { + KEEP(*crti.o(.init)) + KEEP(*(.init)) + KEEP(*crtn.o(.init)) + } > FLASH + + .text : { + *(.text*) + *(.rodata*) + . = ALIGN(4); + } > FLASH + + .fini : { + KEEP(*crti.o(.fini)) + KEEP(*(.fini)) + KEEP(*crtn.o(.fini)) + . = ALIGN(4); + } > FLASH + + /* ARM C++ exception unwinder tables. The unwinder walks .ARM.exidx between + __exidx_start and __exidx_end; .ARM.extab holds the per-function unwind + instructions. Without these, a throw inside a task hard-faults. */ + .ARM.extab : { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .init_array : { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array*)) + PROVIDE_HIDDEN(__preinit_array_end = .); + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array*)) + PROVIDE_HIDDEN(__init_array_end = .); + . = ALIGN(4); + } > FLASH + + _sidata = LOADADDR(.data); + .data : { + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM AT > FLASH + + .bss : { + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + } > RAM + + end = .; /* newlib _sbrk heap start */ + PROVIDE(_end = .); +} diff --git a/embedded/s2-libsrt/patches/0001-srt-baremetal-portability.patch b/embedded/s2-libsrt/patches/0001-srt-baremetal-portability.patch new file mode 100644 index 000000000..79ab1a84f --- /dev/null +++ b/embedded/s2-libsrt/patches/0001-srt-baremetal-portability.patch @@ -0,0 +1,54 @@ +diff --git a/srtcore/core.cpp b/srtcore/core.cpp +index 7b110d43..35d0bdfd 100644 +--- a/srtcore/core.cpp ++++ b/srtcore/core.cpp +@@ -5995,8 +5995,11 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& + + m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS + +- // Uses the smaller MSS between the peers +- m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); ++ // Uses the smaller MSS between the peers. ++ // iMSS is int but m_iMSS is int32_t; on platforms where int32_t is a ++ // distinct type from int (e.g. where int32_t is long), std::min cannot ++ // deduce its template argument. Pin it to int. ++ m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); + + const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; +diff --git a/srtcore/core.h b/srtcore/core.h +index e5fea5d9..74e3353f 100644 +--- a/srtcore/core.h ++++ b/srtcore/core.h +@@ -928,7 +928,10 @@ private: + return; + + m_pSndBuffer->setRateEstimator(rate); +- updateCC(TEV_SYNC, EventVariant(0)); ++ // Where int32_t is a distinct type from int, the literal 0 is ambiguous ++ // between EventVariant(int32_t) and EventVariant(const CPacket*). ++ // Select the int32_t overload explicitly. ++ updateCC(TEV_SYNC, EventVariant(int32_t(0))); + } + + +diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp +index cfbd2ab6..6310bc52 100644 +--- a/srtcore/sync_posix.cpp ++++ b/srtcore/sync_posix.cpp +@@ -400,13 +400,13 @@ srt::sync::CThread& srt::sync::CThread::operator=(CThread& other) + LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!"); + + #ifndef DEBUG +-#if !defined(__ANDROID__) && !defined(__OHOS__) ++#if !defined(__ANDROID__) && !defined(__OHOS__) && !defined(SRT_NO_PTHREAD_CANCEL) + // In case of production build the hanging thread should be terminated + // to avoid hang ups and align with C++11 implementation. + // There is no pthread_cancel on Android. See #1476. This error should not normally + // happen, but if it happen, then detaching the thread. + pthread_cancel(m_thread); +-#endif // __ANDROID__ __OHOS__ ++#endif // __ANDROID__ __OHOS__ SRT_NO_PTHREAD_CANCEL + #else + join(); + #endif diff --git a/embedded/s2-libsrt/posix-shims/endian.h b/embedded/s2-libsrt/posix-shims/endian.h new file mode 100644 index 000000000..2775a5412 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/endian.h @@ -0,0 +1,66 @@ +/* Bare-metal newlib ships no . libsrt's utilities.h includes it on the + * __linux__/__CYGWIN__/__GNU__/__GLIBC__ branch (we select it with -D__GNU__, the + * only non-Linux option that doesn't also trigger epoll elsewhere) and otherwise + * hits an unconditional `#error Endian: platform not supported`. Provide the full + * glibc-style byte-order macro set for little-endian ARM (Cortex-M is LE; GCC + * confirms __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__). Guarded so a later + * can't double-define. */ +#ifndef S2_SHIM_ENDIAN_H +#define S2_SHIM_ENDIAN_H + +#include + +#ifndef __LITTLE_ENDIAN +#define __LITTLE_ENDIAN 1234 +#endif +#ifndef __BIG_ENDIAN +#define __BIG_ENDIAN 4321 +#endif +#ifndef __PDP_ENDIAN +#define __PDP_ENDIAN 3412 +#endif +#ifndef __BYTE_ORDER +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif + +/* Little-endian host: host<->little is identity, host<->big is a byte swap. */ +#ifndef htole16 +#define htole16(x) ((uint16_t)(x)) +#endif +#ifndef le16toh +#define le16toh(x) ((uint16_t)(x)) +#endif +#ifndef htobe16 +#define htobe16(x) __builtin_bswap16((uint16_t)(x)) +#endif +#ifndef be16toh +#define be16toh(x) __builtin_bswap16((uint16_t)(x)) +#endif + +#ifndef htole32 +#define htole32(x) ((uint32_t)(x)) +#endif +#ifndef le32toh +#define le32toh(x) ((uint32_t)(x)) +#endif +#ifndef htobe32 +#define htobe32(x) __builtin_bswap32((uint32_t)(x)) +#endif +#ifndef be32toh +#define be32toh(x) __builtin_bswap32((uint32_t)(x)) +#endif + +#ifndef htole64 +#define htole64(x) ((uint64_t)(x)) +#endif +#ifndef le64toh +#define le64toh(x) ((uint64_t)(x)) +#endif +#ifndef htobe64 +#define htobe64(x) __builtin_bswap64((uint64_t)(x)) +#endif +#ifndef be64toh +#define be64toh(x) __builtin_bswap64((uint64_t)(x)) +#endif + +#endif diff --git a/embedded/s2-libsrt/posix-shims/netdb.h b/embedded/s2-libsrt/posix-shims/netdb.h new file mode 100644 index 000000000..3cebf70ae --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/netdb.h @@ -0,0 +1,39 @@ +/* libsrt's platform_sys.h includes . lwIP provides getaddrinfo + struct + * addrinfo (in lwip/netdb.h, under LWIP_DNS — which we enable in lwipopts), but + * NO getnameinfo and none of the NI_* flags. Forward to lwIP's netdb and supply + * the missing pieces. */ +#ifndef S2_SHIM_NETDB_H +#define S2_SHIM_NETDB_H + +#include +#include + +/* NI_* flags (libsrt's sockaddr_any::str() uses NI_NUMERICHOST/NUMERICSERV/ + * NAMEREQD). Standard POSIX values. */ +#ifndef NI_NUMERICHOST +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#define NI_NUMERICHOST 0x01 +#define NI_NUMERICSERV 0x02 +#define NI_NOFQDN 0x04 +#define NI_NAMEREQD 0x08 +#define NI_DGRAM 0x10 +#endif + +/* lwIP has no getnameinfo. S2 does no reverse-name resolution (the SRT data + * plane uses numeric addresses), so a stub that fails is sufficient: libsrt's + * sockaddr_any::str() treats a nonzero return as "no host" and falls back to + * ":". Real impl deferred — not needed until/unless a binding wants it. */ +#ifndef EAI_FAIL +#define EAI_FAIL (-4) /* nonzero; POSIX getnameinfo returns 0 or an EAI_* code */ +#endif +static inline int getnameinfo(const struct sockaddr* sa, socklen_t salen, + char* host, socklen_t hostlen, + char* serv, socklen_t servlen, int flags) { + (void)sa; (void)salen; (void)flags; + if (host && hostlen) host[0] = '\0'; /* don't leave caller buffers unset */ + if (serv && servlen) serv[0] = '\0'; + return EAI_FAIL; +} + +#endif diff --git a/embedded/s2-libsrt/posix-shims/netinet/in.h b/embedded/s2-libsrt/posix-shims/netinet/in.h new file mode 100644 index 000000000..3e19b89dc --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/netinet/in.h @@ -0,0 +1,46 @@ +/* lwIP defines sockaddr_in/in_addr/IPPROTO_* in lwip/sockets.h, pulled via the + * compat ; lwIP ships no , so forward to it. */ +#ifndef S2_SHIM_NETINET_IN_H +#define S2_SHIM_NETINET_IN_H +#include + +/* libsrt's netinet_any.h / channel.cpp / api.cpp reference sockaddr_in6 + + * IPPROTO_IPV6 + IPV6_* unconditionally (it supports IPv6 at compile time). lwIP + * already defines `struct in6_addr` (+ s6_addr) in lwip/inet.h, and — because + * our lwipopts.h enables LWIP_IPV6 — `struct sockaddr_in6` too. The `#if + * !LWIP_IPV6` block below is therefore an INACTIVE fallback, retained only for + * an IPv4-only configuration; with IPv6 enabled it compiles out. The IPV6_* + * socket-option gap-fills further down stay active unconditionally because lwIP + * doesn't define all of them (e.g. IPV6_UNICAST_HOPS). Cosmetic at runtime — no + * AF_INET6 socket is ever created. */ +#if !LWIP_IPV6 +#include +#ifndef AF_INET6 +#define AF_INET6 10 /* matches lwIP lwip/sockets.h value */ +#endif +struct sockaddr_in6 { + uint8_t sin6_len; + sa_family_t sin6_family; + in_port_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; /* lwIP lwip/inet.h provides in6_addr */ + uint32_t sin6_scope_id; +}; +#endif /* !LWIP_IPV6 */ + +/* IPv6 socket-option names libsrt sets on AF_INET6 sockets. lwIP defines some + * (IPPROTO_IPV6, IPV6_V6ONLY) under LWIP_IPV6 but NOT IPV6_UNICAST_HOPS, and + * none with IPv6 off. Fill the gaps with the standard Linux values (#ifndef so + * lwIP's own defs win where present). Cosmetic here — no AF_INET6 socket is + * created at runtime in the IPv4-only boot smoke / S3 loopback. */ +#ifndef IPPROTO_IPV6 +#define IPPROTO_IPV6 41 +#endif +#ifndef IPV6_UNICAST_HOPS +#define IPV6_UNICAST_HOPS 16 +#endif +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 26 +#endif + +#endif diff --git a/embedded/s2-libsrt/posix-shims/pthread.h b/embedded/s2-libsrt/posix-shims/pthread.h new file mode 100644 index 000000000..22d720336 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/pthread.h @@ -0,0 +1,28 @@ +/* Route libsrt's to FreeRTOS-Plus-POSIX (newlib's is a non-impl + * stub with a different pthread_t). */ +#ifndef S2_SHIM_PTHREAD_H +#define S2_SHIM_PTHREAD_H +#include +#include + +/* FreeRTOS-Plus-POSIX provides NO thread-specific-data (pthread_key_*) API, but + * libsrt's sync_posix.cpp uses one key for its per-thread last-error store. + * Declare the API here; pthread_key_shim.c implements it over FreeRTOS's single + * thread-local-storage slot (configNUM_THREAD_LOCAL_STORAGE_POINTERS=1). The + * definitions link into the firmware, not libsrt.a. */ +#ifndef S2_PTHREAD_KEY_DECLARED +#define S2_PTHREAD_KEY_DECLARED +typedef int pthread_key_t; +#ifdef __cplusplus +extern "C" { +#endif +int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)); +int pthread_key_delete(pthread_key_t key); +int pthread_setspecific(pthread_key_t key, const void* value); +void* pthread_getspecific(pthread_key_t key); +#ifdef __cplusplus +} +#endif +#endif /* S2_PTHREAD_KEY_DECLARED */ + +#endif diff --git a/embedded/s2-libsrt/posix-shims/s2_prefix.h b/embedded/s2-libsrt/posix-shims/s2_prefix.h new file mode 100644 index 000000000..49e594d22 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/s2_prefix.h @@ -0,0 +1,13 @@ +/* Force-included (-include) ahead of every TU when cross-building libsrt, so + * these suppressions land BEFORE any newlib system header runs. newlib's + * declares timer_t (line ~202, guarded by __timer_t_defined / + * _TIMER_T_DECLARED) and FreeRTOS-Plus-POSIX's + * also typedefs it — conflict. Pre-defining the newlib sentinels makes + * FreeRTOS-Plus-POSIX the sole definer (it's the threading layer libsrt binds + * to). The pthread-type and sigval/sigevent collisions are handled by the + * posix-shims/sys/_pthreadtypes.h and posix-shims/sys/signal.h shims. */ +#ifndef S2_PREFIX_H +#define S2_PREFIX_H +#define __timer_t_defined 1 +#define _TIMER_T_DECLARED +#endif diff --git a/embedded/s2-libsrt/posix-shims/sched.h b/embedded/s2-libsrt/posix-shims/sched.h new file mode 100644 index 000000000..9a5428c4c --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/sched.h @@ -0,0 +1,5 @@ +#ifndef S2_SHIM_SCHED_H +#define S2_SHIM_SCHED_H +#include +#include +#endif diff --git a/embedded/s2-libsrt/posix-shims/semaphore.h b/embedded/s2-libsrt/posix-shims/semaphore.h new file mode 100644 index 000000000..98a2f400b --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/semaphore.h @@ -0,0 +1,5 @@ +#ifndef S2_SHIM_SEMAPHORE_H +#define S2_SHIM_SEMAPHORE_H +#include +#include +#endif diff --git a/embedded/s2-libsrt/posix-shims/sys/_pthreadtypes.h b/embedded/s2-libsrt/posix-shims/sys/_pthreadtypes.h new file mode 100644 index 000000000..12c78f040 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/sys/_pthreadtypes.h @@ -0,0 +1,11 @@ +/* Intentionally (almost) empty. libsrt's CMake forces -D_GNU_SOURCE, which bumps + * newlib's __POSIX_VISIBLE to 200809 (>=199506) and so activates newlib's real + * (gated at its line 21). That declares pthread_t / + * pthread_mutex_t / pthread_cond_t / timer_t as NEWLIB types, conflicting with + * the FreeRTOS-Plus-POSIX typedefs libsrt actually uses (via our pthread.h + * shim). S1 never defined _GNU_SOURCE, so it never hit this. Pre-defining the + * newlib include guard suppresses newlib's version; the threading types come + * exclusively from FreeRTOS-Plus-POSIX. */ +#ifndef _SYS__PTHREADTYPES_H_ +#define _SYS__PTHREADTYPES_H_ +#endif diff --git a/embedded/s2-libsrt/posix-shims/sys/ioctl.h b/embedded/s2-libsrt/posix-shims/sys/ioctl.h new file mode 100644 index 000000000..7744c83f9 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/sys/ioctl.h @@ -0,0 +1,6 @@ +/* libsrt uses ioctl(FIONBIO/FIONREAD); lwIP provides lwip_ioctl + these macros + * via lwip/sockets.h (pulled by the compat ). */ +#ifndef S2_SHIM_SYS_IOCTL_H +#define S2_SHIM_SYS_IOCTL_H +#include +#endif diff --git a/embedded/s2-libsrt/posix-shims/sys/signal.h b/embedded/s2-libsrt/posix-shims/sys/signal.h new file mode 100644 index 000000000..0a7d81dcc --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/sys/signal.h @@ -0,0 +1,18 @@ +/* newlib's defines `union sigval` / `struct sigevent` / + * `siginfo_t` under its 199309 visibility gate, with NO sub-sentinel. So does + * FreeRTOS-Plus-POSIX's (its timer needs the complete, + * pthread-style sigevent), with a different guard. When a libsrt TU pulls both + * (newlib via platform_sys.h, FreeRTOS via our pthread.h shim) they collide on + * redefinition. Mask newlib's 199309 block by lowering __POSIX_VISIBLE only + * across the real header, leaving FreeRTOS's as the single definition. The basic + * signal API (SIG*, signal(), raise(), sig_atomic_t) sits below the 199309 gate + * and is unaffected. */ +#ifndef S2_SHIM_SYS_SIGNAL_H +#define S2_SHIM_SYS_SIGNAL_H +#include /* pin __POSIX_VISIBLE before we override it */ +#pragma push_macro("__POSIX_VISIBLE") +#undef __POSIX_VISIBLE +#define __POSIX_VISIBLE 199209 +#include_next +#pragma pop_macro("__POSIX_VISIBLE") +#endif diff --git a/embedded/s2-libsrt/posix-shims/sys/uio.h b/embedded/s2-libsrt/posix-shims/sys/uio.h new file mode 100644 index 000000000..10a9fee4c --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/sys/uio.h @@ -0,0 +1,8 @@ +/* libsrt's common.h includes for `struct iovec` (scatter/gather in + * CPacket sendmsg). Bare-metal newlib ships none; lwIP defines `struct iovec` + * (and lwip_readv/writev) in lwip/sockets.h, pulled via the compat + * . */ +#ifndef S2_SHIM_SYS_UIO_H +#define S2_SHIM_SYS_UIO_H +#include +#endif diff --git a/embedded/s2-libsrt/posix-shims/syslog.h b/embedded/s2-libsrt/posix-shims/syslog.h new file mode 100644 index 000000000..bbe7cb7d9 --- /dev/null +++ b/embedded/s2-libsrt/posix-shims/syslog.h @@ -0,0 +1,17 @@ +/* Bare-metal newlib ships no , but libsrt's logging_api.h includes it + * unconditionally just for the LOG_* level constants (it says so at L33). These + * values mirror libsrt's own common/win/syslog_defs.h exactly. No syslog() + * function is provided — libsrt never calls it (logging is OFF here anyway). */ +#ifndef S2_SHIM_SYSLOG_H +#define S2_SHIM_SYSLOG_H + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#endif diff --git a/embedded/s2-libsrt/pthread_key_shim.c b/embedded/s2-libsrt/pthread_key_shim.c new file mode 100644 index 000000000..95d246a24 --- /dev/null +++ b/embedded/s2-libsrt/pthread_key_shim.c @@ -0,0 +1,71 @@ +/* Minimal pthread thread-specific-data (TSD) for the S2 libsrt port. + * + * FreeRTOS-Plus-POSIX ships no pthread_key_* API, but libsrt's sync_posix.cpp + * needs exactly ONE key (CThreadError's per-thread last-error). We back that key + * with FreeRTOS's single thread-local-storage pointer slot 0 + * (configNUM_THREAD_LOCAL_STORAGE_POINTERS=1). Only one live key is supported, + * which matches libsrt's usage. + * + * Limitation: the key destructor is NOT invoked on task exit (FreeRTOS TLS has + * no per-task delete callback unless configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS + * is enabled). That is acceptable here — libsrt's GC thread is long-lived and a + * bare-metal target never exits; the small per-thread CUDTException simply isn't + * reclaimed on the rare thread teardown. + * + * Pre-scheduler context: libsrt touches this key from a global constructor + * (before vTaskStartScheduler), where there is NO current task, so FreeRTOS's + * vTaskSetThreadLocalStoragePointer(NULL,...) would assert (pxCurrentTCB==NULL). + * Route get/set to a single global "bootstrap" slot until the scheduler runs. + * That bootstrap thread is distinct from any FreeRTOS task, which is correct + * TLS semantics — tasks start with their own (NULL) value. */ +#include +#include +#include +#include /* our shim: pthread_key_t + the prototypes we define here */ + +#define S2_TLS_SLOT 0 + +static int s_key_in_use = 0; +static void* s_bootstrap_tls = NULL; /* TLS for the pre-scheduler bootstrap ctx */ + +static int scheduler_running(void) +{ + return xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; +} + +int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) +{ + (void)destructor; /* not invoked — see file header */ + if (key == NULL) + return EINVAL; /* nothing to return the key through; don't claim the slot */ + if (s_key_in_use) + return EAGAIN; /* only one TLS slot available */ + s_key_in_use = 1; + *key = S2_TLS_SLOT; + return 0; +} + +int pthread_key_delete(pthread_key_t key) +{ + (void)key; + s_key_in_use = 0; + return 0; +} + +int pthread_setspecific(pthread_key_t key, const void* value) +{ + (void)key; + if (scheduler_running()) + vTaskSetThreadLocalStoragePointer(NULL, S2_TLS_SLOT, (void*)value); + else + s_bootstrap_tls = (void*)value; + return 0; +} + +void* pthread_getspecific(pthread_key_t key) +{ + (void)key; + if (scheduler_running()) + return pvTaskGetThreadLocalStoragePointer(NULL, S2_TLS_SLOT); + return s_bootstrap_tls; +} diff --git a/embedded/s2-libsrt/startup.c b/embedded/s2-libsrt/startup.c new file mode 100644 index 000000000..7393a23f9 --- /dev/null +++ b/embedded/s2-libsrt/startup.c @@ -0,0 +1,100 @@ +/* Cortex-M4F startup for QEMU mps2-an386 + newlib/rdimon semihosting + FreeRTOS. + * + * Adapted from crates/baremetal-qemu-c/firmware/startup.c. The only structural + * change for FreeRTOS is the vector table: the kernel's ARM_CM4F port installs + * the context-switch + tick machinery via the SVC, PendSV and SysTick exception + * vectors. Their handler symbols are vPortSVCHandler / xPortPendSVHandler / + * xPortSysTickHandler (see vendor/freertos-kernel/portable/GCC/ARM_CM4F/port.c). + * We wire them into vector slots 11 (SVCall), 14 (PendSV) and 15 (SysTick). + * + * Root-cause notes carried over from c-1: + * - __libc_init_array() calls _init() first; _init is assembled from + * crti.o (prologue) + crtn.o (epilogue). The linker script must sequence + * KEEP(*crti.o(.init)) … KEEP(*crtn.o(.init)) or _init never returns. + * - _exit() is overridden with a raw SYS_EXIT semihosting BKPT for a clean, + * newlib-version-independent QEMU process exit. + */ +#include +#include + +extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss, _estack; +extern char end, _heap_end; +extern int main(void); +extern void __libc_init_array(void); +extern void initialise_monitor_handles(void); /* rdimon */ + +/* FreeRTOS ARM_CM4F port handlers (defined in vendor port.c). */ +extern void vPortSVCHandler(void); +extern void xPortPendSVHandler(void); +extern void xPortSysTickHandler(void); + +/* Forward declaration — defined at the bottom of this file. */ +__attribute__((noreturn)) void _exit(int status); + +void Reset_Handler(void); +static void Default_Handler(void) { for (;;) {} } + +/* Cortex-M vector table. Slots 0..15 are the system exceptions; we only need + the system ones FreeRTOS uses plus reset/NMI/HardFault. Intervening slots + are padded with Default_Handler so the indices line up. */ +__attribute__((section(".isr_vector"), used)) +void (* const g_vectors[])(void) = { + (void (*)(void))(&_estack), /* [0] initial MSP */ + Reset_Handler, /* [1] Reset */ + Default_Handler, /* [2] NMI */ + Default_Handler, /* [3] HardFault */ + Default_Handler, /* [4] MemManage */ + Default_Handler, /* [5] BusFault */ + Default_Handler, /* [6] UsageFault */ + 0, /* [7] reserved */ + 0, /* [8] reserved */ + 0, /* [9] reserved */ + 0, /* [10] reserved */ + vPortSVCHandler, /* [11] SVCall */ + Default_Handler, /* [12] DebugMon */ + 0, /* [13] reserved */ + xPortPendSVHandler, /* [14] PendSV */ + xPortSysTickHandler, /* [15] SysTick */ +}; + +void Reset_Handler(void) { + /* Enable the FPU (CP10/CP11 full access) — hard-float target. */ + volatile uint32_t *cpacr = (volatile uint32_t *)0xE000ED88u; + *cpacr |= (0xFu << 20); + __asm volatile("dsb"); __asm volatile("isb"); + + /* Copy .data from FLASH to RAM. */ + for (uint32_t *s = &_sidata, *d = &_sdata; d < &_edata; ) *d++ = *s++; + /* Zero .bss. */ + for (uint32_t *d = &_sbss; d < &_ebss; ) *d++ = 0; + + initialise_monitor_handles(); + __libc_init_array(); + _exit(main()); + for (;;) {} /* unreachable; satisfies noreturn analysis */ +} + +/* Self-contained heap for newlib malloc / the C++ runtime. */ +void *_sbrk(int incr) { + static char *brk = 0; + if (!brk) brk = &end; + if (brk + incr > &_heap_end || brk + incr < &end) { + errno = ENOMEM; + return (void *)-1; + } + char *prev = brk; brk += incr; return prev; +} + +/* Override newlib's _exit() with a direct ARM semihosting SYS_EXIT BKPT. + * R0 = 0x18 (SYS_EXIT) + * R1 = ADP_Stopped_ApplicationExit (0x20026) for exit(0), + * ADP_Stopped_RunTimeError (0x20023) for any non-zero status. + * QEMU maps ADP_Stopped_ApplicationExit → process exit 0. */ +__attribute__((noreturn)) void _exit(int status) { + register uint32_t r0 __asm("r0") = 0x18u; /* SYS_EXIT */ + register uint32_t r1 __asm("r1") = + (status == 0) ? 0x20026u /* ADP_Stopped_ApplicationExit */ + : 0x20023u; /* ADP_Stopped_RunTimeError */ + __asm volatile("bkpt 0xab" : : "r"(r0), "r"(r1)); + for (;;) {} /* unreachable */ +} diff --git a/embedded/s2-libsrt/sys_arch.c b/embedded/s2-libsrt/sys_arch.c new file mode 100644 index 000000000..89756b932 --- /dev/null +++ b/embedded/s2-libsrt/sys_arch.c @@ -0,0 +1,102 @@ +/* lwIP NO_SYS=0 sys_arch port onto FreeRTOS. Maps lwIP's semaphores/mutexes/ + * mailboxes/threads onto FreeRTOS primitives. Mailboxes are FreeRTOS queues of + * void* (lwIP posts pbuf/api-msg pointers). Standard reference shape (cf. + * lwip-contrib ports/freertos), trimmed to what loopback UDP + sockets need. */ +#include "lwip/opt.h" +#include "lwip/sys.h" +#include "lwip/err.h" +#include "arch/sys_arch.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" + +/* ---- protection (SYS_LIGHTWEIGHT_PROT) ---- */ +sys_prot_t sys_arch_protect(void) { taskENTER_CRITICAL(); return 0; } +void sys_arch_unprotect(sys_prot_t p) { (void)p; taskEXIT_CRITICAL(); } + +void sys_init(void) {} + +u32_t sys_now(void) { return (u32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); } + +/* ---- semaphores (binary/counting) ---- */ +err_t sys_sem_new(sys_sem_t *sem, u8_t count) { + sem->sem = xSemaphoreCreateBinary(); + if (sem->sem == NULL) return ERR_MEM; + if (count > 0) xSemaphoreGive(sem->sem); + return ERR_OK; +} +void sys_sem_free(sys_sem_t *sem) { vSemaphoreDelete(sem->sem); sem->sem = NULL; } +void sys_sem_signal(sys_sem_t *sem) { xSemaphoreGive(sem->sem); } +int sys_sem_valid(sys_sem_t *sem) { return sem->sem != NULL; } +void sys_sem_set_invalid(sys_sem_t *sem) { sem->sem = NULL; } + +u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout_ms) { + TickType_t start = xTaskGetTickCount(); + TickType_t to = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xSemaphoreTake(sem->sem, to) == pdTRUE) { + return (u32_t)((xTaskGetTickCount() - start) * portTICK_PERIOD_MS); + } + return SYS_ARCH_TIMEOUT; +} + +/* ---- mutexes ---- */ +err_t sys_mutex_new(sys_mutex_t *mtx) { + mtx->mtx = xSemaphoreCreateMutex(); + return mtx->mtx == NULL ? ERR_MEM : ERR_OK; +} +void sys_mutex_free(sys_mutex_t *mtx) { vSemaphoreDelete(mtx->mtx); mtx->mtx = NULL; } +void sys_mutex_lock(sys_mutex_t *mtx) { xSemaphoreTake(mtx->mtx, portMAX_DELAY); } +void sys_mutex_unlock(sys_mutex_t *mtx) { xSemaphoreGive(mtx->mtx); } +int sys_mutex_valid(sys_mutex_t *mtx) { return mtx->mtx != NULL; } +void sys_mutex_set_invalid(sys_mutex_t *mtx) { mtx->mtx = NULL; } + +/* ---- mailboxes (queue of void*) ---- */ +err_t sys_mbox_new(sys_mbox_t *mbox, int size) { + mbox->mbox = xQueueCreate((UBaseType_t)size, sizeof(void *)); + return mbox->mbox == NULL ? ERR_MEM : ERR_OK; +} +void sys_mbox_free(sys_mbox_t *mbox) { vQueueDelete(mbox->mbox); mbox->mbox = NULL; } +int sys_mbox_valid(sys_mbox_t *mbox) { return mbox->mbox != NULL; } +void sys_mbox_set_invalid(sys_mbox_t *mbox) { mbox->mbox = NULL; } + +void sys_mbox_post(sys_mbox_t *mbox, void *msg) { + xQueueSendToBack(mbox->mbox, &msg, portMAX_DELAY); +} +err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg) { + return xQueueSendToBack(mbox->mbox, &msg, 0) == pdTRUE ? ERR_OK : ERR_MEM; +} +err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg) { + BaseType_t woken = pdFALSE; + BaseType_t r = xQueueSendToBackFromISR(mbox->mbox, &msg, &woken); + portYIELD_FROM_ISR(woken); + return r == pdTRUE ? ERR_OK : ERR_MEM; +} + +u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout_ms) { + void *dummy; if (msg == NULL) msg = &dummy; + TickType_t start = xTaskGetTickCount(); + TickType_t to = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xQueueReceive(mbox->mbox, &(*msg), to) == pdTRUE) { + return (u32_t)((xTaskGetTickCount() - start) * portTICK_PERIOD_MS); + } + *msg = NULL; + return SYS_ARCH_TIMEOUT; +} +u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg) { + void *dummy; if (msg == NULL) msg = &dummy; + return xQueueReceive(mbox->mbox, &(*msg), 0) == pdTRUE ? 0 : SYS_MBOX_EMPTY; +} + +/* ---- threads ---- */ +sys_thread_t sys_thread_new(const char *name, lwip_thread_fn fn, void *arg, + int stacksize, int prio) { + TaskHandle_t h = NULL; + /* lwIP stacksize is in bytes; FreeRTOS xTaskCreate depth is in words. + * Round up so a non-word-aligned byte size doesn't under-allocate. */ + xTaskCreate((TaskFunction_t)fn, name, + (configSTACK_DEPTH_TYPE)(((size_t)stacksize + sizeof(StackType_t) - 1) / sizeof(StackType_t)), + arg, (UBaseType_t)prio, &h); + return h; +} diff --git a/embedded/s2-libsrt/syscalls_stub.c b/embedded/s2-libsrt/syscalls_stub.c new file mode 100644 index 000000000..df64b8f00 --- /dev/null +++ b/embedded/s2-libsrt/syscalls_stub.c @@ -0,0 +1,23 @@ +/* Bare-metal syscall stubs needed once libsrt is linked. + * + * _getentropy: libsrt seeds initial sequence numbers / socket IDs via + * std::random_device, which on newlib funnels through getentropy() -> the + * _getentropy syscall. Bare metal has no entropy backend, so it is unresolved. + * S2 builds with encryption OFF and never needs cryptographic randomness, so a + * deterministic LCG is enough to link and boot. This is NOT suitable for real + * crypto — a production embedded build must wire a hardware RNG here. */ +#include +#include +#include + +int _getentropy(void* buf, size_t n) +{ + static uint32_t seed = 0x01234567u; + uint8_t* p = (uint8_t*)buf; + if (p == NULL && n != 0) { errno = EFAULT; return -1; } /* fail cleanly */ + for (size_t i = 0; i < n; i++) { + seed = seed * 1103515245u + 12345u; + p[i] = (uint8_t)(seed >> 16); + } + return 0; +} diff --git a/scripts/check-s2-libsrt.sh b/scripts/check-s2-libsrt.sh new file mode 100755 index 000000000..7154f9f4f --- /dev/null +++ b/scripts/check-s2-libsrt.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Build + run the S2 libsrt boot smoke under QEMU mps2-an386 and assert +# srt_startup+create_socket+getsockstate+close+cleanup all succeed on the +# FreeRTOS+lwIP substrate. Cross-builds libsrt.a on first run (cmake). Skips +# when the ARM C++ cross-toolchain, QEMU, or cmake is absent. +set -euo pipefail +cd "$(dirname "$0")/.." + +command -v arm-none-eabi-g++ >/dev/null 2>&1 || { echo "SKIP: arm-none-eabi-g++ not installed"; exit 0; } +command -v qemu-system-arm >/dev/null 2>&1 || { echo "SKIP: qemu-system-arm not installed"; exit 0; } +command -v cmake >/dev/null 2>&1 || { echo "SKIP: cmake not installed"; exit 0; } + +echo "==> building S2 libsrt firmware (cross-builds libsrt.a on first run)" +( cd embedded/s2-libsrt && ./build.sh ) + +echo "==> running under QEMU (mps2-an386)" +OUT=$(timeout 120 qemu-system-arm -machine mps2-an386 -nographic \ + -semihosting-config enable=on,target=native -kernel embedded/s2-libsrt/firmware.elf || true) +echo "$OUT" +grep -q 'PASS: s2_libsrt' <<<"$OUT" || { echo "GATE FAILED"; exit 1; } +echo "OK: cross-compiled libsrt initializes on FreeRTOS+lwIP (startup+socket+cleanup)"