Compare commits

..

1 Commits

Author SHA1 Message Date
claude-noether cdfdac987a bes2600: bus_reset on connection-loss storm to dodge assoc-comeback blackhole
When mac80211 declares connection loss against this AP (typically driven
by inactivity-deauth or beacon-loss), the userspace reauth that follows
sometimes enters a long blackhole: the AP responds to auth with success
but defers assoc with the 802.11v "assoc comeback" timer; ohm retries
faster than the comeback grants permission; the AP eventually fires an
unprotected deauth-reason-6 ("Class 2 frame received from non-
authenticated station"), and recovery only completes via cross-SSID or
cross-channel fallback. Receipts: ~86 s blackhole observed in the
phase-7 rep on 2026-05-07 02:42, with three subsequent BSSIDs returning
assoc comeback timeouts before reason-9 (STA_REQ_ASSOC_WITHOUT_AUTH)
fired. Documented in marfrit/besser:notes/phase4-2026-05-07.md.

When N=3 driver-side connection_loss decisions fire within a 60 s window
on the same vif, skip the ieee80211_connection_loss() path and trigger
the c5.2-introduced bes2600_chrdev_do_bus_reset() instead. The bus
reset removes and re-probes the chip; userspace re-associates with a
fresh chip state, dodging the AP's comeback-timer rejection cycle.

Predicted Phase 7 delta vs current baseline:
- api_connection_loss rate: unchanged (we don't address the trigger)
- conditional probability of >5 s blackhole given event: <= 30 %
- worst-case recovery: 86 s -> < 10 s

Contract pin: bes2600_chrdev_do_bus_reset(sbus_ops, sbus_priv) at
bes2600/bes_chardev.c:455, introduced by c5.2. The function is async-
returning: sbus_ops->bus_reset() schedules an SDIO rescan; the helper
waits up to 3 s for the remove() callback to clear sbus_priv, then
returns. Per-vif state is gone after this point, so the recover work
lives on bes2600_common (hw_priv) and uses the global bes2600_cdev for
the bus_reset call rather than dereferencing per-vif state.

Threshold (3 / 60 s) is well above the steady-state per-vif
connection_loss rate observed in the patch-A phase-7 rep (0.86/h under
sustained load), so a true storm is required to trip it.

Files touched:
- bes2600/bes2600.h: 3 counter fields on struct bes2600_vif, 1
  work_struct on struct bes2600_common, 3 prototypes
- bes2600/sta.c: 3 helpers + storm-account hook in
  bes2600_connection_loss_work + storm-init in bes2600_vif_setup +
  cancel_work_sync in the hw_priv shutdown path; #include bes_chardev.h
  was already pulled in by an earlier c-stack patch
- bes2600/main.c: INIT_WORK alongside other hw_priv work_structs
- bes2600/debug.c: ConnectionLossStormRecoveries seq_printf in the
  per-vif status seq_file output

The cw1200/cw1260 ancestor has no equivalent; this is a clean
addition. checkpatch.pl --no-tree --strict: clean (0/0/0).

Signed-off-by: Claude (noether) <claude@reauktion.de>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:30:09 +02:00
7 changed files with 24 additions and 46 deletions
-12
View File
@@ -484,18 +484,6 @@ int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_pri
return 0; return 0;
} }
/*
* Trigger bes2600_chrdev_do_bus_reset() against the file-global
* bes2600_cdev. Used by host-side recovery paths outside this
* compilation unit (e.g. sta.c connection-loss-storm fast-recover) so
* those callers do not need to reach the static bes2600_cdev directly.
*/
int bes2600_chrdev_trigger_bus_reset(void)
{
return bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
bool bes2600_chrdev_is_wifi_opened(void) bool bes2600_chrdev_is_wifi_opened(void)
{ {
bool wifi_opened = false; bool wifi_opened = false;
-1
View File
@@ -61,7 +61,6 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void);
int bes2600_chrdev_check_system_close(void); int bes2600_chrdev_check_system_close(void);
int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_priv *priv); int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv); int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
int bes2600_chrdev_trigger_bus_reset(void);
void bes2600_chrdev_wakeup_bt(void); void bes2600_chrdev_wakeup_bt(void);
void bes2600_chrdev_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev); void bes2600_chrdev_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev);
void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv); void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv);
+6 -6
View File
@@ -101,7 +101,7 @@ void bes2600_unregister_bh(struct bes2600_common *hw_priv)
coex_deinit_mode(hw_priv); coex_deinit_mode(hw_priv);
#endif #endif
atomic_inc(&hw_priv->bh_term); atomic_add(1, &hw_priv->bh_term);
wake_up(&hw_priv->bh_wq); wake_up(&hw_priv->bh_wq);
flush_workqueue(hw_priv->bh_workqueue); flush_workqueue(hw_priv->bh_workqueue);
@@ -590,7 +590,7 @@ static int bes2600_bh(void *arg)
bes_devel("[BH] Device resume.\n"); bes_devel("[BH] Device resume.\n");
atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED);
wake_up(&hw_priv->bh_evt_wq); wake_up(&hw_priv->bh_evt_wq);
atomic_inc(&hw_priv->bh_rx); atomic_add(1, &hw_priv->bh_rx);
continue; continue;
} }
@@ -758,9 +758,9 @@ tx:
#if 0 /* count is not implemented */ #if 0 /* count is not implemented */
if (ret > 1) if (ret > 1)
atomic_inc(&hw_priv->bh_tx); atomic_add(1, &hw_priv->bh_tx);
#else #else
atomic_inc(&hw_priv->bh_tx); atomic_add(1, &hw_priv->bh_tx);
#endif #endif
#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES) #if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES)
@@ -1134,7 +1134,7 @@ static int bes2600_bh_tx_helper(struct bes2600_common *hw_priv,
tx_len += 4; tx_len += 4;
#endif #endif
atomic_inc(&hw_priv->bh_tx); atomic_add(1, &hw_priv->bh_tx);
tx_len = hw_priv->sbus_ops->align_size( tx_len = hw_priv->sbus_ops->align_size(
hw_priv->sbus_priv, tx_len); hw_priv->sbus_priv, tx_len);
@@ -1435,7 +1435,7 @@ static int bes2600_bh(void *arg)
bes_devel("[BH] Device resume.\n"); bes_devel("[BH] Device resume.\n");
atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED);
wake_up(&hw_priv->bh_evt_wq); wake_up(&hw_priv->bh_evt_wq);
atomic_inc(&hw_priv->bh_rx); atomic_add(1, &hw_priv->bh_rx);
goto done; goto done;
} }
+1 -1
View File
@@ -570,7 +570,7 @@ int bes2600_itp_get_tx(struct bes2600_common *priv, u8 **data,
*burst = 2; *burst = 2;
atomic_set(&priv->bh_tx, 1); atomic_set(&priv->bh_tx, 1);
ktime_get_ts(&itp->last_sent); ktime_get_ts(&itp->last_sent);
atomic_inc(&itp->awaiting_confirm); atomic_add(1, &itp->awaiting_confirm);
spin_unlock_bh(&itp->tx_lock); spin_unlock_bh(&itp->tx_lock);
return 1; return 1;
-2
View File
@@ -497,7 +497,6 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len)
WLAN_LINK_ID_MAX, WLAN_LINK_ID_MAX,
bes2600_skb_dtor, bes2600_skb_dtor,
hw_priv))) { hw_priv))) {
destroy_workqueue(hw_priv->workqueue);
ieee80211_free_hw(hw); ieee80211_free_hw(hw);
return NULL; return NULL;
} }
@@ -509,7 +508,6 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len)
for (; i > 0; i--) for (; i > 0; i--)
bes2600_queue_deinit(&hw_priv->tx_queue[i - 1]); bes2600_queue_deinit(&hw_priv->tx_queue[i - 1]);
bes2600_queue_stats_deinit(&hw_priv->tx_queue_stats); bes2600_queue_stats_deinit(&hw_priv->tx_queue_stats);
destroy_workqueue(hw_priv->workqueue);
ieee80211_free_hw(hw); ieee80211_free_hw(hw);
return NULL; return NULL;
} }
+16 -23
View File
@@ -257,21 +257,18 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
bes2600_pwr_set_busy_event(hw_priv, BES_PWR_LOCK_ON_SCAN); bes2600_pwr_set_busy_event(hw_priv, BES_PWR_LOCK_ON_SCAN);
/* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0, frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len); req->ie_len);
if (!frame.skb) { if (!frame.skb)
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
return -ENOMEM; return -ENOMEM;
}
if (req->ie_len) if (req->ie_len)
skb_put_data(frame.skb, req->ie, req->ie_len); skb_put_data(frame.skb, req->ie, req->ie_len);
/* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
if (frame.skb) { if (frame.skb) {
int ret; int ret;
//if (priv->if_id == 0) //if (priv->if_id == 0)
@@ -289,9 +286,9 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
} }
#endif #endif
if (ret) { if (ret) {
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock); up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock); up(&hw_priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret; return ret;
} }
} }
@@ -321,10 +318,10 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
++hw_priv->scan.n_ssids; ++hw_priv->scan.n_ssids;
} }
up(&hw_priv->conf_lock);
if (frame.skb) if (frame.skb)
dev_kfree_skb(frame.skb); dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
#ifdef WIFI_BT_COEXIST_EPTA_ENABLE #ifdef WIFI_BT_COEXIST_EPTA_ENABLE
bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING); bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING);
#endif #endif
@@ -365,18 +362,14 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
if (req->n_ssids > hw->wiphy->max_scan_ssids) if (req->n_ssids > hw->wiphy->max_scan_ssids)
return -EINVAL; return -EINVAL;
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb)
return -ENOMEM;
/* will be unlocked in bes2600_scan_work() */ /* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock); down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock); down(&hw_priv->conf_lock);
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb) {
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
return -ENOMEM;
}
if (frame.skb) { if (frame.skb) {
int ret; int ret;
if (priv->if_id == 0) if (priv->if_id == 0)
@@ -387,9 +380,9 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
ret = wsm_set_probe_responder(priv, true); ret = wsm_set_probe_responder(priv, true);
} }
if (ret) { if (ret) {
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock); up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock); up(&hw_priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret; return ret;
} }
} }
@@ -421,10 +414,10 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
} }
} }
up(&hw_priv->conf_lock);
if (frame.skb) if (frame.skb)
dev_kfree_skb(frame.skb); dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
queue_work(hw_priv->workqueue, &hw_priv->scan.swork); queue_work(hw_priv->workqueue, &hw_priv->scan.swork);
wiphy_warn(hw->wiphy, "<--[SCAN] Scheduled scan request.\n"); wiphy_warn(hw->wiphy, "<--[SCAN] Scheduled scan request.\n");
return 0; return 0;
+1 -1
View File
@@ -1692,7 +1692,7 @@ report:
void bes2600_connection_loss_storm_recover(struct work_struct *work) void bes2600_connection_loss_storm_recover(struct work_struct *work)
{ {
bes_warn("[bes2600] connection-loss-storm fast-recover: bus_reset\n"); bes_warn("[bes2600] connection-loss-storm fast-recover: bus_reset\n");
bes2600_chrdev_trigger_bus_reset(); bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops, bes2600_cdev.sbus_priv);
/* /*
* After bes2600_chrdev_do_bus_reset() returns, the SDIO core has * After bes2600_chrdev_do_bus_reset() returns, the SDIO core has
* scheduled a remove + rescan; per-vif state may already be gone. * scheduled a remove + rescan; per-vif state may already be gone.