ITADN

cyw43_arch_init() intermittently asserts under FreeRTOS SMP

#3080Closednumancan 创建于 2026-07-19
N
numancancommented
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 条评论