From f9b9f0b6722eca868f6466c0f64e30e719171db0 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 15:31:26 +0000 Subject: [PATCH 1/6] feat: S28.09 FreeRtosLwip LAN9118 netif + NO_SYS=0 UDP target Grow the S28.07 link-probe into a runnable FreeRTOS+lwIP BDD target on QEMU mps2-an385: a hand-written LAN9118 lwIP netif (netif/EthernetIf.c) over the vendored Arm smsc9220 driver (Apache-2.0), tcpip_init + static IP bring-up on the tcpip thread, and the SolidSyslog UDP pipeline wired through the tcpip_callback marshal (S28.06). Verified on QEMU: boot, ARP, and a correct RFC 5424 datagram to 10.0.2.2:5514, clean teardown, no PlusTcp symbols. WIP: oracle BDD run + CI lanes still to come. Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/FreeRtosLwip/CMakeLists.txt | 83 +- Bdd/Targets/FreeRtosLwip/FreeRTOSConfig.h | 64 +- Bdd/Targets/FreeRtosLwip/lwipopts.h | 67 +- Bdd/Targets/FreeRtosLwip/main.c | 578 +++++++- Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c | 236 ++++ Bdd/Targets/FreeRtosLwip/netif/EthernetIf.h | 17 + .../FreeRtosLwip/netif/smsc9220_emac_config.h | 42 + .../FreeRtosLwip/netif/smsc9220_eth_drv.c | 1230 +++++++++++++++++ .../FreeRtosLwip/netif/smsc9220_eth_drv.h | 547 ++++++++ .../FreeRtosLwip/solidsyslog_user_tunables.h | 24 + CMakePresets.json | 5 +- 11 files changed, 2769 insertions(+), 124 deletions(-) create mode 100644 Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c create mode 100644 Bdd/Targets/FreeRtosLwip/netif/EthernetIf.h create mode 100644 Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h create mode 100644 Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c create mode 100644 Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h create mode 100644 Bdd/Targets/FreeRtosLwip/solidsyslog_user_tunables.h diff --git a/Bdd/Targets/FreeRtosLwip/CMakeLists.txt b/Bdd/Targets/FreeRtosLwip/CMakeLists.txt index 43cb7054..3e697091 100644 --- a/Bdd/Targets/FreeRtosLwip/CMakeLists.txt +++ b/Bdd/Targets/FreeRtosLwip/CMakeLists.txt @@ -1,16 +1,18 @@ -# FreeRTOS + lwIP (Raw API) link-probe ELF for QEMU mps2-an385 (Cortex-M3). +# FreeRTOS + lwIP (Raw API, NO_SYS=0) BDD target ELF for QEMU mps2-an385 +# (Cortex-M3). # -# S28.07: cross-build the Platform/LwipRaw adapter tree for a FreeRTOS/ARM -# target, selected via SOLIDSYSLOG_FREERTOS_NET=LWIP, proving it links against -# lwIP core with zero dependency on Platform/PlusTcp / FreeRTOS-Plus-TCP. No -# LAN9118 netif driver and no QEMU run — those land with S28.09, which reuses -# this directory's lwipopts.h / FreeRTOSConfig.h / arch/cc.h and grows main.c -# into the real netif + tcpip integration. +# S28.09: grew the S28.07 link-probe into a runnable BDD target — a real +# LAN9118 lwIP netif (netif/EthernetIf.c over the vendored Arm smsc9220 +# driver), tcpip_init + a static IP, and the SolidSyslog UDP pipeline reaching +# the syslog-ng oracle on QEMU. Selected via SOLIDSYSLOG_FREERTOS_NET=LWIP, +# with zero dependency on Platform/PlusTcp / FreeRTOS-Plus-TCP. # # Source layout mirrors Bdd/Targets/FreeRtos/CMakeLists.txt: upstream -# FreeRTOS-Kernel + lwIP-core sources compile into a separate OBJECT library +# FreeRTOS-Kernel + lwIP-core + lwIP-api + the contrib FreeRTOS sys_arch + the +# vendored LAN9118 driver compile into a separate OBJECT library # (`solid_syslog_freertos_lwip_upstream`) under a relaxed warning set; our own -# code (main.c, the reused Cortex-M3 startup, and the Platform/LwipRaw adapter +# code (main.c, the reused Cortex-M3 startup, netif/EthernetIf.c, the +# Platform/LwipRaw + Platform/FreeRtos adapters, and the shared BddTarget # sources) stays in the executable under the strict project warning bar. set(FREERTOS_KERNEL_PATH "$ENV{FREERTOS_KERNEL_PATH}") @@ -27,32 +29,48 @@ if(NOT LWIP_DIR) "which sets this to /opt/lwip.") endif() -# lwIP ships its source lists as *_SRCS variables via Filelists.cmake (it is -# explicitly NOT an add_subdirectory target). We consume the minimal IPv4 core -# set — no api/ (NO_SYS=1, no sequential/socket API), no ipv6, no netif driver. +# lwIP ships its source lists as *_SRCS variables via Filelists.cmake. For +# NO_SYS=0 we consume the IPv4 core set plus the api/ set (tcpip.c, api_lib, +# api_msg, …) — the tcpip thread, timeouts, and tcpip_callback marshal hop +# live there. No ipv6. include(${LWIP_DIR}/src/Filelists.cmake) +set(LWIP_CONTRIB_FREERTOS_DIR "${LWIP_DIR}/contrib/ports/freertos") + set(FREERTOS_PORT_DIR "${FREERTOS_KERNEL_PATH}/portable/GCC/ARM_CM3") -set(FREERTOS_COMMON_DIR "${CMAKE_SOURCE_DIR}/Bdd/Targets/FreeRtos/Common") +set(FREERTOS_TCP_TARGET_DIR "${CMAKE_SOURCE_DIR}/Bdd/Targets/FreeRtos") +set(FREERTOS_COMMON_DIR "${FREERTOS_TCP_TARGET_DIR}/Common") # --- Upstream OBJECT library -------------------------------------------------- # -# FreeRTOS kernel (minimal: scheduler + heap) + lwIP IPv4 core, compiled with -# the relaxed warning set those upstream sources require. The executable -# consumes the objects via $ so the relaxations don't leak. +# FreeRTOS kernel + lwIP IPv4 core + lwIP api (tcpip thread) + the contrib +# FreeRTOS sys_arch + the vendored Arm LAN9118 driver, compiled with the +# relaxed warning set those upstream / third-party sources require. The +# executable consumes the objects via $ so the relaxations +# don't leak into project code. add_library(solid_syslog_freertos_lwip_upstream OBJECT ${FREERTOS_KERNEL_PATH}/tasks.c ${FREERTOS_KERNEL_PATH}/queue.c ${FREERTOS_KERNEL_PATH}/list.c + ${FREERTOS_KERNEL_PATH}/timers.c + ${FREERTOS_KERNEL_PATH}/event_groups.c ${FREERTOS_PORT_DIR}/port.c ${FREERTOS_KERNEL_PATH}/portable/MemMang/heap_4.c ${lwipcore_SRCS} ${lwipcore4_SRCS} + ${lwipapi_SRCS} # etharp (in core4) emits ARP frames via ethernet_output/ethbroadcast/ # ethzero from netif/ethernet.c — pull that one netif source in (but not - # bridgeif/slipif from lwipnetif_SRCS, which this probe has no use for). + # bridgeif/slipif from lwipnetif_SRCS, which this target has no use for). ${LWIP_DIR}/src/netif/ethernet.c + # lwIP FreeRTOS OS-abstraction port (sys_mutex/sem/mbox/thread) — required + # for NO_SYS=0. + ${LWIP_CONTRIB_FREERTOS_DIR}/sys_arch.c + # Vendored Arm LAN9118 (SMSC9220) low-level driver (Apache-2.0). The lwIP + # netif wrapper over it (netif/EthernetIf.c) stays in the executable under + # the strict bar; this third-party source sits under the relaxations. + ${CMAKE_CURRENT_SOURCE_DIR}/netif/smsc9220_eth_drv.c ) target_compile_options(solid_syslog_freertos_lwip_upstream PRIVATE @@ -61,10 +79,10 @@ target_compile_options(solid_syslog_freertos_lwip_upstream PRIVATE -ffunction-sections -fdata-sections -fno-common - # FreeRTOS-Kernel + lwIP use non-prototype declarations, type-narrowing - # constructs, sign-conversion patterns, and shadowed locals that trip our - # strict host warnings. Scoped to this OBJECT library so the relaxations - # never reach project code. + # FreeRTOS-Kernel + lwIP + the Arm driver use non-prototype declarations, + # type-narrowing constructs, sign-conversion patterns, and shadowed locals + # that trip our strict host warnings. Scoped to this OBJECT library so the + # relaxations never reach project code. -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-implicit-fallthrough @@ -77,9 +95,11 @@ target_compile_options(solid_syslog_freertos_lwip_upstream PRIVATE target_include_directories(solid_syslog_freertos_lwip_upstream PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # FreeRTOSConfig.h, lwipopts.h, arch/cc.h + ${CMAKE_CURRENT_SOURCE_DIR}/netif # smsc9220_eth_drv.h, smsc9220_emac_config.h ${FREERTOS_KERNEL_PATH}/include ${FREERTOS_PORT_DIR} # portmacro.h ${LWIP_DIR}/src/include + ${LWIP_CONTRIB_FREERTOS_DIR}/include # arch/sys_arch.h ) # --- Executable --------------------------------------------------------------- @@ -89,7 +109,10 @@ target_include_directories(solid_syslog_freertos_lwip_upstream PRIVATE add_executable(SolidSyslogBddTargetLwip main.c - ${FREERTOS_COMMON_DIR}/startup.c + ${FREERTOS_TCP_TARGET_DIR}/Startup.c # Ethernet variant: IRQ 13 -> EthernetISR + ${FREERTOS_COMMON_DIR}/CmsdkUart.c + ${FREERTOS_COMMON_DIR}/Syscalls.c + netif/EthernetIf.c ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawAddress.c ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawAddressMessages.c ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawAddressStatic.c @@ -103,6 +126,14 @@ add_executable(SolidSyslogBddTargetLwip ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStreamMessages.c ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStreamStatic.c ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source/SolidSyslogLwipRawMarshal.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexMessages.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetInteractive.c + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetSwitchConfig.c $ ) @@ -118,13 +149,19 @@ target_compile_options(SolidSyslogBddTargetLwip PRIVATE target_include_directories(SolidSyslogBddTargetLwip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # FreeRTOSConfig.h, lwipopts.h, arch/cc.h + ${CMAKE_CURRENT_SOURCE_DIR}/netif # EthernetIf.h, smsc9220_eth_drv.h + ${FREERTOS_COMMON_DIR} # CmsdkUart.h ${CMAKE_SOURCE_DIR}/Core/Interface # SolidSyslog*.h ${CMAKE_SOURCE_DIR}/Core/Source # internal headers consumed by adapters - ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Interface # SolidSyslogLwipRaw{Address,Resolver,Datagram,TcpStream}.h + ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Interface # SolidSyslogLwipRaw{Address,Resolver,Datagram,TcpStream,Marshal}.h ${CMAKE_SOURCE_DIR}/Platform/LwipRaw/Source # SolidSyslogLwipRaw*Private.h + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Interface # SolidSyslogFreeRtos{Mutex,SysUpTime}.h + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source # SolidSyslogFreeRtosMutexPrivate.h + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common # BddTargetInteractive.h, BddTargetSwitchConfig.h, … ${FREERTOS_KERNEL_PATH}/include ${FREERTOS_PORT_DIR} # portmacro.h ${LWIP_DIR}/src/include + ${LWIP_CONTRIB_FREERTOS_DIR}/include # arch/sys_arch.h (via lwip/sys.h) ) target_link_libraries(SolidSyslogBddTargetLwip PRIVATE diff --git a/Bdd/Targets/FreeRtosLwip/FreeRTOSConfig.h b/Bdd/Targets/FreeRtosLwip/FreeRTOSConfig.h index 9b30bd8c..f43cf632 100644 --- a/Bdd/Targets/FreeRtosLwip/FreeRTOSConfig.h +++ b/Bdd/Targets/FreeRtosLwip/FreeRTOSConfig.h @@ -1,55 +1,75 @@ #ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H -/* FreeRTOS kernel configuration for the Cortex-M3 QEMU mps2-an385 lwIP - * link-probe (S28.07). Deliberately leaner than Bdd/Targets/FreeRtos/ - * FreeRTOSConfig.h: this image only has to link the kernel alongside lwIP - * core + the Platform/LwipRaw adapters and start the scheduler with a single - * probe task that _Create/_Destroys each adapter. No timers, no static - * allocation, no IP-stack tasks, no hooks — so there is no hook boilerplate - * to carry. S28.09 introduces the real netif + tcpip integration on top of - * the fuller FreeRtos config. */ +/* FreeRTOS kernel configuration for Cortex-M3 on QEMU mps2-an385 with lwIP + * (Raw API) under NO_SYS=0. lwIP runs its own "tcpip" thread (priority and + * stack sized in lwipopts.h via the contrib FreeRTOS sys_arch), the LAN9118 + * netif RX task at configMAX_PRIORITIES - 2, and the SolidSyslog interactive / + * service tasks at tskIDLE_PRIORITY + 1. + * + * S28.09 grew this from the S28.07 link-probe config: the lwIP FreeRTOS + * sys_arch needs recursive mutexes (the LWIP_TCPIP_CORE_LOCKING mutex) and + * counting semaphores, and SolidSyslogFreeRtosMutex needs static allocation — + * so this now mirrors Bdd/Targets/FreeRtos/FreeRTOSConfig.h (the proven + * networking config for this machine) rather than the lean probe variant. The + * FreeRTOS software-timer service is left ON for parity even though lwIP runs + * its timeouts on the tcpip thread, not FreeRTOS timers. */ #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ((unsigned long) 25000000) #define configTICK_RATE_HZ ((TickType_t) 100) -#define configMAX_PRIORITIES 5 +#define configMAX_PRIORITIES 7 #define configMINIMAL_STACK_SIZE ((unsigned short) 128) -#define configTOTAL_HEAP_SIZE ((size_t) (32 * 1024)) +#define configTOTAL_HEAP_SIZE ((size_t) (96 * 1024)) #define configMAX_TASK_NAME_LEN 16 #define configUSE_TRACE_FACILITY 0 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 -#define configUSE_RECURSIVE_MUTEXES 0 -#define configUSE_COUNTING_SEMAPHORES 0 -#define configUSE_TIMERS 0 -#define configCHECK_FOR_STACK_OVERFLOW 0 -#define configUSE_MALLOC_FAILED_HOOK 0 -#define configSUPPORT_STATIC_ALLOCATION 0 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 8 +#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) +#define configCHECK_FOR_STACK_OVERFLOW 2 +#define configUSE_MALLOC_FAILED_HOOK 1 +/* Static allocation is required by SolidSyslogFreeRtosMutex + * (xSemaphoreCreateMutexStatic places the StaticSemaphore_t inside the + * caller-supplied storage). The idle / timer task static-memory hooks it + * pulls in are satisfied by configKERNEL_PROVIDED_STATIC_MEMORY = 1 — no + * boilerplate in main.c. Dynamic allocation stays on for the lwIP tcpip / + * RX tasks and the interactive / service tasks created via xTaskCreate. */ +#define configSUPPORT_STATIC_ALLOCATION 1 #define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configKERNEL_PROVIDED_STATIC_MEMORY 1 #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES 1 -#define INCLUDE_vTaskPrioritySet 0 -#define INCLUDE_uxTaskPriorityGet 0 -#define INCLUDE_vTaskDelete 0 +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskCleanUpResources 0 #define INCLUDE_vTaskSuspend 1 -#define INCLUDE_vTaskDelayUntil 0 +#define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 /* Cortex-M3 NVIC: top 3 priority bits implemented. */ #define configPRIO_BITS 3 #define configKERNEL_INTERRUPT_PRIORITY (7 << (8 - configPRIO_BITS)) #define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << (8 - configPRIO_BITS)) +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 7 +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* Alias FreeRTOS port handlers to the standard CMSIS names so the vector - * table in Common/startup.c references SVC_Handler / PendSV_Handler / - * SysTick_Handler (and the FreeRTOS port supplies the bodies). */ + * table in Startup.c references SVC_Handler / PendSV_Handler / SysTick_Handler + * (and the FreeRTOS port supplies the bodies). */ #define vPortSVCHandler SVC_Handler #define xPortPendSVHandler PendSV_Handler #define xPortSysTickHandler SysTick_Handler diff --git a/Bdd/Targets/FreeRtosLwip/lwipopts.h b/Bdd/Targets/FreeRtosLwip/lwipopts.h index 35ab3158..06b20a93 100644 --- a/Bdd/Targets/FreeRtosLwip/lwipopts.h +++ b/Bdd/Targets/FreeRtosLwip/lwipopts.h @@ -1,30 +1,48 @@ -/* lwIP options for the QEMU mps2-an385 FreeRTOS+lwIP link-probe target. +/* lwIP options for the QEMU mps2-an385 FreeRTOS + lwIP BDD target. * - * S28.07 scope: prove the Platform/LwipRaw adapters (Address, Resolver, - * Datagram, TcpStream, Marshal) cross-build and link against lwIP core for a - * Cortex-M3 FreeRTOS target, independent of Platform/PlusTcp. There is no - * netif driver and no QEMU run — this config only has to be self-consistent - * enough for lwIP core + the adapters to compile and link. + * S28.09 flipped this from the S28.07 link-probe (NO_SYS=1, no netif) to a + * worked NO_SYS=0 runtime: lwIP runs its own "tcpip" thread, a LAN9118 netif + * (netif/EthernetIf.c) drives the wire, and the SolidSyslog LwipRaw adapters + * reach the core through the tcpip_callback marshal (S28.06). We still only + * use lwIP's Raw API (the adapters call udp_ / tcp_ functions directly via the + * marshal), + * so the sequential netconn / socket API stays OFF — the tcpip thread exists + * for RX delivery (tcpip_input), timeouts, and marshalled callbacks only. * - * NO_SYS=1: the LwipRaw adapters are OS-agnostic and the default direct-call - * marshal (SolidSyslogLwipRaw_SetMarshal, S28.06) is correct for a single - * lwIP-owning context. The worked NO_SYS=0 runtime (tcpip thread + the - * tcpip_callback marshal + a LAN9118 netif) lands with S28.09, which reuses - * this header and flips the OS-coupling options then. - * - * Memory is lwIP-pool managed (no libc malloc, no MEMP_MEM_MALLOC) so the - * footprint is the static, embedded-realistic shape an integrator ships — - * not the host-test shortcut (MEM_LIBC_MALLOC) in - * Tests/Support/LwipFakes/Interface/lwipopts.h. */ + * Memory is lwIP-pool managed (no libc malloc) so the footprint is the static, + * embedded-realistic shape an integrator ships — not the host-test shortcut + * (MEM_LIBC_MALLOC) in Tests/Support/LwipFakes/Interface/lwipopts.h. */ #ifndef SOLIDSYSLOG_FREERTOS_LWIP_LWIPOPTS_H #define SOLIDSYSLOG_FREERTOS_LWIP_LWIPOPTS_H -/* --- OS abstraction --------------------------------------------------- */ -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 0 +/* --- OS abstraction (NO_SYS=0: tcpip thread + FreeRTOS sys_arch) ------- */ +#define NO_SYS 0 +#define SYS_LIGHTWEIGHT_PROT 1 +#define LWIP_TCPIP_CORE_LOCKING 1 +/* Raw API only — no sequential netconn / BSD-socket API. */ #define LWIP_NETCONN 0 #define LWIP_SOCKET 0 +/* tcpip thread. Priorities are numeric here: lwipopts.h is processed via + * lwip/opt.h before any FreeRTOS header, so configMAX_PRIORITIES (7) is not + * visible — TCPIP_THREAD_PRIO 6 == configMAX_PRIORITIES - 1, above the + * LAN9118 RX task (configMAX_PRIORITIES - 2 == 5, set in EthernetIf.c). + * TCPIP_THREAD_STACKSIZE is in BYTES (LWIP_FREERTOS_THREAD_STACKSIZE_IS_ + * STACKWORDS defaults to 0, so the sys_arch divides by sizeof(StackType_t)). */ +/* lwIP's api/err.c maps err_t to errno; pull the E* codes from newlib's + * rather than have lwIP provide its own (which would clash with + * newlib's definitions). */ +#define LWIP_ERRNO_STDINCLUDE 1 + +#define TCPIP_THREAD_NAME "tcpip" +#define TCPIP_THREAD_STACKSIZE 4096 +#define TCPIP_THREAD_PRIO 6 +#define TCPIP_MBOX_SIZE 8 +#define DEFAULT_RAW_RECVMBOX_SIZE 8 +#define DEFAULT_UDP_RECVMBOX_SIZE 8 +#define DEFAULT_TCP_RECVMBOX_SIZE 8 +#define DEFAULT_ACCEPTMBOX_SIZE 8 + /* --- Protocol surface ------------------------------------------------- */ #define LWIP_IPV4 1 #define LWIP_IPV6 0 @@ -37,6 +55,11 @@ #define LWIP_ICMP 1 #define LWIP_IGMP 0 +/* etharp queues the first packet to a destination while ARP resolves it — + * keep queueing on so the first UDP datagram after boot is not dropped + * (mirrors the FreeRTOS-Plus-TCP first-packet ARP behaviour). */ +#define ARP_QUEUEING 1 + /* --- Memory: lwIP-managed static pools (no libc/posix heap) ----------- */ #define MEM_LIBC_MALLOC 0 #define MEMP_MEM_MALLOC 0 @@ -50,6 +73,10 @@ #define MEMP_NUM_PBUF 16 #define MEMP_NUM_RAW_PCB 2 #define MEMP_NUM_ARP_QUEUE 4 +/* tcpip thread message pools: API callbacks (the marshal) + inbound packets + * posted by the netif RX task via tcpip_input. */ +#define MEMP_NUM_TCPIP_MSG_API 8 +#define MEMP_NUM_TCPIP_MSG_INPKT 8 #define PBUF_POOL_SIZE 16 @@ -59,7 +86,7 @@ #define TCP_SND_BUF (4 * TCP_MSS) #define TCP_QUEUE_OOSEQ 0 -/* --- Diagnostics: lean for the cross link probe ----------------------- */ +/* --- Diagnostics ------------------------------------------------------ */ #define LWIP_STATS 0 #define LWIP_NETIF_API 0 #define LWIP_NETIF_STATUS_CALLBACK 0 diff --git a/Bdd/Targets/FreeRtosLwip/main.c b/Bdd/Targets/FreeRtosLwip/main.c index 1c536018..eebddc78 100644 --- a/Bdd/Targets/FreeRtosLwip/main.c +++ b/Bdd/Targets/FreeRtosLwip/main.c @@ -1,49 +1,176 @@ -/* FreeRTOS + lwIP (Raw API) link-probe for QEMU mps2-an385 (Cortex-M3). +/* FreeRTOS + lwIP (Raw API, NO_SYS=0) SolidSyslog BDD target for QEMU + * mps2-an385. * - * S28.07: prove the Platform/LwipRaw adapter tree cross-builds and links for - * a FreeRTOS/ARM target, selected via SOLIDSYSLOG_FREERTOS_NET=LWIP, with zero - * dependency on Platform/PlusTcp or FreeRTOS-Plus-TCP. This de-risks the CMake - * backend switch end-to-end before the heavy netif + QEMU lift (S28.09). + * S28.09: the worked NO_SYS=0 integration the lwIP guide promises. lwIP runs + * its own tcpip thread (tcpip_init); a hand-written LAN9118 netif + * (netif/EthernetIf.c) drives the wire; the SolidSyslog LwipRaw adapters reach + * the lwIP core through the tcpip_callback marshal (S28.06, + * SolidSyslogLwipRaw_SetMarshal). The pipeline mirrors the FreeRTOS-Plus-TCP + * target (Bdd/Targets/FreeRtos/main.c) with the network backend swapped + * PlusTcp -> LwipRaw: a CircularBuffer + FreeRtosMutex feed a Service task that + * drains over UDP, and BddTargetInteractive drives `send N` / `set ` / + * `quit` over the QEMU -serial stdio UART. * - * It is a *link* probe, not a runtime: there is no LAN9118 netif driver, no - * IP bring-up, and CI does not run it on QEMU. The single probe task calls - * lwip_init() and then _Create / _Destroy on each LwipRaw adapter so the - * linker must resolve every adapter entry point (and, transitively, the lwIP - * core symbols they call). Starting the scheduler keeps the FreeRTOS kernel - * symbols referenced too, so the artifact genuinely contains the kernel + - * lwIP + LwipRaw, mirroring the FreeRTOS-Plus-TCP target's shape. + * Scope is UDP this story. The SwitchingSender keeps TCP / TLS slots so + * `set transport tcp|tls` resolves cleanly (to the shared NullSender, which + * drops on the floor) rather than crashing; the real LwipRaw TCP / TLS senders + * land in later E28 stories. * - * lwipopts.h pins NO_SYS=1, so the LwipRaw default direct-call marshal is - * correct and lwIP asks the integrator for two hooks only — sys_now() and - * LWIP_RAND() — both provided below. */ + * Static IPv4 (10.0.2.15) on the QEMU slirp network, host reachable at the + * slirp gateway 10.0.2.2 — numeric, because slirp has no route to the docker + * DNS alias the Linux target uses. */ -#include "FreeRTOS.h" -#include "task.h" +#include "CmsdkUart.h" +#include "EthernetIf.h" -#include "lwip/init.h" -#include "lwip/sys.h" +#include "BddTargetEnterpriseId.h" +#include "BddTargetInteractive.h" +#include "BddTargetIps.h" +#include "BddTargetLanguage.h" +#include "BddTargetSwitchConfig.h" +#include "SolidSyslog.h" +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogCircularBuffer.h" +#include "SolidSyslogConfig.h" +#include "SolidSyslogEndpoint.h" +#include "SolidSyslogError.h" +#include "SolidSyslogFormatter.h" +#include "SolidSyslogFreeRtosMutex.h" +#include "SolidSyslogFreeRtosSysUpTime.h" #include "SolidSyslogLwipRawAddress.h" #include "SolidSyslogLwipRawDatagram.h" +#include "SolidSyslogLwipRawMarshal.h" #include "SolidSyslogLwipRawResolver.h" -#include "SolidSyslogLwipRawTcpStream.h" +#include "SolidSyslogMetaSd.h" +#include "SolidSyslogMutex.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogNullStore.h" +#include "SolidSyslogOriginSd.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStdAtomicCounter.h" +#include "SolidSyslogSwitchingSender.h" +#include "SolidSyslogTimeQuality.h" +#include "SolidSyslogTimeQualitySd.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogUdpSender.h" + +#include "lwip/init.h" +#include "lwip/ip4_addr.h" +#include "lwip/netif.h" +#include "lwip/tcpip.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define CMSDK_UART0_BASE_ADDRESS UINT32_C(0x40004000) + +/* Unprivileged mirror of SOLIDSYSLOG_UDP_DEFAULT_PORT (514) for BDD listeners. */ +#define BDD_TARGET_UDP_PORT 5514U + +/* UDP-only interactive task: BddTargetInteractive's 2048-byte line + name + * frames plus SolidSyslog_Log's two SOLIDSYSLOG_MAX_MESSAGE_SIZE formatter + * frames and newlib printf. *40 (20 KB) matches the empirical pre-TLS budget + * the +TCP target recorded for the same 2048-byte line buffer. heap_4 (96 KB) + * absorbs it alongside the lwIP tcpip / RX tasks. */ +#define INTERACTIVE_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 40U) +#define SERVICE_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 16U) + +static char appName[49] = "SolidSyslogBddTarget"; +static char messageId[33] = "example"; +static char msg[SOLIDSYSLOG_MAX_MESSAGE_SIZE] = "Hello from FreeRTOS lwIP"; +static char host[16] = "10.0.2.2"; +static uint16_t port = (uint16_t) BDD_TARGET_UDP_PORT; +static uint32_t endpointVersion = 0U; + +static struct SolidSyslogMessage testMessage = { + .Facility = SOLIDSYSLOG_FACILITY_LOCAL0, + .Severity = SOLIDSYSLOG_SEVERITY_INFORMATIONAL, + .MessageId = messageId, + .Msg = msg, +}; + +/* lwIP netif descriptor — must outlive the tcpip thread. */ +static struct netif networkInterface; + +/* CircularBuffer + FreeRtosMutex for cross-task emission. 8 max-sized messages + * is comfortably above the 3-message BDD scenarios. */ +enum +{ + BDD_TARGET_BUFFER_MESSAGES = 8 +}; + +static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)]; + +/* Lifecycle mutex serialises SolidSyslog_Service against the teardown path. + * solidSyslogTeardown is set inside the critical section so Service observes + * it atomically with the destroy and self-deletes before the mutex goes. */ +static struct SolidSyslogMutex* lifecycleMutex = NULL; +static struct SolidSyslog* solidSyslog = NULL; +static volatile bool solidSyslogReady = false; +static volatile bool solidSyslogTeardown = false; + +static struct SolidSyslogConfig solidSyslogConfig; +static struct SolidSyslogStructuredData* sdList[3]; +static struct SolidSyslogAtomicCounter* atomicCounter = NULL; +static struct SolidSyslogStructuredData* metaSd = NULL; +static struct SolidSyslogStructuredData* timeQualitySd = NULL; +static struct SolidSyslogStructuredData* originSd = NULL; + +static struct SolidSyslogResolver* resolver = NULL; +static struct SolidSyslogDatagram* datagram = NULL; +static struct SolidSyslogAddress* udpAddress = NULL; +static struct SolidSyslogSender* udpSender = NULL; +static struct SolidSyslogSender* switchingSender = NULL; +static struct SolidSyslogBuffer* buffer = NULL; +static struct SolidSyslogMutex* bufferMutex = NULL; -/* The probe only _Create/_Destroys adapters then idles — 4x the kernel - * minimum is ample headroom and matches the StackType_t (word) units - * xTaskCreate expects. */ -#define PROBE_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 4U) +static TaskHandle_t serviceTaskHandle = NULL; -/* lwIP NO_SYS=1 time source — milliseconds since boot, derived from the - * FreeRTOS tick. Drives lwIP's timeout wheel (which this probe never pumps). */ -u32_t sys_now(void) +static void InteractiveTask(void* argument); +static void ServiceTask(void* argument); +static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context); +static void NetworkBringUp(void* context); +static bool TryUpdateString(char* storage, size_t storageSize, const char* value); +static bool TryParseUInt(const char* value, unsigned long* out); +static void TeardownAll(void); +static void SemihostingExit(int status); + +static uint32_t MmioRead32(uintptr_t address) { - return (u32_t) ((uint64_t) xTaskGetTickCount() * (uint64_t) portTICK_PERIOD_MS); + // NOLINTNEXTLINE(performance-no-int-to-ptr) -- mapping the CMSDK UART MMIO address into a 32-bit volatile pointer. + return *(volatile uint32_t*) address; +} + +static void MmioWrite32(uintptr_t address, uint32_t value) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) -- mapping the CMSDK UART MMIO address into a 32-bit volatile pointer. + *(volatile uint32_t*) address = value; +} + +static void RtosSleep(int milliseconds) +{ + TickType_t ticks = pdMS_TO_TICKS((TickType_t) milliseconds); + if ((milliseconds > 0) && (ticks == 0U)) + { + ticks = 1U; + } + vTaskDelay(ticks); } -/* lwIP randomness source (declared by Bdd/Targets/FreeRtosLwip/arch/cc.h's - * LWIP_RAND). A self-contained xorshift32 keeps TCP ISN selection linkable - * without pulling in newlib rand() or a real entropy backend — the worked - * runtime entropy story belongs to S28.09. */ +static const CmsdkUartMemoryAccess MMIO_ACCESS = {MmioRead32, MmioWrite32, RtosSleep}; + +/* lwIP randomness source (declared by arch/cc.h's LWIP_RAND). sys_now() is + * provided by the contrib FreeRTOS sys_arch under NO_SYS=0, so — unlike the + * S28.07 probe — we do not define it here. A self-contained xorshift32 keeps + * TCP ISN selection deterministic without newlib rand() / a real entropy + * backend; adequate for the BDD smoke test. */ unsigned int LwipPortRand(void) { static uint32_t state = 0x2545F491U; @@ -53,48 +180,385 @@ unsigned int LwipPortRand(void) return (unsigned int) state; } -/* The bounded-connect spin in SolidSyslogLwipRawTcpStream takes a Sleep - * callback. This probe never opens a connection, so a no-op satisfies the - * required-field contract without behaviour. */ -static void ProbeSleep(int milliseconds) +int main(void) +{ + CmsdkUart_Init(&MMIO_ACCESS, CMSDK_UART0_BASE_ADDRESS); + + /* Pin every LwipRaw adapter call onto the tcpip thread. */ + SolidSyslogLwipRaw_SetMarshal(LwipTcpipMarshal); + + /* Create the tcpip thread + mbox + core-lock mutex. Pre-scheduler safe + * (xTaskCreate / xQueueCreate / xSemaphoreCreate all work before + * vTaskStartScheduler); the thread itself only runs once the scheduler + * starts. The netif bring-up is deferred to NetworkBringUp on that thread + * — smsc9220_init() calls vTaskDelay, which would deref a NULL + * pxCurrentTCB if run before the scheduler. */ + tcpip_init(NULL, NULL); + + (void) xTaskCreate(InteractiveTask, "interactive", INTERACTIVE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, NULL); + (void) xTaskCreate( + ServiceTask, "service", SERVICE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, &serviceTaskHandle + ); + + vTaskStartScheduler(); + + for (;;) + { + } + return 0; +} + +/* Runs on the tcpip thread (dispatched via tcpip_callback once the scheduler + * is up) so netif_add, the link/up transitions, and smsc9220_init's vTaskDelay + * all execute in a valid task context with the lwIP core lock held. */ +static void NetworkBringUp(void* context) +{ + (void) context; + ip4_addr_t ipAddress; + ip4_addr_t netmask; + ip4_addr_t gateway; + /* QEMU slirp default: 10.0.2.15 guest, 10.0.2.2 gateway (NATed to the + * QEMU host, where the syslog-ng oracle listens). */ + IP4_ADDR(&ipAddress, 10, 0, 2, 15); + IP4_ADDR(&netmask, 255, 255, 255, 0); + IP4_ADDR(&gateway, 10, 0, 2, 2); + + (void) netif_add(&networkInterface, &ipAddress, &netmask, &gateway, NULL, EthernetIf_Init, tcpip_input); + netif_set_default(&networkInterface); + netif_set_up(&networkInterface); + netif_set_link_up(&networkInterface); +} + +static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) +{ + /* Blocking variant: posts to the tcpip thread and waits for completion, so + * the LwipRaw wrapper can read results immediately after this returns + * (the synchronous-marshal contract in SolidSyslogLwipRawMarshal.h). */ + (void) tcpip_callback(callback, context); +} + +void vApplicationMallocFailedHook(void) +{ + for (;;) + { + } +} + +void vApplicationStackOverflowHook(TaskHandle_t task, char* taskName) +{ + (void) task; + (void) taskName; + for (;;) + { + } +} + +static void GetHostname(struct SolidSyslogFormatter* formatter) +{ + /* RFC 5424 section 6.2.4 rung 2 (static IP address) — read back from the + * netif so a future DHCP slice satisfies the same rung without touching + * this callback. */ + const char* address = ip4addr_ntoa(netif_ip4_addr(&networkInterface)); + SolidSyslogFormatter_BoundedString(formatter, address, strlen(address)); +} + +static void GetAppName(struct SolidSyslogFormatter* formatter) +{ + SolidSyslogFormatter_BoundedString(formatter, appName, strlen(appName)); +} + +static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) +{ + timeQuality->TzKnown = false; + timeQuality->IsSynced = false; + timeQuality->SyncAccuracyMicroseconds = SOLIDSYSLOG_SYNC_ACCURACY_OMIT; +} + +static void GetEndpoint(struct SolidSyslogEndpoint* endpoint) +{ + SolidSyslogFormatter_BoundedString(endpoint->Host, host, strlen(host)); + endpoint->Port = port; +} + +static uint32_t GetEndpointVersion(void) +{ + return endpointVersion; +} + +static void ErrorHandlerEx( + void* context, + enum SolidSyslogSeverity severity, + const struct SolidSyslogErrorSource* source, + uint8_t code +) +{ + (void) context; + const char* sourceName = ""; + const char* message = ""; + if (source != NULL) + { + sourceName = source->Name; + if (source->AsString != NULL) + { + message = source->AsString(code); + } + } + (void) printf("[solidsyslog] severity=%d [%s/%u] %s\n", (int) severity, sourceName, (unsigned) code, message); +} + +static bool OnSet(const char* name, const char* value) +{ + bool handled = false; + if (strcmp(name, "appname") == 0) + { + handled = TryUpdateString(appName, sizeof(appName), value); + } + else if (strcmp(name, "msgid") == 0) + { + handled = TryUpdateString(messageId, sizeof(messageId), value); + } + else if (strcmp(name, "msg") == 0) + { + handled = TryUpdateString(msg, sizeof(msg), value); + } + else if (strcmp(name, "host") == 0) + { + handled = TryUpdateString(host, sizeof(host), value); + } + else if (strcmp(name, "port") == 0) + { + unsigned long parsed = 0U; + if (TryParseUInt(value, &parsed) && (parsed != 0U) && (parsed <= UINT16_MAX)) + { + port = (uint16_t) parsed; + endpointVersion++; + handled = true; + } + } + else if (strcmp(name, "facility") == 0) + { + unsigned long parsed = 0U; + if (TryParseUInt(value, &parsed)) + { + testMessage.Facility = (enum SolidSyslogFacility) parsed; + handled = true; + } + } + else if (strcmp(name, "severity") == 0) + { + unsigned long parsed = 0U; + if (TryParseUInt(value, &parsed)) + { + testMessage.Severity = (enum SolidSyslogSeverity) parsed; + handled = true; + } + } + else if (strcmp(name, "transport") == 0) + { + BddTargetSwitchConfig_SetByName(value); + handled = true; + } + else if (strcmp(name, "shutdown") == 0) + { + unsigned long parsed = 0U; + if (TryParseUInt(value, &parsed)) + { + if (parsed != 0U) + { + TeardownAll(); + SemihostingExit(0); + } + handled = true; + } + } + return handled; +} + +static bool TryUpdateString(char* storage, size_t storageSize, const char* value) +{ + bool updated = false; + size_t length = strlen(value); + if ((length != 0U) && (length < storageSize)) + { + memcpy(storage, value, length); + storage[length] = '\0'; + updated = true; + } + return updated; +} + +static bool TryParseUInt(const char* value, unsigned long* out) { - (void) milliseconds; + bool parsed = false; + if (*value != '\0') + { + char* end = NULL; + unsigned long candidate = strtoul(value, &end, 10); + if (*end == '\0') + { + *out = candidate; + parsed = true; + } + } + return parsed; } -static void ProbeTask(void* parameters) +static void InteractiveTask(void* argument) { - (void) parameters; + (void) argument; - lwip_init(); + /* Bring the netif up on the tcpip thread now the scheduler is running. + * Blocking callback — returns once the interface is added and link/up. */ + (void) tcpip_callback(NetworkBringUp, NULL); - struct SolidSyslogAddress* address = SolidSyslogLwipRawAddress_Create(); - struct SolidSyslogResolver* resolver = SolidSyslogLwipRawResolver_Create(); - struct SolidSyslogDatagram* datagram = SolidSyslogLwipRawDatagram_Create(); + resolver = SolidSyslogLwipRawResolver_Create(); + datagram = SolidSyslogLwipRawDatagram_Create(); + udpAddress = SolidSyslogLwipRawAddress_Create(); - struct SolidSyslogLwipRawTcpStreamConfig streamConfig = { - .GetConnectTimeoutMs = NULL, - .ConnectTimeoutContext = NULL, - .Sleep = ProbeSleep, + struct SolidSyslogUdpSenderConfig udpConfig = { + .Resolver = resolver, + .Datagram = datagram, + .Address = udpAddress, + .Endpoint = GetEndpoint, + .EndpointVersion = GetEndpointVersion, }; - struct SolidSyslogStream* stream = SolidSyslogLwipRawTcpStream_Create(&streamConfig); + udpSender = SolidSyslogUdpSender_Create(&udpConfig); - SolidSyslogLwipRawTcpStream_Destroy(stream); - SolidSyslogLwipRawDatagram_Destroy(datagram); - SolidSyslogLwipRawResolver_Destroy(resolver); - SolidSyslogLwipRawAddress_Destroy(address); + /* SwitchingSender lets `set transport ` flip transport at + * runtime. UDP is wired; TCP / TLS route to the shared NullSender (drop on + * the floor) until later E28 stories wire the LwipRaw TCP / TLS senders. */ + static struct SolidSyslogSender* inners[BDD_TARGET_SWITCH_COUNT]; + inners[BDD_TARGET_SWITCH_UDP] = udpSender; + inners[BDD_TARGET_SWITCH_TCP] = SolidSyslogNullSender_Get(); + inners[BDD_TARGET_SWITCH_TLS] = SolidSyslogNullSender_Get(); + struct SolidSyslogSwitchingSenderConfig switchConfig = { + .Senders = inners, + .SenderCount = BDD_TARGET_SWITCH_COUNT, + .Selector = BddTargetSwitchConfig_Selector, + }; + BddTargetSwitchConfig_SetByName("udp"); + switchingSender = SolidSyslogSwitchingSender_Create(&switchConfig); + + bufferMutex = SolidSyslogFreeRtosMutex_Create(); + buffer = SolidSyslogCircularBuffer_Create(bufferMutex, bufferRing, sizeof(bufferRing)); + lifecycleMutex = SolidSyslogFreeRtosMutex_Create(); + + atomicCounter = SolidSyslogStdAtomicCounter_Create(); + struct SolidSyslogMetaSdConfig metaConfig = { + .Counter = atomicCounter, + .GetSysUpTime = SolidSyslogFreeRtosSysUpTime_Get, + .GetLanguage = BddTargetLanguage_Get, + }; + metaSd = SolidSyslogMetaSd_Create(&metaConfig); + timeQualitySd = SolidSyslogTimeQualitySd_Create(GetTimeQuality); + struct SolidSyslogOriginSdConfig originConfig = { + .Software = "SolidSyslogBddTarget", + .SwVersion = "0.7.0", + .EnterpriseId = BDD_TARGET_ENTERPRISE_ID, + .GetIpCount = BddTargetIps_Count, + .GetIpAt = BddTargetIps_At, + }; + originSd = SolidSyslogOriginSd_Create(&originConfig); + sdList[0] = metaSd; + sdList[1] = timeQualitySd; + sdList[2] = originSd; + + solidSyslogConfig = (struct SolidSyslogConfig) { + .Buffer = buffer, + .Sender = switchingSender, + .Clock = NULL, + .GetHostname = GetHostname, + .GetAppName = GetAppName, + .GetProcessId = NULL, + .Store = SolidSyslogNullStore_Get(), + .Sd = sdList, + .SdCount = sizeof(sdList) / sizeof(sdList[0]), + }; + SolidSyslog_SetErrorHandler(ErrorHandlerEx, NULL); + solidSyslog = SolidSyslog_Create(&solidSyslogConfig); + solidSyslogReady = true; + + BddTargetInteractive_Run(solidSyslog, &testMessage, stdin, BddTargetSwitchConfig_SetByName, OnSet); + + const UBaseType_t interactiveHwm = uxTaskGetStackHighWaterMark(NULL); + const UBaseType_t serviceHwm = (serviceTaskHandle != NULL) ? uxTaskGetStackHighWaterMark(serviceTaskHandle) : 0U; + (void) printf( + "[stack-hwm] interactive=%lu words service=%lu words\n", (unsigned long) interactiveHwm, + (unsigned long) serviceHwm + ); + TeardownAll(); + vTaskDelete(NULL); +} + +static void ServiceTask(void* argument) +{ + (void) argument; + while ((lifecycleMutex == NULL) || !solidSyslogReady) + { + vTaskDelay(pdMS_TO_TICKS(1)); + } for (;;) { - vTaskDelay(pdMS_TO_TICKS(1000)); + SolidSyslogMutex_Lock(lifecycleMutex); + if (solidSyslogTeardown) + { + SolidSyslogMutex_Unlock(lifecycleMutex); + vTaskDelete(NULL); + } + if (solidSyslogReady) + { + SolidSyslog_Service(solidSyslog); + } + SolidSyslogMutex_Unlock(lifecycleMutex); + vTaskDelay(pdMS_TO_TICKS(1)); } } -int main(void) +static void TeardownAll(void) { - (void) xTaskCreate(ProbeTask, "probe", PROBE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, NULL); - vTaskStartScheduler(); + SolidSyslogMutex_Lock(lifecycleMutex); + solidSyslogTeardown = true; + solidSyslogReady = false; + SolidSyslog_Destroy(solidSyslog); + solidSyslog = NULL; + SolidSyslogOriginSd_Destroy(originSd); + SolidSyslogTimeQualitySd_Destroy(timeQualitySd); + SolidSyslogMetaSd_Destroy(metaSd); + SolidSyslogStdAtomicCounter_Destroy(atomicCounter); + SolidSyslogMutex_Unlock(lifecycleMutex); + + /* Give Service one iteration to observe the teardown flag and self-delete + * before the lifecycle mutex is destroyed under it. */ + vTaskDelay(pdMS_TO_TICKS(20)); + serviceTaskHandle = NULL; + + SolidSyslogCircularBuffer_Destroy(buffer); + SolidSyslogFreeRtosMutex_Destroy(bufferMutex); + SolidSyslogFreeRtosMutex_Destroy(lifecycleMutex); + lifecycleMutex = NULL; + SolidSyslogSwitchingSender_Destroy(switchingSender); + SolidSyslogUdpSender_Destroy(udpSender); + SolidSyslogLwipRawAddress_Destroy(udpAddress); + SolidSyslogLwipRawDatagram_Destroy(datagram); + SolidSyslogLwipRawResolver_Destroy(resolver); +} + +static void SemihostingExit(int status) +{ + /* SYS_EXIT_EXTENDED (0x20) — propagates a non-zero status on AArch32 via a + * { reason, subcode } block. QEMU terminates the VM; the for(;;) is + * defensive. */ + const struct + { + uint32_t reason; + uint32_t subcode; + } args = {0x20026U, (uint32_t) status}; + + register int r0 __asm("r0") = 0x20; + register const void* r1 __asm("r1") = &args; + __asm volatile("bkpt 0xAB" : "+r"(r0) : "r"(r1) : "memory"); for (;;) { } - return 0; } diff --git a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c new file mode 100644 index 00000000..117d49b7 --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c @@ -0,0 +1,236 @@ +/* lwIP netif driver for the QEMU mps2-an385 LAN9118 (SMSC9220) Ethernet + * controller, FreeRTOS + lwIP NO_SYS=0. + * + * Bridges lwIP's struct netif to the vendored Arm smsc9220_eth_drv (see + * netif/smsc9220_eth_drv.c, Apache-2.0). The shape is adapted from the lwIP + * contrib ethernetif.c skeleton (BSD, Adam Dunkels) and the FreeRTOS-Plus-TCP + * MPS2_AN385 NetworkInterface.c (MIT) RX/IRQ pattern: + * - low-level output sends a (possibly chained) pbuf via + * smsc9220_send_by_chunks; + * - a dedicated RX task, unblocked by the IRQ-13 EthernetISR through a task + * notification, drains the RX FIFO into pbufs and hands them to lwIP via + * netif->input (tcpip_input under NO_SYS=0, posted to the tcpip thread). */ + +#include "EthernetIf.h" + +#include "smsc9220_emac_config.h" +#include "smsc9220_eth_drv.h" + +#include "lwip/etharp.h" +#include "lwip/pbuf.h" +#include "lwip/stats.h" + +#include "FreeRTOS.h" +#include "task.h" + +#include +#include +#include + +/* QEMU mps2-an385 maps the LAN9118 register block here for the Cortex-M3 + * (CORTEX_M7 would be 0xA0000000 — see the vendored SMM_MPS2.h). */ +#define ETHERNETIF_SMSC9220_BASE UINT32_C(0x40200000) + +/* IRQ 13 is the LAN9118 Ethernet controller on the mps2-an385 NVIC. */ +#define ETHERNETIF_IRQ_NUMBER 13U + +/* The ISR calls vTaskNotifyGiveFromISR, so its NVIC priority must be no more + * urgent than configMAX_SYSCALL_INTERRUPT_PRIORITY. The reset default (0) is + * more urgent and would trip configASSERT on the first FromISR call, so we set + * it explicitly to the (least-urgent) kernel priority. */ +#define ETHERNETIF_IRQ_PRIORITY ((uint8_t) configKERNEL_INTERRUPT_PRIORITY) + +#define ETHERNETIF_RX_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 4U) +#define ETHERNETIF_RX_TASK_PRIORITY (configMAX_PRIORITIES - 2U) + +/* Wait at most this long for an RX notification before re-polling the FIFO — + * a safety net against a missed edge, not the normal wake path. */ +#define ETHERNETIF_RX_BLOCK_MS 1500U + +#define ETHERNETIF_NAME0 'e' +#define ETHERNETIF_NAME1 'n' + +/* Locally administered unicast MAC, matching the +TCP reference target. */ +static const uint8_t ETHERNETIF_MAC[SMSC9220_HWADDR_SIZE] = {0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U}; + +static const struct smsc9220_eth_dev_cfg_t ETHERNETIF_DEV_CFG = {.base = ETHERNETIF_SMSC9220_BASE}; +static struct smsc9220_eth_dev_data_t ETHERNETIF_DEV_DATA = {.state = 0U}; +static const struct smsc9220_eth_dev_t ETHERNETIF_DEV = {ÐERNETIF_DEV_CFG, ÐERNETIF_DEV_DATA}; + +/* Captured in EthernetIf_Init so the RX task can reach netif->input. */ +static struct netif* EthernetIf_Netif = NULL; +static TaskHandle_t EthernetIf_RxTaskHandle = NULL; + +/* RX scratch: a frame is read here contiguously, then pbuf_take fans it into + * the (possibly pool-chained) pbuf. Only the single RX task touches it. */ +static char EthernetIf_RxBuffer[SMSC9220_ETH_MAX_FRAME_SIZE]; + +/* Referenced by the Cortex-M3 vector table (Startup.c) at IRQ 13; the strong + * definition below overrides Startup.c's weak Default_Handler alias. */ +void EthernetISR(void); + +static void EthernetIf_LowLevelInit(struct netif* netif); +static void EthernetIf_WaitMs(uint32_t milliseconds); +static void EthernetIf_NvicEnableIrq(void); +static err_t EthernetIf_LowLevelOutput(struct netif* netif, struct pbuf* p); +static void EthernetIf_RxTask(void* parameters); +static struct pbuf* EthernetIf_LowLevelInput(void); + +err_t EthernetIf_Init(struct netif* netif) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + + EthernetIf_Netif = netif; + netif->name[0] = ETHERNETIF_NAME0; + netif->name[1] = ETHERNETIF_NAME1; + netif->output = etharp_output; + netif->linkoutput = EthernetIf_LowLevelOutput; + + EthernetIf_LowLevelInit(netif); + + return ERR_OK; +} + +static void EthernetIf_LowLevelInit(struct netif* netif) +{ + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + + netif->hwaddr_len = ETHARP_HWADDR_LEN; + memcpy(netif->hwaddr, ETHERNETIF_MAC, SMSC9220_HWADDR_SIZE); + netif->mtu = SMSC9220_ETH_MTU_SIZE; + netif->flags = (u8_t) (NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP); + + /* The ISR notifies this task, so it must exist before the IRQ is enabled. */ + (void) xTaskCreate( + EthernetIf_RxTask, "ethrx", ETHERNETIF_RX_TASK_STACK_DEPTH, NULL, ETHERNETIF_RX_TASK_PRIORITY, + &EthernetIf_RxTaskHandle + ); + + (void) smsc9220_init(dev, EthernetIf_WaitMs); + + smsc9220_disable_all_interrupts(dev); + smsc9220_clear_all_interrupts(dev); + (void) smsc9220_set_fifo_level_irq(dev, SMSC9220_FIFO_LEVEL_IRQ_RX_STATUS_POS, SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MIN); + (void) smsc9220_set_fifo_level_irq(dev, SMSC9220_FIFO_LEVEL_IRQ_TX_STATUS_POS, SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MIN); + (void) smsc9220_set_fifo_level_irq(dev, SMSC9220_FIFO_LEVEL_IRQ_TX_DATA_POS, SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MAX); + + uint32_t macLow = 0U; + uint32_t macHigh = 0U; + memcpy(&macLow, ETHERNETIF_MAC, 4U); + memcpy(&macHigh, ÐERNETIF_MAC[4], 2U); + (void) smsc9220_mac_regwrite(dev, SMSC9220_MAC_REG_OFFSET_ADDRL, macLow); + (void) smsc9220_mac_regwrite(dev, SMSC9220_MAC_REG_OFFSET_ADDRH, macHigh); + + smsc9220_enable_interrupt(dev, SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL); + + EthernetIf_NvicEnableIrq(); +} + +static void EthernetIf_WaitMs(uint32_t milliseconds) +{ + vTaskDelay(pdMS_TO_TICKS(milliseconds)); +} + +static void EthernetIf_NvicEnableIrq(void) +{ + // NOLINTNEXTLINE(performance-no-int-to-ptr) -- Cortex-M3 NVIC IPR byte for IRQ 13. + volatile uint8_t* ipr = (volatile uint8_t*) (UINT32_C(0xE000E400) + ETHERNETIF_IRQ_NUMBER); + *ipr = ETHERNETIF_IRQ_PRIORITY; + + // NOLINTNEXTLINE(performance-no-int-to-ptr) -- Cortex-M3 NVIC ISER (set-enable) register. + volatile uint32_t* iser = (volatile uint32_t*) UINT32_C(0xE000E100); + *iser = (uint32_t) (1UL << (ETHERNETIF_IRQ_NUMBER & 0x1FU)); +} + +static err_t EthernetIf_LowLevelOutput(struct netif* netif, struct pbuf* p) +{ + (void) netif; + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + enum smsc9220_error_t error = SMSC9220_ERROR_NONE; + + for (struct pbuf* q = p; (q != NULL) && (error == SMSC9220_ERROR_NONE); q = q->next) + { + error = smsc9220_send_by_chunks(dev, p->tot_len, (q == p), (const char*) q->payload, q->len); + } + + err_t result = ERR_IF; + if (error == SMSC9220_ERROR_NONE) + { + result = ERR_OK; + LINK_STATS_INC(link.xmit); + } + else + { + LINK_STATS_INC(link.err); + } + return result; +} + +static void EthernetIf_RxTask(void* parameters) +{ + (void) parameters; + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + + for (;;) + { + (void) ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(ETHERNETIF_RX_BLOCK_MS)); + + struct pbuf* p = EthernetIf_LowLevelInput(); + while (p != NULL) + { + if (EthernetIf_Netif->input(p, EthernetIf_Netif) != ERR_OK) + { + pbuf_free(p); + } + p = EthernetIf_LowLevelInput(); + } + + /* The ISR disabled the RX interrupt; re-arm it now the FIFO is drained. */ + smsc9220_enable_interrupt(dev, SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL); + } +} + +static struct pbuf* EthernetIf_LowLevelInput(void) +{ + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + struct pbuf* p = NULL; + + uint32_t length = smsc9220_peek_next_packet_size(dev); + if ((length != 0U) && (length <= sizeof(EthernetIf_RxBuffer))) + { + uint32_t received = smsc9220_receive_by_chunks(dev, EthernetIf_RxBuffer, length); + p = pbuf_alloc(PBUF_RAW, (u16_t) received, PBUF_POOL); + if (p != NULL) + { + (void) pbuf_take(p, EthernetIf_RxBuffer, (u16_t) received); + LINK_STATS_INC(link.recv); + } + else + { + LINK_STATS_INC(link.memerr); + LINK_STATS_INC(link.drop); + } + } + return p; +} + +void EthernetISR(void) +{ + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + BaseType_t higherPriorityTaskWoken = pdFALSE; + const uint32_t rxFifoStatusBit = 1UL << SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL; + + /* Defined non-static in the vendored smsc9220_eth_drv.c. */ + extern uint32_t get_irq_status(const struct smsc9220_eth_dev_t* dev); + + if ((get_irq_status(dev) & rxFifoStatusBit) != 0U) + { + vTaskNotifyGiveFromISR(EthernetIf_RxTaskHandle, &higherPriorityTaskWoken); + smsc9220_clear_interrupt(dev, SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL); + /* Re-enabled by the RX task once it has drained the FIFO. */ + smsc9220_disable_interrupt(dev, SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL); + } + smsc9220_clear_all_interrupts(dev); + + portYIELD_FROM_ISR(higherPriorityTaskWoken); +} diff --git a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.h b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.h new file mode 100644 index 00000000..6def3eb3 --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.h @@ -0,0 +1,17 @@ +/* lwIP netif init callback for the QEMU mps2-an385 LAN9118 (SMSC9220) + * Ethernet controller, under FreeRTOS + lwIP NO_SYS=0. + * + * Pass EthernetIf_Init to netif_add(): it programs the MAC, starts the RX + * FreeRTOS task (driven by the IRQ-13 EthernetISR), and wires the low-level + * output path. The matching EthernetISR (defined in EthernetIf.c) is the + * strong override the Cortex-M3 vector table in Startup.c references at + * IRQ 13. */ +#ifndef SOLIDSYSLOG_FREERTOS_LWIP_ETHERNETIF_H +#define SOLIDSYSLOG_FREERTOS_LWIP_ETHERNETIF_H + +#include "lwip/err.h" +#include "lwip/netif.h" + +err_t EthernetIf_Init(struct netif* netif); + +#endif /* SOLIDSYSLOG_FREERTOS_LWIP_ETHERNETIF_H */ diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h new file mode 100644 index 00000000..9f93468d --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Arm Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SMSC9220_EMAC_CONFIG_H +#define SMSC9220_EMAC_CONFIG_H + +#define SMSC9220_HWADDR_SIZE 6U +#define SMSC9220_BUFF_ALIGNMENT 4U + +/* + * Maximum Transfer Unit + * The IEEE 802.3 specification limits the data portion of the 802.3 frame + * to a minimum of 46 and a maximum of 1522 bytes, this is on L2 level. + */ +#define SMSC9220_ETH_MTU_SIZE 1500U +#define SMSC9220_ETH_IF_NAME "smsc9220" +#define SMSC9220_ETH_MAX_FRAME_SIZE 1522U + +/** \brief Defines for receiver thread */ +#define FLAG_RX 1U +#define LINK_STATUS_THREAD_PRIORITY ( osPriorityNormal ) +#define LINK_STATUS_THREAD_STACKSIZE 512U +#define LINK_STATUS_TASK_PERIOD_MS 200U +#define PHY_STATE_LINK_DOWN false +#define PHY_STATE_LINK_UP true +#define CRC_LENGTH_BYTES 4U + +#endif /* SMSC9220_EMAC_CONFIG_H */ diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c new file mode 100644 index 00000000..48d4b6b5 --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c @@ -0,0 +1,1230 @@ +/* + * Copyright (c) 2016-2019 Arm Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "smsc9220_eth_drv.h" +/*#include "CMSIS/SMM_MPS2.h" */ + +/** Setter bit manipulation macro */ +#define SET_BIT( WORD, BIT_INDEX ) ( ( WORD ) |= ( 1U << ( BIT_INDEX ) ) ) +/** Clearing bit manipulation macro */ +#define CLR_BIT( WORD, BIT_INDEX ) ( ( WORD ) &= ~( 1U << ( BIT_INDEX ) ) ) +/** Getter bit manipulation macro */ +#define GET_BIT( WORD, BIT_INDEX ) ( bool ) ( ( ( WORD ) & ( 1U << ( BIT_INDEX ) ) ) ) + +/** Setter bit-field manipulation macro */ +#define SET_BIT_FIELD( WORD, BIT_MASK, BIT_OFFSET, VALUE ) \ + ( WORD |= ( ( VALUE & BIT_MASK ) << BIT_OFFSET ) ) + +/** Clearing bit-field manipulation macro */ +#define CLR_BIT_FIELD( WORD, BIT_MASK, BIT_OFFSET, VALUE ) \ + ( WORD &= ~( ( VALUE & BIT_MASK ) << BIT_OFFSET ) ) + +/** Getter bit-field manipulation macro */ +#define GET_BIT_FIELD( WORD, BIT_MASK, BIT_OFFSET ) \ + ( ( WORD >> BIT_OFFSET ) & BIT_MASK ) + +/** Millisec timeout macros */ +#define RESET_TIME_OUT_MS 10U +#define REG_WRITE_TIME_OUT_MS 50U +#define PHY_RESET_TIME_OUT_MS 100U +#define INIT_FINISH_DELAY 2000U + +struct smsc9220_eth_reg_map_t +{ + volatile uint32_t rx_data_port; /**< Receive FIFO Ports (offset 0x0) */ + uint32_t reserved1[ 0x7 ]; + volatile uint32_t tx_data_port; /**< Transmit FIFO Ports (offset 0x20) */ + uint32_t reserved2[ 0x7 ]; + + volatile uint32_t rx_status_port; /**< Receive FIFO status port (offset 0x40) */ + volatile uint32_t rx_status_peek; /**< Receive FIFO status peek (offset 0x44) */ + volatile uint32_t tx_status_port; /**< Transmit FIFO status port (offset 0x48) */ + volatile uint32_t tx_status_peek; /**< Transmit FIFO status peek (offset 0x4C) */ + + volatile uint32_t id_revision; /**< Chip ID and Revision (offset 0x50) */ + volatile uint32_t irq_cfg; /**< Main Interrupt Config (offset 0x54) */ + volatile uint32_t irq_status; /**< Interrupt Status (offset 0x58) */ + volatile uint32_t irq_enable; /**< Interrupt Enable Register (offset 0x5C) */ + uint32_t reserved3; /**< Reserved for future use (offset 0x60) */ + volatile uint32_t byte_test; /**< Byte order test 87654321h (offset 0x64) */ + volatile uint32_t fifo_level_irq; /**< FIFO Level Interrupts (offset 0x68) */ + volatile uint32_t rx_cfg; /**< Receive Configuration (offset 0x6C) */ + volatile uint32_t tx_cfg; /**< Transmit Configuration (offset 0x70) */ + volatile uint32_t hw_cfg; /**< Hardware Configuration (offset 0x74) */ + volatile uint32_t rx_datapath_ctrl; /**< RX Datapath Control (offset 0x78) */ + volatile uint32_t rx_fifo_inf; /**< Receive FIFO Information (offset 0x7C) */ + volatile uint32_t tx_fifo_inf; /**< Transmit FIFO Information (offset 0x80) */ + volatile uint32_t pmt_ctrl; /**< Power Management Control (offset 0x84) */ + volatile uint32_t gpio_cfg; /**< GPIO Configuration (offset 0x88) */ + volatile uint32_t gptimer_cfg; /**< GP Timer Configuration (offset 0x8C) */ + volatile uint32_t gptimer_count; /**< GP Timer Count (offset 0x90) */ + uint32_t reserved4; /**< Reserved for future use (offset 0x94) */ + volatile uint32_t word_swap; /**< WORD SWAP Register (offset 0x98) */ + volatile uint32_t free_run_counter; /**< Free Run Counter (offset 0x9C) */ + volatile uint32_t rx_dropped_frames; /**< RX Dropped Frames Counter (offset 0xA0) */ + volatile uint32_t mac_csr_cmd; /**< MAC CSR Synchronizer Cmd (offset 0xA4) */ + volatile uint32_t mac_csr_data; /**< MAC CSR Synchronizer Data (offset 0xA8) */ + volatile uint32_t afc_cfg; /**< AutomaticFlow Ctrl Config (offset 0xAC) */ + volatile uint32_t eeprom_cmd; /**< EEPROM Command (offset 0xB0) */ + volatile uint32_t eeprom_data; /**< EEPROM Data (offset 0xB4) */ +}; + +/** + * \brief TX FIFO Size definitions + * + */ +#define TX_STATUS_FIFO_SIZE_BYTES 512U /*< fixed allocation in bytes */ +#define TX_DATA_FIFO_SIZE_KBYTES_POS 16U +#define TX_DATA_FIFO_SIZE_KBYTES_MASK 0x0FU +#define KBYTES_TO_BYTES_MULTIPLIER 1024U + +/** + * \brief FIFO Info definitions + * + */ +#define FIFO_USED_SPACE_MASK 0xFFFFU +#define DATA_FIFO_USED_SPACE_POS 0U +#define STATUS_FIFO_USED_SPACE_POS 16U + +/** + * \brief MAC CSR Synchronizer Command bit definitions + * + */ +enum mac_csr_cmd_bits_t +{ + MAC_CSR_CMD_RW_INDEX = 30U, + MAC_CSR_CMD_BUSY_INDEX = 31U, +}; + +#define MAC_CSR_CMD_ADDRESS_MASK 0x0FU + +/** + * \brief MAC Control register bit definitions + * + */ +enum mac_reg_cr_bits_t +{ + MAC_REG_CR_RXEN_INDEX = 2U, + MAC_REG_CR_TXEN_INDEX = 3U +}; + +/** + * \brief MII Access register bit definitions + * + */ +enum mac_reg_mii_acc_bits_t +{ + MAC_REG_MII_ACC_BUSY_INDEX = 0U, + MAC_REG_MII_ACC_WRITE_INDEX = 1U, + MAC_REG_MII_ACC_PHYADDR_INDEX = 11U +}; +#define MAC_REG_MII_ACC_MII_REG_MASK 0x1FU +#define MAC_REG_MII_ACC_MII_REG_OFFSET 6U + +/** + * \brief Hardware config register bit definitions + * + */ +enum hw_cfg_reg_bits_t +{ + HW_CFG_REG_SRST_INDEX = 0U, + HW_CFG_REG_SRST_TIMEOUT_INDEX = 1U, + HW_CFG_REG_MUST_BE_ONE_INDEX = 20U, +}; +#define HW_CFG_REG_TX_FIFO_SIZE_POS 16U +#define HW_CFG_REG_TX_FIFO_SIZE_MIN 2U /*< Min Tx fifo size in KB */ +#define HW_CFG_REG_TX_FIFO_SIZE_MAX 14U /*< Max Tx fifo size in KB */ +#define HW_CFG_REG_TX_FIFO_SIZE 5U /*< Tx fifo size in KB */ + +/** + * \brief EEPROM command register bit definitions + * + */ +enum eeprom_cmd_reg_bits_t +{ + EEPROM_CMD_REG_BUSY_INDEX = 31U, +}; + +/** + * \brief PHY Basic Control register bit definitions + * + */ +enum phy_reg_bctrl_reg_bits_t +{ + PHY_REG_BCTRL_RST_AUTO_NEG_INDEX = 9U, + PHY_REG_BCTRL_AUTO_NEG_EN_INDEX = 12U, + PHY_REG_BCTRL_RESET_INDEX = 15U +}; + +/** + * \brief TX Command A bit definitions + * + */ +#define TX_CMD_DATA_START_OFFSET_BYTES_POS 16U +#define TX_CMD_DATA_START_OFFSET_BYTES_MASK 0x1FU + + +enum tx_command_a_bits_t +{ + TX_COMMAND_A_LAST_SEGMENT_INDEX = 12U, + TX_COMMAND_A_FIRST_SEGMENT_INDEX = 13U +}; + +#define TX_CMD_PKT_LEN_BYTES_MASK 0x7FFU +#define TX_CMD_PKT_TAG_MASK 0xFFFFU +#define TX_CMD_PKT_TAG_POS 16U + + +/** + * \brief RX Fifo Status bit definitions + * + */ +enum rx_fifo_status_bits_t +{ + RX_FIFO_STATUS_CRC_ERROR_INDEX = 1U, + RX_FIFO_STATUS_DRIBBLING_BIT_INDEX = 2U, + RX_FIFO_STATUS_MII_ERROR_INDEX = 3U, + RX_FIFO_STATUS_REC_WD_TIMEOUT_INDEX = 4U, + RX_FIFO_STATUS_FRAME_TYPE_INDEX = 5U, + RX_FIFO_STATUS_COLLISION_SEEN_INDEX = 6U, + RX_FIFO_STATUS_FRAME_TOO_LONG_INDEX = 7U, + RX_FIFO_STATUS_MULTICAST_INDEX = 10U, + RX_FIFO_STATUS_RUNT_FRAME_INDEX = 11U, + RX_FIFO_STATUS_LENGTH_ERROR_INDEX = 12U, + RX_FIFO_STATUS_BROADCAST_FRAME_INDEX = 13U, + RX_FIFO_STATUS_ERROR_INDEX = 15U, + RX_FIFO_STATUS_FILTERING_FAIL_INDEX = 30U, +}; +#define RX_FIFO_STATUS_PKT_LENGTH_POS 16U +#define RX_FIFO_STATUS_PKT_LENGTH_MASK 0x3FFFU + +/** + * \brief Interrupt Configuration register bit definitions + * + */ +enum irq_cfg_bits_t +{ + IRQ_CFG_IRQ_TYPE = 0U, + IRQ_CFG_IRQ_POL = 4U, + IRQ_CFG_IRQ_EN_INDEX = 8U +}; + +#define IRQ_CFG_INT_DEAS_MASK 0xFFU +#define IRQ_CFG_INT_DEAS_POS 24U +#define IRQ_CFG_INT_DEAS_10US 0x22U + +/** + * \brief Automatic Flow Control register bit definitions + * + */ +enum afc_bits_t +{ + AFC_ANY_INDEX = 0U, + AFC_ADDR_INDEX = 1U, + AFC_BROADCAST_INDEX = 2U, + AFC_MULTICAST_INDEX = 3U +}; + +#define AFC_BACK_DUR_MASK 0x0FU +#define AFC_BACK_DUR_POS 4U +#define AFC_BACK_DUR 4U /**< equal to 50us */ + +#define AFC_LOW_LEVEL_MASK 0xFFU +#define AFC_LOW_LEVEL_POS 8U +#define AFC_LOW_LEVEL 55U /**< specifies in multiple of 64 bytes */ + +#define AFC_HIGH_LEVEL_MASK 0xFFU +#define AFC_HIGH_LEVEL_POS 16U +#define AFC_HIGH_LEVEL 110U /**< specifies in multiple of 64 bytes */ + +/** + * \brief Auto-Negotiation Advertisement register bit definitions + * + */ +enum aneg_bits_t +{ + ANEG_10_BASE_T_INDEX = 5U, /**< 10Mbps able */ + ANEG_10_BASE_T_FULL_DUPL_INDEX = 6U, /**< 10Mbps with full duplex */ + ANEG_100_BASE_TX_INDEX = 7U, /**< 100Mbps Tx able */ + ANEG_100_BASE_TX_FULL_DUPL_INDEX = 8U, /**< 100Mbps with full duplex */ + ANEG_SYMM_PAUSE_INDEX = 10U, /**< Symmetric Pause */ + ANEG_ASYMM_PAUSE_INDEX = 11U /**< Asymmetric Pause */ +}; + +/** + * \brief Transmit Configuration register bit definitions + * + */ +enum tx_cfg_bits_t +{ + TX_CFG_STOP_INDEX = 0U, /*< stop */ + TX_CFG_ON_INDEX = 1U, /*< on */ + TX_CFG_AO_INDEX = 2U, /*< allow overrun */ + TX_CFG_TXD_DUMP_INDEX = 14U, /*< Data FIFO dump */ + TX_CFG_TXS_DUMP_INDEX = 15U /*< Status FIFO dump */ +}; + +/** + * \brief Chip ID definitions + * + */ +/*#define CHIP_ID 0x9220U */ +#define CHIP_ID 0x0118U +#define CHIP_ID_MASK 0xFFFFU +#define CHIP_ID_POS 16U + +/** + * \brief GPIO Configuration register bit definitions + * + */ +enum gpio_cfg_bits_t +{ + GPIO_CFG_GPIO0_PUSHPULL_INDEX = 16U, /*< GPIO0 push/pull or open-drain */ + GPIO_CFG_GPIO1_PUSHPULL_INDEX = 17U, /*< GPIO1 push/pull or open-drain */ + GPIO_CFG_GPIO2_PUSHPULL_INDEX = 18U, /*< GPIO2 push/pull or open-drain */ + GPIO_CFG_GPIO0_LED_INDEX = 28U, /*< GPIO0 set to LED1 */ + GPIO_CFG_GPIO1_LED_INDEX = 29U, /*< GPIO1 set to LED2 */ + GPIO_CFG_GPIO2_LED_INDEX = 30U /*< GPIO2 set to LED3 */ +}; + + +static void fill_tx_fifo( const struct smsc9220_eth_dev_t * dev, + uint8_t * data, + uint32_t size_bytes ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + uint32_t tx_data_port_tmp = 0; + uint8_t * tx_data_port_tmp_ptr = ( uint8_t * ) &tx_data_port_tmp; + + /*If the data length is not a multiple of 4, then the beginning of the first + * DWORD of the TX DATA FIFO gets filled up with zeros and a byte offset is + * set accordingly to guarantee proper transmission.*/ + uint32_t remainder_bytes = ( size_bytes % 4 ); + uint32_t filler_bytes = ( 4 - remainder_bytes ); + + for( uint32_t i = 0; i < 4; i++ ) + { + if( i < filler_bytes ) + { + tx_data_port_tmp_ptr[ i ] = 0; + } + else + { + tx_data_port_tmp_ptr[ i ] = data[ i - filler_bytes ]; + } + } + + register_map->tx_data_port = tx_data_port_tmp; + size_bytes -= remainder_bytes; + data += remainder_bytes; + + while( size_bytes > 0 ) + { + /* Keep the same endianness in data as in the temp variable */ + tx_data_port_tmp_ptr[ 0 ] = data[ 0 ]; + tx_data_port_tmp_ptr[ 1 ] = data[ 1 ]; + tx_data_port_tmp_ptr[ 2 ] = data[ 2 ]; + tx_data_port_tmp_ptr[ 3 ] = data[ 3 ]; + register_map->tx_data_port = tx_data_port_tmp; + data += 4; + size_bytes -= 4; + } +} + +static void empty_rx_fifo( const struct smsc9220_eth_dev_t * dev, + uint8_t * data, + uint32_t size_bytes ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + uint32_t rx_data_port_tmp = 0; + uint8_t * rx_data_port_tmp_ptr = ( uint8_t * ) &rx_data_port_tmp; + + uint32_t remainder_bytes = ( size_bytes % 4 ); + + size_bytes -= remainder_bytes; + + while( size_bytes > 0 ) + { + /* Keep the same endianness in data as in the temp variable */ + rx_data_port_tmp = register_map->rx_data_port; + data[ 0 ] = rx_data_port_tmp_ptr[ 0 ]; + data[ 1 ] = rx_data_port_tmp_ptr[ 1 ]; + data[ 2 ] = rx_data_port_tmp_ptr[ 2 ]; + data[ 3 ] = rx_data_port_tmp_ptr[ 3 ]; + data += 4; + size_bytes -= 4; + } + + rx_data_port_tmp = register_map->rx_data_port; + + for( uint32_t i = 0; i < remainder_bytes; i++ ) + { + data[ i ] = rx_data_port_tmp_ptr[ i ]; + } +} + +enum smsc9220_error_t smsc9220_mac_regread( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_mac_reg_offsets_t regoffset, + uint32_t * data ) +{ + volatile uint32_t val; + uint32_t maccmd = GET_BIT_FIELD( regoffset, + MAC_CSR_CMD_ADDRESS_MASK, 0 ); + uint32_t time_out = REG_WRITE_TIME_OUT_MS; + + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + /* Make sure there's no pending operation */ + if( !( GET_BIT( register_map->mac_csr_cmd, MAC_CSR_CMD_BUSY_INDEX ) ) ) + { + SET_BIT( maccmd, MAC_CSR_CMD_RW_INDEX ); + SET_BIT( maccmd, MAC_CSR_CMD_BUSY_INDEX ); + register_map->mac_csr_cmd = maccmd; /* Start operation */ + + do + { + val = register_map->byte_test; /* A no-op read. */ + ( void ) val; + + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + } while( time_out && + GET_BIT( register_map->mac_csr_cmd, MAC_CSR_CMD_BUSY_INDEX ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + else + { + *data = register_map->mac_csr_data; + } + } + else + { + return SMSC9220_ERROR_BUSY; + } + + return SMSC9220_ERROR_NONE; +} + +enum smsc9220_error_t smsc9220_mac_regwrite( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_mac_reg_offsets_t regoffset, + uint32_t data ) +{ + volatile uint32_t read = 0; + uint32_t maccmd = GET_BIT_FIELD( regoffset, + MAC_CSR_CMD_ADDRESS_MASK, 0 ); + uint32_t time_out = REG_WRITE_TIME_OUT_MS; + + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + /* Make sure there's no pending operation */ + if( !GET_BIT( register_map->mac_csr_cmd, MAC_CSR_CMD_BUSY_INDEX ) ) + { + register_map->mac_csr_data = data; /* Store data. */ + CLR_BIT( maccmd, MAC_CSR_CMD_RW_INDEX ); + SET_BIT( maccmd, MAC_CSR_CMD_BUSY_INDEX ); + register_map->mac_csr_cmd = maccmd; + + do + { + read = register_map->byte_test; /* A no-op read. */ + ( void ) read; + + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + } while( time_out && + ( register_map->mac_csr_cmd & + GET_BIT( register_map->mac_csr_cmd, MAC_CSR_CMD_BUSY_INDEX ) ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + } + else + { + return SMSC9220_ERROR_BUSY; + } + + return SMSC9220_ERROR_NONE; +} + +enum smsc9220_error_t smsc9220_phy_regread( const struct smsc9220_eth_dev_t * dev, + enum phy_reg_offsets_t regoffset, + uint32_t * data ) +{ + uint32_t val = 0; + uint32_t phycmd = 0; + uint32_t time_out = REG_WRITE_TIME_OUT_MS; + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, &val ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + if( !GET_BIT( val, MAC_REG_MII_ACC_BUSY_INDEX ) ) + { + phycmd = 0; + SET_BIT( phycmd, MAC_REG_MII_ACC_PHYADDR_INDEX ); + SET_BIT_FIELD( phycmd, MAC_REG_MII_ACC_MII_REG_MASK, + MAC_REG_MII_ACC_MII_REG_OFFSET, regoffset ); + CLR_BIT( phycmd, MAC_REG_MII_ACC_WRITE_INDEX ); + SET_BIT( phycmd, MAC_REG_MII_ACC_BUSY_INDEX ); + + if( smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, + phycmd ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + val = 0; + + do + { + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, + &val ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + } while( time_out && ( GET_BIT( val, MAC_REG_MII_ACC_BUSY_INDEX ) ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + else if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_MII_DATA, + data ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + } + else + { + return SMSC9220_ERROR_BUSY; + } + + return SMSC9220_ERROR_NONE; +} + +enum smsc9220_error_t smsc9220_phy_regwrite( const struct smsc9220_eth_dev_t * dev, + enum phy_reg_offsets_t regoffset, + uint32_t data ) +{ + uint32_t val = 0; + uint32_t phycmd = 0; + uint32_t time_out = REG_WRITE_TIME_OUT_MS; + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, &val ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + if( !GET_BIT( val, MAC_REG_MII_ACC_BUSY_INDEX ) ) + { + /* Load the data */ + if( smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_MII_DATA, + ( data & 0xFFFF ) ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + phycmd = 0; + SET_BIT( phycmd, MAC_REG_MII_ACC_PHYADDR_INDEX ); + SET_BIT_FIELD( phycmd, MAC_REG_MII_ACC_MII_REG_MASK, + MAC_REG_MII_ACC_MII_REG_OFFSET, regoffset ); + SET_BIT( phycmd, MAC_REG_MII_ACC_WRITE_INDEX ); + SET_BIT( phycmd, MAC_REG_MII_ACC_BUSY_INDEX ); + + /* Start operation */ + if( smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, + phycmd ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + phycmd = 0; + + do + { + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_MII_ACC, + &phycmd ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + } while( time_out && GET_BIT( phycmd, 0 ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + } + else + { + return SMSC9220_ERROR_BUSY; + } + + return SMSC9220_ERROR_NONE; +} + +uint32_t smsc9220_read_id( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + return register_map->id_revision; +} + +enum smsc9220_error_t smsc9220_soft_reset( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t time_out = RESET_TIME_OUT_MS; + + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + /* Soft reset */ + SET_BIT( register_map->hw_cfg, HW_CFG_REG_SRST_INDEX ); + + do + { + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + } while( time_out && + GET_BIT( register_map->hw_cfg, HW_CFG_REG_SRST_TIMEOUT_INDEX ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + + return SMSC9220_ERROR_NONE; +} + +void smsc9220_set_txfifo( const struct smsc9220_eth_dev_t * dev, + uint32_t val ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + if( ( val >= HW_CFG_REG_TX_FIFO_SIZE_MIN ) && + ( val <= HW_CFG_REG_TX_FIFO_SIZE_MAX ) ) + { + register_map->hw_cfg = val << HW_CFG_REG_TX_FIFO_SIZE_POS; + } +} + +enum smsc9220_error_t smsc9220_set_fifo_level_irq( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_fifo_level_irq_pos_t irq_level_pos, + uint32_t level ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + enum smsc9220_error_t eReturn = SMSC9220_ERROR_PARAM; + + #if ( SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MIN > 0 ) + if( level < SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MIN ) + { + /* An error will be returned. */ + } + else + #endif + + if( level <= SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MAX ) + { + CLR_BIT_FIELD( register_map->fifo_level_irq, SMSC9220_FIFO_LEVEL_IRQ_MASK, + irq_level_pos, SMSC9220_FIFO_LEVEL_IRQ_MASK ); + SET_BIT_FIELD( register_map->fifo_level_irq, SMSC9220_FIFO_LEVEL_IRQ_MASK, + irq_level_pos, level ); + eReturn = SMSC9220_ERROR_NONE; + } + + return eReturn; +} + +enum smsc9220_error_t smsc9220_wait_eeprom( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t time_out = REG_WRITE_TIME_OUT_MS; + + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + do + { + if( dev->data->wait_ms ) + { + dev->data->wait_ms( 1 ); + } + + time_out--; + } while( time_out && + GET_BIT( register_map->eeprom_cmd, EEPROM_CMD_REG_BUSY_INDEX ) ); + + if( !time_out ) + { + return SMSC9220_ERROR_TIMEOUT; + } + + return SMSC9220_ERROR_NONE; +} + +void smsc9220_init_irqs( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + smsc9220_disable_all_interrupts( dev ); + smsc9220_clear_all_interrupts( dev ); + + /* Set IRQ de-assertion interval */ + SET_BIT_FIELD( register_map->irq_cfg, IRQ_CFG_INT_DEAS_MASK, + IRQ_CFG_INT_DEAS_POS, IRQ_CFG_INT_DEAS_10US ); + + /* enable interrupts */ + SET_BIT( register_map->irq_cfg, IRQ_CFG_IRQ_TYPE ); + SET_BIT( register_map->irq_cfg, IRQ_CFG_IRQ_POL ); + SET_BIT( register_map->irq_cfg, IRQ_CFG_IRQ_EN_INDEX ); +} + +enum smsc9220_error_t smsc9220_check_phy( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t phyid1 = 0; + uint32_t phyid2 = 0; + + if( smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_ID1, &phyid1 ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + if( smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_ID2, &phyid2 ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + if( ( ( phyid1 == 0xFFFF ) && ( phyid2 == 0xFFFF ) ) || + ( ( phyid1 == 0x0 ) && ( phyid2 == 0x0 ) ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + return SMSC9220_ERROR_NONE; +} + +enum smsc9220_error_t smsc9220_reset_phy( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t read = 0; + + if( smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_BCTRL, &read ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + SET_BIT( read, PHY_REG_BCTRL_RESET_INDEX ); + + if( smsc9220_phy_regwrite( dev, SMSC9220_PHY_REG_OFFSET_BCTRL, read ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + return SMSC9220_ERROR_NONE; +} + +void smsc9220_advertise_cap( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t aneg_adv = 0; + + smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_ANEG_ADV, &aneg_adv ); + + SET_BIT( aneg_adv, ANEG_10_BASE_T_INDEX ); + SET_BIT( aneg_adv, ANEG_10_BASE_T_FULL_DUPL_INDEX ); + SET_BIT( aneg_adv, ANEG_100_BASE_TX_INDEX ); + SET_BIT( aneg_adv, ANEG_100_BASE_TX_FULL_DUPL_INDEX ); + SET_BIT( aneg_adv, ANEG_SYMM_PAUSE_INDEX ); + SET_BIT( aneg_adv, ANEG_ASYMM_PAUSE_INDEX ); + + smsc9220_phy_regwrite( dev, SMSC9220_PHY_REG_OFFSET_ANEG_ADV, aneg_adv ); +} + +void smsc9220_enable_xmit( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + SET_BIT( register_map->tx_cfg, TX_CFG_ON_INDEX ); +} + +void smsc9220_disable_xmit( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + CLR_BIT( register_map->tx_cfg, TX_CFG_ON_INDEX ); +} + +void smsc9220_enable_mac_xmit( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t mac_cr = 0; + + smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_CR, &mac_cr ); + + SET_BIT( mac_cr, MAC_REG_CR_TXEN_INDEX ); + + smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_CR, mac_cr ); +} + +void smsc9220_disable_mac_xmit( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t mac_cr = 0; + + smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_CR, &mac_cr ); + + CLR_BIT( mac_cr, MAC_REG_CR_TXEN_INDEX ); + + smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_CR, mac_cr ); +} + +void smsc9220_enable_mac_recv( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t mac_cr = 0; + + smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_CR, &mac_cr ); + + SET_BIT( mac_cr, MAC_REG_CR_RXEN_INDEX ); + + smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_CR, mac_cr ); +} + +void smsc9220_disable_mac_recv( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t mac_cr = 0; + + smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_CR, &mac_cr ); + + CLR_BIT( mac_cr, MAC_REG_CR_RXEN_INDEX ); + + smsc9220_mac_regwrite( dev, SMSC9220_MAC_REG_OFFSET_CR, mac_cr ); +} + +int smsc9220_check_id( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t id = smsc9220_read_id( dev ); + + return( ( GET_BIT_FIELD( id, CHIP_ID_MASK, CHIP_ID_POS ) == CHIP_ID ) ? 0 : 1 ); +} + +void smsc9220_enable_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + SET_BIT( register_map->irq_enable, source ); +} + +void smsc9220_disable_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + CLR_BIT( register_map->irq_enable, source ); +} + +void smsc9220_disable_all_interrupts( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + register_map->irq_enable = 0; +} + +void smsc9220_clear_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + SET_BIT( register_map->irq_status, source ); +} + +uint32_t get_irq_status( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + return register_map->irq_status; +} + + +void smsc9220_clear_all_interrupts( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + register_map->irq_status = UINT32_MAX; +} + +int smsc9220_get_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + return GET_BIT( register_map->irq_status, source ); +} + +void smsc9220_establish_link( const struct smsc9220_eth_dev_t * dev ) +{ + uint32_t bcr = 0; + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_BCTRL, &bcr ); + SET_BIT( bcr, PHY_REG_BCTRL_AUTO_NEG_EN_INDEX ); + SET_BIT( bcr, PHY_REG_BCTRL_RST_AUTO_NEG_INDEX ); + smsc9220_phy_regwrite( dev, SMSC9220_PHY_REG_OFFSET_BCTRL, bcr ); + + SET_BIT( register_map->hw_cfg, HW_CFG_REG_MUST_BE_ONE_INDEX ); +} + +enum smsc9220_error_t smsc9220_read_mac_address( const struct smsc9220_eth_dev_t * dev, + char * mac ) +{ + uint32_t mac_low = 0; + uint32_t mac_high = 0; + + if( !mac ) + { + return SMSC9220_ERROR_PARAM; + } + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_ADDRH, &mac_high ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + if( smsc9220_mac_regread( dev, SMSC9220_MAC_REG_OFFSET_ADDRL, &mac_low ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + mac[ 0 ] = mac_low & 0xFF; + mac[ 1 ] = ( mac_low >> 8 ) & 0xFF; + mac[ 2 ] = ( mac_low >> 16 ) & 0xFF; + mac[ 3 ] = ( mac_low >> 24 ) & 0xFF; + mac[ 4 ] = mac_high & 0xFF; + mac[ 5 ] = ( mac_high >> 8 ) & 0xFF; + + return SMSC9220_ERROR_NONE; +} + +uint32_t smsc9220_get_tx_data_fifo_size( const struct smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + uint32_t tx_fifo_size = + GET_BIT_FIELD( register_map->hw_cfg, + TX_DATA_FIFO_SIZE_KBYTES_MASK, + TX_DATA_FIFO_SIZE_KBYTES_POS ) * KBYTES_TO_BYTES_MULTIPLIER; + + return( tx_fifo_size - TX_STATUS_FIFO_SIZE_BYTES ); +} + +enum smsc9220_error_t smsc9220_init( const struct smsc9220_eth_dev_t * dev, + void ( * wait_ms_function )( uint32_t ) ) +{ + uint32_t phyreset = 0; + enum smsc9220_error_t error = SMSC9220_ERROR_NONE; + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + if( !wait_ms_function ) + { + return SMSC9220_ERROR_PARAM; + } + + dev->data->wait_ms = wait_ms_function; + + error = smsc9220_check_id( dev ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + error = smsc9220_soft_reset( dev ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + smsc9220_set_txfifo( dev, HW_CFG_REG_TX_FIFO_SIZE ); + + SET_BIT_FIELD( register_map->afc_cfg, AFC_BACK_DUR_MASK, + AFC_BACK_DUR_POS, AFC_BACK_DUR ); + SET_BIT_FIELD( register_map->afc_cfg, AFC_LOW_LEVEL_MASK, + AFC_LOW_LEVEL_POS, AFC_LOW_LEVEL ); + SET_BIT_FIELD( register_map->afc_cfg, AFC_HIGH_LEVEL_MASK, + AFC_HIGH_LEVEL_POS, AFC_HIGH_LEVEL ); + + error = smsc9220_wait_eeprom( dev ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + /* Configure GPIOs as LED outputs. */ + register_map->gpio_cfg = 0; + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO0_PUSHPULL_INDEX ); + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO1_PUSHPULL_INDEX ); + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO2_PUSHPULL_INDEX ); + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO0_LED_INDEX ); + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO1_LED_INDEX ); + SET_BIT( register_map->gpio_cfg, GPIO_CFG_GPIO2_LED_INDEX ); + + smsc9220_init_irqs( dev ); + + /* Configure MAC addresses here if needed. */ + + error = smsc9220_check_phy( dev ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + error = smsc9220_reset_phy( dev ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + if( dev->data->wait_ms ) + { + dev->data->wait_ms( PHY_RESET_TIME_OUT_MS ); + } + + /* Checking whether phy reset completed successfully.*/ + error = smsc9220_phy_regread( dev, SMSC9220_PHY_REG_OFFSET_BCTRL, + &phyreset ); + + if( error != SMSC9220_ERROR_NONE ) + { + return error; + } + + if( GET_BIT( phyreset, PHY_REG_BCTRL_RESET_INDEX ) ) + { + return SMSC9220_ERROR_INTERNAL; + } + + smsc9220_advertise_cap( dev ); + smsc9220_establish_link( dev ); + + smsc9220_enable_mac_xmit( dev ); + smsc9220_enable_xmit( dev ); + smsc9220_enable_mac_recv( dev ); + + /* This sleep is compulsory otherwise txmit/receive will fail. */ + if( dev->data->wait_ms ) + { + dev->data->wait_ms( INIT_FINISH_DELAY ); + } + + dev->data->state = 1; + + return SMSC9220_ERROR_NONE; +} + +enum smsc9220_error_t smsc9220_send_by_chunks( const struct smsc9220_eth_dev_t * dev, + uint32_t total_payload_length, + bool is_new_packet, + const char * data, + uint32_t current_size ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + bool is_first_segment = false; + bool is_last_segment = false; + uint32_t txcmd_a, txcmd_b = 0; + uint32_t tx_buffer_free_space = 0; + volatile uint32_t xmit_stat = 0; + + if( !data ) + { + return SMSC9220_ERROR_PARAM; + } + + if( is_new_packet ) + { + is_first_segment = true; + dev->data->ongoing_packet_length = total_payload_length; + dev->data->ongoing_packet_length_sent = 0; + } + else if( ( dev->data->ongoing_packet_length != total_payload_length ) || + ( dev->data->ongoing_packet_length_sent >= total_payload_length ) ) + { + return SMSC9220_ERROR_PARAM; + } + + /* Would next chunk fit into buffer? */ + tx_buffer_free_space = GET_BIT_FIELD( register_map->tx_fifo_inf, + FIFO_USED_SPACE_MASK, + DATA_FIFO_USED_SPACE_POS ); + + if( current_size > tx_buffer_free_space ) + { + return SMSC9220_ERROR_INTERNAL; /* Not enough space in FIFO */ + } + + if( ( dev->data->ongoing_packet_length_sent + current_size ) == + total_payload_length ) + { + is_last_segment = true; + } + + txcmd_a = 0; + txcmd_b = 0; + + if( is_last_segment ) + { + SET_BIT( txcmd_a, TX_COMMAND_A_LAST_SEGMENT_INDEX ); + } + + if( is_first_segment ) + { + SET_BIT( txcmd_a, TX_COMMAND_A_FIRST_SEGMENT_INDEX ); + } + + uint32_t data_start_offset_bytes = ( 4 - ( current_size % 4 ) ); + + SET_BIT_FIELD( txcmd_a, TX_CMD_PKT_LEN_BYTES_MASK, 0, current_size ); + SET_BIT_FIELD( txcmd_a, TX_CMD_DATA_START_OFFSET_BYTES_MASK, + TX_CMD_DATA_START_OFFSET_BYTES_POS, + data_start_offset_bytes ); + + SET_BIT_FIELD( txcmd_b, TX_CMD_PKT_LEN_BYTES_MASK, 0, current_size ); + SET_BIT_FIELD( txcmd_b, TX_CMD_PKT_TAG_MASK, TX_CMD_PKT_TAG_POS, + current_size ); + + register_map->tx_data_port = txcmd_a; + register_map->tx_data_port = txcmd_b; + + fill_tx_fifo( dev, ( uint8_t * ) data, current_size ); + + if( is_last_segment ) + { + /* Pop status port for error check */ + xmit_stat = register_map->tx_status_port; + ( void ) xmit_stat; + } + + dev->data->ongoing_packet_length_sent += current_size; + return SMSC9220_ERROR_NONE; +} + +uint32_t smsc9220_get_rxfifo_data_used_space( const struct + smsc9220_eth_dev_t * dev ) +{ + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + + return GET_BIT_FIELD( register_map->rx_fifo_inf, FIFO_USED_SPACE_MASK, + DATA_FIFO_USED_SPACE_POS ); +} + +uint32_t smsc9220_receive_by_chunks( const struct smsc9220_eth_dev_t * dev, + char * data, + uint32_t dlen ) +{ + uint32_t packet_length_byte = 0; + + if( !data ) + { + return 0; /* Invalid input parameter, cannot read */ + } + + packet_length_byte = dlen; /*_RB_ Hard set to length read from peek register. */ + dev->data->current_rx_size_words = packet_length_byte; + + empty_rx_fifo( dev, ( uint8_t * ) data, packet_length_byte ); + dev->data->current_rx_size_words = 0; + return packet_length_byte; +} + +uint32_t smsc9220_peek_next_packet_size( const struct + smsc9220_eth_dev_t * dev ) +{ + uint32_t packet_size = 0; + struct smsc9220_eth_reg_map_t * register_map = + ( struct smsc9220_eth_reg_map_t * ) dev->cfg->base; + volatile uint32_t rx_status_from_peek = 0; + + if( smsc9220_get_rxfifo_data_used_space( dev ) ) + { + rx_status_from_peek = register_map->rx_status_peek; + /* Warning: try reading from port as peek is often zero. */ + rx_status_from_peek = register_map->rx_status_port; + + packet_size = GET_BIT_FIELD( rx_status_from_peek, + RX_FIFO_STATUS_PKT_LENGTH_MASK, + RX_FIFO_STATUS_PKT_LENGTH_POS ); + } + else + { + rx_status_from_peek = -1; + } + + return packet_size; +} diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h new file mode 100644 index 00000000..72d383fb --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2016-2019 Arm Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * \file smsc9220_drv.h + * \brief Generic driver for SMSC9220 Ethernet controller + */ + +#ifndef __SMSC9220_ETH_H__ + #define __SMSC9220_ETH_H__ + + #include + #include + + #ifdef __cplusplus + extern "C" { + #endif + +/** SMSC9220 device configuration structure */ + struct smsc9220_eth_dev_cfg_t + { + const uint32_t base; /*!< SMSC9220 base address */ + }; + +/** SMSC9220 device data structure */ + struct smsc9220_eth_dev_data_t + { + uint32_t state; /*!< Indicates if the SMSC9220 driver + * is initialized and enabled */ + void (* wait_ms) ( uint32_t ); /*!< function pointer to system's millisec delay + * function, will be used for delays */ + uint32_t ongoing_packet_length; /*!< size in bytes of the packet + * is being sent */ + uint32_t ongoing_packet_length_sent; /*!< size in bytes of the packet + * has been sent */ + uint32_t current_rx_size_words; /*!< Data length in words, + * currently is being read */ + }; + +/** SMSC9220 device structure */ + struct smsc9220_eth_dev_t + { + const struct smsc9220_eth_dev_cfg_t * const cfg; /*!< configuration */ + struct smsc9220_eth_dev_data_t * const data; /*!< data */ + }; + +/** + * \brief Error code definitions + * + */ + enum smsc9220_error_t + { + SMSC9220_ERROR_NONE = 0U, /*!< no error */ + SMSC9220_ERROR_TIMEOUT = 1U, /*!< timeout */ + SMSC9220_ERROR_BUSY = 2U, /*!< no error */ + SMSC9220_ERROR_PARAM = 3U, /*!< invalid parameter */ + SMSC9220_ERROR_INTERNAL = 4U /*!< internal error */ + }; + +/** + * \brief Interrupt source definitions + * + */ + enum smsc9220_interrupt_source + { + SMSC9220_INTERRUPT_GPIO0 = 0U, + SMSC9220_INTERRUPT_GPIO1 = 1U, + SMSC9220_INTERRUPT_GPIO2 = 2U, + SMSC9220_INTERRUPT_RX_STATUS_FIFO_LEVEL = 3U, + SMSC9220_INTERRUPT_RX_STATUS_FIFO_FULL = 4U, + /* 5 Reserved according to Datasheet */ + SMSC9220_INTERRUPT_RX_DROPPED_FRAME = 6U, + SMSC9220_INTERRUPT_TX_STATUS_FIFO_LEVEL = 7U, + SMSC9220_INTERRUPT_TX_STATUS_FIFO_FULL = 8U, + SMSC9220_INTERRUPT_TX_DATA_FIFO_AVAILABLE = 9U, + SMSC9220_INTERRUPT_TX_DATA_FIFO_OVERRUN = 10U, + /* 11, 12 Reserved according to Datasheet */ + SMSC9220_INTERRUPT_TX_ERROR = 13U, + SMSC9220_INTERRUPT_RX_ERROR = 14U, + SMSC9220_INTERRUPT_RX_WATCHDOG_TIMEOUT = 15U, + SMSC9220_INTERRUPT_TX_STATUS_OVERFLOW = 16U, + SMSC9220_INTERRUPT_TX_POWER_MANAGEMENT = 17U, + SMSC9220_INTERRUPT_PHY = 18U, + SMSC9220_INTERRUPT_GP_TIMER = 19U, + SMSC9220_INTERRUPT_RX_DMA = 20U, + SMSC9220_INTERRUPT_TX_IOC = 21U, + /* 22 Reserved according to Datasheet*/ + SMSC9220_INTERRUPT_RX_DROPPED_FRAME_HALF = 23U, + SMSC9220_INTERRUPT_RX_STOPPED = 24U, + SMSC9220_INTERRUPT_TX_STOPPED = 25U, + /* 26 - 30 Reserved according to Datasheet*/ + SMSC9220_INTERRUPT_SW = 31U + }; + +/** + * \brief MAC register offset definitions + * + */ + enum smsc9220_mac_reg_offsets_t + { + SMSC9220_MAC_REG_OFFSET_CR = 0x1U, + SMSC9220_MAC_REG_OFFSET_ADDRH = 0x2U, + SMSC9220_MAC_REG_OFFSET_ADDRL = 0x3U, + SMSC9220_MAC_REG_OFFSET_HASHH = 0x4U, + SMSC9220_MAC_REG_OFFSET_HASHL = 0x5U, + SMSC9220_MAC_REG_OFFSET_MII_ACC = 0x6U, + SMSC9220_MAC_REG_OFFSET_MII_DATA = 0x7U, + SMSC9220_MAC_REG_OFFSET_FLOW = 0x8U, + SMSC9220_MAC_REG_OFFSET_VLAN1 = 0x9U, + SMSC9220_MAC_REG_OFFSET_VLAN2 = 0xAU, + SMSC9220_MAC_REG_OFFSET_WUFF = 0xBU, + SMSC9220_MAC_REG_OFFSET_WUCSR = 0xCU, + SMSC9220_MAC_REG_OFFSET_COE_CR = 0xDU + }; + +/** + * \brief PHY register offset definitions + * + */ + enum phy_reg_offsets_t + { + SMSC9220_PHY_REG_OFFSET_BCTRL = 0U, + SMSC9220_PHY_REG_OFFSET_BSTATUS = 1U, + SMSC9220_PHY_REG_OFFSET_ID1 = 2U, + SMSC9220_PHY_REG_OFFSET_ID2 = 3U, + SMSC9220_PHY_REG_OFFSET_ANEG_ADV = 4U, + SMSC9220_PHY_REG_OFFSET_ANEG_LPA = 5U, + SMSC9220_PHY_REG_OFFSET_ANEG_EXP = 6U, + SMSC9220_PHY_REG_OFFSET_MCONTROL = 17U, + SMSC9220_PHY_REG_OFFSET_MSTATUS = 18U, + SMSC9220_PHY_REG_OFFSET_CSINDICATE = 27U, + SMSC9220_PHY_REG_OFFSET_INTSRC = 29U, + SMSC9220_PHY_REG_OFFSET_INTMASK = 30U, + SMSC9220_PHY_REG_OFFSET_CS = 31U + }; + +/* Bit definitions for PHY Basic Status Register */ + #define PHY_REG_BSTATUS_EXTENDED_CAPABILITIES_INDEX 0U + #define PHY_REG_BSTATUS_JABBER_DETECT_INDEX 1U + #define PHY_REG_BSTATUS_LINK_STATUS_INDEX 2U + #define PHY_REG_BSTATUS_AUTO_NEG_ABILITY_INDEX 3U + #define PHY_REG_BSTATUS_REMOTE_FAULT_INDEX 4U + #define PHY_REG_BSTATUS_AUTO_NEG_COMPLETE_INDEX 5U + #define PHY_REG_BSTATUS_10BASE_T_HALF_DUPLEX_INDEX 11U + #define PHY_REG_BSTATUS_10BASE_T_FULL_DUPLEX_INDEX 12U + #define PHY_REG_BSTATUS_100BASE_TX_HALF_DUPLEX_INDEX 13U + #define PHY_REG_BSTATUS_100BASE_TX_FULL_DUPLEX_INDEX 14U + #define PHY_REG_BSTATUS_100BASE_T4_INDEX 15U + +/** + * \brief FIFO Level Interrupt bit definitions + * + */ + enum smsc9220_fifo_level_irq_pos_t + { + SMSC9220_FIFO_LEVEL_IRQ_RX_STATUS_POS = 0U, + SMSC9220_FIFO_LEVEL_IRQ_TX_STATUS_POS = 16U, + SMSC9220_FIFO_LEVEL_IRQ_TX_DATA_POS = 24U + }; + +/** + * \brief FIFO Level Interrupt limits + * + */ + #define SMSC9220_FIFO_LEVEL_IRQ_MASK 0xFFU + #define SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MIN 0U + #define SMSC9220_FIFO_LEVEL_IRQ_LEVEL_MAX SMSC9220_FIFO_LEVEL_IRQ_MASK + +/** + * \brief Initializes SMSC9220 Ethernet controller to a known default state: + * - device ID is checked + * - global interrupt is enabled, but all irq sources are disabled + * - all capabilities are advertised + * - 10Mbps able + * - 10Mbps with full duplex + * - 100Mbps Tx able + * - 100Mbps with full duplex + * - Symmetric Pause + * - Asymmetric Pause + * - Establish link enabled + * - Rx enabled + * - Tx enabled + * Init should be called prior to any other process and + * it's the caller's responsibility to follow proper call order. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] wait_ms_function function pointer to a millisec delay function + * for proper timing of some processes + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_init( const struct smsc9220_eth_dev_t * dev, + void ( * wait_ms_function )( uint32_t ) ); + +/** + * \brief Reads the MAC register. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] regoffset Register offset + * \param[in, out] data Pointer to register will be read + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_mac_regread( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_mac_reg_offsets_t regoffset, + uint32_t * data ); + +/** + * \brief Writes the MAC register. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] regoffset Register offset + * \param[in] data Register value to write + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_mac_regwrite( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_mac_reg_offsets_t regoffset, + uint32_t data ); + +/** + * \brief Reads the PHY register. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] regoffset Register offset + * \param[out] data Register value is read + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_phy_regread( const struct smsc9220_eth_dev_t * dev, + enum phy_reg_offsets_t, + uint32_t * data ); + +/** + * \brief Writes the PHY register. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] regoffset Register offset + * \param[in] data Register value to write + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_phy_regwrite( const struct smsc9220_eth_dev_t * dev, + enum phy_reg_offsets_t, + uint32_t data ); + +/** + * \brief Reads the Ethernet Controller's ID. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return ID number + */ + uint32_t smsc9220_read_id( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Initiates a soft reset, returns failure or success. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_soft_reset( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Sets the Maximum Transmission Unit by Tx fifo size. + * Note: The MTU will be smaller by 512 bytes, + * which is used by the status. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] val Size of the fifo in kbytes + * \ref HW_CFG_REG_TX_FIFO_SIZE_MIN + * \ref HW_CFG_REG_TX_FIFO_SIZE_MAX + */ + void smsc9220_set_txfifo( const struct smsc9220_eth_dev_t * dev, + uint32_t val ); + +/** + * \brief Sets the FIFO level interrupt for a given source. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] irq_level_pos Bit position of the FIFO to set + * \ref smsc9220_fifo_level_irq_pos_t + * \param[in] level Level of the FIFO, when the FIFO used space is greater + * than this value, corresponding interrupt will be generated. + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_set_fifo_level_irq( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_fifo_level_irq_pos_t irq_level_pos, + uint32_t level ); + +/** + * \brief Waits for EEPROM to be ready to use. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_wait_eeprom( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Initializes irqs by clearing and disabling all interrupt sources + * and enable interrupts. Since all interrupt sources are disabled, + * interrupt won't be triggered, until interrupt sources won't be + * enabled by \ref smsc9220_enable_interrupt + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_init_irqs( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Checks PHY ID registers. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_check_phy( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Resets PHY. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_reset_phy( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Advertises all speeds and pauses capabilities. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_advertise_cap( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Enables transmission. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_enable_xmit( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Disables transmission. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_disable_xmit( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Enables MAC Transmitter. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_enable_mac_xmit( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Disables MAC Transmitter. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_disable_mac_xmit( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Enables receiving. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_enable_mac_recv( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Disables receiving. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_disable_mac_recv( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Enables the given interrupt source. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] source Enum of the interrupt source. + */ + void smsc9220_enable_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ); + +/** + * \brief Disables the given interrupt source. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] source Enum of the interrupt source. + */ + void smsc9220_disable_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ); + +/** + * \brief Disables all interrupt sources. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_disable_all_interrupts( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Clears the given interrupt source. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] source Enum of the interrupt source. + */ + void smsc9220_clear_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ); + +/** + * \brief Clears all interrupt sources. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_clear_all_interrupts( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Gets the status of the given interrupt source. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] source Enum of the interrupt source. + * + * \return non-zero if the given interrupt source is triggered, zero otherwise + */ + int smsc9220_get_interrupt( const struct smsc9220_eth_dev_t * dev, + enum smsc9220_interrupt_source source ); + +/** + * \brief Establishes link + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + */ + void smsc9220_establish_link( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Reads the Ethernet Controller's MAC address from its EEPROM. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in,out] mac array will include the read MAC address in + * 6 bytes hexadecimal format. + * It should be allocated by the caller to 6 bytes. + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_read_mac_address( const struct smsc9220_eth_dev_t * dev, + char * mac ); + +/** + * \brief Check device ID. + * + * \return error code /ref smsc9220_error_t + */ + int smsc9220_check_id( const struct smsc9220_eth_dev_t * dev ); + +/** + * \brief Gets the data size of the Tx buffer, aka Maximum Transmission Unit + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return Fifo data size in bytes + */ + uint32_t smsc9220_get_tx_data_fifo_size( const struct + smsc9220_eth_dev_t * dev ); + +/** + * \brief Sends data from the given buffer as an Ethernet packet. + * The full packet length must be specified at the beginning + * of a new packet transmission. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in] total_payload_length Length of the ethernet payload. + * Should be equal to the sum of passed buffers within a packet. + * \param[in] is_new_packet Should be set to true if the input buffer has to + * be sent as the start of a new packet or as a full packet. + * \param[in] data Pointer to the data buffer to be sent. + * \param[in] current_size Size of the data in bytes. + * + * \return error code /ref smsc9220_error_t + */ + enum smsc9220_error_t smsc9220_send_by_chunks( const struct smsc9220_eth_dev_t * dev, + uint32_t total_payload_length, + bool is_new_packet, + const char * data, + uint32_t current_size ); + +/** + * \brief Reads an incoming Ethernet packet into the given buffer. + * Stops reading at packet border. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * \param[in,out] data Pointer to a pre-allocated input buffer. + * Allocating sufficient memory space is the caller's + * responsibility, which is typically done by calling + * \ref smsc9220_peek_next_packet_size. + * \param[in] dlen Length of the allocated data in bytes. + * + * \return Number of bytes read from the Rx FIFO into the given buffer. + */ + uint32_t smsc9220_receive_by_chunks( const struct smsc9220_eth_dev_t * dev, + char * data, + uint32_t dlen ); + +/** + * \brief Get the used space of Rx fifo in bytes. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return Data received and waiting for read in bytes + */ + uint32_t smsc9220_get_rxfifo_data_used_space( const struct + smsc9220_eth_dev_t * dev ); + +/** + * \brief Gets the size of next unread packet in Rx buffer, using the peak + * register, which is not destructive so can be read asynchronously. + * Warning: In case of heavy receiving loads, this register may not + * be in perfect sync. + * + * \param[in] dev Ethernet device structure \ref smsc9220_eth_dev_t + * + * \return Size of the next packet in bytes, read from the Rx Peek register. + */ + uint32_t smsc9220_peek_next_packet_size( const struct + smsc9220_eth_dev_t * dev ); + + #ifdef __cplusplus +} /* extern "C" */ + #endif + +#endif /* __SMSC9220_ETH_H__ */ diff --git a/Bdd/Targets/FreeRtosLwip/solidsyslog_user_tunables.h b/Bdd/Targets/FreeRtosLwip/solidsyslog_user_tunables.h new file mode 100644 index 00000000..58e88042 --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/solidsyslog_user_tunables.h @@ -0,0 +1,24 @@ +#ifndef SOLIDSYSLOG_USER_TUNABLES_H +#define SOLIDSYSLOG_USER_TUNABLES_H + +/* FreeRTOS + lwIP BDD-target tuning. Mirrors + * Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h — the lwIP target runs the + * same Cortex-M3 with the same task-stack and FreeRtosMutex constraints. + * + * SolidSyslog_Log builds a Formatter and working buffer of + * SOLIDSYSLOG_MAX_MESSAGE_SIZE on the caller's stack, so dropping from 2048 + * (the RFC 5424 section 6.1 SHOULD value) to 512 (the library's pre-S12.12 + * default) reclaims ~4.5KB per call — material on a Cortex-M3. RFC 5424 + * receivers must still accept 480 bytes, which this exceeds. The + * message-size-1500 BDD scenarios are tag-gated off at runtime by the + * tunable-driven check in Bdd/features/environment.py. */ +#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 + +/* The target creates two FreeRtosMutex instances: `bufferMutex` (gating the + * CircularBuffer producers against the Service-task drain) and + * `lifecycleMutex` (serialising SolidSyslog_Service against teardown). The + * library default of 1 would silently fall the second Create back to + * NullMutex — Lock/Unlock would become no-ops. */ +#define SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE 2U + +#endif /* SOLIDSYSLOG_USER_TUNABLES_H */ diff --git a/CMakePresets.json b/CMakePresets.json index 08b8c986..fa092218 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -119,13 +119,14 @@ }, { "name": "freertos-cross-lwip", - "displayName": "FreeRTOS+lwIP cross link-probe (ARM Cortex-M3, mps2-an385)", + "displayName": "FreeRTOS+lwIP cross BDD target (ARM Cortex-M3, mps2-an385)", "binaryDir": "${sourceDir}/build/${presetName}", "toolchainFile": "${sourceDir}/Bdd/Targets/FreeRtos/cmake/arm-none-eabi.cmake", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", - "SOLIDSYSLOG_FREERTOS_NET": "LWIP" + "SOLIDSYSLOG_FREERTOS_NET": "LWIP", + "SOLIDSYSLOG_USER_TUNABLES_FILE": "${sourceDir}/Bdd/Targets/FreeRtosLwip/solidsyslog_user_tunables.h" } } ], From ba1b5394ca01d84fa1eb675d9243c4926d3a532a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 15:37:34 +0000 Subject: [PATCH 2/6] ci: S28.09 advisory bdd-freertos-qemu-lwip lane + docker-compose pair Add the syslog-ng-freertos-lwip / behave-freertos-lwip compose pair (mirrors the +TCP pair, points at the lwIP ELF, UDP-only tag filter with a @freertoslwipwip escape hatch) and the advisory bdd-freertos-qemu-lwip CI job (not in summary.needs). Grow build-freertos-target-lwip to upload the BDD tunables mirror; refresh the link-probe wording to BDD target across ci.yml, the top-level CMake status, and the README. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 76 +++++++++++++++++++++++++++--- Bdd/Targets/FreeRtosLwip/README.md | 61 ++++++++++++++++-------- CMakeLists.txt | 13 +++-- ci/docker-compose.bdd.yml | 60 +++++++++++++++++++++++ 4 files changed, 178 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 701e3d20..78471e8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1037,20 +1037,20 @@ jobs: - name: Configure (ARM cross, NET=LWIP) run: cmake --preset freertos-cross-lwip - - name: Cross-build FreeRTOS+lwIP link-probe ELF + - name: Cross-build FreeRTOS+lwIP BDD target ELF run: cmake --build --preset freertos-cross-lwip --target SolidSyslogBddTargetLwip - name: Assert no PlusTcp / Plus-TCP symbols linked run: | elf=build/freertos-cross-lwip/Bdd/Targets/FreeRtosLwip/SolidSyslogBddTargetLwip.elf - arm-none-eabi-nm "$elf" > /tmp/lwip-probe-symbols.txt - if grep -E -i 'PlusTcp|FreeRTOS_socket|FreeRTOS_sendto|FreeRTOS_recv|FreeRTOS_IPInit' /tmp/lwip-probe-symbols.txt; then - echo "::error::PlusTcp / FreeRTOS-Plus-TCP symbol linked into the lwIP probe ELF" + arm-none-eabi-nm "$elf" > /tmp/lwip-symbols.txt + if grep -E -i 'PlusTcp|FreeRTOS_socket|FreeRTOS_sendto|FreeRTOS_recv|FreeRTOS_IPInit' /tmp/lwip-symbols.txt; then + echo "::error::PlusTcp / FreeRTOS-Plus-TCP symbol linked into the lwIP BDD target ELF" exit 1 fi - echo "OK: no PlusTcp / FreeRTOS-Plus-TCP symbols in the lwIP probe ELF" + echo "OK: no PlusTcp / FreeRTOS-Plus-TCP symbols in the lwIP BDD target ELF" - - name: Upload FreeRTOS+lwIP link-probe ELF + - name: Upload FreeRTOS+lwIP BDD target ELF uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: solid-syslog-bdd-target-freertos-lwip @@ -1058,6 +1058,16 @@ jobs: retention-days: 1 compression-level: 0 + # BDD-side mirror of build-time tunables (configure_file). The lwIP bdd + # lane consumes this so environment.py's runtime tag gates track the lwIP + # target's SOLIDSYSLOG_MAX_MESSAGE_SIZE (512, same as +TCP). + - name: Upload BDD Python tunables (FreeRTOS+lwIP) + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: bdd-tunables-freertos-lwip + path: Bdd/features/steps/solidsyslog_tunables.py + retention-days: 1 + bdd-freertos-qemu-plustcp: needs: build-freertos-target-plustcp runs-on: ubuntu-latest @@ -1110,6 +1120,60 @@ jobs: if: failure() run: docker compose -f ci/docker-compose.bdd.yml logs --no-color + # Advisory (S28.09): not in summary.needs, so a failure does not block + # merges while the lwIP netif beds in. S28.11 promotes it to required. + bdd-freertos-qemu-lwip: + needs: build-freertos-target-lwip + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: solid-syslog-bdd-target-freertos-lwip + path: build/freertos-cross-lwip/Bdd/Targets/FreeRtosLwip/ + + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: bdd-tunables-freertos-lwip + path: Bdd/features/steps/ + + - name: Make ELF readable + run: chmod +r build/freertos-cross-lwip/Bdd/Targets/FreeRtosLwip/SolidSyslogBddTargetLwip.elf + + - name: Prepare BDD output directory + run: mkdir -p Bdd/junit && chmod 777 Bdd/junit + + - name: Run BDD tests + run: > + docker compose -f ci/docker-compose.bdd.yml + up --abort-on-container-exit --exit-code-from behave-freertos-lwip + behave-freertos-lwip syslog-ng-freertos-lwip + + - name: BDD Test Report (FreeRTOS+lwIP) + uses: dorny/test-reporter@v3 + if: success() || failure() + with: + name: Test Results (BDD FreeRTOS+lwIP) + path: Bdd/junit/TESTS-*.xml + reporter: java-junit + + - name: Upload JUnit XML + if: success() || failure() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: junit-bdd-freertos-qemu-lwip + path: Bdd/junit/TESTS-*.xml + retention-days: 1 + + - name: Compose logs on failure + if: failure() + run: docker compose -f ci/docker-compose.bdd.yml logs --no-color + summary: if: always() && github.event_name == 'pull_request' needs: [build-linux-gcc, build-linux-tunable-override, build-linux-clang, sanitize-linux-gcc, coverage-linux-gcc, analyze-tidy, analyze-tidy-freertos-plustcp, analyze-cppcheck, analyze-format, analyze-iwyu, analyze-iwyu-freertos-plustcp, bdd-linux-syslog-ng, build-windows-msvc, bdd-windows-otel, integration-linux-openssl, integration-linux-mbedtls, integration-windows-openssl, build-freertos-host-tdd-plustcp, build-freertos-target-plustcp, bdd-freertos-qemu-plustcp] diff --git a/Bdd/Targets/FreeRtosLwip/README.md b/Bdd/Targets/FreeRtosLwip/README.md index c686f9de..4e66d3c0 100644 --- a/Bdd/Targets/FreeRtosLwip/README.md +++ b/Bdd/Targets/FreeRtosLwip/README.md @@ -1,25 +1,40 @@ -# FreeRTOS + lwIP (Raw API) link-probe target +# FreeRTOS + lwIP (Raw API) BDD target -`SolidSyslogBddTargetLwip` is a **cross-build link probe**, not a runnable BDD -target. It exists to prove — cheaply, in CI — that the `Platform/LwipRaw/` -adapter tree (`Address`, `Resolver`, `Datagram`, `TcpStream`, `Marshal`) -cross-compiles and links for a Cortex-M3 FreeRTOS target against lwIP core, -**with zero dependency on `Platform/PlusTcp/` or FreeRTOS-Plus-TCP** (S28.07, -epic E28). +`SolidSyslogBddTargetLwip` is a runnable FreeRTOS-on-lwIP BDD target for QEMU +`mps2-an385` (Cortex-M3). It proves the `Platform/LwipRaw/` adapter tree +(`Address`, `Resolver`, `Datagram`, `TcpStream`, `Marshal`) end-to-end against +lwIP core under `NO_SYS=0`, **with zero dependency on `Platform/PlusTcp/` or +FreeRTOS-Plus-TCP** (S28.09, epic E28). It is the worked counterpart to the +FreeRTOS-Plus-TCP target in [`../FreeRtos/`](../FreeRtos/) — same interactive / +service-task shape, Common code, startup, linker, and CMSDK UART, with the +network backend swapped PlusTcp → LwipRaw. It is selected by `SOLIDSYSLOG_FREERTOS_NET=LWIP` (see the top-level -`CMakeLists.txt`), built by the `freertos-cross-lwip` preset and the advisory -`build-freertos-target-lwip` CI lane. There is no LAN9118 netif driver and no -QEMU run: `main.c` starts the scheduler and a single probe task that -`_Create`/`_Destroy`s each adapter, so the linker must resolve every adapter -entry point and the lwIP core symbols behind it. - -`lwipopts.h` pins `NO_SYS=1` (the LwipRaw default direct-call marshal is -correct for a single lwIP-owning context). The worked `NO_SYS=0` runtime — a -real netif, `tcpip_init`, the `tcpip_callback` marshal (S28.06), and the UDP -BDD feature green on QEMU — lands with **S28.09**, which reuses this -directory's `lwipopts.h`, `FreeRTOSConfig.h`, and `arch/cc.h` and grows -`main.c` into the full integration. +`CMakeLists.txt`), built by the `freertos-cross-lwip` preset, and run on QEMU by +the advisory `bdd-freertos-qemu-lwip` CI lane against the shared syslog-ng +oracle. + +## What it does + +- `main.c`: installs the `tcpip_callback` marshal + (`SolidSyslogLwipRaw_SetMarshal`, S28.06), calls `tcpip_init`, then brings the + netif up on the tcpip thread (`netif_add` + static IP `10.0.2.15` + link-up). + Each `send N` over the UART emits N RFC 5424 datagrams to + `{10.0.2.2, 5514}` via `SolidSyslogUdpSender` over `SolidSyslogLwipRawDatagram`. +- `netif/EthernetIf.c`: a hand-written lwIP `netif` driver over the vendored Arm + LAN9118 (SMSC9220) low-level driver. RX is driven by the IRQ-13 `EthernetISR` + through a task notification; TX sends pbufs via `smsc9220_send_by_chunks`. +- `netif/smsc9220_eth_drv.{c,h}`, `netif/smsc9220_emac_config.h`: the Arm + low-level driver, vendored verbatim from FreeRTOS-Plus-TCP's MPS2_AN385 + network interface (Apache-2.0; copyright and license headers preserved). + +## Scope + +UDP for S28.09. The `SwitchingSender` keeps TCP / TLS slots wired to the shared +`NullSender` so `set transport tcp|tls` resolves cleanly (drops on the floor) +rather than crashing; the real LwipRaw TCP / TLS senders land in later E28 +stories. `lwipopts.h` runs `NO_SYS=0` (tcpip thread + the contrib FreeRTOS +`sys_arch`); `FreeRTOSConfig.h` mirrors the +TCP networking config. ## Build @@ -30,3 +45,11 @@ cmake --build --preset freertos-cross-lwip --target SolidSyslogBddTargetLwip Requires the `cpputest-freertos-cross` container (arm-none-eabi toolchain, `FREERTOS_KERNEL_PATH=/opt/freertos/kernel`, `LWIP_PATH=/opt/lwip`). + +## Run on QEMU + +```sh +docker compose -f ci/docker-compose.bdd.yml up \ + --abort-on-container-exit --exit-code-from behave-freertos-lwip \ + behave-freertos-lwip syslog-ng-freertos-lwip +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 2307d5f4..7ffc226b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,15 +168,14 @@ if(NOT SOLIDSYSLOG_FREERTOS_NET STREQUAL "PLUSTCP" endif() if(SOLIDSYSLOG_FREERTOS_NET STREQUAL "LWIP") message(STATUS - "SOLIDSYSLOG_FREERTOS_NET=LWIP: cross builds the FreeRtosLwip " - "link-probe (Address+Resolver+Datagram+TcpStream+Marshal against " - "lwIP core); netif + QEMU BDD arrive in S28.09.") + "SOLIDSYSLOG_FREERTOS_NET=LWIP: cross builds the FreeRtosLwip BDD " + "target (LAN9118 netif + NO_SYS=0 tcpip + LwipRaw adapters), run on " + "QEMU mps2-an385 by the advisory bdd-freertos-qemu-lwip lane (S28.09).") endif() if(SOLIDSYSLOG_FREERTOS_NET STREQUAL "BOTH") message(STATUS "SOLIDSYSLOG_FREERTOS_NET=BOTH: cross builds the PlusTcp BDD target; " - "the lwIP backend cross-builds in isolation under NET=LWIP " - "(S28.07) — netif + QEMU BDD arrive in S28.09.") + "the lwIP backend cross-builds in isolation under NET=LWIP.") endif() # Build-time tunables (E21: Port-Time Configurability). Integrators override @@ -274,8 +273,8 @@ endif() # toolchain file (see Bdd/Targets/FreeRtos/cmake/arm-none-eabi.cmake); it # only enters the build when a freertos-cross preset is active. The chosen # networking backend picks which target: PLUSTCP/BOTH builds the full -# FreeRTOS-Plus-TCP BDD target; LWIP builds the FreeRtosLwip link-probe -# (S28.07) — a stub proving Platform/LwipRaw cross-links with no PlusTcp. +# FreeRTOS-Plus-TCP BDD target; LWIP builds the FreeRtosLwip BDD target +# (S28.09) — LAN9118 netif + NO_SYS=0 lwIP + LwipRaw adapters, no PlusTcp. if(CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") if(SOLIDSYSLOG_FREERTOS_NET STREQUAL "LWIP") add_subdirectory(Bdd/Targets/FreeRtosLwip) diff --git a/ci/docker-compose.bdd.yml b/ci/docker-compose.bdd.yml index 8e0cfd4e..a59c9696 100644 --- a/ci/docker-compose.bdd.yml +++ b/ci/docker-compose.bdd.yml @@ -123,12 +123,72 @@ services: # solidsyslog_user_tunables.h changes. command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and not @windows_wip and (@udp or @tcp or @tls or @mtls)' Bdd/features/ + # FreeRTOS + lwIP pair (S28.09). Same shape as the freertos (+TCP) pair — + # its own syslog-ng on its own bridge network, behave sharing the netns so + # QEMU's slirp gateway 10.0.2.2 NATs to the pair's loopback where syslog-ng + # listens on 0.0.0.0:5514. The only differences are EXAMPLE_BINARY (the lwIP + # ELF) and the tag filter: this advisory lane runs UDP only for S28.09 — + # TCP / TLS / mTLS / store land in later E28 stories, and @freertoslwipwip + # is the per-scenario escape hatch while the netif is bedding in. + syslog-ng-freertos-lwip: + image: balabit/syslog-ng:4.8.2 + volumes: + - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf + - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro + - bdd-output-freertos-lwip:/var/log/syslog-ng + - syslog-ng-ctl-freertos-lwip:/var/lib/syslog-ng + entrypoint: ["bash", "-c"] + command: + - | + syslog-ng -F & + pid=$$! + deadline=$$((SECONDS + 30)) + while [ ! -S /var/lib/syslog-ng/syslog-ng.ctl ]; do + kill -0 "$$pid" 2>/dev/null || { echo "syslog-ng exited unexpectedly"; exit 1; } + [ "$$SECONDS" -ge "$$deadline" ] && { echo "Timed out waiting for syslog-ng control socket"; kill "$$pid" 2>/dev/null; exit 1; } + sleep 0.1 + done + chmod 0777 /var/lib/syslog-ng/syslog-ng.ctl + wait + networks: + freertos-lwip: + aliases: + - syslog-ng + healthcheck: + test: ["CMD-SHELL", "test -S /var/lib/syslog-ng/syslog-ng.ctl"] + interval: 2s + timeout: 5s + retries: 10 + + behave-freertos-lwip: + # Cross image carries qemu-system-arm + python3 + behave. + image: ghcr.io/davidcozens/cpputest-freertos-cross:sha-a0c1c0a + user: "0" + volumes: + - ..:/workspaces/SolidSyslog + - bdd-output-freertos-lwip:/workspaces/SolidSyslog/Bdd/output + - syslog-ng-ctl-freertos-lwip:/var/lib/syslog-ng + working_dir: /workspaces/SolidSyslog + network_mode: service:syslog-ng-freertos-lwip + depends_on: + syslog-ng-freertos-lwip: + condition: service_healthy + environment: + # The QEMU launch + UART `set` protocol are target-agnostic, so the lwIP + # ELF rides the existing freertos driver — only the binary path differs. + - BDD_TARGET=freertos + - EXAMPLE_BINARY=build/freertos-cross-lwip/Bdd/Targets/FreeRtosLwip/SolidSyslogBddTargetLwip.elf + command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @freertoslwipwip and not @rtc and not @windows_wip and @udp and not @tcp and not @tls and not @mtls and not @store' Bdd/features/ + volumes: bdd-output-linux: bdd-output-freertos: + bdd-output-freertos-lwip: syslog-ng-ctl-linux: syslog-ng-ctl-freertos: + syslog-ng-ctl-freertos-lwip: networks: linux: freertos: + freertos-lwip: From 96dfb66e7843868a03c55cadfa8e8349f75edb55 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 16:07:42 +0000 Subject: [PATCH 3/6] fix: S28.09 warm up gateway ARP; isolate vendored driver for clang-format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LwipRaw datagram sends PBUF_REF packets pointing at a transient buffer, so a first send that hit an ARP miss had its queued copy reference freed memory and was dropped (confirmed on the wire: seqId=1 lost, seqId=2 delivered). Warm up the gateway ARP at bring-up (etharp_request) and block the interactive task — never the tcpip thread — until the cache populates before the pipeline goes live. Verified: both seqId=1 and seqId=2 now reach the wire. Also move the vendored Arm smsc9220 driver into netif/smsc9220/ behind a DisableFormat .clang-format so the analyze-format lane does not reflow third-party code, and clang-format the hand-written netif / main / config files. Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/FreeRtosLwip/CMakeLists.txt | 7 ++- Bdd/Targets/FreeRtosLwip/main.c | 63 ++++++++++++++++--- Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c | 8 ++- .../FreeRtosLwip/netif/smsc9220/.clang-format | 5 ++ .../{ => smsc9220}/smsc9220_emac_config.h | 0 .../netif/{ => smsc9220}/smsc9220_eth_drv.c | 0 .../netif/{ => smsc9220}/smsc9220_eth_drv.h | 0 7 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 Bdd/Targets/FreeRtosLwip/netif/smsc9220/.clang-format rename Bdd/Targets/FreeRtosLwip/netif/{ => smsc9220}/smsc9220_emac_config.h (100%) rename Bdd/Targets/FreeRtosLwip/netif/{ => smsc9220}/smsc9220_eth_drv.c (100%) rename Bdd/Targets/FreeRtosLwip/netif/{ => smsc9220}/smsc9220_eth_drv.h (100%) diff --git a/Bdd/Targets/FreeRtosLwip/CMakeLists.txt b/Bdd/Targets/FreeRtosLwip/CMakeLists.txt index 3e697091..0bf56b11 100644 --- a/Bdd/Targets/FreeRtosLwip/CMakeLists.txt +++ b/Bdd/Targets/FreeRtosLwip/CMakeLists.txt @@ -70,7 +70,7 @@ add_library(solid_syslog_freertos_lwip_upstream OBJECT # Vendored Arm LAN9118 (SMSC9220) low-level driver (Apache-2.0). The lwIP # netif wrapper over it (netif/EthernetIf.c) stays in the executable under # the strict bar; this third-party source sits under the relaxations. - ${CMAKE_CURRENT_SOURCE_DIR}/netif/smsc9220_eth_drv.c + ${CMAKE_CURRENT_SOURCE_DIR}/netif/smsc9220/smsc9220_eth_drv.c ) target_compile_options(solid_syslog_freertos_lwip_upstream PRIVATE @@ -95,7 +95,7 @@ target_compile_options(solid_syslog_freertos_lwip_upstream PRIVATE target_include_directories(solid_syslog_freertos_lwip_upstream PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # FreeRTOSConfig.h, lwipopts.h, arch/cc.h - ${CMAKE_CURRENT_SOURCE_DIR}/netif # smsc9220_eth_drv.h, smsc9220_emac_config.h + ${CMAKE_CURRENT_SOURCE_DIR}/netif/smsc9220 # smsc9220_eth_drv.h, smsc9220_emac_config.h ${FREERTOS_KERNEL_PATH}/include ${FREERTOS_PORT_DIR} # portmacro.h ${LWIP_DIR}/src/include @@ -149,7 +149,8 @@ target_compile_options(SolidSyslogBddTargetLwip PRIVATE target_include_directories(SolidSyslogBddTargetLwip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # FreeRTOSConfig.h, lwipopts.h, arch/cc.h - ${CMAKE_CURRENT_SOURCE_DIR}/netif # EthernetIf.h, smsc9220_eth_drv.h + ${CMAKE_CURRENT_SOURCE_DIR}/netif # EthernetIf.h + ${CMAKE_CURRENT_SOURCE_DIR}/netif/smsc9220 # smsc9220_eth_drv.h, smsc9220_emac_config.h ${FREERTOS_COMMON_DIR} # CmsdkUart.h ${CMAKE_SOURCE_DIR}/Core/Interface # SolidSyslog*.h ${CMAKE_SOURCE_DIR}/Core/Source # internal headers consumed by adapters diff --git a/Bdd/Targets/FreeRtosLwip/main.c b/Bdd/Targets/FreeRtosLwip/main.c index eebddc78..95f31c29 100644 --- a/Bdd/Targets/FreeRtosLwip/main.c +++ b/Bdd/Targets/FreeRtosLwip/main.c @@ -55,6 +55,7 @@ #include "SolidSyslogTunables.h" #include "SolidSyslogUdpSender.h" +#include "lwip/etharp.h" #include "lwip/init.h" #include "lwip/ip4_addr.h" #include "lwip/netif.h" @@ -98,6 +99,9 @@ static struct SolidSyslogMessage testMessage = { /* lwIP netif descriptor — must outlive the tcpip thread. */ static struct netif networkInterface; +/* Gateway IP, kept at file scope so the ARP warm-up (which resolves it before + * the first datagram) can reach it after bring-up. */ +static ip4_addr_t gatewayAddress; /* CircularBuffer + FreeRtosMutex for cross-task emission. 8 max-sized messages * is comfortably above the 3-message BDD scenarios. */ @@ -137,6 +141,8 @@ static void InteractiveTask(void* argument); static void ServiceTask(void* argument); static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context); static void NetworkBringUp(void* context); +static void WarmUpGatewayArp(void); +static void GatewayResolvedQuery(void* context); static bool TryUpdateString(char* storage, size_t storageSize, const char* value); static bool TryParseUInt(const char* value, unsigned long* out); static void TeardownAll(void); @@ -196,9 +202,8 @@ int main(void) tcpip_init(NULL, NULL); (void) xTaskCreate(InteractiveTask, "interactive", INTERACTIVE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, NULL); - (void) xTaskCreate( - ServiceTask, "service", SERVICE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, &serviceTaskHandle - ); + (void + ) xTaskCreate(ServiceTask, "service", SERVICE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, &serviceTaskHandle); vTaskStartScheduler(); @@ -216,17 +221,56 @@ static void NetworkBringUp(void* context) (void) context; ip4_addr_t ipAddress; ip4_addr_t netmask; - ip4_addr_t gateway; /* QEMU slirp default: 10.0.2.15 guest, 10.0.2.2 gateway (NATed to the * QEMU host, where the syslog-ng oracle listens). */ IP4_ADDR(&ipAddress, 10, 0, 2, 15); IP4_ADDR(&netmask, 255, 255, 255, 0); - IP4_ADDR(&gateway, 10, 0, 2, 2); + IP4_ADDR(&gatewayAddress, 10, 0, 2, 2); - (void) netif_add(&networkInterface, &ipAddress, &netmask, &gateway, NULL, EthernetIf_Init, tcpip_input); + (void) netif_add(&networkInterface, &ipAddress, &netmask, &gatewayAddress, NULL, EthernetIf_Init, tcpip_input); netif_set_default(&networkInterface); netif_set_up(&networkInterface); netif_set_link_up(&networkInterface); + + /* Kick off ARP resolution for the gateway now, so the cache is warm before + * the first datagram. SolidSyslogLwipRawDatagram sends PBUF_REF packets + * pointing at a transient buffer; if the first send hit an ARP miss the + * queued copy would reference freed memory and be lost (the documented + * first-packet drop). The reply is processed by this tcpip thread; the + * interactive task waits for the cache to populate via WarmUpGatewayArp. */ + (void) etharp_request(&networkInterface, &gatewayAddress); +} + +/* Marshalled onto the tcpip thread: report whether the gateway's MAC is in the + * ARP cache yet. The bool* context is set to the result. */ +static void GatewayResolvedQuery(void* context) +{ + struct eth_addr* ethRet = NULL; + const ip4_addr_t* ipRet = NULL; + *(bool*) context = (etharp_find_addr(&networkInterface, &gatewayAddress, ðRet, &ipRet) >= 0); +} + +/* Blocks the calling (interactive) task — never the tcpip thread, which must + * stay free to process the ARP reply — until the gateway resolves or a bounded + * deadline passes. Generous deadline: QEMU is markedly slower than host. */ +static void WarmUpGatewayArp(void) +{ + enum + { + WARM_UP_ATTEMPTS = 60, + WARM_UP_INTERVAL_MS = 50 + }; + + for (int attempt = 0; attempt < WARM_UP_ATTEMPTS; attempt++) + { + bool resolved = false; + (void) tcpip_callback(GatewayResolvedQuery, &resolved); + if (resolved) + { + break; + } + vTaskDelay(pdMS_TO_TICKS(WARM_UP_INTERVAL_MS)); + } } static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) @@ -411,6 +455,10 @@ static void InteractiveTask(void* argument) * Blocking callback — returns once the interface is added and link/up. */ (void) tcpip_callback(NetworkBringUp, NULL); + /* Wait for the gateway ARP to resolve before any datagram goes out, so the + * first send is not lost to a cache miss (see WarmUpGatewayArp). */ + WarmUpGatewayArp(); + resolver = SolidSyslogLwipRawResolver_Create(); datagram = SolidSyslogLwipRawDatagram_Create(); udpAddress = SolidSyslogLwipRawAddress_Create(); @@ -483,7 +531,8 @@ static void InteractiveTask(void* argument) const UBaseType_t interactiveHwm = uxTaskGetStackHighWaterMark(NULL); const UBaseType_t serviceHwm = (serviceTaskHandle != NULL) ? uxTaskGetStackHighWaterMark(serviceTaskHandle) : 0U; (void) printf( - "[stack-hwm] interactive=%lu words service=%lu words\n", (unsigned long) interactiveHwm, + "[stack-hwm] interactive=%lu words service=%lu words\n", + (unsigned long) interactiveHwm, (unsigned long) serviceHwm ); diff --git a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c index 117d49b7..611b14a4 100644 --- a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c +++ b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c @@ -2,7 +2,7 @@ * controller, FreeRTOS + lwIP NO_SYS=0. * * Bridges lwIP's struct netif to the vendored Arm smsc9220_eth_drv (see - * netif/smsc9220_eth_drv.c, Apache-2.0). The shape is adapted from the lwIP + * netif/smsc9220/, Apache-2.0). The shape is adapted from the lwIP * contrib ethernetif.c skeleton (BSD, Adam Dunkels) and the FreeRTOS-Plus-TCP * MPS2_AN385 NetworkInterface.c (MIT) RX/IRQ pattern: * - low-level output sends a (possibly chained) pbuf via @@ -102,7 +102,11 @@ static void EthernetIf_LowLevelInit(struct netif* netif) /* The ISR notifies this task, so it must exist before the IRQ is enabled. */ (void) xTaskCreate( - EthernetIf_RxTask, "ethrx", ETHERNETIF_RX_TASK_STACK_DEPTH, NULL, ETHERNETIF_RX_TASK_PRIORITY, + EthernetIf_RxTask, + "ethrx", + ETHERNETIF_RX_TASK_STACK_DEPTH, + NULL, + ETHERNETIF_RX_TASK_PRIORITY, &EthernetIf_RxTaskHandle ); diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220/.clang-format b/Bdd/Targets/FreeRtosLwip/netif/smsc9220/.clang-format new file mode 100644 index 00000000..9f525c39 --- /dev/null +++ b/Bdd/Targets/FreeRtosLwip/netif/smsc9220/.clang-format @@ -0,0 +1,5 @@ +# Vendored third-party code (Arm SMSC9220 LAN9118 driver, Apache-2.0). Keep it +# byte-for-byte as upstream ships it — DisableFormat makes clang-format a no-op +# here so the analyze-format lane does not try to reflow it to project style. +DisableFormat: true +SortIncludes: Never diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h b/Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_emac_config.h similarity index 100% rename from Bdd/Targets/FreeRtosLwip/netif/smsc9220_emac_config.h rename to Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_emac_config.h diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c b/Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_eth_drv.c similarity index 100% rename from Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.c rename to Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_eth_drv.c diff --git a/Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h b/Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_eth_drv.h similarity index 100% rename from Bdd/Targets/FreeRtosLwip/netif/smsc9220_eth_drv.h rename to Bdd/Targets/FreeRtosLwip/netif/smsc9220/smsc9220_eth_drv.h From a456b2a0b8444daad0092400935f8911e9b14f19 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 17:17:05 +0100 Subject: [PATCH 4/6] =?UTF-8?q?docs:=20S28.09=20DEVLOG=20=E2=80=94=20FreeR?= =?UTF-8?q?tosLwip=20netif=20+=20UDP=20oracle=20parity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- DEVLOG.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 49018ad6..1b91aeff 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -12957,3 +12957,71 @@ MISRA rule — different category, doesn't set precedent. every untriaged style finding becomes a hard gate. Worth E24 discussion. +## 2026-05-29 — S28.09 FreeRtosLwip LAN9118 netif + NO_SYS=0 UDP on QEMU + +### Decisions + +- **First-packet ARP drop was a PBUF_REF lifetime bug, fixed with a + bring-up ARP warm-up, not by switching to PBUF_RAM.** `LwipRawDatagram` + sends zero-copy (PBUF_REF over the integrator's transient marshal + buffer). When the first datagram hit an ARP cache miss, lwIP queued + the pbuf for post-resolution transmission — but the referenced buffer + was freed the moment `_SendTo` returned, so the queued seqId=1 went + out as garbage / was dropped while seqId=2 (cache now warm) delivered. + Rather than pay a copy on every send, `main.c`'s `WarmUpGatewayArp` + issues `etharp_request(gw)` at netif bring-up and polls + `etharp_find_addr` (marshalled off the tcpip thread) until the gateway + is resolved before the logging pipeline goes live. pcap now shows + gratuitous ARP → gw ARP request → reply → UDP seqId=1 → seqId=2 in + order. This keeps the zero-copy contract intact and pushes the + one-time resolution cost to setup. + +- **Vendored Arm smsc9220 LAN9118 driver isolated from clang-format.** + `Bdd/Targets/FreeRtosLwip/netif/smsc9220/` carries its Apache-2.0 + upstream headers and a `DisableFormat: true` `.clang-format` so the + `analyze-format` lane won't reflow third-party source. The + hand-written netif glue (`netif/EthernetIf.c`) is ours and stays + under the project style. + +- **NO_SYS=0 with the tcpip thread; lwIP calls marshalled via S28.06's + `_SetMarshal`.** `lwipopts.h` runs the full tcpip thread + contrib + `sys_arch.c`. `main` calls `tcpip_init` pre-scheduler; netif bring-up + runs from the interactive task via `tcpip_callback` because + `smsc9220_init` calls `vTaskDelay` and must run post-scheduler. The + Datagram/TcpStream adapters route through a + `tcpip_callback_with_block` marshal shim installed with + `SolidSyslogLwipRaw_SetMarshal`. + +- **Oracle parity confirms the UDP path.** Slice 4 ran the + `behave-freertos-lwip` + `syslog-ng-freertos-lwip` compose pair in + WSL (no docker-in-docker in the freertos-target container). Result: + 8 features / 25 scenarios / 96 steps passed, 0 failed — a strict + subset of the +TCP lane's scenarios under the same library config, + so parity was expected and observed. Delivery truth comes from the + syslog-ng oracle's "receives a message with priority/hostname/PROCID" + assertions, not an ad-hoc host listener (slirp doesn't reliably + deliver the NATed datagram to a host-loopback listener even though + it's correct on the wire). + +- **Advisory CI lane only this story.** `bdd-freertos-qemu-lwip` runs + UDP-only (`@udp and not @tcp/@tls/@mtls/@store`) with + `@freertoslwipwip` as the per-scenario escape hatch, and is NOT in + `summary.needs` — promotion to a required gate is S28.11's job. + +### Deferred + +- **Slice 6 (LwipRaw TCP into the SwitchingSender)** — left for S28.10. + S28.09 is scoped to the netif + UDP; the SwitchingSender's TCP/TLS + slots stay wired to `NullSender` here. Pulling TCP in would be scope + creep across a story boundary. + +- **`build-freertos-target-lwip` → required-gate promotion** — stays + advisory until S28.11. + +### Open questions + +- Does any other lwIP zero-copy send site (a future TCP path) carry the + same transient-buffer-lifetime assumption the UDP warm-up now papers + over at the ARP layer, or is gateway warm-up sufficient because + established TCP connections never re-resolve mid-stream? + From 32bb0c19c932fd6f34fe4b040714f39a6a26d20a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 18:19:39 +0100 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20S28.09=20address=20PR=20#476=20revie?= =?UTF-8?q?w=20=E2=80=94=20task-create=20guards,=20endpoint=20version,=20R?= =?UTF-8?q?X=20drain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the actionable, in-scope findings from the CodeRabbit review of #476, matching the established Bdd/Targets/FreeRtos (+TCP) sibling where it diverged: - main.c: check both xTaskCreate results and SemihostingExit(1) on failure rather than entering vTaskStartScheduler with a missing task (sibling does the same guard). - main.c: bump endpointVersion when `set host` changes the host, so a host change takes effect without also changing port (sibling already does this). - main.c: correct the LwipTcpipMarshal comment — tcpip_callback only blocks until the work is queued; synchronicity here comes from TCPIP_THREAD_PRIO being above every marshalling task. Records the LOCK_TCPIP_CORE follow-up. - EthernetIf.c: drain an oversize/corrupt RX frame from the FIFO in word-aligned chunks instead of returning NULL, which would re-peek the same frame forever and wedge receive. - README.md / DEVLOG.md: fix the vendored driver paths (netif/smsc9220/) and the tcpip_callback wording. Oracle re-run green (8 features / 25 scenarios / 96 steps, 0 failed). Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/FreeRtosLwip/README.md | 8 +++--- Bdd/Targets/FreeRtosLwip/main.c | 29 ++++++++++++++++----- Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c | 27 +++++++++++++++++++ DEVLOG.md | 13 ++++++--- 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/Bdd/Targets/FreeRtosLwip/README.md b/Bdd/Targets/FreeRtosLwip/README.md index 4e66d3c0..82711b14 100644 --- a/Bdd/Targets/FreeRtosLwip/README.md +++ b/Bdd/Targets/FreeRtosLwip/README.md @@ -24,9 +24,11 @@ oracle. - `netif/EthernetIf.c`: a hand-written lwIP `netif` driver over the vendored Arm LAN9118 (SMSC9220) low-level driver. RX is driven by the IRQ-13 `EthernetISR` through a task notification; TX sends pbufs via `smsc9220_send_by_chunks`. -- `netif/smsc9220_eth_drv.{c,h}`, `netif/smsc9220_emac_config.h`: the Arm - low-level driver, vendored verbatim from FreeRTOS-Plus-TCP's MPS2_AN385 - network interface (Apache-2.0; copyright and license headers preserved). +- `netif/smsc9220/smsc9220_eth_drv.{c,h}`, `netif/smsc9220/smsc9220_emac_config.h`: + the Arm low-level driver, vendored verbatim from FreeRTOS-Plus-TCP's MPS2_AN385 + network interface (Apache-2.0; copyright and license headers preserved). Kept in + its own `smsc9220/` subdirectory with a `DisableFormat` `.clang-format` so + `analyze-format` leaves the third-party source untouched. ## Scope diff --git a/Bdd/Targets/FreeRtosLwip/main.c b/Bdd/Targets/FreeRtosLwip/main.c index 95f31c29..c5d83a56 100644 --- a/Bdd/Targets/FreeRtosLwip/main.c +++ b/Bdd/Targets/FreeRtosLwip/main.c @@ -201,9 +201,16 @@ int main(void) * pxCurrentTCB if run before the scheduler. */ tcpip_init(NULL, NULL); - (void) xTaskCreate(InteractiveTask, "interactive", INTERACTIVE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, NULL); - (void - ) xTaskCreate(ServiceTask, "service", SERVICE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, &serviceTaskHandle); + if (xTaskCreate(InteractiveTask, "interactive", INTERACTIVE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, NULL) != + pdPASS) + { + SemihostingExit(1); + } + if (xTaskCreate(ServiceTask, "service", SERVICE_TASK_STACK_DEPTH, NULL, tskIDLE_PRIORITY + 1, &serviceTaskHandle) != + pdPASS) + { + SemihostingExit(1); + } vTaskStartScheduler(); @@ -275,9 +282,15 @@ static void WarmUpGatewayArp(void) static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) { - /* Blocking variant: posts to the tcpip thread and waits for completion, so - * the LwipRaw wrapper can read results immediately after this returns - * (the synchronous-marshal contract in SolidSyslogLwipRawMarshal.h). */ + /* tcpip_callback posts to the tcpip thread and returns once the message is + * queued — not once the callback has run. We satisfy the synchronous-marshal + * contract (SolidSyslogLwipRawMarshal.h: results must be ready when this + * returns) by priority: TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 1) is + * strictly above every task that marshals, so the post immediately preempts + * the caller, the tcpip thread runs the callback to completion, then control + * returns here. See PR #476 review (#4) — switching to the + * LOCK_TCPIP_CORE/UNLOCK_TCPIP_CORE pair would make this synchronous + * independent of priorities. */ (void) tcpip_callback(callback, context); } @@ -368,6 +381,10 @@ static bool OnSet(const char* name, const char* value) else if (strcmp(name, "host") == 0) { handled = TryUpdateString(host, sizeof(host), value); + if (handled) + { + endpointVersion++; + } } else if (strcmp(name, "port") == 0) { diff --git a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c index 611b14a4..3e98589b 100644 --- a/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c +++ b/Bdd/Targets/FreeRtosLwip/netif/EthernetIf.c @@ -75,6 +75,7 @@ static void EthernetIf_NvicEnableIrq(void); static err_t EthernetIf_LowLevelOutput(struct netif* netif, struct pbuf* p); static void EthernetIf_RxTask(void* parameters); static struct pbuf* EthernetIf_LowLevelInput(void); +static void EthernetIf_DiscardFrame(uint32_t length); err_t EthernetIf_Init(struct netif* netif) { @@ -215,9 +216,35 @@ static struct pbuf* EthernetIf_LowLevelInput(void) LINK_STATS_INC(link.drop); } } + else if (length > sizeof(EthernetIf_RxBuffer)) + { + /* An oversize/corrupt frame must still be pulled from the RX FIFO or it + * is peeked again forever and wedges all subsequent receive. */ + EthernetIf_DiscardFrame(length); + LINK_STATS_INC(link.lenerr); + LINK_STATS_INC(link.drop); + } return p; } +/* The RX data FIFO must be drained by the frame's full word-aligned footprint + * or the read pointer desyncs. The vendored driver only reads a whole packet + * into a caller buffer, so drain a too-large frame in buffer-sized, + * word-aligned chunks (the final, possibly shorter, chunk carries the + * sub-word remainder) and discard each. */ +static void EthernetIf_DiscardFrame(uint32_t length) +{ + const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; + const uint32_t chunkCap = sizeof(EthernetIf_RxBuffer) & ~3U; + + while (length > 0U) + { + uint32_t chunk = (length > chunkCap) ? chunkCap : length; + (void) smsc9220_receive_by_chunks(dev, EthernetIf_RxBuffer, chunk); + length -= chunk; + } +} + void EthernetISR(void) { const struct smsc9220_eth_dev_t* dev = ÐERNETIF_DEV; diff --git a/DEVLOG.md b/DEVLOG.md index 1b91aeff..b9497751 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -12988,9 +12988,16 @@ MISRA rule — different category, doesn't set precedent. `sys_arch.c`. `main` calls `tcpip_init` pre-scheduler; netif bring-up runs from the interactive task via `tcpip_callback` because `smsc9220_init` calls `vTaskDelay` and must run post-scheduler. The - Datagram/TcpStream adapters route through a - `tcpip_callback_with_block` marshal shim installed with - `SolidSyslogLwipRaw_SetMarshal`. + Datagram/TcpStream adapters route through `LwipTcpipMarshal` (installed + with `SolidSyslogLwipRaw_SetMarshal`), which calls lwIP `tcpip_callback`. + Note `tcpip_callback` only blocks until the work is *queued*, not until + it runs — the synchronous-marshal contract is satisfied here by priority: + `TCPIP_THREAD_PRIO` is `configMAX_PRIORITIES - 1`, above every task that + marshals, so the post preempts the caller and the callback completes + before control returns. PR #476 review flagged that this is implicit; + the `LOCK_TCPIP_CORE`/`UNLOCK_TCPIP_CORE` pair (core-locking is already + enabled) would make it synchronous independent of priority — tracked as + a follow-up. - **Oracle parity confirms the UDP path.** Slice 4 ran the `behave-freertos-lwip` + `syslog-ng-freertos-lwip` compose pair in From be7a7c3695523ca51eb983cf8ab22d6ac3d4480d Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 29 May 2026 18:30:36 +0100 Subject: [PATCH 6/6] fix: S28.09 marshal lwIP calls under LOCK_TCPIP_CORE, not tcpip_callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #476 review #4. tcpip_callback only blocks until the work is queued, so LwipTcpipMarshal previously relied on TCPIP_THREAD_PRIO preempting every marshalling task to appear synchronous — a correct-but-implicit invariant. LWIP_TCPIP_CORE_LOCKING is enabled, so run the callback in the caller's task context under a LOCK_TCPIP_CORE/UNLOCK_TCPIP_CORE pair instead: unconditionally synchronous regardless of priority, with no per-send mailbox message. The ARP warm-up query reuses the same marshal, which also closes the stack-`resolved` race in WarmUpGatewayArp. NetworkBringUp stays on tcpip_callback (its smsc9220_init calls vTaskDelay and must not run under the held core lock). Oracle re-run green (8 features / 25 scenarios / 96 steps, 0 failed). Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/FreeRtosLwip/main.c | 25 ++++++++++++++----------- DEVLOG.md | 21 ++++++++++++--------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Bdd/Targets/FreeRtosLwip/main.c b/Bdd/Targets/FreeRtosLwip/main.c index c5d83a56..aebaffb9 100644 --- a/Bdd/Targets/FreeRtosLwip/main.c +++ b/Bdd/Targets/FreeRtosLwip/main.c @@ -271,7 +271,10 @@ static void WarmUpGatewayArp(void) for (int attempt = 0; attempt < WARM_UP_ATTEMPTS; attempt++) { bool resolved = false; - (void) tcpip_callback(GatewayResolvedQuery, &resolved); + /* Same synchronous marshal the LwipRaw adapters use: the query runs under + * the core lock and writes `resolved` before this returns, so there is no + * race between the queued callback and the next loop iteration. */ + LwipTcpipMarshal(GatewayResolvedQuery, &resolved); if (resolved) { break; @@ -282,16 +285,16 @@ static void WarmUpGatewayArp(void) static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) { - /* tcpip_callback posts to the tcpip thread and returns once the message is - * queued — not once the callback has run. We satisfy the synchronous-marshal - * contract (SolidSyslogLwipRawMarshal.h: results must be ready when this - * returns) by priority: TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 1) is - * strictly above every task that marshals, so the post immediately preempts - * the caller, the tcpip thread runs the callback to completion, then control - * returns here. See PR #476 review (#4) — switching to the - * LOCK_TCPIP_CORE/UNLOCK_TCPIP_CORE pair would make this synchronous - * independent of priorities. */ - (void) tcpip_callback(callback, context); + /* The synchronous-marshal contract (SolidSyslogLwipRawMarshal.h) requires the + * callback's results to be ready when this returns. lwIP's tcpip_callback only + * blocks until the work is *queued*, so it cannot satisfy that by itself. + * LWIP_TCPIP_CORE_LOCKING is enabled, so we run the callback in the caller's + * own task context under the core lock instead: unconditionally synchronous, + * independent of task priority, and no per-send mailbox message. The lock is + * recursive and our callbacks never re-marshal, so this cannot self-deadlock. */ + LOCK_TCPIP_CORE(); + callback(context); + UNLOCK_TCPIP_CORE(); } void vApplicationMallocFailedHook(void) diff --git a/DEVLOG.md b/DEVLOG.md index b9497751..ac43dd48 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -12989,15 +12989,18 @@ MISRA rule — different category, doesn't set precedent. runs from the interactive task via `tcpip_callback` because `smsc9220_init` calls `vTaskDelay` and must run post-scheduler. The Datagram/TcpStream adapters route through `LwipTcpipMarshal` (installed - with `SolidSyslogLwipRaw_SetMarshal`), which calls lwIP `tcpip_callback`. - Note `tcpip_callback` only blocks until the work is *queued*, not until - it runs — the synchronous-marshal contract is satisfied here by priority: - `TCPIP_THREAD_PRIO` is `configMAX_PRIORITIES - 1`, above every task that - marshals, so the post preempts the caller and the callback completes - before control returns. PR #476 review flagged that this is implicit; - the `LOCK_TCPIP_CORE`/`UNLOCK_TCPIP_CORE` pair (core-locking is already - enabled) would make it synchronous independent of priority — tracked as - a follow-up. + with `SolidSyslogLwipRaw_SetMarshal`). The synchronous-marshal contract + requires the callback's results to be ready when the marshal returns; + lwIP `tcpip_callback` only blocks until the work is *queued*, so it + cannot satisfy that on its own. `LWIP_TCPIP_CORE_LOCKING` is enabled, so + the marshal runs the callback in the caller's task context under a + `LOCK_TCPIP_CORE`/`UNLOCK_TCPIP_CORE` pair — unconditionally synchronous, + independent of task priority, no per-send mailbox message. The ARP + warm-up query reuses the same marshal, so its `resolved` flag is written + before it is read. (PR #476 review #4: an earlier revision relied on + `tcpip_callback` + `TCPIP_THREAD_PRIO` preemption, which worked but left + an implicit "marshalling tasks must be below the tcpip thread" invariant; + the core-lock pair removes it.) - **Oracle parity confirms the UDP path.** Slice 4 ran the `behave-freertos-lwip` + `syslog-ng-freertos-lwip` compose pair in