cyw43_arch_init() intermittently asserts under FreeRTOS SMP
Calling `cyw43_arch_init()` (`pico_cyw43_arch_lwip_sys_freertos`) from a FreeRTOS task that isn't pinned to a single core sometimes fails with:
```
assertion "get_core_num() == async_context_core_num(cyw43_async_context)" failed:
file ".../pico_cyw43_driver/cyw43_driver.c", line 65, function: cyw43_irq_init
```
SDK 2.3.0 (code unchanged on `develop`), Pico W, FreeRTOS-Kernel V11.1.0 SMP, `configNUMBER_OF_CORES=2`, `configUSE_CORE_AFFINITY=1`, `NO_SYS=0`. I first hit this in an application that initializes cyw43 at runtime to connect to Wi-Fi — cyw43_arch_init() failed sometimes, not always. To rule out anything specific to my application, I isolated it with the stress test below, which reproduces the assert on its own.
The cause seems to be that `async_context_freertos_init()` reads the current core twice (once for `core_num`, once to pick the worker task's affinity), and an unpinned caller can migrate cores in between.
```c
// src/rp2_common/pico_async_context/async_context_freertos.c (2.3.0, identical on develop)
bool async_context_freertos_init(async_context_freertos_t *self, async_context_freertos_config_t *config) {
...
self->core.core_num = get_core_num(); // (1) caller's current core captured here
...
self->task_handle = xTaskCreate[Static](...); // semaphores/timer/task created in between - any may yield/preempt
...
#if configNUMBER_OF_CORES > 1
UBaseType_t core_id = config->task_core_id;
if (core_id == (UBaseType_t)-1) {
core_id = portGET_CORE_ID(); // (2) current core read again here
}
vTaskCoreAffinitySet(self->task_handle, 1u << core_id);
#endif
```
The pico_w/wifi/freertos examples only pin in NO_SYS mode — per the comment below, sys mode is expected to be handled by cyw43_arch_freertos itself, so pinning doesn't appear to be an intended requirement (this issue is that expectation not holding):
```c
#if NO_SYS && configUSE_CORE_AFFINITY && configNUM_CORES > 1
// we must bind the main task to one core (well at least while the init is called)
// (note we only do this in NO_SYS mode, because cyw43_arch_freertos
// takes care of it otherwise)
vTaskCoreAffinitySet(task, 1);
#endif
```
A stress test (below) hits the assert within ~10 init/deinit cycles; with the caller pinned, 200+ cycles run clean.
<details>
<summary>repro code</summary>
```c
#define HOG_PRIORITY (tskIDLE_PRIORITY + 1UL)
#define REPRO_PRIORITY (tskIDLE_PRIORITY + 1UL)
#define HOG_STACK configMINIMAL_STACK_SIZE
#define REPRO_STACK (configMINIMAL_STACK_SIZE * 4)
static void hog_task(void* arg) {
(void)arg;
for (;;) {
for (volatile int i = 0; i < 50000; i++) {
}
vTaskDelay(1);
}
}
static void repro_task(void* arg) {
(void)arg;
for (int cycle = 0;; cycle++) {
// delay to increase probability of task migration
vTaskDelay(pdMS_TO_TICKS(1 + (get_rand_32() % 20)));
int r = cyw43_arch_init();
if (r != 0) {
printf("cycle %d: init failed %d\n", cycle, r);
}
vTaskDelay(pdMS_TO_TICKS(100));
cyw43_arch_deinit();
printf("cycle %d ok\n", cycle);
}
}
// linked against pico_cyw43_arch_lwip_sys_freertos
void cyw43_init_race_condition_test(void) {
TaskHandle_t h;
xTaskCreate(hog_task, "hog0", HOG_STACK, NULL, HOG_PRIORITY, &h);
vTaskCoreAffinitySet(h, 1u << 0);
xTaskCreate(hog_task, "hog1", HOG_STACK, NULL, HOG_PRIORITY, &h);
vTaskCoreAffinitySet(h, 1u << 1);
xTaskCreate(repro_task, "repro", REPRO_STACK, NULL, REPRO_PRIORITY, &h);
}
```
_Note on visibility: this only fires in debug builds — the asserts are compiled out with NDEBUG_
**Workaround:**
```c
UBaseType_t old_affinity = vTaskCoreAffinityGet(NULL);
vTaskCoreAffinitySet(NULL, 1u << 0);
int r = cyw43_arch_init();
vTaskCoreAffinitySet(NULL, old_affinity);
```
</details>
Probably related: #1526, #1478, raspberrypi/pico-examples#736 (still open, on 2.2.0 which includes the #1526 fix).
Will open a PR with a fix referencing this issue.
关闭于 16 天前 1 条评论