fix: avoid spurious WARN in rwnx_close during USB teardown

Unplugging the adapter while the interface is connecting or disconnecting
prints a spurious kernel warning:

  rwnx_close connecting or disconnecting, not finish
  WARNING: CPU: ... rwnx_close+0x96/0x3f0 [aic8800_fdrv]

rwnx_close() waits up to 4 seconds for drv_conn_state to leave
CONNECTING/DISCONNECTING, but that state only changes when the firmware
answers the pending request. During USB teardown the bus is already down
by the time rwnx_close() runs, and rwnx_send_msg() drops the message and
returns 0, so the confirmation never arrives. The wait therefore always
runs its full timeout and fires WARN_ON(1) for a condition that cannot be
satisfied.

Skip both waits when the bus is down, using the same condition already
used a few lines below in the vif_started path. This removes the wait
itself, not just the warning: previously teardown blocked for up to 4 (and
2) seconds waiting on a dead bus.

Reproduced on 6.12.107+deb13 with an AIC8800D80: unplug while connected
fires the warning on main and produces none with this change, while the
replug path still re-enumerates and restores Wi-Fi and Bluetooth. Builds
on 5.15.0-191, 6.1.0-53, 6.8.0-139, 6.11.0-8 and 6.12.107+deb13 with no
new warnings.
This commit is contained in:
Lucas Kawatoko
2026-09-24 09:07:55 -03:00
parent c97644a43a
commit 9cf132d9b5
+17
View File
@@ -1518,6 +1518,17 @@ static int rwnx_close(struct net_device *dev)
RWNX_DBG(RWNX_FN_ENTRY_STR); RWNX_DBG(RWNX_FN_ENTRY_STR);
/*
* A CONNECTING/DISCONNECTING state is only left when the firmware answers
* the pending request. If the bus is already down the answer can never
* arrive: rwnx_send_msg() silently drops the message and returns 0
* (rwnx_msg_tx.c), so the wait below would always run its full timeout and
* fire the spurious WARN_ON() during USB teardown. Skip the wait when the
* bus is down, using the same condition the vif_started path uses below.
*/
if (bus_if &&
(usbdev == NULL || (usbdev->bus_if->state != BUS_DOWN_ST &&
usbdev->state != USB_DOWN_ST))) {
test_counter = waiting_counter; test_counter = waiting_counter;
while(atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING|| while(atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING||
atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING){ atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING){
@@ -1530,6 +1541,7 @@ static int rwnx_close(struct net_device *dev)
break; break;
} }
} }
}
#if defined(AICWF_USB_SUPPORT) || defined(AICWF_SDIO_SUPPORT) #if defined(AICWF_USB_SUPPORT) || defined(AICWF_SDIO_SUPPORT)
if (rwnx_hw->scanning){ if (rwnx_hw->scanning){
@@ -1587,6 +1599,10 @@ static int rwnx_close(struct net_device *dev)
test_counter = waiting_counter; test_counter = waiting_counter;
if(atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTED){ if(atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTED){
rwnx_set_conn_state(rwnx_vif, &rwnx_vif->drv_conn_state, RWNX_DRV_STATUS_DISCONNECTING); rwnx_set_conn_state(rwnx_vif, &rwnx_vif->drv_conn_state, RWNX_DRV_STATUS_DISCONNECTING);
/* Same reasoning as above: with the bus down the disconnect
* confirmation cannot arrive, so do not wait for it. */
if (usbdev->bus_if->state != BUS_DOWN_ST &&
usbdev->state != USB_DOWN_ST) {
rwnx_send_sm_disconnect_req(rwnx_hw, rwnx_vif, 3); rwnx_send_sm_disconnect_req(rwnx_hw, rwnx_vif, 3);
while (atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING) { while (atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING) {
AICWFDBG(LOGDEBUG, "%s wifi is disconnecting, waiting 100ms for state to stable\r\n", __func__); AICWFDBG(LOGDEBUG, "%s wifi is disconnecting, waiting 100ms for state to stable\r\n", __func__);
@@ -1597,6 +1613,7 @@ static int rwnx_close(struct net_device *dev)
} }
} }
} }
}
#ifdef CONFIG_USE_P2P0 #ifdef CONFIG_USE_P2P0
if(!rwnx_vif->is_p2p_vif || ( rwnx_vif->is_p2p_vif && rwnx_hw->is_p2p_alive)){ if(!rwnx_vif->is_p2p_vif || ( rwnx_vif->is_p2p_vif && rwnx_hw->is_p2p_alive)){
if (rwnx_vif->is_p2p_vif) if (rwnx_vif->is_p2p_vif)