Closed anecdata closed 9 months ago
I'm baffled.
Release 9.0.0-alpha.6
release notes show
Port and board-specific changes RP2040 • Add wifi.stop_ap(). https://github.com/adafruit/circuitpython/pull/8622, https://github.com/adafruit/circuitpython/pull/8590
Implement stop_ap for RP2 1451d77
But there would have been some kind of merge conflict? My SDK knowledge is minimal, not sure what each of these are really doing, but features of both are needed: #8326 allows station and AP to run independently or concurrently and allows stopping AP (once). #8590 allows AP to be stopped in a way that it can be started again.
void common_hal_wifi_radio_stop_ap(wifi_radio_obj_t *self) {
if (!common_hal_wifi_radio_get_enabled(self)) {
mp_raise_RuntimeError(translate("wifi is not enabled"));
}
if (cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_AP) != CYW43_LINK_DOWN) { // Disassociate from WLAN
cyw43_wifi_leave(&cyw43_state, CYW43_ITF_AP);
// Stop AP
cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, false, 0);
bindings_cyw43_wifi_enforce_pm();
}
}
void common_hal_wifi_radio_stop_ap(wifi_radio_obj_t *self) {
if (!common_hal_wifi_radio_get_enabled(self)) {
mp_raise_RuntimeError(translate("wifi is not enabled"));
}
cyw43_arch_disable_ap_mode();
const size_t timeout_ms = 500;
uint64_t start = port_get_raw_ticks(NULL);
uint64_t deadline = start + timeout_ms;
while (port_get_raw_ticks(NULL) < deadline && (cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_AP) != CYW43_LINK_DOWN)) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
break;
}
}
bindings_cyw43_wifi_enforce_pm();
}
...but 9.0.0-alpha.6 matches #8326:
...so the merge was dropped because it conflicted?
Looks like #8326 isn't stopping the AP. That's what the call to cyw43_wifi_set_up(..., false, 0)
in #8590 does.
@anecdata You're right. We need elements of both for this to work correctly. I'll submit a pull after lunch, will you be willing to test it?
Yes!
Hmmm. Problem is deeper than I thought. The call to cyw43_arch_disable_ap_mode()
in #8326 is calling cyw43_wifi_set_up(..., false, ...)
so that's not the error. There's a recurring bit of code in #8326:
while (port_get_raw_ticks(NULL) < deadline && (cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA) != CYW43_LINK_xxxx)) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
break;
}
}
that looks like it delays but never actually times out if what it's waiting for fails to complete. Maybe related?
I'm setting up some debug tracing for a closer look now.
There is a TODO in common_hal_wifi_radio_start_ap()
that describes just this problem:
/* TODO: If the AP is stopped once it cannot be restarted.
* This means that if if the user does:
*
* wifi.radio.start_ap(...)
* wifi.radio.stop_ap()
* wifi.radio.start_ap(...)
*
* The second start_ap will fail.
*/
Prior to #8326 common_hal_wifi_radio_start_ap()
was not testing cyw43_tcpip_link_status()
after calling cyw43_arch_enable_ap_mode()
so with #8590 applied it appeared that the AP could be started, stopped, and then started again successfully. #8326 adds testing of cyw43_tcpip_link_status()
which returns CYW43_LINK_DOWN
for the second start attempt, resulting in the RuntimeError: AP could not be started
exception.
The CYW43_LINK_DOWN
status is returned because the CYW43 driver's status flags for the AP interface indicates that it is up, but it has not received a link up
indication from the CYW43439. To narrow this down I'm activating tracing inside the CYW43 driver which I now suspect is at fault.
I have updated the CYW43 driver to tag v1.0.2
, but that does not resolve the problem.
Tracing asynchronous events from the CYW43439 shows that it is reporting that the AP is up on the second start attempt. So, there's a status reporting problem in the CYW43 driver.
Starting AP...
[ 6409] ASYNC(0001,LINK,0,0,1) // CYW43_EV_LINK, link is up
[ 6411] ASYNC(0001,LINK,0,0,1) // CYW43_EV_LINK, link is up
AP tcpip status: 3 // CYW43_LINK_UP
AP wifi status: 0
...wifi.radio.ipv4_address_ap=192.168.4.1
Stopping AP...
[ 11573] ASYNC(0000,LINK,0,4,1) // CYW43_EV_LINK, link is down
...wifi.radio.ipv4_address_ap=None
Starting AP...
[ 16803] ASYNC(0001,LINK,0,0,1) // CYW43_EV_LINK, link is up
[ 16805] ASYNC(0001,LINK,0,0,1) // CYW43_EV_LINK, link is up
AP tcpip status: 0 // CYW43_LINK_DOWN reported in error
AP wifi status: 0
[ 17413] ASYNC(0000,LINK,0,4,1)
Traceback (most recent call last):
File "code.py", line 21, in <module>
RuntimeError: AP could not be started
Not a status reporting error in the CYW43 driver. The interface flags are getting clobbered due to the AP interface being initialized (possibly in error) after the CYW43439 has brought the link up. Here's what happens on the second AP start:
common_hal_wifi_radio_start_ap()
invokes cyw43_arch_enable_ap_mode()
cyw43_arch_enable_ap_mode()
invokes cyw43_wifi_setup()
cyw43_wifi_setup()
invokes cyw43_wifi_ap_init()
and cyw43_wifi_ap_set_up()
. This is where the CYW43439 is commanded to bring the link up.NETIF_FLAG_LINK_UP
is set indicating so.cyw43_wifi_setup()
invokes cyw43_cb_tcpip_init()
.cyw43_cb_tcpip_init()
invokes netif_add()
to add the interface.netif_add()
invokes cyw43_netif_init()
.cyw43_netif_init()
resets the interface flags. The NETIF_FLAG_LINK_UP
flag is cleared.common_hal_wifi_radio_start_ap()
invokes cyw43_tcpip_link_status()
which tests NETIF_FLAG_LINK_UP
and returns CYW43_LINK_DOWN
.There is a race condition in the CYW43 driver that is the root cause of this error. I cannot find a workaround, so I'll report it to the CYW43 driver project and we'll have to wait for them to issue a fix.
On the first start of the AP, the CYW43439 takes longer to bring the link up and signal asynchronously than it does on subsequent attempts. For the first AP start cyw43_wifi_setup()
is able to complete CYW43_cb_tcpip_init()
before the CYW43439 signals that the link is up, so NETIF_FLAG_LINK_UP
does not get clobbered. On subsequent starts of the AP, the CYW43439 signals that the link is up almost immediately so CYW43_cb_tcpip_init()
clears NETIF_FLAG_LINK_UP
after it has already been set.
Submitted pulls georgerobotics/cyw43-driver#107 and georgerobotics/cyw43-driver#108 to resolve this issue and allow the latest cyw43-driver
to build in CircuitPython, respectively.
@dhalbert Please tag this 3rd party. We will need to create a pull to update cyw43-driver
once the above pulls are accepted.
Still an issue in beta.0
Adafruit CircuitPython 9.0.0-beta.0 on 2024-01-27; Raspberry Pi Pico W with rp2040
.
Result...
...
Starting AP... wifi.radio.ipv4_address_ap=192.168.4.1
Stopping AP... wifi.radio.ipv4_address_ap=None
Starting AP... Traceback (most recent call last):
File "code.py", line 48, in start_ap
RuntimeError: AP could not be started
wifi.radio.ipv4_address_ap=None
...
@anecdata We need to pick up our local cyw43-driver
fork since we're waiting for the fix to be integrated upstream.
Oh, I saw that fork change but thought it would be included with main
. Is there a way to release with the driver fork, until the upstream change is accepted?
CircuitPython version
Code/REPL
Behavior
9.0.0-alpha.6
is the first release to have both #8590 and #8326, but there seems to be a regression. Under 8.2.8 and 8.2.9 (with 8590, but not with 8326), the code above runs fine. But under9.0.0-alpha.6
, I can't find a sequence that lets me start an AP a second time:...ad infinitum
Description
No response
Additional information
Code runs fine on
espressif
, but note that a delay is needed afterstart_ap()
foripv4_address_ap
to become active.