Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/stm32h563-m33mu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ jobs:
ok=0
for _ in $(seq 1 10); do
check_alive
resp="$(curl -k -s --tlsv1.3 --max-time 20 "https://${ip}/" 2>/tmp/curl_err.log || true)"
resp="$(curl -k -s --tlsv1.3 --max-time 60 "https://${ip}/" 2>/tmp/curl_err.log || true)"
if echo "${resp}" | grep -q "wolfIP Status"; then
ok=1; break
fi
Expand All @@ -287,7 +287,7 @@ jobs:
ok=0
for _ in $(seq 1 5); do
check_alive
resp="$(echo "TLS-ping" | timeout 10 openssl s_client \
resp="$(echo "TLS-ping" | timeout 60 openssl s_client \
-connect "${ip}:8443" -quiet 2>/dev/null || true)"
if echo "${resp}" | grep -q "TLS-ping"; then
ok=1; break
Expand Down
2 changes: 2 additions & 0 deletions IDE/STM32Cube/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ uint32_t wolfIP_getrandom(void)

**Alternative: Use DHCP** instead of static IP by enabling DHCP in Software Packs -> wolfIP settings.

**With FreeRTOS:** when wolfIP runs under the FreeRTOS port (`src/port/freeRTOS`), define `WOLFIP_USE_FREERTOS` so the ETH RX interrupt wakes the poll task through `wolfip_freertos_notify_from_isr()`. The ETH interrupt priority must be at or below `configMAX_SYSCALL_INTERRUPT_PRIORITY`.

### Step 4: Build and Test

1. Build the project
Expand Down
2 changes: 1 addition & 1 deletion src/port/freeRTOS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ close(fd);

- `int wolfip_freertos_socket_init(struct wolfIP *ipstack, UBaseType_t poll_task_priority, uint16_t poll_task_stack_words);`
- `int socket_last_error(void);`
- `void wolfip_freertos_notify_from_isr(void);` - call from a link driver's receive interrupt so an arriving frame is serviced at once instead of after up to `WOLFIP_FREERTOS_POLL_MAX_MS`. Does nothing before `wolfip_freertos_socket_init()`. Only call it from interrupts at or below `configMAX_SYSCALL_INTERRUPT_PRIORITY`. Each call ends the poll task's sleep, so under sustained receive load it can starve lower-priority tasks; mask the RX interrupt in the ISR and re-enable it from `ll->poll` once the RX ring is drained.
- `void wolfip_freertos_notify_from_isr(void);` - call from a link driver's receive interrupt so an arriving frame is serviced at once instead of after up to `WOLFIP_FREERTOS_POLL_MAX_MS`. Does nothing before `wolfip_freertos_socket_init()`. Only call it from interrupts at or below `configMAX_SYSCALL_INTERRUPT_PRIORITY`. Each call ends the poll task's sleep, so under sustained receive load it can starve lower-priority tasks; mask the RX interrupt in the ISR and re-enable it from `ll->poll` once the RX ring is drained. The STM32 HAL driver (`src/port/stm32_hal`) calls it from its RX interrupt when built with `WOLFIP_USE_FREERTOS`, but leaves the interrupt unmasked: the HAL IRQ handler clears the RX status only while the RX interrupt is enabled, so masking it from the RX callback can leave the interrupt asserted.
- Socket calls:
- `socket`, `bind`, `listen`, `accept`, `connect`, `close`
- `send`, `sendto`, `recv`, `recvfrom`
Expand Down
114 changes: 74 additions & 40 deletions src/port/stm32_hal/stm32_hal_eth.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include "stm32_hal_eth.h"
#include "main.h"
#include <string.h>
#ifdef WOLFIP_USE_FREERTOS
#include "bsd_socket.h"
#endif

/*
* Supported STM32 families for automatic RMII/MII configuration:
Expand Down Expand Up @@ -117,63 +120,85 @@ int stm32_hal_eth_configure_interface(void)
#endif
}

/* RX buffer for HAL ETH - must be 32-byte aligned for DMA on STM32H5/H7 */
/* RX buffers for HAL ETH - must be 32-byte aligned for DMA on STM32H5/H7.
* HAL_ETH_ReadData() hands a descriptor back to the DMA with a fresh buffer
* before it returns the frame, so one more buffer than descriptors keeps the
* buffer being copied out of the DMA's reach. This relies on the HAL detaching
* every descriptor it consumes, in ring order, which it only does while
* HAL_ETH_RxLinkCallback() keeps *pStart set. */
#define ETH_RX_BUFFER_SIZE 1536
static uint8_t rx_buffer[ETH_RX_BUFFER_SIZE] __attribute__((aligned(32)));

/* Track if we have a pending received frame */
static volatile uint8_t rx_frame_pending = 0;
static volatile uint32_t rx_frame_length = 0;

/* Flag set by RX complete interrupt - avoids blocking HAL_ETH_ReadData calls */
static volatile uint8_t rx_data_available = 0;
#define ETH_RX_BUFFER_CNT (ETH_RX_DESC_CNT + 1)
static uint8_t rx_buffers[ETH_RX_BUFFER_CNT][ETH_RX_BUFFER_SIZE]
__attribute__((aligned(32)));
static uint32_t rx_buffer_next = 0;

/* Frame returned by the last HAL_ETH_ReadData() */
static uint8_t *rx_frame_buf = NULL;
static uint32_t rx_frame_length = 0;
static uint32_t rx_frame_segs = 0;

#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
#define ETH_DCACHE_ENABLED() ((SCB->CCR & SCB_CCR_DC_Msk) != 0U)
#endif

/**
* @brief HAL ETH RX allocation callback
* Called by HAL when it needs a buffer to store received data
*/
void HAL_ETH_RxAllocateCallback(uint8_t **buff)
{
*buff = rx_buffer;
*buff = rx_buffers[rx_buffer_next];
Comment thread
aidangarske marked this conversation as resolved.
Resolved
rx_buffer_next = (rx_buffer_next + 1) % ETH_RX_BUFFER_CNT;
}

/**
* @brief HAL ETH RX link callback
* Called by HAL when a complete frame has been received
* Called by HAL for each descriptor of a received frame
*/
void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length)
{
(void)pStart;
(void)pEnd;
(void)buff;

rx_frame_length = Length;
rx_frame_pending = 1;
if (*pStart == NULL) {
*pStart = buff;
rx_frame_buf = buff;
rx_frame_length = Length;
rx_frame_segs = 1;
}
else {
rx_frame_segs++;
}
}

/**
* @brief HAL ETH RX Complete Callback
* Called by HAL from ETH IRQ handler when a frame has been received.
* This requires ETH global interrupt to be enabled in CubeMX NVIC settings.
* With the FreeRTOS port, wakes the poll task. The RX interrupt is left
* enabled: masking it here races the HAL handler, which clears RI and NIS
* only while RIE is set, and a frame landing in between leaves NIS stuck
* with nothing to clear it.
*/
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth_ptr)
{
(void)heth_ptr;
rx_data_available = 1;
#ifdef WOLFIP_USE_FREERTOS
wolfip_freertos_notify_from_isr();
#endif
}

/**
* @brief Poll for received Ethernet frames
*
* Uses hybrid approach: primarily interrupt-driven but also polls periodically
* to catch any missed interrupts. This ensures reliable reception across all
* STM32 families while avoiding the blocking behavior of polling every cycle.
* Returns one frame per call; wolfIP keeps calling until it returns 0, so
* every frame already in the RX ring is delivered in one poll cycle. A frame
* that spans several descriptors does not fit one RX buffer and is dropped.
*/
static int hal_eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
{
uint32_t frame_len = 0;
uint32_t frame_len;
uint32_t segs;
uint32_t i;
void *payload = NULL;
static uint32_t poll_count = 0;

(void)dev;

Expand All @@ -182,27 +207,30 @@ static int hal_eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
return 0;
}

poll_count++;

/* Check for data: either interrupt flagged it, or periodic poll (every 100 cycles) */
if (rx_data_available || (poll_count % 100) == 0) {
rx_data_available = 0;

if (HAL_ETH_ReadData(&heth, &payload) == HAL_OK) {
if (rx_frame_pending && rx_frame_length > 0) {
frame_len = rx_frame_length;
if (frame_len > len) {
frame_len = len;
}
memcpy(frame, rx_buffer, frame_len);
rx_frame_pending = 0;
rx_frame_length = 0;
return (int)frame_len;
}
for (i = 0; ; i++) {
if (i == ETH_RX_DESC_CNT ||
HAL_ETH_ReadData(&heth, &payload) != HAL_OK) {
return 0;
}
segs = rx_frame_segs;
rx_frame_segs = 0;
if (segs == 1) {
break;
}
}

return 0;
frame_len = rx_frame_length;
if (frame_len > len) {
frame_len = len;
}
#ifdef ETH_DCACHE_ENABLED
if (ETH_DCACHE_ENABLED()) {
SCB_InvalidateDCache_by_Addr((uint32_t *)rx_frame_buf,
(int32_t)ETH_RX_BUFFER_SIZE);
}
#endif
memcpy(frame, rx_frame_buf, frame_len);
return (int)frame_len;
}

/**
Expand All @@ -219,6 +247,12 @@ static int hal_eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
return -1;
}

#ifdef ETH_DCACHE_ENABLED
if (ETH_DCACHE_ENABLED()) {
SCB_CleanDCache_by_Addr((uint32_t *)frame, (int32_t)len);
}
#endif

/* Configure TX buffer */
tx_buffer.buffer = frame;
tx_buffer.len = len;
Expand Down
18 changes: 12 additions & 6 deletions src/port/stm32_hal/stm32_hal_eth.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@
* 1. **ETH Peripheral** (Connectivity -> ETH)
* - Mode: RMII (or MII depending on your board's PHY)
*
* 2. **NVIC Settings** (System Core -> NVIC) - CRITICAL
* - ETH global interrupt: **ENABLED** for prompt frame notification
* - Without it, frames are still detected by the periodic RX poll
* (every 100th poll) but with added latency
* 2. **NVIC Settings** (System Core -> NVIC)
* - ETH global interrupt: **ENABLED** (required by HAL_ETH_Start_IT)
* - With the FreeRTOS port, build with WOLFIP_USE_FREERTOS so the RX
* interrupt wakes the poll task; the ETH interrupt priority must then
* be at or below configMAX_SYSCALL_INTERRUPT_PRIORITY
*
* 3. **GPIO Configuration**
* - CubeMX auto-configures correct pins for NUCLEO boards
Expand All @@ -98,8 +99,13 @@
*
* ## Implementation Notes
*
* - Uses hybrid RX: interrupt-driven (HAL_ETH_Start_IT) + periodic polling
* - DMA buffers are 32-byte aligned (required for STM32H5/H7)
* - Each poll drains every frame waiting in the RX ring
* - DMA buffers are 32-byte aligned (required for STM32H5/H7), one per RX
* descriptor plus one
* - Set heth.Init.RxBuffLen to 1536: a frame longer than RxBuffLen spans
* several descriptors and is dropped
* - On Cortex-M7 with the D-cache enabled, RX buffers are invalidated and
* TX frames cleaned around each DMA transfer
* - Assumes CubeMX generates `heth` and `TxConfig` as extern globals
*/

Expand Down
6 changes: 5 additions & 1 deletion tools/scripts/test-interop-wolfguard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,12 @@ if command -v apt-get &>/dev/null; then
apt-get install -y -qq \
build-essential autoconf automake libtool pkg-config \
linux-headers-"$KERNEL_VERSION" \
linux-modules-extra-"$KERNEL_VERSION" \
iproute2 socat kmod git check tcpdump 2>&1 | tail -1
# Ubuntu's 7.0 kernels ship udp_tunnel in linux-modules and have no
# linux-modules-extra package
if apt-cache show linux-modules-extra-"$KERNEL_VERSION" >/dev/null 2>&1; then
apt-get install -y -qq linux-modules-extra-"$KERNEL_VERSION" 2>&1 | tail -1
fi
elif command -v dnf &>/dev/null; then
dnf install -y -q \
gcc make autoconf automake libtool pkgconfig \
Expand Down
Loading