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
6 changes: 6 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ jobs:
set -euo pipefail
timeout --preserve-status 5m ./build/test-ttl-expired

- name: Run FreeRTOS BSD wrapper tests
timeout-minutes: 2
run: |
set -euo pipefail
for t in build/test-freertos-*; do timeout --preserve-status 1m "$t"; done
Comment thread
Frauschi marked this conversation as resolved.

- name: Testing ICMP socket by stealing system calls in ping
timeout-minutes: 2
run: |
Expand Down
8 changes: 7 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,13 @@ Processes pending network events.
- Parameters:
- s: wolfIP instance
- now: Current timestamp
- Returns: Number of events processed
- Returns: Milliseconds until the stack next needs `wolfIP_poll()` for its own deadlines, at most `WOLFIP_POLL_MAX_WAIT_MS` (default 1000); 0 when work is still pending; negative on error. Received frames are not included: a caller that sleeps for the returned time must also wake when its link driver has a frame.

```c
typedef void (*wolfIP_wake_cb)(void *arg);
void wolfIP_set_wake_cb(struct wolfIP *s, wolfIP_wake_cb cb, void *arg);
```
Registers a callback the stack calls when a socket call (or `wolfIP_recv()` outside `wolfIP_poll()`) leaves work for the next `wolfIP_poll()`, such as a queued frame, a newly armed timer or a socket event raised again after a partial read. While a callback is set, a timer armed between polls starts at the next `wolfIP_poll()`, when its frame goes out, instead of at the time of the previous one. It is never called from inside `wolfIP_poll()`, and runs in the caller's context with whatever lock the caller holds, so it should only signal the thread that runs `wolfIP_poll()`. Pass `NULL` to unregister. Call it before other threads use the stack, or under the lock that serializes `wolfIP_poll()` and the socket calls.

```c
void wolfIP_recv(struct wolfIP *s, void *buf, uint32_t len);
Expand Down
4 changes: 2 additions & 2 deletions docs/http_server_howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ httpd_register_handler(&httpd, "/status", status_handler);

/* Main loop: the server runs entirely inside wolfIP_poll(). */
for (;;) {
uint32_t ms_next = wolfIP_poll(s, now_ms());
/* sleep up to ms_next, service other work, then loop */
int ms_next = wolfIP_poll(s, now_ms());
/* sleep up to ms_next, or until the link driver has a frame */
}
```

Expand Down
13 changes: 7 additions & 6 deletions docs/migrating_from_lwIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,7 @@ Internally, the FreeRTOS port uses:
* a poll task that calls `wolfIP_poll()`;
* callbacks from wolfIP that wake blocked tasks by giving the socket’s semaphore.

The FreeRTOS poll task locks the stack, calls `wolfIP_poll(ipstack, now_ms)`, unlocks the stack, bounds the next sleep between a minimum and maximum, converts milliseconds to ticks, and then calls `vTaskDelay()`. The default wrapper constants include `WOLFIP_FREERTOS_BSD_MAX_FDS 16`, `WOLFIP_FREERTOS_POLL_MAX_MS 20`, and `WOLFIP_FREERTOS_POLL_MIN_MS 5`.
The FreeRTOS poll task locks the stack, calls `wolfIP_poll(ipstack, now_ms)`, unlocks the stack, bounds the returned time to the next deadline between a minimum and maximum, converts milliseconds to ticks, and then waits on a semaphore for that long. The stack gives the semaphore through `wolfIP_set_wake_cb()` whenever a socket call queues work, and a link driver can give it from its RX interrupt. The default wrapper constants include `WOLFIP_FREERTOS_BSD_MAX_FDS 16`, `WOLFIP_FREERTOS_POLL_MAX_MS 5`, and `WOLFIP_FREERTOS_POLL_MIN_MS 1`.

### 10.2 Blocking semantics in the wrapper

Expand Down Expand Up @@ -1216,10 +1216,10 @@ static void wolfip_os_poll_task(void *arg)

for (;;) {
uint64_t now_ms = os_time_millis();
uint32_t next_ms;
int next_ms;

os_mutex_lock(&g_core_lock);
next_ms = (uint32_t)wolfIP_poll(ipstack, now_ms);
next_ms = wolfIP_poll(ipstack, now_ms);
os_mutex_unlock(&g_core_lock);

if (next_ms < OS_WOLFIP_POLL_MIN_MS) {
Expand All @@ -1230,7 +1230,8 @@ static void wolfip_os_poll_task(void *arg)
next_ms = OS_WOLFIP_POLL_MAX_MS;
}

os_sleep_ms(next_ms);
/* Given by the wolfIP_set_wake_cb() callback and the RX interrupt. */
os_sem_wait_timeout(&g_wake, next_ms);
}
}
```
Expand Down Expand Up @@ -1386,9 +1387,9 @@ Follow these rules in the new OS port:

### 11.5 Poll-task timing

The FreeRTOS wrapper bounds the poll delay between 5 ms and 20 ms by default. That is a reasonable starting point for an RTOS port because it prevents the poll task from spinning while still giving TCP timers, ACKs, retransmissions, and queued TX work regular progress.
The FreeRTOS wrapper sleeps until the deadline `wolfIP_poll()` returns, bounded between 1 ms and 5 ms by default, and socket calls wake it early through `wolfIP_set_wake_cb()`. The minimum prevents the poll task from spinning; the maximum bounds how long a received frame waits when the link driver has no RX interrupt.

For latency-sensitive products, reduce the maximum delay. For power-sensitive products, allow a larger maximum delay only after confirming that retransmission behavior, DNS, DHCP, and application latency still meet product requirements.
For latency-sensitive products without an RX interrupt, reduce the maximum delay, or wake the poll task from the RX interrupt. For power-sensitive products, allow a larger maximum delay only after confirming that receive latency and application behavior still meet product requirements.

---

Expand Down
13 changes: 7 additions & 6 deletions docs/migrating_from_lwIP_JP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ int close(int sockfd);
* `wolfIP_poll()` を呼び出すポールタスク;
* ソケットのセマフォを与えることでブロックされたタスクを起動する wolfIP からのコールバック。

FreeRTOS ポールタスクはスタックをロックし、`wolfIP_poll(ipstack, now_ms)` を呼び出し、スタックをアンロックし、次のスリープを最小と最大の間に制限し、ミリ秒をティックに変換し、`vTaskDelay()` を呼び出します。デフォルトのラッパー定数には `WOLFIP_FREERTOS_BSD_MAX_FDS 16`、`WOLFIP_FREERTOS_POLL_MAX_MS 20`、`WOLFIP_FREERTOS_POLL_MIN_MS 5` が含まれます。
FreeRTOS ポールタスクはスタックをロックし、`wolfIP_poll(ipstack, now_ms)` を呼び出し、スタックをアンロックし、戻り値の次の期限までの時間を最小と最大の間に制限し、ミリ秒をティックに変換し、その時間だけセマフォを待ちます。ソケット呼び出しが作業をキューに入れると、スタックは `wolfIP_set_wake_cb()` を通じてセマフォを与えます。リンクドライバは RX 割り込みからも与えることができます。デフォルトのラッパー定数には `WOLFIP_FREERTOS_BSD_MAX_FDS 16`、`WOLFIP_FREERTOS_POLL_MAX_MS 5`、`WOLFIP_FREERTOS_POLL_MIN_MS 1` が含まれます。

### 10.2 ラッパーのブロッキング動作

Expand Down Expand Up @@ -1215,10 +1215,10 @@ static void wolfip_os_poll_task(void *arg)

for (;;) {
uint64_t now_ms = os_time_millis();
uint32_t next_ms;
int next_ms;

os_mutex_lock(&g_core_lock);
next_ms = (uint32_t)wolfIP_poll(ipstack, now_ms);
next_ms = wolfIP_poll(ipstack, now_ms);
os_mutex_unlock(&g_core_lock);

if (next_ms < OS_WOLFIP_POLL_MIN_MS) {
Expand All @@ -1229,7 +1229,8 @@ static void wolfip_os_poll_task(void *arg)
next_ms = OS_WOLFIP_POLL_MAX_MS;
}

os_sleep_ms(next_ms);
/* wolfIP_set_wake_cb() のコールバックと RX 割り込みが与える */
os_sem_wait_timeout(&g_wake, next_ms);
}
}
```
Expand Down Expand Up @@ -1385,9 +1386,9 @@ int recv(int public_fd, void *buf, size_t len, int flags)

### 11.5 ポールタスクのタイミング

FreeRTOS ラッパーはデフォルトでポール遅延を 5 ms から 20 ms の間に制限します。これは RTOS ポートの合理的な出発点です。ポールタスクがスピンするのを防ぎながら、TCP タイマー、ACK、再送信、キュー済み TX 作業に定期的なプログレスを与えるためです。
FreeRTOS ラッパーは `wolfIP_poll()` が返す期限までスリープし、デフォルトではその時間を 1 ms から 5 ms の間に制限します。ソケット呼び出しは `wolfIP_set_wake_cb()` を通じてポールタスクを早期に起こします。最小値はポールタスクのスピンを防ぎ、最大値はリンクドライバに RX 割り込みがない場合に受信フレームが待つ時間を制限します。

レイテンシが重要な製品では、最大遅延を小さくしてください。省電力が重要な製品では、再送信動作、DNS、DHCP、アプリケーションレイテンシが製品要件を満たすことを確認した後にのみ、より大きな最大遅延を許可してください。
RX 割り込みのないレイテンシ重視の製品では、最大遅延を小さくするか、RX 割り込みからポールタスクを起こしてください。省電力が重要な製品では、受信レイテンシとアプリケーション動作が製品要件を満たすことを確認した後にのみ、より大きな最大遅延を許可してください。

---

Expand Down
39 changes: 26 additions & 13 deletions docs/porting_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ static int tap_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
(void)ll;
pfd.fd = tap_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 2);
ret = poll(&pfd, 1, 0);
if (ret < 0) {
perror("poll");
return -1; /* driver error */
Expand Down Expand Up @@ -730,48 +730,61 @@ socket-offload model) for contrast.
| Binary semaphore / event per socket | Sleep a task until its socket is ready. |
| Task creation | Run the poll task. |
| Millisecond clock | Provide `now_ms` to `wolfIP_poll()`. |
| Sleep/delay | Idle the poll task between cycles. |
| Timed wait on a wakeable object | Idle the poll task until the next deadline; socket calls and the RX interrupt end it early. |

That is the whole dependency list. wolfIP needs no dynamic memory, no per-socket
threads, and no timer callbacks from the OS.

### 6.2 The poll task: the heartbeat of the stack

The poll task is a forever-loop that takes the core mutex, runs one poll cycle,
releases the mutex, and sleeps for a bounded interval. `wolfIP_poll()` returns
`>= 0` on success and a negative value on error; the FreeRTOS version clamps the
sleep to a `[MIN, MAX]` window so the task neither spins nor oversleeps:
releases the mutex, and sleeps until the deadline `wolfIP_poll()` returns. The
FreeRTOS version clamps the sleep to a `[MIN, MAX]` window so the task neither
spins nor oversleeps:

```c
static void wolfip_bsd_poll_task(void *arg)
{
struct wolfIP *ipstack = (struct wolfIP *)arg;

for (;;) {
uint32_t next_ms;
int next_ms;
TickType_t delay_ticks;
uint64_t now_ms = (uint64_t)xTaskGetTickCount() * (uint64_t)portTICK_PERIOD_MS;
uint64_t now_ms = (uint64_t)xTaskGetTickCount() * 1000u / configTICK_RATE_HZ;

/* One poll cycle under the global lock so socket ops and timer
* processing see a consistent core state. */
xSemaphoreTake(g_lock, portMAX_DELAY);
next_ms = (uint32_t)wolfIP_poll(ipstack, now_ms);
next_ms = wolfIP_poll(ipstack, now_ms);
xSemaphoreGive(g_lock);

if (next_ms < WOLFIP_FREERTOS_POLL_MIN_MS) next_ms = WOLFIP_FREERTOS_POLL_MIN_MS;
if (next_ms > WOLFIP_FREERTOS_POLL_MAX_MS) next_ms = WOLFIP_FREERTOS_POLL_MAX_MS;

delay_ticks = pdMS_TO_TICKS(next_ms);
if (delay_ticks == 0) delay_ticks = 1; /* always yield at least 1 tick */
vTaskDelay(delay_ticks);
(void)xSemaphoreTake(g_wake, delay_ticks);
}
}
```

The default bounds are 5 ms minimum and 20 ms maximum. That floor stops the
task from busy-spinning; the ceiling guarantees TCP retransmit timers, delayed
ACKs, DHCP, and DNS still fire promptly. Lower the ceiling for latency, raise it
for power — but verify TCP behaviour after raising it.
`wolfIP_poll()` returns the milliseconds until its next timer, ARP retry or
other deadline. It cannot see frames the driver has not handed over yet, so the
ceiling (`WOLFIP_FREERTOS_POLL_MAX_MS`, default 5 ms) bounds how long a received
frame waits on a driver without an RX interrupt. The floor
(`WOLFIP_FREERTOS_POLL_MIN_MS`, default 1 ms, never less than one tick) stops
the task from spinning when `wolfIP_poll()` reports work still pending. Raise
the ceiling for power, but verify receive latency after raising it.

The sleep ends early when `g_wake` is given. The port registers a callback with
`wolfIP_set_wake_cb()` that gives it; the core calls it whenever a socket call
queues a frame or arms a timer, so transmits leave at once. A link driver's RX
interrupt can do the same through `wolfip_freertos_notify_from_isr()`. Such an
interrupt-driven wake has no minimum sleep: if frames arrive faster than one
poll cycle runs, the poll task never blocks and starves lower-priority tasks.
Mask the RX interrupt in the ISR and re-enable it from `ll->poll` once the RX
ring is drained. Like any FreeRTOS `...FromISR` call, the ISR must run at or
below `configMAX_SYSCALL_INTERRUPT_PRIORITY`.

### 6.3 The core mutex

Expand Down
20 changes: 12 additions & 8 deletions src/port/freeRTOS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This directory provides a FreeRTOS integration layer for wolfIP with:
## Design

1. A global lock protects wolfIP core/socket operations.
2. A poll thread/task calls `wolfIP_poll()` periodically.
2. A poll thread/task calls `wolfIP_poll()` and sleeps until the deadline it returns, bounded by `WOLFIP_FREERTOS_POLL_MIN_MS`/`WOLFIP_FREERTOS_POLL_MAX_MS`. The core wakes it early when a socket call queues work, and a link driver can wake it from its RX interrupt.
3. Blocking socket operations:
- Try the underlying non-blocking wolfIP socket call.
- If `-WOLFIP_EAGAIN`, register a callback and block on a FreeRTOS semaphore.
Expand All @@ -26,20 +26,20 @@ This gives application code standard blocking socket behavior while wolfIP remai

## Poll Task Example

The integration creates a dedicated task similar to:
The integration registers a wake callback with `wolfIP_set_wake_cb()` that gives the binary semaphore `g_wake`, and creates a dedicated task similar to:

```c
static void wolfip_poll_task(void *arg)
{
struct wolfIP *ipstack = (struct wolfIP *)arg;

for (;;) {
uint32_t next_ms;
int next_ms;
TickType_t delay_ticks;
uint64_t now_ms = (uint64_t)xTaskGetTickCount() * (uint64_t)portTICK_PERIOD_MS;
uint64_t now_ms = (uint64_t)xTaskGetTickCount() * 1000u / configTICK_RATE_HZ;

xSemaphoreTake(g_lock, portMAX_DELAY);
next_ms = (uint32_t)wolfIP_poll(ipstack, now_ms);
next_ms = wolfIP_poll(ipstack, now_ms);
xSemaphoreGive(g_lock);

if (next_ms < WOLFIP_FREERTOS_POLL_MIN_MS) {
Expand All @@ -53,11 +53,13 @@ static void wolfip_poll_task(void *arg)
if (delay_ticks == 0) {
delay_ticks = 1;
}
vTaskDelay(delay_ticks);
(void)xSemaphoreTake(g_wake, delay_ticks);
}
}
```

`WOLFIP_FREERTOS_POLL_MAX_MS` bounds how long a received frame waits when the link driver has no RX interrupt. `WOLFIP_FREERTOS_POLL_MIN_MS` keeps the task from spinning when `wolfIP_poll()` reports work still pending.

## Integration Steps

1. Include headers:
Expand Down Expand Up @@ -91,6 +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.
- Socket calls:
- `socket`, `bind`, `listen`, `accept`, `connect`, `close`
- `send`, `sendto`, `recv`, `recvfrom`
Expand All @@ -101,8 +104,9 @@ close(fd);
Defined in `bsd_socket.c`:

- `WOLFIP_FREERTOS_BSD_MAX_FDS` (default: `16`)
- `WOLFIP_FREERTOS_POLL_MIN_MS` (default: `5`)
- `WOLFIP_FREERTOS_POLL_MAX_MS` (default: `20`)
- `WOLFIP_FREERTOS_POLL_MIN_MS` (default: `1`)
- `WOLFIP_FREERTOS_POLL_MAX_MS` (default: `5`)
- `WOLFIP_BSD_DEBUG_CALLBACK` (default: `0`) - set to `1` to log socket callbacks from the poll task

Override via compiler flags, for example:

Expand Down
Loading
Loading