Skip to content

Commit c06c291

Browse files
Rewrite CYW43 driver for LWIP thread processing
Don't use any async_context for the __FREERTOS case because the extra context mutex causes deadlocks. Rewrite and simplify the CYW43 driver from the SDK to use FreeRTOS native primitives, no TaskHandle, and hook into the LWIP thread.
1 parent 90d543e commit c06c291

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

cores/rp2040/lwip_wrap.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,4 +842,44 @@ extern "C" {
842842
return;
843843
}
844844

845+
#ifndef __FREERTOS
846+
extern bool __real_cyw43_driver_init(async_context_t *context)'
847+
bool __wrap_cyw43_driver_init(async_context_t *context) {
848+
return __real_cyw43_driver_init(async_context_t *context);
849+
}
850+
extern void __real_cyw43_driver_deinit(async_context_t *context);
851+
void __wrap_cyw43_driver_deinit(async_context_t *context) {
852+
__real_cyw43_driver_deinit(context);
853+
}
854+
extern void __real_cyw43_thread_enter(void);
855+
void __wrap_cyw43_thread_enter() {
856+
__real_cyw43_thread_enter();
857+
}
858+
extern void __real_cyw43_thread_exit(void);
859+
void __wrap_cyw43_thread_exit() {
860+
__real_cyw43_thread_exit();
861+
}
862+
extern void __real_cyw43_thread_lock_check(void);
863+
void __wrap_cyw43_thread_lock_check() {
864+
__real_cyw43_thread_lock_check();
865+
}
866+
extern void __real_cyw43_await_background_or_timeout_us(uint32_t timeout_us);
867+
void _wrap_cyw43_await_background_or_timeout_us(uint32_t timeout_us) {
868+
__real_cyw43_await_background_or_timeout_us(timeout_us);
869+
}
870+
extern void __real_cyw43_delay_ms(uint32_t ms);
871+
void __wrap_cyw43_delay_ms(uint32_t ms) {
872+
__real_cyw43_delay_ms(ms);
873+
}
874+
extern void __real_cyw43_delay_us(uint32_t us);
875+
void __wrap_cyw43_delay_us(uint32_t us) {
876+
__real_cyw43_delay_us(us);
877+
}
878+
extern void __real_cyw43_post_poll_hook(void);
879+
void __wrap_cyw43_post_poll_hook(void) {
880+
__real_cyw43_post_poll_hook();
881+
}
882+
883+
#endif
884+
845885
}; // extern "C"

lib/core_wrap.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@
7979
-Wl,--wrap=cyw43_cb_tcpip_deinit
8080

8181
-Wl,--wrap=__stack_chk_fail
82+
83+
-Wl,--wrap=cyw43_driver_init
84+
-Wl,--wrap=cyw43_driver_deinit
85+
-Wl,--wrap=cyw43_thread_enter
86+
-Wl,--wrap=cyw43_thread_exit
87+
-Wl,--wrap=cyw43_thread_lock_check
88+
-Wl,--wrap=cyw43_await_background_or_timeout_us
89+
-Wl,--wrap=cyw43_delay_ms
90+
-Wl,--wrap=cyw43_delay_us
91+
-Wl,--wrap=cyw43_post_poll_hook
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Rewrite the CYW43 driver to use a native FreeRTOS task and primitives
2+
// It seems hard(impossible?) to have both an async_context mutex and the
3+
// LWIP one, owned by different TaskHandle_t's, and not end up in a
4+
// deadlock case. LWIP task may need to call CYW43 functions but if LWIP
5+
// was invoked *by* the CYW43 async_context that mutex is already taken by
6+
// the async_context task and any cyw43 calls from the LWIP task will
7+
// deadlock since they can't re-active the async_context task to resolve
8+
// the mutex. Ouroboros time...
9+
10+
// Based off of pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c,
11+
// Copyright (c) 2022 Raspberry Pi (Trading) Ltd.,
12+
// SPDX-License-Identifier: BSD-3-Clause
13+
14+
#ifdef __FREERTOS
15+
16+
#include <FreeRTOS.h>
17+
#include "task.h"
18+
#include "semphr.h"
19+
20+
21+
22+
#include "hardware/gpio.h"
23+
#include "hardware/irq.h"
24+
#include "pico/unique_id.h"
25+
extern "C" {
26+
#include "cyw43.h"
27+
#include "pico/cyw43_driver.h"
28+
}
29+
#include "pico/async_context.h"
30+
#include <lwip_wrap.h>
31+
32+
#ifndef CYW43_GPIO_IRQ_HANDLER_PRIORITY
33+
#define CYW43_GPIO_IRQ_HANDLER_PRIORITY 0x40
34+
#endif
35+
36+
#ifndef CYW43_SLEEP_CHECK_MS
37+
#define CYW43_SLEEP_CHECK_MS 50
38+
#endif
39+
40+
static SemaphoreHandle_t _cyw43_arch_mutex;
41+
static SemaphoreHandle_t _cyw43_irq_called_binary;
42+
43+
static void cb_cyw43_do_poll(void *context);
44+
static __callback_req _irqBuffer;
45+
46+
static void cyw43_set_irq_enabled(bool enabled) {
47+
#ifndef NDEBUG
48+
assert(get_core_num() == 0);
49+
#endif
50+
gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, enabled);
51+
}
52+
53+
// GPIO interrupt handler to tell us there's cyw43 has work to do
54+
static void cyw43_gpio_irq_handler() {
55+
#ifndef NDEBUG
56+
assert(get_core_num() == 0);
57+
#endif
58+
uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE);
59+
if (events & GPIO_IRQ_LEVEL_HIGH) {
60+
// As we use a high level interrupt, it will go off forever until it's serviced
61+
// So disable the interrupt until this is done. It's re-enabled again by CYW43_POST_POLL_HOOK
62+
// which is called at the end of cyw43_poll_func
63+
cyw43_set_irq_enabled(false);
64+
lwip_callback(cb_cyw43_do_poll, nullptr, &_irqBuffer);
65+
}
66+
}
67+
68+
static uint32_t cyw43_irq_init(__unused void *param) {
69+
#ifndef NDEBUG
70+
assert(get_core_num() == 0);
71+
#endif
72+
gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, cyw43_gpio_irq_handler, CYW43_GPIO_IRQ_HANDLER_PRIORITY);
73+
cyw43_set_irq_enabled(true);
74+
irq_set_enabled(IO_IRQ_BANK0, true);
75+
return 0;
76+
}
77+
78+
extern "C" void __wrap_cyw43_post_poll_hook() {
79+
#ifndef NDEBUG
80+
assert(get_core_num() == 0);
81+
#endif
82+
cyw43_set_irq_enabled(true);
83+
}
84+
85+
void cyw43_schedule_internal_poll_dispatch(__unused void (*func)()) {
86+
lwip_callback(cb_cyw43_do_poll, nullptr);
87+
}
88+
89+
static int64_t cb_cyw43_sleep_timeout_reached(alarm_id_t id, void *ptr) {
90+
(void) id;
91+
(void) ptr;
92+
static __callback_req _sleepIRQBuffer;
93+
// This will be in IRQ context, so do a lwip callback. Only one at a time can be outstanding so this single struct is good enough
94+
lwip_callback(cb_cyw43_do_poll, nullptr, &_sleepIRQBuffer);
95+
return 0; // Don't reschedule
96+
}
97+
98+
// By construction, this will only be called from the LWIP thread on core 0
99+
static void cb_cyw43_do_poll(void *context) { //, __unused async_when_pending_worker_t *worker) {
100+
#ifndef NDEBUG
101+
assert(get_core_num() == 0);
102+
#endif
103+
if (cyw43_poll) {
104+
if (cyw43_sleep > 0) {
105+
cyw43_sleep--;
106+
}
107+
cyw43_poll();
108+
if (cyw43_sleep) {
109+
add_alarm_in_ms (CYW43_SLEEP_CHECK_MS, cb_cyw43_sleep_timeout_reached, nullptr, true);
110+
} else {
111+
// Nothing to do. We have 1-shot alarms
112+
}
113+
}
114+
}
115+
116+
extern "C" bool __wrap_cyw43_driver_init(async_context_t *context) {
117+
assert(get_core_num() == 0);
118+
_cyw43_arch_mutex = xSemaphoreCreateRecursiveMutex();
119+
_cyw43_irq_called_binary = xSemaphoreCreateBinary();
120+
cyw43_init(&cyw43_state);
121+
cyw43_irq_init(nullptr);
122+
return true;
123+
}
124+
125+
extern "C" void __wrap_cyw43_driver_deinit(async_context_t *context) {
126+
panic("unsipported");
127+
}
128+
129+
// Prevent background processing in pensv and access by the other core
130+
// These methods are called in pensv context and on either core
131+
// They can be called recursively
132+
extern "C" void __wrap_cyw43_thread_enter() {
133+
xSemaphoreTakeRecursive(_cyw43_arch_mutex, portTICK_PERIOD_MS);
134+
}
135+
136+
extern "C" void __wrap_cyw43_thread_exit() {
137+
xSemaphoreGiveRecursive(_cyw43_arch_mutex);
138+
}
139+
140+
#ifndef NDEBUG
141+
extern "C" void __wrap_cyw43_thread_lock_check() {
142+
// TODO
143+
}
144+
#endif
145+
146+
extern "C" void __wrap_cyw43_await_background_or_timeout_us(uint32_t timeout_us) {
147+
if (__get_current_exception() > 0) {
148+
vTaskDelay((timeout_us / 1000) / portTICK_PERIOD_MS);
149+
return;
150+
}
151+
// Try and take a binary semaphore that only the IRQ will give or timeout trying
152+
xSemaphoreTake(_cyw43_irq_called_binary, (timeout_us / 1000) / portTICK_PERIOD_MS);
153+
}
154+
155+
extern "C" void __wrap_cyw43_delay_ms(uint32_t ms) {
156+
vTaskDelay(ms / portTICK_PERIOD_MS);
157+
}
158+
159+
extern "C" void __wrap_cyw43_delay_us(uint32_t us) {
160+
if (us >= 1000) {
161+
uint32_t ms = us / 1000;
162+
vTaskDelay(ms / portTICK_PERIOD_MS);
163+
us -= ms * 1000;
164+
}
165+
delayMicroseconds(us);
166+
}
167+
168+
// Generate a mac address if one is not set in otp
169+
extern "C" void cyw43_hal_generate_laa_mac(__unused int idx, uint8_t buf[6]) {
170+
CYW43_DEBUG("Warning. No mac in otp. Generating mac from board id\n");
171+
pico_unique_board_id_t board_id;
172+
pico_get_unique_board_id(&board_id);
173+
memcpy(buf, &board_id.id[2], 6);
174+
buf[0] &= (uint8_t)~0x1; // unicast
175+
buf[0] |= 0x2; // locally administered
176+
}
177+
178+
// Return mac address
179+
extern "C" void cyw43_hal_get_mac(__unused int idx, uint8_t buf[6]) {
180+
// The mac should come from cyw43 otp.
181+
// This is loaded into the state after the driver is initialised
182+
// cyw43_hal_generate_laa_mac is called by the driver to generate a mac if otp is not set
183+
memcpy(buf, cyw43_state.mac, 6);
184+
}
185+
186+
187+
#endif

0 commit comments

Comments
 (0)