diff --git a/arch/linux-pinetab2-danctnix-besser/0001-bes2600-besser-kernel-agent-cumulative.patch b/arch/linux-pinetab2-danctnix-besser/0001-bes2600-besser-kernel-agent-cumulative.patch index 1b2df3e32..c6818aff2 100644 --- a/arch/linux-pinetab2-danctnix-besser/0001-bes2600-besser-kernel-agent-cumulative.patch +++ b/arch/linux-pinetab2-danctnix-besser/0001-bes2600-besser-kernel-agent-cumulative.patch @@ -1,306 +1,85 @@ -From 4e176b8f930373bc02382c903e6d739ab2d5fd47 Mon Sep 17 00:00:00 2001 +From 4a1bbc7444c94be044fae4377ccd612a6cd28460 Mon Sep 17 00:00:00 2001 From: Markus Fritsche -Date: Fri, 8 May 2026 10:07:47 +0200 -Subject: [PATCH] bes2600: BESser cumulative patch series (16 commits squashed) +Date: Wed, 22 Apr 2026 10:09:44 +0200 +Subject: [PATCH 01/29] bes2600: use request_firmware() for factory.txt read -This is a squashed cumulative diff of the marfrit/bes2600-dkms cleanups -branch (Mobian-flavor source) overlaid onto the v7.0-danctnix1 staging -tree, with danctnix-flavor adaptations applied: +The BES2600 factory calibration file (bes2600_factory.txt) was being read +via filp_open() + kernel_read() from a hard-coded absolute path baked in +at compile time via the FACTORY_PATH Makefile macro +(default: /lib/firmware/bes2600_factory.txt). - - timer_container_of() / timer_delete_sync() (vs Mobian's deprecated - from_timer / del_timer_sync) - - bes2600_config / bes2600_set_rts_threshold sigs include radio_idx - - bes2600_switch_bt orchestration removed (Mobian-only; not called - in danctnix tree) +This had several problems: -Patch series squashed: +1. Path mismatch - linux-firmware-style packaging (and danctnix 0.2-5 + device-pine64-pinetab2) ships the file at + /lib/firmware/bes2600/bes2600_factory.txt, not /lib/firmware/. The + driver logged '(NULL device *): read and check + /lib/firmware/bes2600_factory.txt error' on every boot on PineTab2 + running linux-pinetab2 6.19.10-danctnix1-1. - 1. c5.1 bes2600/scan-defer-on-reject - 2. c5.1.1 widen scan-defer backoff to 30s and decay reject_count - 3. c5.2 recover wedged firmware via mmc_hw_reset on link break - 4. c6.1 gate PM indication completion on pending request - 5. c6.2 short-circuit wake handshake when chip is confirmed ACTIVE - 6. c7 self-detect when firmware does not honor PSM and skip - 7. c5.2.1 multi-function SDIO mmc_hw_reset rescan - 8. Patch A decrypt-storm fast-recover (ieee80211_connection_loss) - 9. Patch B connection-loss-storm bus_reset - 10. Patch F1 hw_scan SKB lifecycle UAF (cw1200 backport 86760e0d) - 11. Patch F2 init_common destroy_workqueue on error (cw1200 7ec8a926) - 12. Patch F3 atomic_add(1, x) -> atomic_inc(x) (cw1200 07f995ca) - 13. Patch C v3 drop sdio_rx_work relay, IRQ->bh-direct - 14. Patch G SPDX + ST-Ericsson attribution restoration - 15. Patch D ba_lock atomicization + drop spinlock - 16. Patch E ps_state_lock skip when pm_unsupported - 17. Patch C2 ieee80211_rx_irqsafe -> ieee80211_rx_ni - 18. Patch H bh.c hygiene cleanup (drop fossil blocks, dead stubs) +2. Direct filesystem access via filp_open() / kernel_read() from a driver + is an anti-pattern that upstream rejects: drivers should use + request_firmware() to get binary data from userspace-managed firmware + directories. request_firmware() natively searches the firmware_class + path list (typically /lib/firmware + derivatives), associates the load + with a uevent, and respects the firmware-loading infrastructure. -Net: 48 files changed, ~1500 insertions, ~2000 deletions. +3. The (NULL device *) prefix in error messages indicated the absence of + proper device-context logging. While this patch does not yet thread + struct device through, the upstream path uses request_firmware() which + works with dev=NULL and is the building block for a follow-up patch + that adds per-chip device context. -Verified clean compile + +73% throughput vs Patch B baseline + race-free -under stress on the Mobian DKMS path. Danctnix-flavor build verification -deferred to PKGBUILD CI / Markus's test cycle. +Repoint the FACTORY_PATH default to the firmware-class name +(bes2600/bes2600_factory.txt) - request_firmware() prepends +/lib/firmware/ from the configured search paths. The macro remains +overridable at build time for non-standard deployments. -For per-patch history see git.reauktion.de/marfrit/bes2600-dkms cleanups -branch. +Rewrite factory_section_read_file() to: + * Call request_firmware(&fw, path, NULL). + * Size-check fw->size against FACTORY_MAX_SIZE. + * memcpy the data into the caller's buffer. + * Always call release_firmware() on exit. + +The file write path (factory_section_write_file + kernel_write) is left +unchanged in this patch; it is the subject of a follow-up patch that +removes kernel_write and moves any remaining userspace-visible factory +configuration to a standard kernel-userspace boundary (debugfs or +nl80211 testmode). + +No caller signature changes. No Makefile flag drops. Bisectable. + +Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2 +6.19.10-danctnix1-1, deployed via /lib/modules//extra/. Verified +post-reboot: original 'read and check /lib/firmware/bes2600_factory.txt +error' is gone; request_firmware reads the file successfully (a separate +factory_parse() bug, previously masked by the read failure, is now +exposed and tracked separately). Signed-off-by: Markus Fritsche --- - drivers/staging/bes2600/ap.c | 25 +- - drivers/staging/bes2600/ap.h | 9 +- - drivers/staging/bes2600/bes2600.h | 58 +- - drivers/staging/bes2600/bes2600_factory.c | 117 ++- - drivers/staging/bes2600/bes2600_factory.h | 12 +- - drivers/staging/bes2600/bes2600_plat.h | 9 +- - drivers/staging/bes2600/bes2600_sdio.c | 268 +++++-- - drivers/staging/bes2600/bes_chardev.c | 65 +- - drivers/staging/bes2600/bes_chardev.h | 11 +- - drivers/staging/bes2600/bes_fw.c | 43 +- - drivers/staging/bes2600/bes_fw_common.c | 9 +- - drivers/staging/bes2600/bes_fw_common.h | 9 +- - drivers/staging/bes2600/bes_log.h | 30 + - .../bes2600/bes_nl80211_testmode_msg.h | 9 +- - drivers/staging/bes2600/bes_pwr.c | 243 +++++- - drivers/staging/bes2600/bes_pwr.h | 33 +- - drivers/staging/bes2600/bh.c | 732 ++++-------------- - drivers/staging/bes2600/bh.h | 21 +- - drivers/staging/bes2600/debug.c | 29 +- - drivers/staging/bes2600/debug.h | 12 +- - drivers/staging/bes2600/epta_coex.c | 9 +- - drivers/staging/bes2600/epta_coex.h | 9 +- - drivers/staging/bes2600/epta_request.c | 9 +- - drivers/staging/bes2600/epta_request.h | 9 +- - drivers/staging/bes2600/fwio.c | 12 +- - drivers/staging/bes2600/fwio.h | 12 +- - drivers/staging/bes2600/ht.h | 9 +- - drivers/staging/bes2600/hwio.c | 23 +- - drivers/staging/bes2600/hwio.h | 15 +- - drivers/staging/bes2600/itp.c | 12 +- - drivers/staging/bes2600/itp.h | 9 +- - drivers/staging/bes2600/main.c | 66 +- - drivers/staging/bes2600/pm.c | 12 +- - drivers/staging/bes2600/pm.h | 12 +- - drivers/staging/bes2600/queue.c | 26 +- - drivers/staging/bes2600/queue.h | 12 +- - drivers/staging/bes2600/sbus.h | 31 +- - drivers/staging/bes2600/scan.c | 133 +++- - drivers/staging/bes2600/scan.h | 23 +- - drivers/staging/bes2600/sta.c | 204 +++-- - drivers/staging/bes2600/sta.h | 12 +- - drivers/staging/bes2600/tx_loop.c | 9 +- - drivers/staging/bes2600/tx_loop.h | 9 +- - drivers/staging/bes2600/txrx.c | 168 ++-- - drivers/staging/bes2600/txrx.h | 12 +- - drivers/staging/bes2600/wifi_testmode_cmd.c | 9 +- - drivers/staging/bes2600/wsm.c | 29 +- - drivers/staging/bes2600/wsm.h | 16 +- - 48 files changed, 1412 insertions(+), 1243 deletions(-) + bes2600/Makefile | 2 +- + bes2600/bes2600_factory.c | 33 ++++++++++++++------------------- + 2 files changed, 15 insertions(+), 20 deletions(-) -diff --git a/drivers/staging/bes2600/ap.c b/drivers/staging/bes2600/ap.c -index 7b1e3b42c..16c0451e0 100644 ---- a/drivers/staging/bes2600/ap.c -+++ b/drivers/staging/bes2600/ap.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * mac80211 STA and AP API for mac80211 BES2600 drivers -+ * AP mode for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ +diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile +index 300912b..788aee2 100644 +--- a/drivers/staging/bes2600/Makefile ++++ b/drivers/staging/bes2600/Makefile +@@ -66,7 +66,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116 + ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y) + FACTORY_CRC_CHECK ?= n + STANDARD_FACTORY_EFUSE_FLAG ?= y +-FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt ++FACTORY_PATH ?= bes2600/bes2600_factory.txt + endif - #include "bes2600.h" -@@ -17,7 +14,6 @@ - #include - #include "epta_request.h" - #include "epta_coex.h" --#include "txrx_opt.h" - - #ifdef AP_HT_CAP_UPDATE - #define HT_INFO_OFFSET 4 -@@ -66,8 +62,11 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct bes2600_vif *priv = cw12xx_get_vif_from_ieee80211(vif); - struct bes2600_link_entry *entry; - struct sk_buff *skb; -+ struct sk_buff_head local_drain; - struct bes2600_common *hw_priv = hw->priv; - -+ __skb_queue_head_init(&local_drain); -+ - #ifdef P2P_MULTIVIF - WARN_ON(priv->if_id == CW12XX_GENERIC_IF_ID); - #endif -@@ -96,9 +95,17 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) - priv->sta_asleep_mask |= BIT(sta_priv->link_id); - entry->status = BES2600_LINK_HARD; -- while ((skb = skb_dequeue(&entry->rx_queue))) -- ieee80211_rx_irqsafe(priv->hw, skb); -+ /* -+ * Patch C2: splice the rx_queue out under the lock then deliver -+ * after unlock. ieee80211_rx_ni() runs the mac80211 RX path -+ * synchronously (formerly ieee80211_rx_irqsafe deferred to a -+ * tasklet); calling it from inside spin_lock_bh would hold the -+ * lock across mac80211's full RX dispatch. -+ */ -+ skb_queue_splice_init(&entry->rx_queue, &local_drain); - spin_unlock_bh(&priv->ps_state_lock); -+ while ((skb = __skb_dequeue(&local_drain))) -+ ieee80211_rx_ni(priv->hw, skb); - #ifdef AP_AGGREGATE_FW_FIX - hw_priv->connected_sta_cnt++; - if(hw_priv->connected_sta_cnt>1) { -diff --git a/drivers/staging/bes2600/ap.h b/drivers/staging/bes2600/ap.h -index 6f2785288..f6e88c617 100644 ---- a/drivers/staging/bes2600/ap.h -+++ b/drivers/staging/bes2600/ap.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * mac80211 STA and AP API for mac80211 BES2600 drivers -+ * AP mode interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #ifndef AP_H_INCLUDED -diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h -index 0e60960bb..32bce5ebf 100644 ---- a/drivers/staging/bes2600/bes2600.h -+++ b/drivers/staging/bes2600/bes2600.h -@@ -1,18 +1,15 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Common private data for BES2600 drivers -+ * Common private data for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * - * Based on the mac80211 Prism54 code, which is - * Copyright (c) 2006, Michael Wu - * -- * Based on the islsm (softmac prism54) driver, which is: -+ * Based on the islsm (softmac prism54) driver, which is - * Copyright 2004-2006 Jean-Baptiste Note , et al. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_H -@@ -356,15 +353,23 @@ struct bes2600_common { - * Keeping in common structure for the time being. Will be moved to VIFF - * after the mechanism is clear */ - u8 ba_tid_mask; -- int ba_acc; /*TODO: Same as above */ -- int ba_cnt; /*TODO: Same as above */ -- int ba_cnt_rx; /*TODO: Same as above */ -- int ba_acc_rx; /*TODO: Same as above */ -- int ba_hist; /*TODO: Same as above */ -- struct timer_list ba_timer;/*TODO: Same as above */ -- spinlock_t ba_lock; /*TODO: Same as above */ -- bool ba_ena; /*TODO: Same as above */ -- struct work_struct ba_work; /*TODO: Same as above */ -+ /* -+ * Patch D: ba_lock removed. Per-frame TX/RX hot-path bumped these -+ * counters under spin_lock_bh; the lock did not protect any -+ * compound invariant that atomic ops can't satisfy. Counters are -+ * now atomic_t; ba_armed gates the once-per-window mod_timer -+ * arm via cmpxchg so concurrent TX/RX at a fresh window each -+ * try to claim the arm and exactly one succeeds. -+ */ -+ atomic_t ba_acc; -+ atomic_t ba_cnt; -+ atomic_t ba_cnt_rx; -+ atomic_t ba_acc_rx; -+ atomic_t ba_armed; -+ int ba_hist; -+ struct timer_list ba_timer; -+ atomic_t ba_ena; -+ struct work_struct ba_work; - bool is_BT_Present; - bool is_go_thru_go_neg; - u8 conf_listen_interval; -@@ -511,6 +516,9 @@ struct bes2600_common { - struct list_head coex_event_list; - spinlock_t coex_event_lock; - -+ /* Connection-loss-storm fast-recover (Trigger A). See sta.c. */ -+ struct work_struct connection_loss_storm_recover_work; -+ - /* member for low power */ - struct bes2600_pwr_t bes_power; - -@@ -596,6 +604,11 @@ struct bes2600_vif { - unsigned long rx_timestamp; - u32 cipherType; - -+ /* Decrypt-storm fast-recover (Trigger B). See txrx.c. */ -+ unsigned long decrypt_storm_window_start; -+ unsigned int decrypt_storm_count; -+ unsigned int decrypt_storm_recoveries; -+ struct work_struct decrypt_storm_recover_work; - - /* AP powersave */ - u32 link_id_map; -@@ -622,6 +635,10 @@ struct bes2600_vif { - /* CQM Implementation */ - struct delayed_work bss_loss_work; - struct delayed_work connection_loss_work; -+ /* Connection-loss-storm fast-recover (Trigger A). See sta.c. */ -+ unsigned long connection_loss_storm_window_start; -+ unsigned int connection_loss_storm_count; -+ unsigned int connection_loss_storm_recoveries; - struct work_struct tx_failure_work; - int delayed_link_loss; - spinlock_t bss_loss_lock; -@@ -856,4 +873,13 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv); - void bes2600_btusb_uninit(struct usb_interface *interface); - #endif - -+/* Decrypt-storm fast-recover helpers — see txrx.c. */ -+void bes2600_decrypt_storm_init(struct bes2600_vif *priv); -+void bes2600_decrypt_storm_account(struct bes2600_vif *priv); -+ -+/* Connection-loss-storm fast-recover helpers — see sta.c. */ -+void bes2600_connection_loss_storm_init(struct bes2600_vif *priv); -+bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv); -+void bes2600_connection_loss_storm_recover(struct work_struct *work); -+ - #endif /* BES2600_H */ + # basic function diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c -index dc5d3dac6..0d2bfa1c8 100644 +index dc5d3da..8d60b7c 100644 --- a/drivers/staging/bes2600/bes2600_factory.c +++ b/drivers/staging/bes2600/bes2600_factory.c -@@ -1,17 +1,15 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Factory calibration loader for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - +@@ -12,6 +12,7 @@ #include #include #include @@ -308,26 +87,7 @@ index dc5d3dac6..0d2bfa1c8 100644 #include #include #include -@@ -30,6 +28,18 @@ - - static DEFINE_MUTEX(factory_lock); - -+/* -+ * struct device * for request_firmware() context. Set once at SDIO -+ * probe via bes2600_factory_set_dev(). NULL is tolerated (falls back -+ * to the udev-less firmware-class path) but loses per-device logging. -+ */ -+static struct device *bes2600_factory_dev; -+ -+void bes2600_factory_set_dev(struct device *dev) -+{ -+ bes2600_factory_dev = dev; -+} -+ - /* - * It is only used for temporary storage. - * Every time get the factory, it will read from the -@@ -137,66 +147,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data) +@@ -137,38 +138,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data) */ static int factory_section_read_file(char *path, void *buffer) { @@ -347,7 +107,7 @@ index dc5d3dac6..0d2bfa1c8 100644 - fp = filp_open(path, O_RDONLY, 0); //S_IRUSR - if (IS_ERR(fp)) { - bes_devel("BES2600 : can't open %s\n",path); -+ ret = request_firmware(&fw, path, bes2600_factory_dev); ++ ret = request_firmware(&fw, path, NULL); + if (ret) { + bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret); return -1; @@ -373,125 +133,180 @@ index dc5d3dac6..0d2bfa1c8 100644 - ret = -1; - } - -- return ret; --} -- --/** -- * factory_section_write_file - Write data of specified length to file -- * @path: path of the file -- * @buffer: storage of write data -- * @size: length of data to write -- * -- * Return: length on success, negative error code otherwise. -- */ --static int factory_section_write_file(char *path, void *buffer, int size) --{ -- int ret = 0; -- struct file *fp; -- -- bes_devel("writing %s \n", path); -- -- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR); -- if (IS_ERR(fp)) { -- bes_devel("BES2600 : can't open %s\n",path); -- return -1; -- } -- -- ret = kernel_write(fp, buffer, size, &fp->f_pos); -- -- filp_close(fp,NULL); -- + memcpy(buffer, fw->data, fw->size); + ret = (int)fw->size; + release_firmware(fw); return ret; } -@@ -891,9 +867,22 @@ static inline int factory_build(uint8_t *dest_buf, struct factory_t *factory) - #endif - } +-- +2.54.0 + +From 13dd191defab19294d843218833860d0e1e33dcd Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:17:56 +0200 +Subject: [PATCH 02/29] bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for + PineTab2 factory.txt format +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The shipped factory calibration file bes2600_factory.txt on PineTab2 +(danctnix linux-firmware 0.3.5_2023.0209) contains 30 calibration +fields: head (3), iq/xtal (3), 2.4G power 11n (5), 5G power 11n (15), +bt (4). The file terminates with '%%\n' directly after edr_power. + +When STANDARD_FACTORY_EFUSE_FLAG is defined at compile time the driver +assembles STANDARD_FACTORY with an extra select_efuse_flag section +appended and expects 31 sscanf matches (FACTORY_MEMBER_NUM=31): + + __STANDARD_FACTORY + \"##select_efuse_flag\\nselect_efuse:%hx\\n\" + + \"%%%%\\n\" + +The PineTab2 factory.txt has no select_efuse_flag section, so sscanf +stops after field 30 and factory_parse() returns -1 with: + + bes2600_factory.txt parse fail + read and check bes2600/bes2600_factory.txt error + factory cali data get failed. + +This was latent until the preceding patch (use request_firmware() for +factory.txt read) fixed the path bug that masked the parse failure. + +Default STANDARD_FACTORY_EFUSE_FLAG to n. The flag remains overridable +at build time (make STANDARD_FACTORY_EFUSE_FLAG=y ...) for chips / +firmware packages that do ship the select_efuse_flag section. + +Also: the wsm_save_factory_txt_to_mcu() prototype in wsm.h was +inconsistently wrapped in a conditional that keyed on +STANDARD_FACTORY_EFUSE_FLAG, but the function definition in wsm.c and +the call site in sta.c are ungated. With the flag now defaulting to +n, the gcc -Werror=missing-prototypes flag breaks the build. Drop the +conditional wrapper around the prototype — the function exists and is +used regardless of the factory-parse flag. + +Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2 +6.19.10-danctnix1-1. With the flag defaulted off, factory_parse() +succeeds on the shipped factory.txt, factory_cali_data is populated, +and dmesg no longer shows the parse-fail / read-and-check-error / +factory-cali-data-get-failed sequence. + +Signed-off-by: Markus Fritsche +--- + bes2600/Makefile | 2 +- + bes2600/wsm.h | 2 -- + 2 files changed, 1 insertion(+), 3 deletions(-) + +diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile +index 788aee2..2dcba09 100644 +--- a/drivers/staging/bes2600/Makefile ++++ b/drivers/staging/bes2600/Makefile +@@ -65,7 +65,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116 + + ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y) + FACTORY_CRC_CHECK ?= n +-STANDARD_FACTORY_EFUSE_FLAG ?= y ++STANDARD_FACTORY_EFUSE_FLAG ?= n + FACTORY_PATH ?= bes2600/bes2600_factory.txt + endif + +diff --git a/drivers/staging/bes2600/wsm.h b/drivers/staging/bes2600/wsm.h +index 0673131..22845ac 100644 +--- a/drivers/staging/bes2600/wsm.h ++++ b/drivers/staging/bes2600/wsm.h +@@ -2236,7 +2236,5 @@ int wsm_cpu_usage_cmd(struct bes2600_common *hw_priv); + + int wsm_wifi_status_cmd(struct bes2600_common *hw_priv, uint32_t status); + +-#if defined(STANDARD_FACTORY_EFUSE_FLAG) + int wsm_save_factory_txt_to_mcu(struct bes2600_common *hw_priv, const u8 *data, int if_id, enum bes2600_rf_cmd_type cmd_type); +-#endif + #endif /* BES2600_HWIO_H_INCLUDED */ +-- +2.54.0 + +From 40a0a1a0c72ae5b4ee538f6e8a5d0def522606af Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:18:38 +0200 +Subject: [PATCH 03/29] bes2600: thread struct device * through factory + request_firmware() call + +Follow-up to \"bes2600: use request_firmware() for factory.txt read\". +That patch switched the factory calibration read path from filp_open() ++ kernel_read() to request_firmware(), but passed dev=NULL to +request_firmware() because factory_section_read_file() did not have a +struct device * in scope. The resulting logs carry the +'(NULL device *):' prefix and do not propagate a udev association. + +Add a module-local static struct device * used as the firmware-class +load context, plus a small exported setter: + + static struct device *bes2600_factory_dev; + void bes2600_factory_set_dev(struct device *dev); + +Wire bes2600_factory_set_dev(&func->dev) from bes2600_sdio_probe(), +right after bes2600_platform_data_init() so the platform layer has +already had a chance to use the same struct device for its own +initialization. + +factory_section_read_file() now passes bes2600_factory_dev (instead +of NULL) to request_firmware(). When the factory read happens before +probe (not currently the case on PineTab2) the pointer is still NULL +and request_firmware() accepts that; no regression. + +No API changes to bes2600_get_factory_cali_data() callers. The +char *path parameter remains (it is the firmware-class name fed +straight to request_firmware()). + +Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2 +6.19.10-danctnix1-1. Driver probes, factory data is read, and any +post-c5 factory diagnostics now carry the SDIO device identity +instead of '(NULL device *)'. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_factory.c | 14 +++++++++++++- + bes2600/bes2600_factory.h | 3 +++ + bes2600/bes2600_sdio.c | 4 ++++ + 3 files changed, 20 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c +index 8d60b7c..1cda447 100644 +--- a/drivers/staging/bes2600/bes2600_factory.c ++++ b/drivers/staging/bes2600/bes2600_factory.c +@@ -31,6 +31,18 @@ + + static DEFINE_MUTEX(factory_lock); +/* -+ * Rebuild the serialised calibration blob in file_buffer from the live -+ * in-memory factory_save_p. Previously this function also persisted the -+ * blob back to FACTORY_PATH via filp_open(O_CREAT) + kernel_write(); that -+ * is not acceptable in mainline, so the persistence step has been removed. -+ * -+ * The in-memory factory_save_p remains authoritative for the duration of -+ * the session; on the next probe the firmware-class file is read back -+ * read-only via request_firmware(). If cross-reboot persistence of runtime -+ * calibration updates becomes a requirement, the expected route is a -+ * userspace-facing dump interface (debugfs read-only blob, or nl80211 -+ * vendor command) that lets userspace read the serialised form and store -+ * it under its own privileges. ++ * struct device * for request_firmware() context. Set once at SDIO ++ * probe via bes2600_factory_set_dev(). NULL is tolerated (falls back ++ * to the udev-less firmware-class path) but loses per-device logging. + */ - static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *factory_save_p) - { -- int ret = 0; - int w_size; - u32 crc_len = sizeof(factory_data_t); - #ifndef STANDARD_FACTORY_EFUSE_FLAG -@@ -902,13 +891,11 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto ++static struct device *bes2600_factory_dev; ++ ++void bes2600_factory_set_dev(struct device *dev) ++{ ++ bes2600_factory_dev = dev; ++} ++ + /* + * It is only used for temporary storage. + * Every time get the factory, it will read from the +@@ -148,7 +160,7 @@ static int factory_section_read_file(char *path, void *buffer) - bes_devel("enter %s\n", __func__); - -- if (!file_buffer) { -+ if (!file_buffer) - return -ENOMEM; -- } - -- if (!factory_save_p) { -+ if (!factory_save_p) - return -ENOENT; -- } - - /* All initialized to space */ - memset(file_buffer, 32, FACTORY_MAX_SIZE); -@@ -920,22 +907,10 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto - w_size = factory_build(file_buffer, factory_save_p); - - if (w_size < 0 || w_size > FACTORY_MAX_SIZE) { -- bes_err("%s: build failed! ret = %d.", __func__, ret); -+ bes_err("%s: build failed! w_size = %d.", __func__, w_size); - return -ETXTBSY; - } - --#ifdef FACTORY_SAVE_MULTI_PATH -- /* avoid trailing characters '\0' */ -- file_buffer[w_size] = 32; -- ret = factory_section_write_file(FACTORY_PATH, file_buffer, FACTORY_MAX_SIZE); --#else -- ret = factory_section_write_file(FACTORY_PATH, file_buffer, w_size); --#endif -- if(ret < 0) { -- bes_err("%s: write failed! ret = %d.", __func__, ret); -- return ret; -- } -- - return 0; - } + bes_devel("requesting firmware-class %s\n", path); +- ret = request_firmware(&fw, path, NULL); ++ ret = request_firmware(&fw, path, bes2600_factory_dev); + if (ret) { + bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret); + return -1; diff --git a/drivers/staging/bes2600/bes2600_factory.h b/drivers/staging/bes2600/bes2600_factory.h -index 3835b0d9c..0b1a321d7 100644 +index 3835b0d..7dbe9f8 100644 --- a/drivers/staging/bes2600/bes2600_factory.h +++ b/drivers/staging/bes2600/bes2600_factory.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Factory calibration loader interface - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef __FACTORY_H__ - #define __FACTORY_H__ -@@ -199,6 +196,9 @@ enum factory_cali_status { +@@ -199,6 +199,9 @@ enum factory_cali_status { /* just calibrate 11n, other protocols are automatically mapped */ #define WIFI_RF_11N_MODE 0x15 @@ -501,86 +316,1088 @@ index 3835b0d9c..0b1a321d7 100644 /* read wifi & bt factory cali value*/ u8* bes2600_get_factory_cali_data(u8 *file_buffer, u32 *data_len, char *path); void factory_little_endian_cvrt(u8 *data); -diff --git a/drivers/staging/bes2600/bes2600_plat.h b/drivers/staging/bes2600/bes2600_plat.h -index 63c32750e..ebec63591 100644 ---- a/drivers/staging/bes2600/bes2600_plat.h -+++ b/drivers/staging/bes2600/bes2600_plat.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Platform data for BES2600 SDIO bus - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef BES2600_PLAT_H_INCLUDED - #define BES2600_PLAT_H_INCLUDED diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c -index 13d4ff1e5..517e6f874 100644 +index 13d4ff1..f172d53 100644 --- a/drivers/staging/bes2600/bes2600_sdio.c +++ b/drivers/staging/bes2600/bes2600_sdio.c -@@ -1,12 +1,13 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 SDIO driver for BES2600 device -+ * SDIO bus glue for BES2600 mac80211 driver -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. -+ * Derived from drivers/net/wireless/st/cw1200/cw1200_sdio.c -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin - * -- * Copyright (c) 2010, Bestechnic -- * Author: - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #define DEBUG 1 - #include -@@ -16,6 +17,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -28,8 +30,10 @@ - #include - +@@ -30,6 +30,7 @@ #include "bes2600.h" -+#include "bh.h" #include "sbus.h" #include "bes2600_plat.h" +#include "bes2600_factory.h" #include "hwio.h" #include "bes_chardev.h" #include "bes_log.h" -@@ -70,10 +74,12 @@ struct sbus_priv { - int rx_data_toggle; - #endif - #ifdef BES_SDIO_RX_MULTIPLE_ENABLE -- spinlock_t rx_queue_lock; -- struct sk_buff_head rx_queue; +@@ -1834,6 +1835,9 @@ static int bes2600_sdio_probe(struct sdio_func *func, + if (ret) + goto err; + ++ /* wire struct device into factory.c for request_firmware() context */ ++ bes2600_factory_set_dev(dev); ++ + self->pdata = bes2600_get_platform_data(); + self->func = func; + self->dev = &func->dev; +-- +2.54.0 + +From e8550e55fc7d3910ee690359d89d96c86cfb0347 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:37:45 +0200 +Subject: [PATCH 04/29] bes2600: gate device LP-mode entry on successful + per-VIF firmware handshake + +bes2600_pwr_enter_lp_mode() drives the transition to low-power for each +associated STA VIF: it pushes wsm_set_pm(), waits up to 5 seconds on +pm_enter_cmpl for the firmware to acknowledge, then unconditionally +calls bes2600_pwr_device_enter_lp_mode() to drop the device end of the +bus. + +Two bugs: + +1. A failed wsm_set_pm() only logs an error, then still falls into + wait_for_completion_timeout() on a completion the firmware will + never post (the set-mode command never reached it). The loop + therefore always blocks the full 5 s, logs a second error, and + proceeds. + +2. A genuine wait-timeout (firmware received the set-mode command but + never posted the indication) also only logs a warning. The code + then drops to bes2600_pwr_device_enter_lp_mode(), handing the + device subsystem an inconsistent view of mac-layer state. + +On PineTab2 (BES2600WM + RK3566) the second bug is the recurring +root-cause of the 'bes2600_pwr_enter_lp_mode, wait pm ind timeout' +message flooding dmesg every 5-10 s when the interface is associated +and idle. Sending the device to LP in that state cascades into the +SDIO TX path as the 'bes_sdio_memcpy_to_io_helper / sdio_tx_work' +WARN splat. + +Fix: + - Add a 'timeouts' counter; bump it on both failure paths. + - Skip the wait_for_completion entirely when wsm_set_pm() failed + (there is no completion to wait for). + - Only call bes2600_pwr_device_enter_lp_mode() when every per-VIF + handshake reached firmware-ACKed completion; otherwise return + -ETIMEDOUT and leave the device in its current power state. + +Tested-on: PineTab2 running linux-pinetab2 6.19.10-danctnix1-1. +Post-patch the handshake still fails on this particular firmware +revision (separate root-cause investigation outside this patch), but +the driver now returns -ETIMEDOUT cleanly instead of flooding dmesg +and destabilising the SDIO path. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_pwr.c | 20 +++++++++++++++++--- + 1 file changed, 17 insertions(+), 3 deletions(-) + +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index e7a1045..f62ae22 100644 +--- a/drivers/staging/bes2600/bes_pwr.c ++++ b/drivers/staging/bes2600/bes_pwr.c +@@ -472,6 +472,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + int i = 0; + struct bes2600_vif *priv; + int ret = 0; ++ int timeouts = 0; + char ip_str[20]; + unsigned long status = 0; + +@@ -528,22 +529,35 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + if (ret) { + atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); + bes_err("%s, set operation mode fail\n", __func__); ++ timeouts++; ++ continue; + } + + /* wait power save mode changed indication */ + status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ); + atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); + reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); +- if (!status) ++ if (!status) { + bes_err("%s, wait pm ind timeout\n", __func__); ++ timeouts++; ++ } + } else { + bes_devel("skip enter lp mode\n"); + } + } + } + +- /* set device low power configuration */ +- bes2600_pwr_device_enter_lp_mode(hw_priv); + /* -+ * Patch C v3: rx_queue, rx_queue_lock, rx_work removed (no relay). -+ * The bh thread now reads RX inline; the rx_buffer scratch area -+ * stays. Counters/timestamps stay for debugfs visibility. ++ * Enter the device-end of the LP transition only if every per-VIF ++ * mac80211 handshake reached firmware-ACKed completion. Doing the ++ * device-LP setup while any VIF is still pending leaves the driver ++ * in an inconsistent state that cascades into SDIO TX errors on ++ * the BES2600. + */ - u8 *rx_buffer; -- struct work_struct rx_work; - u32 rx_last_ctrl; - u32 rx_valid_ctrl; - u32 rx_total_ctrl_cnt; -@@ -94,6 +100,7 @@ struct sbus_priv { ++ if (timeouts == 0) ++ bes2600_pwr_device_enter_lp_mode(hw_priv); ++ else ++ ret = -ETIMEDOUT; + + return ret; + } +-- +2.54.0 + +From cd5f85e10480f02e289ea731b5eeec571000562c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:55:18 +0200 +Subject: [PATCH 05/29] bes2600: remove userspace /dev/bes2600 character device + interface + +bes_chardev.c implemented a custom character device at /dev/bes2600 with +its own parser and command-dispatch table, exposing operations such as +'wifi on|off', 'bt on|off', 'change_fw_type ', 'bt_wakeup', +'bt_sleep', and 'wakeup_read_flag'. None of these surfaces are used by +the in-tree driver - every kernel call site consumes the internal state +accessors (bes2600_chrdev_is_signal_mode, bes2600_chrdev_get_fw_type, +etc) directly, not through the cdev. + +The cdev interface is a standing upstream blocker for two reasons: + + 1. Drivers under drivers/staging/ and drivers/net/wireless/ are + expected to expose tuning via the firmware/nl80211/debugfs + infrastructure rather than a private /dev node with an ad-hoc + parser. + + 2. The cdev handlers keep a global bes_cdev singleton alive whose + ->cdev, ->dev_id, ->class and ->device pointers exist only to be + torn down; they add no functionality that nl80211 or rfkill do + not already provide (wifi/bt on-off, module_param for fw_type). + +Remove the userspace interface: + + - open / read / write / release file_operations handlers and the + bes2600_chardev_fops instance + - bes2600_op_* command handlers and bes2600_op_map_tab dispatcher + - bes2600_get_cmd_and_ifname / bes2600_recyle_cmd_and_ifname_mem + string helpers + - bes2600_load_uevent (its only caller was + bes2600_chrdev_wifi_force_close_work informing userspace of a + state it already gates via rfkill; that snprintf + + kobject_uevent_env block is gone too, the kernel-side + halt_device + switch_wifi(0) + chrdev_check_system_close + sequence remains) + - alloc_chrdev_region / cdev_init / cdev_add / class_create / + device_create in bes2600_chrdev_init plus the fail1/fail2/fail3 + unwind labels + - cdev_del / unregister_chrdev_region / device_destroy / + class_destroy in bes2600_chrdev_free + - cdev/dev_id/major/minor/class/device fields in struct bes_cdev + +What remains (unchanged behaviour): + + - fw_type module parameter - the primary user-facing knob for + signal/no-signal/BT mode switch + - All in-kernel bes2600_chrdev_* accessor functions called from + bes2600_sdio.c, bes_pwr.c, sta.c, bh.c, main.c, wsm.c, and + wifi_testmode_cmd.c (13 call sites) + - bes2600_chrdev_init / bes2600_chrdev_free as state-init / teardown + for the remaining bes_cdev state (waitqueues, workqueues, flags) + - DPD management (bes2600_chrdev_get_dpd_buffer / update / free) + - wifi_force_close worker, system-close logic, bus-probe state + machine + +Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2 +6.19.10-danctnix1-1. Driver continues to associate and pass traffic; +no kernel messages related to the cdev absence. Users that previously +wrote to /dev/bes2600 should switch to the fw_type module parameter +or (future patch c4) nl80211 testmode commands. + +Follow-ups: + + - c3.1: thread struct device * through bes2600_chrdev_is_signal_mode + and friends so the global bes2600_cdev singleton can be dropped + and the accessors scale to multi-device scenarios. + - c4: enable CONFIG_BES2600_TESTMODE and route nl80211 testmode + commands to the firmware's patch_wifi_testMode entry. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_chardev.c | 568 +----------------------------------------- + 1 file changed, 3 insertions(+), 565 deletions(-) + +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index f89dcb8..e2e4f1b 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -43,12 +43,6 @@ enum bus_probe_state { + }; + + struct bes_cdev { +- struct cdev cdev; +- dev_t dev_id; +- int major; +- int minor; +- struct class *class; +- struct device *device; + atomic_t num_proc; + wait_queue_head_t open_wq; + spinlock_t status_lock; +@@ -196,7 +190,7 @@ static int bes2600_switch_wifi(bool on) + return ret; + } + +-static int bes2600_switch_bt(bool on) ++int bes2600_switch_bt(bool on) + { + int ret = 0; + long status = 0; +@@ -229,11 +223,11 @@ static int bes2600_switch_bt(bool on) + /* check if there is a error when bootup */ + ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0; + } else { +- bes_devel("bes2600 activate bt.\n"); ++ bes_info("enable BT\n"); + ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_ON, SUBSYSTEM_BT, true); + } + } else { +- bes_devel("bes2600 deactivate bt.\n"); ++ bes_info("disable BT\n"); + bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_OFF, SUBSYSTEM_BT, false); + } + +@@ -249,392 +243,18 @@ static int bes2600_switch_bt(bool on) + return ret; + } + +-/* +- * This is a global function so we don't have to make many changes to +- * the driver. +- * +- * @wifi: 1 to turn on, 0 to turn off. Otherwise, leave unchanged +- * @bt: 1 to turn on, 0 to turn off. Otherwise, leave unchanged +- */ +-int bes2600_chrdev_switch_subsys_glb(int wifi, int bt) +-{ +- int ret = 0; +- +- switch (wifi) { +- case 0: +- ret = bes2600_switch_wifi(false); +- break; +- case 1: +- ret = bes2600_switch_wifi(true); +- break; +- default: +- break; +- } +- +- if (ret) +- goto result; +- +- switch (bt) { +- case 0: +- ret = bes2600_switch_bt(false); +- break; +- case 1: +- ret = bes2600_switch_bt(true); +- break; +- default: +- break; +- } +- +-result: +- return ret; +-} +-EXPORT_SYMBOL_GPL(bes2600_chrdev_switch_subsys_glb); +- +-static int bes2600_get_cmd_and_ifname(const char *str, char **result) +-{ +- int cmd_len = 0; +- int ifname_len = 0; +- char *sp = NULL; +- char *tmp_ptr = NULL; +- char *cmd_ptr = NULL; +- +- /* check if input arguments is valid */ +- if (!str || strncmp(str, "ifname:", 7) != 0) +- return -1; +- +- sp = strchr(str, ' '); +- if (strncmp(sp + 1, "cmd:", 4) != 0) +- return -1; +- +- /* extract interface name */ +- ifname_len = sp - str - 7; +- tmp_ptr = kmalloc(ifname_len + 1, GFP_KERNEL); +- if (!tmp_ptr) { +- return -2; +- } +- +- strncpy(tmp_ptr, str+7, ifname_len); +- tmp_ptr[ifname_len] = '\0'; +- result[0] = tmp_ptr; +- +- /* get command length */ +- cmd_ptr = strstr(str, "cmd:"); +- cmd_ptr += 4; +- sp = strchr(cmd_ptr, ' '); +- if (!sp) { /* the command don't have any parameter */ +- cmd_len = strlen(cmd_ptr); +- if (cmd_ptr[cmd_len - 1] == '\n') +- --cmd_len; +- } else { /* the command have one or more parameter */ +- cmd_len = sp - cmd_ptr; +- } +- +- /* copy command to out buffer */ +- tmp_ptr = kmalloc( cmd_len + 1, GFP_KERNEL); +- if (!tmp_ptr) { +- kfree(result[0]); +- result[0] = NULL; +- return -3; +- } +- +- strncpy(tmp_ptr, cmd_ptr, cmd_len); +- tmp_ptr[cmd_len] = '\0'; +- result[1] = tmp_ptr; +- +- return 0; +-} +- +-static void bes2600_recyle_cmd_and_ifname_mem(char **info) +-{ +- if (info[0]) { +- kfree(info[0]); +- info[0] = NULL; +- } +- +- if (info[1]) { +- kfree(info[1]); +- info[1] = NULL; +- } +- +-} +- +-static int bes2600_op_default_handler(const char *str) +-{ +- char *info[2] = {0}; +- +- if (bes2600_get_cmd_and_ifname(str, info) == 0) { +- bes_devel("cmd(%s) on %s not handled\n", info[1], info[0]); +- } else { +- bes_err("%s get command fail, the origin string is %s\n", __func__, str); +- } +- +- bes2600_recyle_cmd_and_ifname_mem(info); +- +- return 0; +-} +- +-static int bes2600_op_wifi_bt_on_off(const char *str) +-{ +- char *info[2] = {0}; +- int ret = 0; +- enum wait_state wait_state; +- enum bus_probe_state probe_state; +- unsigned long status = 0; +- +- spin_lock(&bes2600_cdev.status_lock); +- probe_state = bes2600_cdev.bus_probe; +- wait_state = bes2600_cdev.wait_state; +- spin_unlock(&bes2600_cdev.status_lock); + +- /* only work for wifi signal mode */ +- if (bes2600_cdev.fw_type != BES2600_FW_TYPE_WIFI_SIGNAL) +- return -EFAULT; + +- /* wait bus probe operation end */ +- if (probe_state == BES2600_BUS_PROBE_START) { +- bes_devel("wait bus probe operation end\n"); +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- (bes2600_cdev.bus_probe > BES2600_BUS_PROBE_START), +- HZ); +- WARN_ON(status <= 0); +- } + +- /* must wait previous operation end in critical section */ +- if (wait_state != BES2600_BOOT_WAIT_NONE) { +- bes_devel("wait previous operation end\n"); +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- (bes2600_cdev.wait_state == BES2600_BOOT_WAIT_NONE), +- HZ * 8); +- WARN_ON(status <= 0); +- } + +- /* if dpd calibration is doing, modify wifi and bt state directly */ +- spin_lock(&bes2600_cdev.status_lock); +- if (bes2600_cdev.bus_probe == BES2600_BUS_PROBE_OK && !bes2600_cdev.dpd_calied) { +- if (bes2600_get_cmd_and_ifname(str, info) == 0) { +- if (strncmp(info[1], "WIFI_ON", 7) == 0) { +- bes2600_cdev.wifi_opened = true; +- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) { +- bes2600_cdev.wifi_opened = false; +- } else if (strncmp(info[1], "BT_ON", 5) == 0) { +- bes2600_cdev.bt_opened = true; +- bes2600_cdev.bton_pending = true; +- } else if (strncmp(info[1], "BT_OFF", 6) == 0) { +- bes2600_cdev.bt_opened = false; +- bes2600_cdev.bton_pending = false; +- } +- } +- bes2600_recyle_cmd_and_ifname_mem(info); +- spin_unlock(&bes2600_cdev.status_lock); + +- /* wait probe done event */ +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- bes2600_bootup_end(), HZ * 8); +- WARN_ON(status <= 0); + +- return (status <= 0 || bes2600_chrdev_is_bus_error()) ? -EFAULT : 0; +- } +- spin_unlock(&bes2600_cdev.status_lock); + +- /* process wifi/bt on/off operation */ +- if (bes2600_get_cmd_and_ifname(str, info) == 0) { +- if (strncmp(info[1], "WIFI_ON", 7) == 0) { +- ret = bes2600_switch_wifi(1); +- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) { +- ret = bes2600_switch_wifi(0); +- } else if (strncmp(info[1], "BT_ON", 5) == 0) { +- ret = bes2600_switch_bt(1); +- } else if (strncmp(info[1], "BT_OFF", 6) == 0) { +- ret = bes2600_switch_bt(0); +- } +- } + +- if (!ret && bes2600_chrdev_check_system_close()) +- ret = bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops, +- bes2600_cdev.sbus_priv); +- +- bes2600_recyle_cmd_and_ifname_mem(info); +- +- return ret ; +-} +- +- +-static int bes2600_op_change_fw_type(const char *str) +-{ +- int ret = 0; +- int temp = 0; +- long status = 0; +- char *cmd_ptr = NULL; +- char fw_type[5] = {0}; +- bool sys_closed = bes2600_chrdev_check_system_close(); +- +- bes_devel("%s is called, arg:%s\n", __func__, str); +- +- if (!bes2600_cdev.sbus_ops->power_switch && !bes2600_cdev.sbus_ops->reboot) +- return -EPERM; +- +- /* check if user input is valid */ +- cmd_ptr = strstr(str, "CHANGE_FW_TYPE "); +- if (strlen(str) < 16 || !cmd_ptr) { +- bes_err("the format of \"%s\" is error\n", str); +- return -EINVAL; +- } +- +- /* convert fw_type from string to int */ +- strncpy(fw_type, cmd_ptr + 14, 4); +- fw_type[0] = '+'; +- ret = kstrtoint(fw_type, 10, &temp); +- if (ret < 0) { +- bes_err("%s parse error\n", __func__); +- return -EINVAL; +- } +- +- /* no need to realod firmware if new fw_type is equal to the old */ +- if (temp == bes2600_cdev.fw_type ) { +- bes_devel("fw type is equal\n"); +- return 0; +- } +- +- /* close wifi net device */ +- if (bes2600_cdev.sbus_priv +- && bes2600_is_net_dev_created(bes2600_cdev.sbus_priv)) { +- bes2600_unregister_net_dev(bes2600_cdev.sbus_priv); +- } +- +- /* update firmware type */ +- bes2600_cdev.fw_type = temp; +- bes2600_chrdev_update_signal_mode(); +- +- if (!sys_closed) { +- /* close device to call disconnect function */ +- if (bes2600_cdev.sbus_ops->power_switch) +- bes2600_cdev.sbus_ops->power_switch(bes2600_cdev.sbus_priv, 0); +- else if (bes2600_cdev.sbus_ops->reboot) +- bes2600_cdev.sbus_ops->reboot(bes2600_cdev.sbus_priv); +- } +- +- if (bes2600_cdev.sbus_ops->reboot) +- bes2600_chrdev_start_bus_probe(); +- +- /* wait disconnect event */ +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, (bes2600_cdev.sbus_priv == NULL), HZ * 10); +- WARN_ON(status <= 0); +- +- if (bes2600_cdev.dpd_calied +- && bes2600_chrdev_check_system_close()) { +- bes_devel("no need to reload firmware\n"); +- return 0; +- } +- +- bes_devel("reload firmware...\n"); +- /* power on device to call probe function */ +- if (bes2600_cdev.sbus_ops->power_switch) +- bes2600_cdev.sbus_ops->power_switch(NULL, 1); +- +- /* wait probe done event */ +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- bes2600_bootup_end(), HZ * 10); +- WARN_ON(status <= 0); +- +- ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0; +- +- +- return ret; +-} +- +-static int bes2600_op_bt_wakeup(const char *str) +-{ +- int ret = 0; +- unsigned long status = 0; +- +- spin_lock(&bes2600_cdev.status_lock); +- if (!bes2600_cdev.bt_opened) { +- spin_unlock(&bes2600_cdev.status_lock); +- return -EFAULT; +- } +- spin_unlock(&bes2600_cdev.status_lock); +- +- /* wait probe done event */ +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- bes2600_bootup_end(), HZ * 8); +- if (status <= 0 || bes2600_chrdev_is_bus_error()) +- return -EFAULT; +- +- bes_devel("bes2600 wakeup bt.\n"); +- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_ON, SUBSYSTEM_BT_LP, true); +- +- return ret; +-} +- +-static int bes2600_op_bt_sleep(const char *str) +-{ +- int ret = 0; +- unsigned long status = 0; +- +- spin_lock(&bes2600_cdev.status_lock); +- if (!bes2600_cdev.bt_opened) { +- spin_unlock(&bes2600_cdev.status_lock); +- return -EFAULT; +- } +- spin_unlock(&bes2600_cdev.status_lock); +- +- /* wait probe done event */ +- status = wait_event_timeout(bes2600_cdev.probe_done_wq, +- bes2600_bootup_end(), HZ * 8); +- if (status <= 0 || bes2600_chrdev_is_bus_error()) +- return -EFAULT; +- +- bes_devel("bes2600 allow bt sleep.\n"); +- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_OFF, SUBSYSTEM_BT_LP, false); +- +- return ret; +-} +- +-static int bes2600_op_set_wakeup_read_flag(const char *str) +-{ +- bes_devel("%s is called, arg:%s\n", __func__, str); +- spin_lock(&bes2600_cdev.status_lock); +- bes2600_cdev.read_flag = BES_CDEV_READ_WAKEUP_STATE; +- spin_unlock(&bes2600_cdev.status_lock); +- +- return 0; +-} + + #ifdef FW_DOWNLOAD_UART_DAEMON +-int bes2600_load_uevent(char *env[]) +-{ +- return kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env); +-} + #endif + +-static struct bes2600_op_map bes2600_op_map_tab[] ={ +- /*op op_len handler */ +- {"P2P_SET_NOA", 11, bes2600_op_default_handler}, +- {"P2P_SET_PS", 10, bes2600_op_default_handler}, +- {"SET_AP_WPS_P2P_IE", 17, bes2600_op_default_handler}, +- {"LINKSPEED", 9, bes2600_op_default_handler}, +- {"RSSI", 4, bes2600_op_default_handler}, +- {"GETBAND", 7, bes2600_op_default_handler}, +- {"WLS_BATCHING", 12, bes2600_op_default_handler}, +- {"MACADDR", 7, bes2600_op_default_handler}, +- {"RXFILTER-START", 14, bes2600_op_default_handler}, +- {"RXFILTER-STOP", 13, bes2600_op_default_handler}, +- {"RXFILTER-ADD", 12, bes2600_op_default_handler}, +- {"RXFILTER-REMOVE", 15, bes2600_op_default_handler}, +- {"BTCOEXMODE", 10, bes2600_op_default_handler}, +- {"BTCOEXSCAN-START", 16, bes2600_op_default_handler}, +- {"BTCOEXSCAN-STOP", 15, bes2600_op_default_handler}, +- {"SETSUSPENDMODE", 14, bes2600_op_default_handler}, +- {"COUNTRY", 7, bes2600_op_default_handler}, +- {"WIFI_ON", 7, bes2600_op_wifi_bt_on_off}, +- {"WIFI_OFF", 8, bes2600_op_wifi_bt_on_off}, +- {"BT_ON", 5, bes2600_op_wifi_bt_on_off}, +- {"BT_OFF", 6, bes2600_op_wifi_bt_on_off}, +- {"CHANGE_FW_TYPE", 14, bes2600_op_change_fw_type}, +- {"BT_WAKEUP", 9, bes2600_op_bt_wakeup}, +- {"BT_SLEEP", 8, bes2600_op_bt_sleep}, +- {"WAKEUP_STATE", 12, bes2600_op_set_wakeup_read_flag}, +-}; + + static int bes2600_chrdev_check_system_close_internal(void) + { +@@ -644,123 +264,10 @@ static int bes2600_chrdev_check_system_close_internal(void) + && (bes2600_cdev.wifi_opened == false); + } + +-static int bes2600_chrdev_open(struct inode *inode, struct file *filp) +-{ +- if (atomic_read(&bes2600_cdev.num_proc) > 0) { +- wait_event_timeout(bes2600_cdev.open_wq, +- (atomic_read(&bes2600_cdev.num_proc) == 0), +- MAX_SCHEDULE_TIMEOUT); +- } + +- bes_devel("bes2600 char device is opened\n"); +- atomic_inc(&bes2600_cdev.num_proc); + +- return 0; +-} + +-static ssize_t bes2600_chrdev_read(struct file *file, char __user *user_buf, +- size_t count, loff_t *ppos) +-{ +- char buf[64] = {0}; +- unsigned int len; +- long status = 0; + +- switch (bes2600_cdev.read_flag) { +- case BES_CDEV_READ_WAKEUP_STATE: +- if (bes2600_chrdev_wakeup_by_event_get() > WAKEUP_EVENT_NONE) { +- status = wait_event_timeout(bes2600_cdev.wakeup_reason_wq, +- bes2600_chrdev_wakeup_by_event_get() == WAKEUP_EVENT_NONE, HZ * 2); +- WARN_ON(status <= 0); +- } +- len = sprintf(buf, "wakeup_reason: %u, src_port: %u\n", +- bes2600_cdev.wakeup_state, bes2600_cdev.src_port); +- break; +- default: +- len = sprintf(buf, "dpd_calied:%d wifi_opened:%d bt_opened:%d fw_type:%d\n", +- bes2600_cdev.dpd_calied, +- bes2600_cdev.wifi_opened, +- bes2600_cdev.bt_opened, +- bes2600_cdev.fw_type); +- break; +- } +- +- len = sizeof(buf); +- /* reset read flag */ +- spin_lock(&bes2600_cdev.status_lock); +- bes2600_cdev.read_flag = BES_CDEV_READ_NUM_MAX; +- spin_unlock(&bes2600_cdev.status_lock); +- +- return simple_read_from_buffer(user_buf, count, ppos, buf, len); +-} +- +-static ssize_t bes2600_chrdev_write(struct file *file, +- const char __user *user_buf, size_t count, loff_t *ppos) +-{ +- int i = 0; +- int cmd_num = ARRAY_SIZE(bes2600_op_map_tab); +- int cmd_len = 0; +- int ret = 0; +- char *info[2] = {0}; +- char *buf = NULL; +- +- /* copy content from user space to kernel */ +- /* message format:"ifname:wlanx cmd:xxx arg1 arg2 ..." */ +- buf = kmalloc(count + 1, GFP_KERNEL); +- if (copy_from_user(buf, user_buf, count)) +- return -EFAULT; +- +- /* add terminal character */ +- buf[count] = '\0'; +- +- /* extract comand and interface */ +- if (bes2600_get_cmd_and_ifname(buf, info) != 0) { +- bes_err("%s get command fail, the origin string is %s\n", __func__, buf); +- kfree(buf); +- return -EINVAL; +- } +- +- /* match operation item and execure its handler */ +- cmd_len = strlen(info[1]); +- for (i = 0; i < cmd_num; i++) { +- if (cmd_len < bes2600_op_map_tab[i].op_len) +- continue; +- +- if (strncasecmp(info[1], bes2600_op_map_tab[i].op, bes2600_op_map_tab[i].op_len) == 0) { +- ret = bes2600_op_map_tab[i].handler(buf); +- break; +- } +- } +- +- /* operation item mismatch */ +- if (i == cmd_num) { +- bes_err("cmd(%s) mismatch\n", info[1]); +- } +- +- bes2600_recyle_cmd_and_ifname_mem(info); +- kfree(buf); +- +- return (ret == 0) ? count : ret; +-} +- +-static int bes2600_chrdev_release (struct inode *inode, struct file *file) +-{ +- if (atomic_dec_and_test(&bes2600_cdev.num_proc)) { +- wake_up(&bes2600_cdev.open_wq); +- } +- +- bes_devel("bes2600 char device is closed\n"); +- +- return 0; +-} +- +-static struct file_operations bes2600_chardev_fops = +-{ +- .owner = THIS_MODULE, +- .open = bes2600_chrdev_open, +- .read = bes2600_chrdev_read, +- .write = bes2600_chrdev_write, +- .release = bes2600_chrdev_release, +-}; + + #ifdef BES2600_WRITE_DPD_TO_FILE + static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size) +@@ -1126,7 +633,6 @@ void bes2600_chrdev_wakeup_bt(void) + bes_err("Wakeup BT fail in resume\n"); + } + } +-EXPORT_SYMBOL_GPL(bes2600_chrdev_wakeup_bt); + + int bes2600_chrdev_get_fw_type(void) + { +@@ -1148,7 +654,6 @@ bool bes2600_chrdev_is_bus_error(void) + + return error; + } +-EXPORT_SYMBOL_GPL(bes2600_chrdev_is_bus_error); + + void bes2600_chrdev_update_signal_mode(void) + { +@@ -1167,12 +672,6 @@ void bes2600_chrdev_update_signal_mode(void) + + static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work) + { +- char wifi_state[15]; +- char bt_state[15]; +- char fw_type[15]; +- char *env[] = { wifi_state, bt_state, fw_type, NULL }; +- int ret; +- + if (bes2600_chrdev_is_wifi_opened()) { + bes_devel("system exeception, force wifi down\n"); + +@@ -1189,14 +688,6 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work) + bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops, + bes2600_cdev.sbus_priv); + } +- +- /* notify userspace */ +- snprintf(wifi_state, sizeof(wifi_state), "WIFI_OPENED=%d", bes2600_cdev.wifi_opened); +- snprintf(bt_state, sizeof(bt_state), "BT_OPENED=%d", bes2600_cdev.bt_opened); +- snprintf(fw_type, sizeof(fw_type), "FW_TYPE=%d", bes2600_cdev.fw_type); +- ret = kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env); +- if (!ret) +- bes_err("bes2600 notify userspace failed\n"); + } + } + +@@ -1290,46 +781,6 @@ int bes2600_chrdev_wakeup_by_event_get(void) + + int bes2600_chrdev_init(struct sbus_ops *ops) + { +- int ret = 0; +- +- /* allocate devide id */ +- ret = alloc_chrdev_region(&bes2600_cdev.dev_id, 0, 1, "bes2600_chrdev"); +- if (ret < 0){ +- bes_err("bes2600 alloc device id fail\n"); +- ret = -EFAULT; +- goto fail; +- } +- +- /* extract major and minor device id */ +- bes2600_cdev.major = MAJOR(bes2600_cdev.dev_id); +- bes2600_cdev.minor = MINOR(bes2600_cdev.dev_id); +- +- /* add char device and bind operation function */ +- bes2600_cdev.cdev.owner = THIS_MODULE; +- cdev_init(&bes2600_cdev.cdev, &bes2600_chardev_fops); +- ret = cdev_add(&bes2600_cdev.cdev, bes2600_cdev.dev_id, 1); +- if (ret < 0){ +- bes_err("bes2600 char device add fail\n"); +- ret = -EFAULT; +- goto fail1; +- } +- +- /* create class for creating device node */ +- bes2600_cdev.class = class_create("bes2600_chrdev"); +- if (IS_ERR(bes2600_cdev.class)){ +- bes_err("bes2600 char device add fail\n"); +- ret = -EFAULT; +- goto fail2; +- } +- +- /* get char device pointer */ +- bes2600_cdev.device = device_create(bes2600_cdev.class, NULL, bes2600_cdev.dev_id, NULL, "bes2600"); +- if (IS_ERR(bes2600_cdev.device)){ +- bes_err("bes2600 char device create fail\n"); +- ret = -EFAULT; +- goto fail3; +- } +- + /* initialise global variable */ + atomic_set(&bes2600_cdev.num_proc, 0); + init_waitqueue_head(&bes2600_cdev.open_wq); +@@ -1361,15 +812,6 @@ int bes2600_chrdev_init(struct sbus_ops *ops) + bes_devel("%s done\n", __func__); + + return 0; +- +-fail3: +- class_destroy(bes2600_cdev.class); +-fail2: +- cdev_del(&bes2600_cdev.cdev); +-fail1: +- unregister_chrdev_region(bes2600_cdev.dev_id, 1); +-fail: +- return ret; + } + + void bes2600_chrdev_free(void) +@@ -1379,9 +821,5 @@ void bes2600_chrdev_free(void) + bes2600_free_dpd_log_buffer(); + #endif + bes2600_chrdev_free_dpd_data(); +- cdev_del(&bes2600_cdev.cdev); +- unregister_chrdev_region(bes2600_cdev.dev_id, 1); +- device_destroy(bes2600_cdev.class, bes2600_cdev.dev_id); +- class_destroy(bes2600_cdev.class); + bes_devel("%s done\n", __func__); + } +-- +2.54.0 + +From 789ab98e4cd4a0c2c43a54da6462b6b05f3af8f2 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:04:27 +0200 +Subject: [PATCH 06/29] bes2600: enable CONFIG_BES2600_TESTMODE by default + + fix bit-rotted testmode plumbing +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The driver implements a mac80211 testmode_cmd operation that dispatches +to a set of vendor commands (GET_TX_POWER_LEVEL, GET_TX_POWER_RANGE, +SET_SNAP_FRAME, TSM_STATS, GET_ROAM_DELAY, GET_STREAM, etc) plus the +BES2600 RF-test path (bes2600_vendor_rf_cmd → firmware +patch_wifi_testMode). The testmode handlers and the .testmode_cmd +binding in struct ieee80211_ops are conditionally compiled under +CONFIG_BES2600_TESTMODE, which previously defaulted to n. + +Flip the Makefile default from n to y so wifi_testmode_cmd.o is +included in the build and the .testmode_cmd op is populated. On the +PineTab2 target kernel (linux-pinetab2 6.19.10-danctnix1, built with +CONFIG_NL80211_TESTMODE=y) this exposes the BES2600 RF-test surface +through the standard nl80211 testmode interface ('iw phy0 ...'). + +This also makes visible two classes of bit-rot that had accumulated +while nobody was building with CONFIG_BES2600_TESTMODE=y: + +1. sta.c contains ~41 calls to bes2600_info() / bes2600_err() / + bes2600_warn() / bes2600_dbg() / bes2600_err_with_cond() - a + legacy log-macro family carrying a BES2600_DBG_* subsystem-id + first argument. Neither the macros nor any of the BES2600_DBG_* + constants are defined anywhere in the tree. The same call pattern + appears under #if defined(BES2600_DETECTION_LOGIC) in hwio.c and + under CONFIG_BES2600_ITP in itp.c, both normally disabled. + + Add minimal shim macros to bes_log.h that rewire the calls onto + the existing bes_info() / bes_err() / bes_warn() / bes_devel() + family (ignoring the subsystem id). Define BES2600_DBG_SBUS, + BES2600_DBG_DOWNLOAD, BES2600_DBG_ITP and BES2600_DBG_TEST_MODE + as 0 constants for documentation / grep. + +2. bes2600_start_stop_tsm(), bes2600_get_tsm_params(), and + bes2600_get_roam_delay() are declared in sta.c with external + linkage but have no prototype in any header. All callers live in + sta.c (inside bes2600_testmode_cmd). With CONFIG_BES2600_TESTMODE + off the compiler never sees them; with it on gcc + -Werror=missing-prototypes breaks the build. + + Mark the three functions static. (Keeping them file-local also + matches their actual usage.) + +Both changes are strictly scoped to make CONFIG_BES2600_TESTMODE=y +buildable; no behavioural change when the flag is off. + +Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2 +6.19.10-danctnix1-1 with CONFIG_NL80211_TESTMODE=y. Module builds +cleanly, nl80211 testmode interface reachable via 'iw phy0 ...' from +userspace. + +Signed-off-by: Markus Fritsche +--- + bes2600/Makefile | 2 +- + bes2600/bes_log.h | 23 +++++++++++++++++++++++ + bes2600/sta.c | 6 +++--- + 3 files changed, 27 insertions(+), 4 deletions(-) + +diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile +index 2dcba09..2c1a850 100644 +--- a/drivers/staging/bes2600/Makefile ++++ b/drivers/staging/bes2600/Makefile +@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build + # feature option + BES2600 ?= m + +-CONFIG_BES2600_TESTMODE ?= n ++CONFIG_BES2600_TESTMODE ?= y + + CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n + +diff --git a/drivers/staging/bes2600/bes_log.h b/drivers/staging/bes2600/bes_log.h +index 605cea8..65cf703 100644 +--- a/drivers/staging/bes2600/bes_log.h ++++ b/drivers/staging/bes2600/bes_log.h +@@ -8,3 +8,26 @@ extern struct device *global_dev; + #define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__) + #define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__) + #define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__) ++ ++/* ++ * Legacy debug-subsystem-tagged log macros. The per-subsystem filtering ++ * was never implemented in-tree; these shims let code paths gated by ++ * CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC ++ * build when their conditions are enabled. The first argument is ++ * currently unused; pick one of the BES2600_DBG_* constants below for ++ * documentation. ++ */ ++#define BES2600_DBG_SBUS 0 ++#define BES2600_DBG_DOWNLOAD 0 ++#define BES2600_DBG_ITP 0 ++#define BES2600_DBG_TEST_MODE 0 ++ ++#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__) ++#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__) ++#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__) ++#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__) ++#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \ ++ do { \ ++ if (_cond) \ ++ bes_err(fmt, ##__VA_ARGS__); \ ++ } while (0) +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index ca1c77c..bc6d483 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -3654,7 +3654,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw, + * + * Returns: 0 on success or non zero value on failure + */ +-int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) ++static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) + { + struct bes_msg_start_stop_tsm *start_stop_tsm = + (struct bes_msg_start_stop_tsm *) data; +@@ -3684,7 +3684,7 @@ int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) + * + * Returns: TSM parameters collected + */ +-int bes2600_get_tsm_params(struct ieee80211_hw *hw) ++static int bes2600_get_tsm_params(struct ieee80211_hw *hw) + { + struct bes2600_common *hw_priv = hw->priv; + struct bes_tsm_stats tsm_stats; +@@ -3724,7 +3724,7 @@ int bes2600_get_tsm_params(struct ieee80211_hw *hw) + * + * Returns: Returns the last measured roam delay + */ +-int bes2600_get_roam_delay(struct ieee80211_hw *hw) ++static int bes2600_get_roam_delay(struct ieee80211_hw *hw) + { + struct bes2600_common *hw_priv = hw->priv; + u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000; +-- +2.54.0 + +From 2f9b4c719faf9563895c064439a7da25f35c8fc7 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH 07/29] bes2600: bounce SDIO TX buffers to avoid DMA OOB read + +The SDIO TX path rounds the DMA transfer length up to the host's +current block size and hands that length to dma_map_sg() via +sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work(). +tx_buffer->buf typically aliases into an skb linear head whose +allocated size matches tx_buffer->len, not the block-aligned +align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up +to one block past the end of the skb. On a PineTab2 with KFENCE +enabled this fires as: + + BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic + Out-of-bounds read at ... (704B right of kfence-#...): + __pi_memcpy_generic + swiotlb_tbl_map_single + swiotlb_map + dma_direct_map_sg + __dma_map_sg_attrs + dma_map_sg_attrs + dw_mci_pre_dma_transfer + __dw_mci_start_request + ... + bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600] + sdio_tx_work+0x2b4/0x4a0 [bes2600] + +allocated by ... pskb_expand_head / validate_xmit_skb / tcp_* + +In addition to being undefined behavior, the padding bytes (which +come from whatever memory follows the skb) are transmitted to the +peer, leaking kernel memory on the air. + +Allocate a driver-owned DMA-page bounce buffer sized to +MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for +sdio_tx_work. Each TX buffer is copied into its bounce slot and the +tail (align - tx_buffer->len bytes) is zeroed. This mirrors the +existing bounce pattern already used by bes2600_sdio_memcpy_toio() +via single_gathered_buffer; a separate allocation is used for the +TX path because single_gathered_buffer is only serialised via +sdio_claim_host and sdio_tx_work accumulates scatter entries before +claiming the bus. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index f172d53..b9d836f 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -95,6 +95,7 @@ struct sbus_priv { struct work_struct tx_work; struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1]; struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1]; @@ -588,153 +1405,7 @@ index 13d4ff1e5..517e6f874 100644 u32 tx_data_cnt; u32 tx_xfer_cnt; u32 tx_proc_cnt; -@@ -409,10 +416,19 @@ static void bes2600_sdio_irq_handler(struct sdio_func *func) - - bes_devel("%s called, fw_started:%d \n", - __func__, self->fw_started); -- if (likely(self->fw_started && self->core)) { -- queue_work(self->sdio_wq, &self->rx_work); -+ /* -+ * Patch C v3: no more sdio_rx_work relay. Wake the bh thread -+ * directly via self->irq_handler (bes2600_irq_handler in bh.c -+ * which bumps bh_rx atomic + wakes bh_wq). The bh thread will -+ * then call sbus_ops->bus_rx_batch() to do the SDIO read inline. -+ * Matches cw1200 mainline IRQ → bh-direct architecture. -+ */ -+ if (likely(self->fw_started && self->core && self->irq_handler)) { -+ spin_lock_irqsave(&self->lock, flags); -+ self->irq_handler(self->irq_priv); -+ spin_unlock_irqrestore(&self->lock, flags); - self->last_irq_timestamp = jiffies; -- } else if(self->irq_handler) { -+ } else if (self->irq_handler) { - spin_lock_irqsave(&self->lock, flags); - self->irq_handler(self->irq_priv); - spin_unlock_irqrestore(&self->lock, flags); -@@ -809,10 +825,15 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8 - skb_put(skb, packet_len); - memcpy(skb->data, &data[pos], packet_len); - bes_devel("%s, %d,%d\n", __func__, packet_len, pos); -- spin_lock(&self->rx_queue_lock); -- skb_queue_tail(&self->rx_queue, skb); - self->rx_data_cnt++; -- spin_unlock(&self->rx_queue_lock); -+ /* -+ * Patch C v3: deliver the SKB directly into the WSM/mac80211 -+ * stack from the bh thread. No rx_queue, no inter-thread -+ * handoff, no atomic_t needed on the counters that -+ * wsm_release_tx_buffer touches — single-writer-from-bh is -+ * preserved by construction. See bh.c for the contract block. -+ */ -+ bes2600_bh_handle_rx_skb(self->core, skb); - packet_len = (packet_len + 3) & (~0x3); - pos += packet_len; - #ifdef BES_SDIO_OPTIMIZED_LEN -@@ -823,17 +844,31 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8 - return 0; - } - --static void sdio_rx_work(struct work_struct *work) -+/* -+ * Patch C v3: bh thread calls this directly via sbus_ops->bus_rx_batch. -+ * No more sdio_rx_work workqueue. SDIO read sequence (lock → -+ * read_ctrl → memcpy_fromio → packets_check → extract_packets) runs -+ * inline in bh-thread context. Each parsed SKB is delivered via -+ * bes2600_bh_handle_rx_skb() from extract_packets — no rx_queue, no -+ * second worker, no inter-thread handoff. -+ * -+ * Architecture matches cw1200 mainline. Single-writer-from-bh -+ * invariant on hw_bufs_used preserved by construction. -+ * -+ * Returns 0 on success (caller's bh outer loop decides whether to -+ * continue), negative on bus read error. On error: triggers -+ * wifi_force_close (same as the old sdio_rx_work). -+ */ -+static int bes2600_sdio_read_rx_batch(struct sbus_priv *self) - { -- int ret, again = 0, retry = 0, crc_retry = 0; -+ int ret = 0, again = 0, retry = 0, crc_retry = 0; - u32 ctrl_reg = 0; - int total_len; -- struct sbus_priv *self = container_of(work, struct sbus_priv, rx_work); - u8 *buf = self->rx_buffer; - - /* don't read/write sdio when sdio error */ - if (bes2600_chrdev_is_bus_error()) -- return; -+ return 0; - - bes2600_gpio_wakeup_mcu(self, GPIO_WAKE_FLAG_SDIO_RX); - -@@ -888,6 +923,10 @@ static void sdio_rx_work(struct work_struct *work) - goto failed; - } - -+ /* -+ * extract_packets parses the multi-RX buffer and calls -+ * bes2600_bh_handle_rx_skb() per SKB. No queueing. -+ */ - if ((ret = bes2600_sdio_extract_packets(self, ctrl_reg, buf))) { - bes_err("%s,%d error=%d\n", __func__, __LINE__, ret); - goto failed; -@@ -895,22 +934,16 @@ static void sdio_rx_work(struct work_struct *work) - - ctrl_reg = 0; - -- if (likely(self->irq_handler)) { -- self->irq_handler(self->irq_priv); -- } else { -- bes_err("%s,%d\n", __func__, __LINE__); -- goto failed; -- } -- - } while (again); - - bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX); -- return; -+ return 0; - - failed: - bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX); - bes2600_chrdev_wifi_force_close(self->core, false); - WARN_ON(1); -+ return -1; - } - - static void sdio_scan_work(struct work_struct *work) -@@ -918,26 +951,11 @@ static void sdio_scan_work(struct work_struct *work) - bes_warn("%s: this function does nothing\n", __FUNCTION__); - } - --static void *bes2600_sdio_pipe_read(struct sbus_priv *self) --{ -- struct sk_buff *skb; -- -- if (bes2600_chrdev_is_bus_error()) { -- return bes2600_tx_loop_read(self->core); -- } -- -- spin_lock(&self->rx_queue_lock); -- skb = skb_dequeue(&self->rx_queue); -- if (skb) -- self->rx_proc_cnt++; -- spin_unlock(&self->rx_queue_lock); -- if (likely(self->fw_started == true && -- !bes2600_pwr_device_is_idle(self->core) && -- self->core->hw_bufs_used > 0)) -- if (!skb) -- queue_work(self->sdio_wq, &self->rx_work); -- return skb; --} -+/* Patch C v3: bes2600_sdio_pipe_read deleted. bh thread reads the -+ * SDIO bus inline via bes2600_sdio_read_rx_batch (sbus_ops->bus_rx_batch). -+ * No rx_queue, no skb_dequeue, no relay. bes2600_tx_loop_read remains -+ * for the test bus error-fallback path but is now invoked at higher -+ * level. */ - - #endif - -@@ -1135,7 +1153,26 @@ static void sdio_tx_work(struct work_struct *work) +@@ -1136,7 +1137,26 @@ static void sdio_tx_work(struct work_struct *work) } } @@ -762,94 +1433,1145 @@ index 13d4ff1e5..517e6f874 100644 total_len += align; ++scatters; /*del_node:*/ -@@ -1174,7 +1211,14 @@ static void sdio_tx_work(struct work_struct *work) - } - } while (crc_retry <= 10); - sdio_release_host(self->func); -- queue_work(self->sdio_wq, &self->rx_work); -+ /* -+ * Patch C v3: wake the bh thread to check for any RX -+ * that piggybacked on this TX window. Bumps bh_rx -+ * atomic; bh's wait_event will pick it up and call -+ * sbus_ops->bus_rx_batch(). -+ */ -+ if (likely(self->irq_handler)) -+ self->irq_handler(self->irq_priv); - if (ret) { - bes_err("%s,%d err=%d,%d,%d\n", __func__, __LINE__, ret, scatters, cur_blk); - sdio_work_debug(self); -@@ -1225,12 +1269,11 @@ static int bes2600_sdio_misc_init(struct sbus_priv *self, struct bes2600_common - self->next_toggle = 0; - #endif - #ifdef BES_SDIO_RX_MULTIPLE_ENABLE -- spin_lock_init(&self->rx_queue_lock); -- skb_queue_head_init(&self->rx_queue); -+ /* Patch C v3: rx_queue / rx_queue_lock removed (no relay). */ - self->rx_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM)); - if (!self->rx_buffer) +@@ -1857,6 +1877,17 @@ static int bes2600_sdio_probe(struct sdio_func *func, + if (!self->single_gathered_buffer) return -ENOMEM; -- INIT_WORK(&self->rx_work, sdio_rx_work); -+ /* Patch C v3: sdio_rx_work removed; bh thread does the read. */ #endif - #ifdef BES_SDIO_TX_MULTIPLE_ENABLE - INIT_LIST_HEAD(&self->tx_bufferlist); -@@ -1367,7 +1410,14 @@ static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag) - - /* error check */ - if((self->gpio_wakup_flags & BIT(flag)) != 0) { -- bes_err( "repeat set gpio_wake_flag, sub_sys:%d", flag); -+ /* -+ * Multiple subsystems holding wake is the steady-state case -+ * (e.g. WIFI + BT both want MCU awake). Demoted from bes_err -+ * to bes_devel since it isn't an error - the GPIO is already -+ * asserted high and the subsystem is now also tracked. -+ */ -+ bes_devel("repeat set gpio_wake_flag, sub_sys:%d\n", flag); -+ self->gpio_wakup_flags |= BIT(flag); - mutex_unlock(&self->io_mutex); - return; - } -@@ -1399,7 +1449,11 @@ static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag) - - /* error check */ - if((self->gpio_wakup_flags & BIT(flag)) == 0) { -- bes_err( "repeat clear gpio_wake_flag, sub_sys:%d", flag); -+ /* -+ * Mirror of the wake path: a clear when the bit is already -+ * clear is racy bookkeeping, not a hardware error. -+ */ -+ bes_devel("repeat clear gpio_wake_flag, sub_sys:%d\n", flag); - mutex_unlock(&self->io_mutex); - return; - } -@@ -1548,22 +1602,15 @@ static int bes2600_sdio_active(struct sbus_priv *self, int sub_system) - - static void bes2600_sdio_empty_work(struct sbus_priv *self) - { --#ifdef BES_SDIO_RX_MULTIPLE_ENABLE -- struct sk_buff *skb; --#endif - #ifdef BES_SDIO_TX_MULTIPLE_ENABLE - struct bes_sdio_tx_list_t *tx_buffer, *temp; ++#ifdef BES_SDIO_TX_MULTIPLE_ENABLE ++ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL, ++ get_order(MAX_SDIO_TRANSFER_LEN)); ++ if (!self->tx_bounce) { ++#ifndef SDIO_HOST_ADMA_SUPPORT ++ free_pages((unsigned long)self->single_gathered_buffer, ++ get_order(MAX_SDIO_TRANSFER_LEN)); ++#endif ++ return -ENOMEM; ++ } ++#endif + #ifdef BES_SDIO_RXTX_TOGGLE + self->fw_started = false; #endif +@@ -1984,6 +2015,12 @@ static void bes2600_sdio_remove(struct sdio_func *func) + if (self->single_gathered_buffer) { + free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN)); + } ++#endif ++#ifdef BES_SDIO_TX_MULTIPLE_ENABLE ++ if (self->tx_bounce) { ++ free_pages((unsigned long)self->tx_bounce, ++ get_order(MAX_SDIO_TRANSFER_LEN)); ++ } + #endif + kfree(self); + } +-- +2.54.0 + +From 0c1f98df59fc3c330b370f1b5b54e8d780278d2a Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 19:31:25 +0200 +Subject: [PATCH 08/29] bes2600: drop kernel_write() persistence from factory + cali save + +Following the conversion of the factory-calibration READ path to +request_firmware() (earlier in this series), the factory-calibration +WRITE path in factory_section_write_file() was still using +filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write() to persist +updated calibration data back to FACTORY_PATH +(default /lib/firmware/bes2600/bes2600_factory.txt). + +Writing to files under /lib/firmware/ from kernel code is a +standing upstream blocker for staging and for drivers/net/wireless/ +submission generally: + + - filp_open()/kernel_write() bypass the firmware-class abstraction, + the LSM framework, and user/group/mode enforcement that governs + the firmware search paths. They have been repeatedly called out + in staging-prep reviews. + - The kernel runs with capabilities that userspace does not (CAP_ + DAC_OVERRIDE effectively); quietly rewriting firmware blobs that + userspace owns is a surprise contract. + - A module unload / reboot immediately after the write races the + writeback and can leave a truncated calibration file on disk. + +Remove factory_section_write_file() and its two call sites in +bes2600_wifi_cali_table_save(). The in-memory factory_save_p +remains authoritative for the duration of the session: the WSM +command handlers that triggered this path (power-cali-table, +freq-cali, efuse-flag, power-cali-flag) already update the live +struct factory_t, and reads served from file_buffer pick up the +rebuilt serialised form immediately. On the next probe the +firmware-class file is re-read read-only via request_firmware(), +as set up by the earlier patch. + +If cross-reboot persistence of runtime-updated calibration becomes +a requirement, the expected route is a userspace-visible dump +interface -- a read-only debugfs file exporting the serialised +blob, or an nl80211 vendor command -- that lets userspace copy the +values to a chosen location under its own privileges. Such a +facility can land as a follow-up without touching the core driver +write path again. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_factory.c | 63 +++++++++++---------------------------- + 1 file changed, 17 insertions(+), 46 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c +index 1cda447..1b43b41 100644 +--- a/drivers/staging/bes2600/bes2600_factory.c ++++ b/drivers/staging/bes2600/bes2600_factory.c +@@ -179,34 +179,6 @@ static int factory_section_read_file(char *path, void *buffer) + return ret; + } - #ifdef BES_SDIO_RX_MULTIPLE_ENABLE -- cancel_work_sync(&self->rx_work); -- while (1) { -- skb = skb_dequeue(&self->rx_queue); -- if (skb) -- dev_kfree_skb(skb); -- else -- break; +-/** +- * factory_section_write_file - Write data of specified length to file +- * @path: path of the file +- * @buffer: storage of write data +- * @size: length of data to write +- * +- * Return: length on success, negative error code otherwise. +- */ +-static int factory_section_write_file(char *path, void *buffer, int size) +-{ +- int ret = 0; +- struct file *fp; +- +- bes_devel("writing %s \n", path); +- +- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR); +- if (IS_ERR(fp)) { +- bes_devel("BES2600 : can't open %s\n",path); +- return -1; - } +- +- ret = kernel_write(fp, buffer, size, &fp->f_pos); +- +- filp_close(fp,NULL); +- +- return ret; +-} +- + static inline int factory_parse(uint8_t *source_buf, struct factory_t *factory) + { + int ret = 0; +@@ -898,9 +870,22 @@ static inline int factory_build(uint8_t *dest_buf, struct factory_t *factory) + #endif + } + ++/* ++ * Rebuild the serialised calibration blob in file_buffer from the live ++ * in-memory factory_save_p. Previously this function also persisted the ++ * blob back to FACTORY_PATH via filp_open(O_CREAT) + kernel_write(); that ++ * is not acceptable in mainline, so the persistence step has been removed. ++ * ++ * The in-memory factory_save_p remains authoritative for the duration of ++ * the session; on the next probe the firmware-class file is read back ++ * read-only via request_firmware(). If cross-reboot persistence of runtime ++ * calibration updates becomes a requirement, the expected route is a ++ * userspace-facing dump interface (debugfs read-only blob, or nl80211 ++ * vendor command) that lets userspace read the serialised form and store ++ * it under its own privileges. ++ */ + static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *factory_save_p) + { +- int ret = 0; + int w_size; + u32 crc_len = sizeof(factory_data_t); + #ifndef STANDARD_FACTORY_EFUSE_FLAG +@@ -909,13 +894,11 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto + + bes_devel("enter %s\n", __func__); + +- if (!file_buffer) { ++ if (!file_buffer) + return -ENOMEM; +- } + +- if (!factory_save_p) { ++ if (!factory_save_p) + return -ENOENT; +- } + + /* All initialized to space */ + memset(file_buffer, 32, FACTORY_MAX_SIZE); +@@ -927,22 +910,10 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto + w_size = factory_build(file_buffer, factory_save_p); + + if (w_size < 0 || w_size > FACTORY_MAX_SIZE) { +- bes_err("%s: build failed! ret = %d.", __func__, ret); ++ bes_err("%s: build failed! w_size = %d.", __func__, w_size); + return -ETXTBSY; + } + +-#ifdef FACTORY_SAVE_MULTI_PATH +- /* avoid trailing characters '\0' */ +- file_buffer[w_size] = 32; +- ret = factory_section_write_file(FACTORY_PATH, file_buffer, FACTORY_MAX_SIZE); +-#else +- ret = factory_section_write_file(FACTORY_PATH, file_buffer, w_size); +-#endif +- if(ret < 0) { +- bes_err("%s: write failed! ret = %d.", __func__, ret); +- return ret; +- } +- + return 0; + } + +-- +2.54.0 + +From 0768e11da638457b3455e426de924f9e2e551641 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:04:11 +0200 +Subject: [PATCH 09/29] bes2600: drop BES2600_WRITE_DPD_TO_FILE kernel_*() file + paths +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +bes_chardev.c carried three functions gated behind the +BES2600_WRITE_DPD_TO_FILE Kconfig/make-flag (default off): + + - bes2600_chrdev_write_dpd_data_to_file() + filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write() + writing a raw DPD calibration blob back to + BES2600_DPD_PATH (default /data/cfg/bes2600_dpd.bin, an + Android-AOSP path). + + - bes2600_chrdev_read_and_check_dpd_data() + filp_open(O_RDONLY) + kernel_read() reading the DPD blob + from either BES2600_DPD_GOLDEN_PATH (/data/cfg/…) or + BES2600_DEFAULT_DPD_PATH (/lib/firmware/bes2600_dpd.bin), + followed by a CRC/version sanity check. + + - bes2600_chrdev_dpd_is_vaild() (sic), the CRC/version helper + used only by the read path. + +Plus the bes_cdev.no_dpd field, its module_param, and two +intrusion sites in bes2600_chrdev_get_dpd_data() and +bes2600_chrdev_update_dpd_data() that invoke the above. + +The Makefile defaults BES2600_WRITE_DPD_TO_FILE=n, so in a stock +build all of this is dead code. It is still a standing upstream +blocker for exactly the same reasons as the factory-txt write +path removed in the preceding patch: + + - filp_open() + kernel_read()/kernel_write() bypass the + firmware-class abstraction and LSM-governed access control + that apply to /lib/firmware/. + - The write target /data/cfg/ is an Android AOSP convention + that does not exist on a Linux distribution and cannot be + created by the kernel anyway. + - A runtime DPD re-calibration is intended to reduce TX EVM + after temperature or aging drift; persisting the result via + kernel_write() is fundamentally a userspace concern (debugfs + dump + userspace tool is the expected route). + +Remove the entire #ifdef BES2600_WRITE_DPD_TO_FILE block from +bes_chardev.c (including the inner #ifdef inside +bes2600_chrdev_read_and_check_dpd_data() guarding a +DPD_BIN_FILE_SIZE size check that only applied to the read-back- +its-own-write case), the no_dpd field and module_param, and the +two invocation sites. Drop the Kconfig/make-flag and the three +associated PATH macros from the Makefile. Net: -155 lines, no +remaining filp_open/kernel_read/kernel_write anywhere in +bes_chardev.c. + +The in-memory DPD state path is unchanged: bes2600_chrdev_get_dpd_ +buffer() still allocates a kmalloc'd buffer used by the firmware- +download path, bes2600_chrdev_update_dpd_data() still validates +the buffer's CRC and transitions bes2600_cdev.wait_state on +success, and bes2600_chrdev_free_dpd_data() still releases the +buffer on unload. Only the file-I/O side-channel is removed. + +Signed-off-by: Markus Fritsche +--- + bes2600/Makefile | 12 ---- + bes2600/bes_chardev.c | 143 ------------------------------------------ + 2 files changed, 155 deletions(-) + +diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile +index 2c1a850..0dd3606 100644 +--- a/drivers/staging/bes2600/Makefile ++++ b/drivers/staging/bes2600/Makefile +@@ -28,7 +28,6 @@ CONFIG_BES2600_WIFI_BOOT_ON ?= y + CONFIG_BES2600_BT_BOOT_ON ?= n + + BES2600_GPIO_WAKEUP_AP ?= n +-BES2600_WRITE_DPD_TO_FILE ?= n + BES2600_TX_MORE_RETRY ?= n + + # bes evb +@@ -93,12 +92,6 @@ ccflags-y += -DBES_UNIFIED_PM + ccflags-y += -DBES_SDIO_OPTIMIZED_LEN + ccflags-y += -DBES2600_HOST_TIMESTAMP_DEBUG + +-ifeq ($(BES2600_WRITE_DPD_TO_FILE),y) +-BES2600_DPD_PATH ?= /data/cfg/bes2600_dpd.bin +-BES2600_DEFAULT_DPD_PATH ?= /lib/firmware/bes2600_dpd.bin +-BES2600_DPD_GOLDEN_PATH ?= /data/cfg/bes2600_dpd_golden.bin +-endif +- + ifeq ($(BES2600_DUMP_FW_DPD_LOG),y) + BES2600_DPD_LOG_PATH ?= /data/applog/bes2600_dpd_log.log + endif +@@ -135,9 +128,6 @@ ccflags-y += $(call boolen_flag,BSS_LOSS_CHECK,y) + ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_PATH) + ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_DEVICE) + ccflags-y += $(call string_flag,BES2600_DRV_VERSION) +-ccflags-y += $(call string_flag,BES2600_DPD_PATH) +-ccflags-y += $(call string_flag,BES2600_DEFAULT_DPD_PATH) +-ccflags-y += $(call string_flag,BES2600_DPD_GOLDEN_PATH) + + ccflags-y += $(call boolen_flag,BES2600_INDEPENDENT_EVB,y) + ccflags-y += $(call boolen_flag,BES2600_INTEGRATED_MODULE_V1,y) +@@ -159,8 +149,6 @@ ccflags-y += $(call boolen_flag,FACTORY_SAVE_MULTI_PATH,y) + ccflags-y += $(call boolen_flag,FACTORY_CRC_CHECK,y) + + ccflags-y += $(call boolen_flag,BES2600_GPIO_WAKEUP_AP,y) +-ccflags-y += $(call boolen_flag,BES2600_WRITE_DPD_TO_FILE,y) +- + ccflags-y += $(call boolen_flag,BES2600_DUMP_FW_DPD_LOG,y) + ccflags-y += $(call string_flag,BES2600_DPD_LOG_PATH) + +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index e2e4f1b..a02d6d9 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -63,9 +63,6 @@ struct bes_cdev { + struct delayed_work probe_timeout_work; + enum bus_probe_state bus_probe; + struct work_struct wifi_force_close_work; +-#ifdef BES2600_WRITE_DPD_TO_FILE +- int no_dpd; +-#endif + enum pend_read_op read_flag; + enum wakeup_event wakeup_by_event; /* used to filter unwanted event wakeup reason report */ + u16 wakeup_state; /* for userspace check wakeup reason */ +@@ -85,9 +82,6 @@ struct bes2600_op_map { + + static struct bes_cdev bes2600_cdev; + module_param_named(fw_type, bes2600_cdev.fw_type, int, 0644); +-#ifdef BES2600_WRITE_DPD_TO_FILE +-module_param_named(no_dpd, bes2600_cdev.no_dpd, int, 0644); +-#endif + + extern int bes2600_register_net_dev(struct sbus_priv *bus_priv); + extern int bes2600_unregister_net_dev(struct sbus_priv *bus_priv); +@@ -269,137 +263,8 @@ static int bes2600_chrdev_check_system_close_internal(void) + + + +-#ifdef BES2600_WRITE_DPD_TO_FILE +-static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size) +-{ +- int ret = 0; +- struct file *fp; +- +- if (buffer == NULL || size == 0) +- return 0; +- +- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR); +- if (IS_ERR(fp)) { +- bes_err("BES2600 : can't open %s\n",path); +- return -1; +- } +- +- ret = kernel_write(fp, buffer, size, &fp->f_pos); +- if (ret < 0) +- bes_err("write dpd to file failed\n"); +- +- filp_close(fp,NULL); +- +- bes_devel("write dpd to %s\n", path); +- +- return ret; +-} +- +-static bool bes2600_chrdev_dpd_is_vaild(u8 *dpd_data) +-{ +- u32 cal_crc = 0; +- u32 dpd_crc = le32_to_cpup((__le32 *)(dpd_data)); +- u32 dpd_ver = le32_to_cpup((__le32 *)(dpd_data + DPD_VERSION_OFFSET)); +- +- /* check version */ +- if (dpd_ver < DPD_CUR_VERSION) +- return false; +- +- cal_crc ^= 0xffffffffL; +- cal_crc = crc32_le(cal_crc, dpd_data + 4, DPD_BIN_SIZE - 4); +- cal_crc ^= 0xffffffffL; +- +- /* check if the dpd data is valid */ +- if (cal_crc != dpd_crc) { +- bes_err( +- "bes2600 dpd data from file check failed, calc_crc:0x%08x dpd_crc: 0x%08x\n", +- cal_crc, dpd_crc); +- return false; +- } +- +- return true; +-} +- +-static int bes2600_chrdev_read_and_check_dpd_data(const char *file, u8 **data, u32 *len) +-{ +- int ret = 0; +- u8* read_data = NULL; +- struct file *fp; +- +- /* open file */ +- fp = filp_open(file, O_RDONLY, 0);//S_IRUSR +- if (IS_ERR(fp)) { +- bes_devel("BES2600 : can't open %s\n",file); +- return -1; +- } +- +-#ifdef BES2600_WRITE_DPD_TO_FILE +- if (fp->f_inode->i_size != DPD_BIN_FILE_SIZE) { +- bes_err( +- "bes2600 dpd data file size check failed, read_size: %lld file_size: %d\n", +- fp->f_inode->i_size, DPD_BIN_FILE_SIZE); +- filp_close(fp, NULL); +- return -1; +- } +-#endif +- +- /* allocate memory for storing reading data */ +- read_data = kmalloc(fp->f_inode->i_size, GFP_KERNEL); +- if (read_data == NULL) { +- bes_devel("%s alloc mem fail\n", __func__); +- goto err1; +- } +- +- /* read data from file */ +- ret = kernel_read(fp, read_data, fp->f_inode->i_size, &fp->f_pos); +- if (ret < DPD_BIN_SIZE) { +- bes_err("%s read fail, ret=%d\n", __func__, ret); +- goto err2; +- } +- +- /* check dpd version and crc */ +- if (!bes2600_chrdev_dpd_is_vaild(read_data)) +- goto err2; +- +- /* close file */ +- filp_close(fp, NULL); +- +- /* copy data to external */ +- *data = read_data; +- *len = DPD_BIN_SIZE;; +- +- /* output debug information */ +- bes_devel("read dpd data from %s\n", file); +- +- return 0; +- +-err2: +- kfree(read_data); +-err1: +- filp_close(fp, NULL); +- *data = NULL; +- *len = 0; +- return -1; +-} +-#endif +- + const u8* bes2600_chrdev_get_dpd_data(u32 *len) + { +-#ifdef BES2600_WRITE_DPD_TO_FILE +- if (!bes2600_cdev.dpd_calied && bes2600_cdev.no_dpd) { +- /* read dpd data from file that stores factory dpd calibration data */ +- if ((bes2600_chrdev_read_and_check_dpd_data(BES2600_DPD_GOLDEN_PATH, +- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0) && +- (bes2600_chrdev_read_and_check_dpd_data(BES2600_DEFAULT_DPD_PATH, +- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0)) { +- bes_err("%s read dpd data fail\n", __func__); +- return NULL; +- } else { +- bes2600_cdev.dpd_calied = true; +- } +- } +-#endif +- + if (!bes2600_cdev.dpd_calied) + return NULL; + if (len) +@@ -460,14 +325,6 @@ int bes2600_chrdev_update_dpd_data(void) + } + spin_unlock(&bes2600_cdev.status_lock); + +-#ifdef BES2600_WRITE_DPD_TO_FILE +- /* write dpd data to file */ +- memset(bes2600_cdev.dpd_data + DPD_BIN_SIZE, 0, DPD_BIN_FILE_SIZE - DPD_BIN_SIZE); +- bes2600_chrdev_write_dpd_data_to_file(BES2600_DPD_PATH, +- bes2600_cdev.dpd_data, DPD_BIN_FILE_SIZE); +-#endif +- +- + return 0; + } + +-- +2.54.0 + +From c3d28aea4603fec51b66cfa438dd546722d53272 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:19:27 +0200 +Subject: [PATCH 10/29] bes2600: drop orphan DATA_DUMP_OBSERVE and + access_file() file I/O + +Two dead-in-default-build file-I/O sites remain in the driver +after the factory and chardev kernel_*() removals in the preceding +patches: + + - bes_fw.c DATA_DUMP_OBSERVE: four #ifdef DATA_DUMP_OBSERVE + blocks built around the firmware-download path that open + /lib/firmware/bes2002_fw_write.bin via filp_open(O_CREAT | + O_RDWR), then log every transmitted firmware chunk via + vfs_write() inside a get_fs()/set_fs(KERNEL_DS) wrapper. The + controlling #define at bes_fw.c line 128 is commented out + ('//#define DATA_DUMP_OBSERVE'), so none of this is ever + compiled in a stock build. + + - main.c access_file(): a helper gated on + GET_MAC_ADDR_METHOD == 2 || == 3 (default 4) using the same + get_fs()/set_fs()/vfs_read()/vfs_write() pattern. No caller + in the tree references it -- it was orphaned when the methods + that consumed it were refactored out. + +Both sites are unbuildable on modern kernels anyway: get_fs() / +set_fs() were removed from arm64 and the generic uaccess path in +the v5.10 era, and the legacy vfs_read() / vfs_write() variants +that took userspace-typed buffers went with them. The in-kernel +replacements would be kernel_read() / kernel_write(), which this +series is explicitly removing from the driver. + +Remove both blocks, the commented-out '//#define DATA_DUMP_OBSERVE' +line, and the access_file() definition and its #if gate. No +behaviour change in any default or non-default build, because +nothing compiled or linked in the first place. After this patch +the driver contains zero filp_open / kernel_read / kernel_write / +vfs_read / vfs_write references -- a precondition for a +drivers/staging/bes2600/ linux-wireless RFC. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_fw.c | 34 ---------------------------------- + bes2600/main.c | 35 ----------------------------------- + 2 files changed, 69 deletions(-) + +diff --git a/drivers/staging/bes2600/bes_fw.c b/drivers/staging/bes2600/bes_fw.c +index 133c945..d612c3c 100644 +--- a/drivers/staging/bes2600/bes_fw.c ++++ b/drivers/staging/bes2600/bes_fw.c +@@ -125,8 +125,6 @@ int bes_host_slave_sync(struct bes2600_common *hw_priv) + } + */ + +-//#define DATA_DUMP_OBSERVE +- + static int bes_firmware_download_write_reg(struct platform_fw_t *fw_data, u32 addr, u32 val) + { + u8 frame_num = 0; +@@ -468,14 +466,6 @@ static int bes_firmware_download(struct platform_fw_t *fw_data, const char *fw_n + + const struct firmware *fw_bin; + +-#ifdef DATA_DUMP_OBSERVE +- char *observe; +- size_t observe_len; +- loff_t observe_off = 0; +- mm_segment_t old_fs; +- struct file *observe_file = NULL; +-#endif +- + struct fw_msg_hdr_t header; + struct fw_info_t fw_info; + struct download_fw_t download_addr; +@@ -583,14 +573,6 @@ retry: + } + download_addr.addr = fw_info.addr; + +-#ifdef DATA_DUMP_OBSERVE +- observe_file = filp_open("/lib/firmware/bes2002_fw_write.bin", O_CREAT | O_RDWR, 0); +- if (IS_ERR(observe_file)) { +- bes_err("create data_dump file err:%ld\n", IS_ERR(observe_file)); +- observe_file = NULL; +- } +-#endif +- + while (code_length) { + + #if 1 +@@ -640,17 +622,6 @@ retry: + //mdelay(5000); + bes_devel("tx_download_firmware_data:%x %d\n", download_addr.addr, length); + +-#ifdef DATA_DUMP_OBSERVE +- if (observe_file) { +- observe = (char *)(long_buf + sizeof(struct fw_msg_hdr_t) + sizeof(struct download_fw_t)); +- observe_len = length - sizeof(struct fw_msg_hdr_t) - sizeof(struct download_fw_t); +- old_fs = get_fs(); +- set_fs(KERNEL_DS); +- vfs_write(observe_file, observe, observe_len, &observe_off); +- set_fs(old_fs); +- } +-#endif +- + ret = bes2600_data_write(long_buf, length > 512 ? length : 512); + if (ret) { + bes_err("tx download fw data err:%d\n", ret); +@@ -832,11 +803,6 @@ retry: + + err2: + kfree(long_buf); +-#ifdef DATA_DUMP_OBSERVE +- if (observe_file) { +- filp_close(observe_file, NULL); +- } +-#endif + err1: + kfree(short_buf); + release_firmware(fw_bin); +diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c +index 3b0b7a3..7cbb3a9 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -795,41 +795,6 @@ void bes2600_core_release(struct bes2600_common *self) + return; + } + +-#if (GET_MAC_ADDR_METHOD == 2) || (GET_MAC_ADDR_METHOD == 3) /* To use macaddr and ps mode of customers */ +-int access_file(char *path, char *buffer, int size, int isRead) +-{ +- int ret=0; +- struct file *fp; +- mm_segment_t old_fs = get_fs(); +- +- if(isRead) +- fp = filp_open(path,O_RDONLY,S_IRUSR); +- else +- fp = filp_open(path,O_CREAT|O_WRONLY,S_IRUSR); +- +- if (IS_ERR(fp)) { +- bes_err("BES2600 : can't open %s\n", path); +- return -1; +- } +- +- if (isRead) { +- fp->f_pos = 0; +- set_fs(KERNEL_DS); +- ret = vfs_read(fp,buffer,size,&fp->f_pos); +- set_fs(old_fs); +- } else { +- fp->f_pos = 0; +- set_fs(KERNEL_DS); +- ret = vfs_write(fp,buffer,size,&fp->f_pos); +- set_fs(old_fs); +- } +- filp_close(fp,NULL); +- +- bes_info("BES2600 : access_file return code(%d)\n", ret); +- return ret; +-} +-#endif +- + int bes2600_wifi_start(struct bes2600_common *hw_priv) + { + int ret = 0, if_id; +-- +2.54.0 + +From 894c502cd541079a8a26d61cd4289af9001b3046 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:35:17 +0200 +Subject: [PATCH 11/29] bes2600: demote 'wait pm ind timeout' from bes_err to + bes_devel + +bes2600_pwr_enter_lp_mode() logs 'wait pm ind timeout' at bes_err +level every time wait_for_completion_timeout() on the firmware's +PM-change indication returns 0. The preceding patch ('bes2600: +gate device LP-mode entry on successful per-VIF firmware +handshake') already handles this case correctly: the per-VIF +timeouts counter is incremented, the function returns +-ETIMEDOUT, and the device-side LP transition is skipped -- the +cascade into sdio_tx_work splats and [RX] Receive failure +messages is prevented. + +The timeout itself is benign steady-state noise on the PineTab2 +(BES2600WM). Firmware occasionally misses the 5 s PM-change +deadline when mac80211 flips power-save rapidly during +association or roaming; observed rate on a quiet, associated +ohm is roughly 3-10 events per 10 min of uptime, with no +user-visible effect. Keeping it at bes_err() level (== KERN_ERR, +priority 3) floods dmesg with what is already a handled +condition and makes real SDIO / PM errors harder to spot. + +Demote to bes_devel() (== KERN_DEBUG gated on the driver's debug +flag). The gate in the caller is unchanged, so the downstream +suppression behaviour introduced by the earlier patch remains. +Real pathologies -- bes_err("set operation mode fail") on the +same path, and the timeouts != 0 / -ETIMEDOUT return consumed +by callers -- still surface at bes_err() / return-value level. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_pwr.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index f62ae22..474b6f1 100644 +--- a/drivers/staging/bes2600/bes_pwr.c ++++ b/drivers/staging/bes2600/bes_pwr.c +@@ -538,7 +538,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); + reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); + if (!status) { +- bes_err("%s, wait pm ind timeout\n", __func__); ++ bes_devel("%s, wait pm ind timeout\n", __func__); + timeouts++; + } + } else { +-- +2.54.0 + +From 844e2245a1ed517b3a0bc487fec1a100304f0b44 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 24 Apr 2026 21:31:45 +0200 +Subject: [PATCH 13/29] bes2600: defer scan and soften WARN on firmware reject + +On a BES2600-based PineTab2, mac80211's background-scan cadence +(about every 30 s when associated) triggers a two-step WARN splat +pattern, visible in dmesg roughly 30 times per 10 min of regular +WiFi use: + + wsm_generic_confirm ret 2 + WARNING: at wsm_handle_rx+0x8a4/0xf30 [bes2600] + ... full stack trace ... + ieee80211 phy0: wsm_generic_confirm failed for request 0x0007. + WARNING: at bes2600_scan_work+0x5d4/0x810 [bes2600] + ... full stack trace ... + ieee80211 phy0: [SCAN] Scan failed (-22). + +0x0007 is the WSM start-scan request; status 2 is the firmware's +rejected-by-policy response, which it returns for at least two +conditions: + + a) BT A2DP streaming in non-FDD coex mode -- the coex arbiter + in firmware won't grant an off-channel window while a SCO/ + A2DP link is queued. + b) A firmware-internal busy state whose exact trigger the + driver cannot observe directly (confirmed on ohm with BT + disconnected -- rejection still fires). Likely transient + firmware-PM transitions. + +Both are protocol-level policy responses, not kernel bugs, so the +full stack-trace WARN treatment is counterproductive: it buries +real problems and gets new users convinced the driver is broken. + +Three-part fix: + + 1. struct bes2600_scan grows two fields -- reject_count and + backoff_until -- zero-initialised via the existing + ieee80211_alloc_hw()-provided kzalloc. + + 2. bes2600_scan_work() now consults bes2600_scan_should_defer() + before calling bes2600_scan_start(). The helper short- + circuits in two cases: + + - coex_is_bt_a2dp() is true and coex is not in FDD mode, + since we already know the firmware will reject; + - BES2600_SCAN_REJECT_THRESHOLD (3) consecutive rejections + have fired and the BES2600_SCAN_BACKOFF_JIFFIES (10 s) + backoff window has not yet elapsed. + + On defer or on a real firmware rejection, reject_count is + bumped and backoff_until is refreshed. A successful scan + clears reject_count. + + 3. The WARN_ON(hw_priv->scan.status) at the scan_start() call + site is replaced with a plain branch into the existing + fail: label. wsm_generic_confirm()'s WARN() becomes a + bes_devel() -- the per-request wiphy_warn in wsm_handle_rx + (which includes the offending request id) is kept, so real + debugging information is still on tape. + +Net behaviour: + + - Expected rejections no longer produce stack traces. The only + log line that remains on a rejected background scan is the + upstream-caller's wiphy_warn identifying request 0x0007 or + equivalent. + - The driver stops hammering the firmware with doomed scan + requests -- 3 rejections trigger a 10 s pause, during which + bes2600_scan_work() returns without issuing WSM 0x0007. + - The scan-completion path is unchanged; mac80211 sees the + scan complete with no results and reissues on its normal + cadence. + - Real protocol-layer bugs (unexpected underflow in the + confirm buffer) still WARN_ON at the 'underflow:' label. + +Verified on ohm (PineTab2, linux-pinetab2 6.19.10-danctnix1-1): +WARN splat count dropped from 32 to 0 per 10 min uptime. WiFi +stays associated. No regression in other counters (KFENCE, +sdio_tx_work, RX failure, PS Mode Error, factory cali fail all +remain 0). + +Signed-off-by: Markus Fritsche +--- + bes2600/scan.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++- + bes2600/scan.h | 11 +++++++++ + bes2600/wsm.c | 14 +++++++++++- + 3 files changed, 83 insertions(+), 2 deletions(-) + +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index 3bfa535..5f6af3b 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -14,11 +14,50 @@ + #include "scan.h" + #include "sta.h" + #include "pm.h" ++#include "epta_coex.h" + #include "epta_request.h" + #include "bes_pwr.h" + ++/* ++ * After this many consecutive WSM scan rejections from firmware, stop ++ * issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state ++ * that's rejecting them (coex window, firmware-internal busy) clear. ++ */ ++#define BES2600_SCAN_REJECT_THRESHOLD 3 ++#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ) ++ + static void bes2600_scan_restart_delayed(struct bes2600_vif *priv); + ++/* ++ * Decide whether to skip sending the next WSM scan command without ++ * bothering the firmware. Two triggers: ++ * ++ * 1. BT A2DP is streaming in non-FDD coex mode. The firmware is ++ * known to reject scan requests during that window; short- ++ * circuiting here saves a WSM round-trip and avoids the ++ * wsm_generic_confirm / scan_work warning cascade that follows. ++ * ++ * 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive ++ * rejections on recent scan attempts and the backoff window has ++ * not yet elapsed. Whatever was rejecting them is likely still ++ * rejecting them; give it time. ++ * ++ * Returns true if the caller should abandon the scan iteration. ++ */ ++static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv) ++{ ++#ifdef WIFI_BT_COEXIST_EPTA_ENABLE ++ if (!coex_is_fdd_mode() && coex_is_bt_a2dp()) ++ return true; ++#endif ++ ++ if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD && ++ time_before(jiffies, hw_priv->scan.backoff_until)) ++ return true; ++ ++ return false; ++} ++ + #ifdef CONFIG_BES2600_TESTMODE + static int bes2600_advance_scan_start(struct bes2600_common *hw_priv) + { +@@ -703,10 +742,29 @@ void bes2600_scan_work(struct work_struct *work) + wsm_unlock_tx(hw_priv); + } else + #endif ++ { ++ if (bes2600_scan_should_defer(hw_priv)) { ++ hw_priv->scan.status = -EBUSY; ++ hw_priv->scan.reject_count++; ++ hw_priv->scan.backoff_until = ++ jiffies + BES2600_SCAN_BACKOFF_JIFFIES; ++ wiphy_dbg(priv->hw->wiphy, ++ "[SCAN] deferred (coex/backoff, reject_count=%u)\n", ++ hw_priv->scan.reject_count); ++ kfree(scan.ch); ++ goto fail; ++ } + hw_priv->scan.status = bes2600_scan_start(priv, &scan); ++ } + kfree(scan.ch); +- if (WARN_ON(hw_priv->scan.status)) ++ if (hw_priv->scan.status) { ++ hw_priv->scan.reject_count++; ++ hw_priv->scan.backoff_until = ++ jiffies + BES2600_SCAN_BACKOFF_JIFFIES; ++ /* Lower callers already logged the reason at wiphy_warn. */ + goto fail; ++ } ++ hw_priv->scan.reject_count = 0; + hw_priv->scan.curr = it; + } + up(&hw_priv->conf_lock); +diff --git a/drivers/staging/bes2600/scan.h b/drivers/staging/bes2600/scan.h +index e50fa36..1f3adea 100644 +--- a/drivers/staging/bes2600/scan.h ++++ b/drivers/staging/bes2600/scan.h +@@ -42,6 +42,17 @@ struct bes2600_scan { + struct delayed_work probe_work; + int direct_probe; + u8 if_id; + /* -+ * Patch C v3: rx_work and rx_queue removed. Counters still -+ * reset for the next attach cycle. ++ * Track consecutive firmware-side WSM scan rejections so we can ++ * back off briefly instead of re-issuing the same scan on every ++ * mac80211 background-scan tick. Firmware returns WSM status != 0 ++ * for a handful of transient conditions (BT A2DP active in non- ++ * FDD coex, firmware-internal busy windows) and keeps rejecting ++ * until the state clears; retrying at full cadence just floods ++ * dmesg. + */ - self->rx_last_ctrl = 0; - self->rx_total_ctrl_cnt = 0; - self->rx_continuous_ctrl_cnt = 0; -@@ -1756,6 +1803,55 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self) ++ unsigned int reject_count; ++ unsigned long backoff_until; + }; + + int bes2600_hw_scan(struct ieee80211_hw *hw, +diff --git a/drivers/staging/bes2600/wsm.c b/drivers/staging/bes2600/wsm.c +index d40df30..55a4e2b 100644 +--- a/drivers/staging/bes2600/wsm.c ++++ b/drivers/staging/bes2600/wsm.c +@@ -134,8 +134,20 @@ static int wsm_generic_confirm(struct bes2600_common *hw_priv, + struct wsm_buf *buf) + { + u32 status = WSM_GET32(buf); +- if (WARN(status != WSM_STATUS_SUCCESS, "wsm_generic_confirm ret %u", status)) ++ ++ /* ++ * A non-SUCCESS status here is a firmware-side policy decision for ++ * the command whose confirm this is -- commonly WSM status 2 for ++ * scan (0x0407) rejected because of a coex window or transient ++ * firmware-busy state. It is not a driver/kernel bug, so avoid the ++ * WARN()/stack-trace treatment; the caller already emits a ++ * wiphy_warn identifying the request id and will propagate the ++ * error to mac80211. ++ */ ++ if (status != WSM_STATUS_SUCCESS) { ++ bes_devel("%s ret %u\n", __func__, status); + return -EINVAL; ++ } + return 0; + + underflow: +-- +2.54.0 + +From 179c2e0bf852734631acfc56b2478775215cc5f6 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 14:32:18 +0200 +Subject: [PATCH 14/29] bes2600: widen scan-defer backoff to 30s and decay + count on quiet + +The scan-defer logic added in the previous patch ("bes2600: defer +scan and soften WARN on firmware reject") used a 10-second backoff +window and never cleared reject_count outside of a successful scan. +Field testing on a PineTab2 (linux-pinetab2 6.19.10-danctnix1) shows +two distinct mac80211 scan-retry cadences in practice: + + * Idle background scans every ~5 minutes when associated -- well + outside any plausible backoff, the defer guard correctly falls + through to a real WSM scan attempt. + + * Roam-evaluation bursts triggered when mac80211 wants to find a + candidate AP for handover (signal degradation, beacon loss, + locally-generated DEAUTH_LEAVING reason=3). Cadence is ~12 s, and + one boot reproduced 14 such rejected scans in 3 minutes during a + single burst, none of which engaged the defer guard because every + retry landed just outside the 10 s window. + +Two-line behaviour change to fix that: + + 1. BES2600_SCAN_BACKOFF_JIFFIES grows from 10*HZ to 30*HZ, so a + 12 s-cadence burst stays inside the window across consecutive + rejects and the third reject in the burst trips the threshold + guard. The 5 min idle case is still naturally past the window + and is unaffected. + + 2. bes2600_scan_should_defer() resets reject_count to 0 when + time_after(jiffies, backoff_until). Without this, reject_count + accumulated indefinitely across the slow-cadence rejects, so an + isolated reject after long quiet would have tripped the + threshold the moment it arrived. After the change, count is + latched only inside an active burst and decays cleanly when the + burst ends. + +Net effect on a roam burst: + + * t=0 reject #1 (count 1, backoff_until = t0 + 30s) + * t=12 reject #2 (count 2, backoff_until = t1 + 30s) + * t=24 reject #3 (count 3, threshold met, next scan deferred) + * t=36 defer fires, no WSM round-trip, reject not sent + * ... defers continue until the firmware-policy state clears + * scan succeeds -> reject_count = 0, normal cadence resumes + +WSM 0x0007 confirm rejections in a burst drop from ~14 to ~3 (just +the scans needed to reach the threshold). wpa_supplicant's reason=3 +locally-generated disconnects driven by exhausted roam candidates +during the same burst window also drop. + +No new state, no new symbols, no change to mac80211-facing semantics: +the deferred scan still completes via the existing fail: path with +status=-EBUSY, the same response a real firmware-busy would produce. + +Signed-off-by: Markus Fritsche +--- + bes2600/scan.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index 5f6af3b..b944adc 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -22,9 +22,17 @@ + * After this many consecutive WSM scan rejections from firmware, stop + * issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state + * that's rejecting them (coex window, firmware-internal busy) clear. ++ * ++ * The backoff has to be at least as long as the natural mac80211 scan- ++ * retry cadence, otherwise the next attempt lands outside the window ++ * and bypasses the defer guard. Observed in the wild on PineTab2: ++ * roam-evaluation bursts at ~12 s cadence, idle background scans at ++ * ~5 min cadence. 30 s catches the burst and leaves the slow case ++ * alone (the firmware-policy state has had minutes to clear by then ++ * anyway). + */ + #define BES2600_SCAN_REJECT_THRESHOLD 3 +-#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ) ++#define BES2600_SCAN_BACKOFF_JIFFIES (30 * HZ) + + static void bes2600_scan_restart_delayed(struct bes2600_vif *priv); + +@@ -40,7 +48,9 @@ static void bes2600_scan_restart_delayed(struct bes2600_vif *priv); + * 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive + * rejections on recent scan attempts and the backoff window has + * not yet elapsed. Whatever was rejecting them is likely still +- * rejecting them; give it time. ++ * rejecting them; give it time. If the backoff has elapsed without ++ * a fresh reject refreshing it, the burst is over and we reset the ++ * count so an isolated reject doesn't immediately re-trip. + * + * Returns true if the caller should abandon the scan iteration. + */ +@@ -51,6 +61,9 @@ static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv) + return true; + #endif + ++ if (time_after(jiffies, hw_priv->scan.backoff_until)) ++ hw_priv->scan.reject_count = 0; ++ + if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD && + time_before(jiffies, hw_priv->scan.backoff_until)) + return true; +-- +2.54.0 + +From 22b799f5a21c0046aad46676519e5f03a0d105fd Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Sun, 26 Apr 2026 22:31:58 +0200 +Subject: [PATCH 15/29] bes2600: recover wedged firmware via mmc_hw_reset on + link break + +When the LMAC active monitor detects 'link break between lmac and host' +(the hw_buf_used==pending watchdog in bes2600_bh_lmac_active_monitor), +bes2600_chrdev_wifi_force_close(hw_priv, true) is invoked to tear the +device down and prepare for a fresh probe. On the wifi_force_close_work +side this calls bes2600_chrdev_do_system_close() which dispatches +sbus_ops->power_switch(0). + +On PineTab2 (RK3566 + BES2600WM over SDIO) this recovery path is a +no-op: + + * bes2600_sdio_power_down() writes a SYSTEM_CLOSE host-int message, + clears MMC_CAP_NONREMOVABLE, and schedules sdio_scan_work, which is + the literal one-line stub bes_warn("...this function does + nothing\n"). + * bes2600_sdio_on() (the eventual power_switch(1) counterpart) + toggles pdata->powerup, which is NULL on PineTab2 because the + wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 device + tree node (see arch/arm64/boot/dts/rockchip/rk3566-pinetab2.dtsi: + 'The reset pin is claimed by sdio_mmcseq, It is better to move it + to U-Boot so the OS can use it.'). + +Net result: the chip is never reset. The function drivers are not +removed (the SDIO core has no signal that the card is gone), the +firmware stays wedged, and a subsequent rmmod bes2600 leaves the SDIO +function in a half-torn-down state. modprobe bes2600 then fails with +'probe with driver bes2600_wlan failed with error -123' (-ENOMEDIUM) +on both functions (:1 wifi, :2 BT-companion) until a full system +reboot. + +Observed on PineTab2 (linux-pinetab2 6.19.10-danctnix1-1) after ~150 +minutes of background-scan rejects (wsm_generic_confirm 0x0007, +[SCAN] Scan failed (-22)) accumulating until the LMAC stopped +acknowledging TX buffers (hw_buf_used:24 pending:24). Reproducible +under sustained scan pressure. + +Add a sbus operation bus_reset() that the recovery path can call when +power_switch() has no effective chip-reset signal of its own. Provide +an SDIO implementation that calls mmc_hw_reset(self->func->card), +which on a multi-function SDIO card (PineTab2 binds func 1 for WLAN +and func 2 for the BT-companion path) takes the remove-and-rescan +path: mmc_sdio_hw_reset() marks the card removed and schedules +mmc_rescan, which tears down the bound function drivers and re-detects +the card on the next sweep, in turn reinvoking bes2600_sdio_probe(). +With a single function probed it instead invokes mmc_power_cycle() +directly, which on PineTab2 toggles the wifi-reset GPIO via +sdio_pwrseq. + +Add bes2600_chrdev_do_bus_reset() as the chrdev-side helper. It +invokes the bus op and then waits on probe_done_wq for the SDIO +remove() callback to clear sbus_priv, mirroring the wait pattern +already used by bes2600_chrdev_do_system_close() so that a subsequent +bes2600_switch_wifi(true) sees a clean state and can wait on the +fresh probe. + +Wire it into bes2600_chrdev_wifi_force_close_work(): when halt_dev is +set (the hard-exception path used by both +bes2600_bh_lmac_active_monitor and bes2600_bh_mcu_active_monitor) and +the underlying bus implements bus_reset, take the new recovery path; +otherwise fall back to the legacy power_switch(0) sequence so this +patch is a no-op on USB or any other future bus that does not provide +bus_reset. + +mmc_hw_reset() is exported by the MMC core and is the canonical +recovery primitive; calling it without holding the SDIO host claim is +correct because the multi-func remove-and-rescan path acquires the +host claim via the mmc workqueue, and the single-func mmc_power_cycle +path does not require the host claim. + +No DT change is required: this works against the existing PineTab2 +DTS, where the wifi-reset GPIO and the optional sdio_pwrkey GPIO (on +v2.0 boards) are both already configured as MMC pwrseq resets. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_sdio.c | 29 +++++++++++++++++++++ + bes2600/bes_chardev.c | 59 ++++++++++++++++++++++++++++++++++++++++-- + bes2600/bes_chardev.h | 1 + + bes2600/sbus.h | 8 ++++++ + 4 files changed, 95 insertions(+), 2 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index b9d836f..f7f86d7 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1777,6 +1778,33 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self) sdio_work_debug(self); } @@ -874,48 +2596,16 @@ index 13d4ff1e5..517e6f874 100644 + */ +static int bes2600_sdio_bus_reset(struct sbus_priv *self) +{ -+ struct mmc_host *host; -+ int ret; -+ + if (!self || !self->func || !self->func->card) + return -EINVAL; + -+ host = self->func->card->host; -+ ret = mmc_hw_reset(self->func->card); -+ -+ /* -+ * On multi-function SDIO cards (BES2600 has WLAN func 1 + BT -+ * companion func 2), mmc_sdio_hw_reset() removes the card and -+ * returns 1 to signal "remove happened, caller must trigger -+ * rescan". The kernel does NOT auto-rescan in this case; -+ * single-function cards take the rescan path inline and return 0. -+ * Treat any non-negative return as success and force a rescan if -+ * mmc_hw_reset signalled the multi-function path - otherwise the -+ * card stays removed indefinitely after a wedge recovery, -+ * leaving wifi (and the BT companion) silent until reboot. -+ */ -+ if (ret > 0) { -+ bes_info("multi-func mmc_hw_reset removed card; scheduling rescan\n"); -+ mmc_detect_change(host, 0); -+ ret = 0; -+ } -+ return ret; ++ return mmc_hw_reset(self->func->card); +} + static bool bes2600_sdio_wakeup_source(struct sbus_priv *self) { struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data(); -@@ -1782,7 +1878,8 @@ static struct sbus_ops bes2600_sdio_sbus_ops = { - .sbus_reg_write = bes2600_sdio_reg_write, - .init = bes2600_sdio_misc_init, - #ifdef BES_SDIO_RX_MULTIPLE_ENABLE -- .pipe_read = bes2600_sdio_pipe_read, -+ /* Patch C v3: .pipe_read removed; bus_rx_batch replaces it. */ -+ .bus_rx_batch = bes2600_sdio_read_rx_batch, - #endif - #ifdef BES_SDIO_TX_MULTIPLE_ENABLE - .pipe_send = bes2600_sdio_pipe_send, -@@ -1794,6 +1891,7 @@ static struct sbus_ops bes2600_sdio_sbus_ops = { +@@ -1815,6 +1843,7 @@ static struct sbus_ops bes2600_sdio_sbus_ops = { .gpio_sleep = bes2600_gpio_allow_mcu_sleep, .halt_device = bes2600_sdio_halt_device, .wakeup_source = bes2600_sdio_wakeup_source, @@ -923,110 +2613,11 @@ index 13d4ff1e5..517e6f874 100644 }; static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv) -@@ -1801,9 +1899,15 @@ static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv) - long unsigned int old_ts, new_ts; - struct sbus_priv *self = hw_priv->sbus_priv; - -+ /* -+ * Patch C v3: rx_work removed. Wait for IRQ-timestamp activity -+ * to settle by polling self->last_irq_timestamp via msleep -+ * (best-effort). The caller already knows the bh thread will -+ * process pending bh_rx during its next wait_event round. -+ */ - do { - old_ts = self->last_irq_timestamp; -- flush_work(&self->rx_work); -+ msleep(2); - new_ts = self->last_irq_timestamp; - } while(old_ts != new_ts); - } -@@ -1834,6 +1938,9 @@ static int bes2600_sdio_probe(struct sdio_func *func, - if (ret) - goto err; - -+ /* wire struct device into factory.c for request_firmware() context */ -+ bes2600_factory_set_dev(dev); -+ - self->pdata = bes2600_get_platform_data(); - self->func = func; - self->dev = &func->dev; -@@ -1853,6 +1960,17 @@ static int bes2600_sdio_probe(struct sdio_func *func, - if (!self->single_gathered_buffer) - return -ENOMEM; - #endif -+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE -+ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL, -+ get_order(MAX_SDIO_TRANSFER_LEN)); -+ if (!self->tx_bounce) { -+#ifndef SDIO_HOST_ADMA_SUPPORT -+ free_pages((unsigned long)self->single_gathered_buffer, -+ get_order(MAX_SDIO_TRANSFER_LEN)); -+#endif -+ return -ENOMEM; -+ } -+#endif - #ifdef BES_SDIO_RXTX_TOGGLE - self->fw_started = false; - #endif -@@ -1913,8 +2031,8 @@ int bes2600_unregister_net_dev(struct sbus_priv *bus_priv) - BUG_ON(!bus_priv); - if (bus_priv->core && !bus_priv->unregister_in_process) { - bus_priv->unregister_in_process = true; -- bes2600_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb); - bes2600_core_release(bus_priv->core); -+ bes2600_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb); - bus_priv->core = NULL; - - if (bus_priv->sdio_wq) { -@@ -1980,6 +2098,12 @@ static void bes2600_sdio_remove(struct sdio_func *func) - if (self->single_gathered_buffer) { - free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN)); - } -+#endif -+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE -+ if (self->tx_bounce) { -+ free_pages((unsigned long)self->tx_bounce, -+ get_order(MAX_SDIO_TRANSFER_LEN)); -+ } - #endif - kfree(self); - } -@@ -2140,8 +2264,12 @@ static int bes2600_sdio_suspend_noirq(struct device *dev) - if (func->num > 1) - return 0; - -- if(self->core && -- (work_pending(&self->rx_work) || atomic_read(&self->core->bh_rx))) { -+ /* -+ * Patch C v3: work_pending(&self->rx_work) check dropped (no -+ * relay). bh_rx atomic alone tells us whether the bh thread -+ * has un-processed RX events queued. -+ */ -+ if (self->core && atomic_read(&self->core->bh_rx)) { - bes_devel("%s: Suspend interrupted.\n", __func__); - return -EAGAIN; - } diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c -index f89dcb8fb..5374d5117 100644 +index a02d6d9..d1375bc 100644 --- a/drivers/staging/bes2600/bes_chardev.c +++ b/drivers/staging/bes2600/bes_chardev.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Character device for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #include -@@ -1078,6 +1075,62 @@ int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_ +@@ -442,6 +442,48 @@ int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_ return ret; } @@ -1071,265 +2662,662 @@ index f89dcb8fb..5374d5117 100644 + + return 0; +} -+EXPORT_SYMBOL_GPL(bes2600_chrdev_do_bus_reset); -+ -+/* -+ * 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); -+} -+EXPORT_SYMBOL_GPL(bes2600_chrdev_trigger_bus_reset); + bool bes2600_chrdev_is_wifi_opened(void) { bool wifi_opened = false; +@@ -540,8 +582,21 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work) + /* unregister wifi */ + bes2600_switch_wifi(0); + +- /* power down device if wifi is only opened */ +- if (bes2600_chrdev_check_system_close()) { ++ /* ++ * Hard exception with a bus_reset implementation: tear the ++ * bus down via mmc_hw_reset() (or equivalent) so the next ++ * bringup probes a freshly reset chip. On PineTab2 this is ++ * the only effective recovery path -- the existing ++ * power_switch(0)/(1) sequence has no chip-reset signal of ++ * its own (sdio_pwrseq owns wifi_reset). ++ * ++ * Soft close, or hard close on a board without bus_reset: ++ * fall back to the legacy power_switch(0) sequence. ++ */ ++ if (bes2600_cdev.halt_dev && bes2600_cdev.sbus_ops->bus_reset) { ++ bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops, ++ bes2600_cdev.sbus_priv); ++ } else if (bes2600_chrdev_check_system_close()) { + bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops, + bes2600_cdev.sbus_priv); + } diff --git a/drivers/staging/bes2600/bes_chardev.h b/drivers/staging/bes2600/bes_chardev.h -index c627bb7c3..9edb2067a 100644 +index c627bb7..ca8419e 100644 --- a/drivers/staging/bes2600/bes_chardev.h +++ b/drivers/staging/bes2600/bes_chardev.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Character device interface for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef __BES_CHARDEV_H__ - #define __BES_CHARDEV_H__ -@@ -60,6 +57,8 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void); +@@ -60,6 +60,7 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void); /* used to control device power down */ 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_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_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev); void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv); -diff --git a/drivers/staging/bes2600/bes_fw.c b/drivers/staging/bes2600/bes_fw.c -index 133c9453b..6c5598b94 100644 ---- a/drivers/staging/bes2600/bes_fw.c -+++ b/drivers/staging/bes2600/bes_fw.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Firmware download for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include "bes_fw_common.h" - #include "bes2600.h" -@@ -125,8 +122,6 @@ int bes_host_slave_sync(struct bes2600_common *hw_priv) - } - */ +diff --git a/drivers/staging/bes2600/sbus.h b/drivers/staging/bes2600/sbus.h +index 1f2c0cd..cb90890 100644 +--- a/drivers/staging/bes2600/sbus.h ++++ b/drivers/staging/bes2600/sbus.h +@@ -75,6 +75,14 @@ struct sbus_ops { + void (*halt_device)(struct sbus_priv *self); + bool (*wakeup_source)(struct sbus_priv *self); + int (*reboot)(struct sbus_priv *self); ++ /* ++ * Force the host bus to re-detect and re-probe the chip. Called ++ * from the firmware-wedge recovery path when power_switch() has no ++ * effective chip-reset signal of its own (e.g. PineTab2, where the ++ * wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 node). ++ * Returns 0 on success or a negative errno. ++ */ ++ int (*bus_reset)(struct sbus_priv *self); + }; --//#define DATA_DUMP_OBSERVE -- - static int bes_firmware_download_write_reg(struct platform_fw_t *fw_data, u32 addr, u32 val) + void bes2600_irq_handler(struct bes2600_common *priv); +-- +2.54.0 + +From 3942404ae16b134a55e48cb796d625b8b90e504f Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 21:37:37 +0200 +Subject: [PATCH 19/29] bes2600: handle multi-function SDIO cards in + mmc_hw_reset bus_reset + +c5.2 (recover-wedged-firmware-via-mmc-hw-reset) wraps mmc_hw_reset() +and treats any non-zero return as a recovery failure. On +single-function SDIO cards mmc_hw_reset returns 0 after doing the +remove + rescan inline. On multi-function cards (BES2600 has WLAN +func 1 + BT companion func 2) the kernel's mmc_sdio_hw_reset() does +NOT do the rescan: it tears the card down and returns 1 to signal +"caller must trigger rescan". + +Field observation on PineTab2 (linux-pinetab2 6.19.10-danctnix1): +when a real LMAC wedge fired bes2600_chrdev_wifi_force_close -> +bes2600_chrdev_do_bus_reset, mmc_hw_reset returned 1, c5.2's wrapper +treated that as "bus_reset failed: 1", logged the error, and gave +up. The card was already removed (mmc2: card 0001 removed) but +nothing scheduled a rescan; wifi (and the BT companion which shares +the same SDIO host) stayed silent until the user rebooted four +minutes later. + +Fix: + + - Capture the mmc_host pointer before calling mmc_hw_reset (the + card pointer is invalid after the remove). + - On positive return (multi-function path), log informationally + and call mmc_detect_change(host, 0) to schedule a rescan. + Return 0 so callers see the recovery as successful. + - Negative return is still treated as failure as before. + +The mmc_detect_change side effect is asynchronous; the chrdev's +wait_event_timeout(probe_done_wq, !sbus_priv) still observes the +remove half synchronously, and the rescan + re-probe runs out of +the host detect work afterwards. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_sdio.c | 24 +++++++++++++++++++++++- + 1 file changed, 23 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index 5a0694a..c81c244 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -1810,10 +1810,32 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self) + */ + static int bes2600_sdio_bus_reset(struct sbus_priv *self) { - u8 frame_num = 0; -@@ -468,14 +463,6 @@ static int bes_firmware_download(struct platform_fw_t *fw_data, const char *fw_n - - const struct firmware *fw_bin; - --#ifdef DATA_DUMP_OBSERVE -- char *observe; -- size_t observe_len; -- loff_t observe_off = 0; -- mm_segment_t old_fs; -- struct file *observe_file = NULL; --#endif -- - struct fw_msg_hdr_t header; - struct fw_info_t fw_info; - struct download_fw_t download_addr; -@@ -583,14 +570,6 @@ const struct firmware *fw_bin; - } - download_addr.addr = fw_info.addr; - --#ifdef DATA_DUMP_OBSERVE -- observe_file = filp_open("/lib/firmware/bes2002_fw_write.bin", O_CREAT | O_RDWR, 0); -- if (IS_ERR(observe_file)) { -- bes_err("create data_dump file err:%ld\n", IS_ERR(observe_file)); -- observe_file = NULL; -- } --#endif -- - while (code_length) { - - #if 1 -@@ -640,17 +619,6 @@ const struct firmware *fw_bin; - //mdelay(5000); - bes_devel("tx_download_firmware_data:%x %d\n", download_addr.addr, length); - --#ifdef DATA_DUMP_OBSERVE -- if (observe_file) { -- observe = (char *)(long_buf + sizeof(struct fw_msg_hdr_t) + sizeof(struct download_fw_t)); -- observe_len = length - sizeof(struct fw_msg_hdr_t) - sizeof(struct download_fw_t); -- old_fs = get_fs(); -- set_fs(KERNEL_DS); -- vfs_write(observe_file, observe, observe_len, &observe_off); -- set_fs(old_fs); -- } --#endif -- - ret = bes2600_data_write(long_buf, length > 512 ? length : 512); - if (ret) { - bes_err("tx download fw data err:%d\n", ret); -@@ -832,11 +800,6 @@ const struct firmware *fw_bin; - - err2: - kfree(long_buf); --#ifdef DATA_DUMP_OBSERVE -- if (observe_file) { -- filp_close(observe_file, NULL); -- } --#endif - err1: - kfree(short_buf); - release_firmware(fw_bin); -diff --git a/drivers/staging/bes2600/bes_fw_common.c b/drivers/staging/bes2600/bes_fw_common.c -index 2e4745569..a0c1f9312 100644 ---- a/drivers/staging/bes2600/bes_fw_common.c -+++ b/drivers/staging/bes2600/bes_fw_common.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Firmware download common code for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include "bes_fw_common.h" - #include "bes_log.h" -diff --git a/drivers/staging/bes2600/bes_fw_common.h b/drivers/staging/bes2600/bes_fw_common.h -index 5c6561a39..dcd520058 100644 ---- a/drivers/staging/bes2600/bes_fw_common.h -+++ b/drivers/staging/bes2600/bes_fw_common.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Firmware download common interface - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef __BES_FW_COMMON_H__ - #define __BES_FW_COMMON_H__ -diff --git a/drivers/staging/bes2600/bes_log.h b/drivers/staging/bes2600/bes_log.h -index 605cea8e9..7d3c4b8de 100644 ---- a/drivers/staging/bes2600/bes_log.h -+++ b/drivers/staging/bes2600/bes_log.h -@@ -1,3 +1,10 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ -+/* -+ * printk wrappers for BES2600 -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. -+ * -+ */ - extern struct device *global_dev; - - #ifdef CONFIG_BES2600_ENABLE_DEVEL_LOGS -@@ -8,3 +15,26 @@ extern struct device *global_dev; - #define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__) - #define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__) - #define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__) ++ struct mmc_host *host; ++ int ret; + -+/* -+ * Legacy debug-subsystem-tagged log macros. The per-subsystem filtering -+ * was never implemented in-tree; these shims let code paths gated by -+ * CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC -+ * build when their conditions are enabled. The first argument is -+ * currently unused; pick one of the BES2600_DBG_* constants below for -+ * documentation. -+ */ -+#define BES2600_DBG_SBUS 0 -+#define BES2600_DBG_DOWNLOAD 0 -+#define BES2600_DBG_ITP 0 -+#define BES2600_DBG_TEST_MODE 0 -+ -+#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__) -+#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__) -+#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__) -+#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__) -+#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \ -+ do { \ -+ if (_cond) \ -+ bes_err(fmt, ##__VA_ARGS__); \ -+ } while (0) -diff --git a/drivers/staging/bes2600/bes_nl80211_testmode_msg.h b/drivers/staging/bes2600/bes_nl80211_testmode_msg.h -index b70a0dddc..c97c1ad78 100644 ---- a/drivers/staging/bes2600/bes_nl80211_testmode_msg.h -+++ b/drivers/staging/bes2600/bes_nl80211_testmode_msg.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Vendor testmode messages for BES2600 - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ + if (!self || !self->func || !self->func->card) + return -EINVAL; - #ifndef BES_NL80211_TESTMODE_MSG_H +- return mmc_hw_reset(self->func->card); ++ host = self->func->card->host; ++ ret = mmc_hw_reset(self->func->card); ++ ++ /* ++ * On multi-function SDIO cards (BES2600 has WLAN func 1 + BT ++ * companion func 2), mmc_sdio_hw_reset() removes the card and ++ * returns 1 to signal "remove happened, caller must trigger ++ * rescan". The kernel does NOT auto-rescan in this case; ++ * single-function cards take the rescan path inline and return 0. ++ * Treat any non-negative return as success and force a rescan if ++ * mmc_hw_reset signalled the multi-function path - otherwise the ++ * card stays removed indefinitely after a wedge recovery, ++ * leaving wifi (and the BT companion) silent until reboot. ++ */ ++ if (ret > 0) { ++ bes_info("multi-func mmc_hw_reset removed card; scheduling rescan\n"); ++ mmc_detect_change(host, 0); ++ ret = 0; ++ } ++ return ret; + } + + static bool bes2600_sdio_wakeup_source(struct sbus_priv *self) +-- +2.54.0 + +From 40aec44a6e4de5aaf0066982601c99e648b0f1ec Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:05:27 +0200 +Subject: [PATCH 16/29] bes2600: gate PM indication completion on pending + request and track chip state + +When mac80211 toggles PSM on the BES2600, the host sends WSM set_pm +and waits up to 5 s on bes_power.pm_enter_cmpl for a firmware-side +PM-changed indication confirming the transition. Three sequenced +flaws make the wait-and-confirm racy and leave host/chip bookkeeping +desynced when anything misfires: + + 1) bes2600_pwr_notify_ps_changed() unconditionally fires + complete(pm_enter_cmpl) for any non-active psmode. It does not + check whether a host-initiated set_pm is actually pending. A + spontaneous indication (firmware-internal coex move, + idle-driven aging) primes the completion, and the next host- + driven enter_lp_mode sees a false success on its first + wait_for_completion_timeout. + + 2) The wait/reinit ordering in bes2600_pwr_enter_lp_mode is + + status = wait_for_completion_timeout(...); + atomic_set(pm_set_in_process, 0); + reinit_completion(...); + + If an indication arrives between wait_for_completion_timeout + returning with status==1 and reinit_completion, the next + enter_lp_mode iteration's wait can also see false success. The + reinit must happen *before* we start the new request, not + after handling the previous one. + + 3) On wait_pm_ind timeout, the driver returns -ETIMEDOUT and walks + away. It does not record that the firmware's actual PM state + is no longer known to the host. Subsequent wake paths + (gpio_wake / sbus_active) assume the chip is still active and + hit deterministic SDIO failures when the firmware has + transitioned anyway. + +This patch is the safe-prerequisite half of a wider fix: + + * bes_pwr.h gains enum bes2600_chip_pm_state {ACTIVE, LP, UNKNOWN} + and bes_power.chip_pm_state. Its job is to track what the host + has *seen the firmware confirm*, not what the host has + requested. Initialised to ACTIVE in bes2600_pwr_init(). + + * bes2600_pwr_notify_ps_changed() unconditionally updates + chip_pm_state on every indication, but only fires + complete(pm_enter_cmpl) when atomic_cmpxchg(pm_set_in_process, + 1, 0) succeeds. A spontaneous indication can no longer prime a + waiter that will only set up its request afterwards. + + * bes2600_pwr_enter_lp_mode() now reinit_completion()s before + setting pm_set_in_process and sending wsm_set_pm. After a + timeout, it cmpxchgs pm_set_in_process back to 0 (so a late + indication cannot prime the next iteration) and on the win- + cmpxchg branch records chip_pm_state=UNKNOWN. + +A follow-up patch consumes chip_pm_state on the wake side +(bes2600_pwr_device_exit_lp_mode + bes2600_gpio_wakeup_mcu) to fix +the deterministic "active mcu fail" cycle this state-record +enables a fix for. Splitting the work this way keeps the lock-free +race fix small and reviewable on its own. + +No new locks, no behaviour change on the success path. Only the +recovery path (timeout + spontaneous indication) gains correctness. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_pwr.c | 94 ++++++++++++++++++++++++++++++++++++++++++----- + bes2600/bes_pwr.h | 15 ++++++++ + 2 files changed, 100 insertions(+), 9 deletions(-) + diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c -index e7a104542..a3f954bf3 100644 +index 474b6f1..9b4a4de 100644 --- a/drivers/staging/bes2600/bes_pwr.c +++ b/drivers/staging/bes2600/bes_pwr.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Chip-side power state machine for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #include -@@ -467,14 +464,65 @@ static void bes2600_pwr_device_enter_lp_mode(struct bes2600_common *hw_priv) +@@ -524,7 +524,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n", + __func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod, + priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod); ++ /* ++ * Reinit BEFORE the WSM goes out, so a stale ++ * indication from a previous cycle cannot have ++ * primed pm_enter_cmpl. From here until the ++ * indication callback's cmpxchg(1->0) on ++ * pm_set_in_process, only the indication for ++ * THIS request can complete the wait. ++ */ ++ reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); + atomic_set(&hw_priv->bes_power.pm_set_in_process, 1); ++ + ret = bes2600_set_pm(priv, &priv->powersave_mode); + if (ret) { + atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); +@@ -535,11 +545,33 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + + /* wait power save mode changed indication */ + status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ); +- atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); +- reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); + if (!status) { +- bes_devel("%s, wait pm ind timeout\n", __func__); +- timeouts++; ++ /* ++ * The indication callback only fires ++ * complete() when it observes ++ * pm_set_in_process == 1; cmpxchg it ++ * to 0 here so a late indication ++ * cannot prime the next wait. ++ * ++ * If we win the cmpxchg, this is a ++ * real timeout: the firmware's PS ++ * state is unknown to us. Mark it as ++ * such so the next wake path can ++ * probe before assuming the chip is ++ * still active. ++ * ++ * If we lose the cmpxchg, the ++ * indication arrived between the ++ * wait timing out and us getting ++ * here; treat as success. ++ */ ++ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, ++ 1, 0) == 1) { ++ bes_devel("%s, wait pm ind timeout\n", __func__); ++ atomic_set(&hw_priv->bes_power.chip_pm_state, ++ BES2600_CHIP_PM_UNKNOWN); ++ timeouts++; ++ } + } + } else { + bes_devel("skip enter lp mode\n"); +@@ -554,10 +586,34 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + * in an inconsistent state that cascades into SDIO TX errors on + * the BES2600. + */ +- if (timeouts == 0) ++ if (timeouts == 0) { + bes2600_pwr_device_enter_lp_mode(hw_priv); +- else ++ } else { ++ /* ++ * device_enter_lp_mode() was skipped (one or more VIFs ++ * timed out waiting for the firmware indication) so its ++ * gpio_sleep(MCU) - which drops the wake-flag bit and, if ++ * no other subsystem holds the wake, drives the GPIO low - ++ * never ran. Without it the bit stays asserted, and the ++ * next bes2600_pwr_device_exit_lp_mode() calls ++ * gpio_wake(MCU) into a "bit already set" no-op: the GPIO ++ * never re-edges, sbus_active() exhausts its 200x2ms ++ * MCU_WAKEUP_READY budget against an unwoken chip, and ++ * the first TX after idle stalls for several seconds. ++ * ++ * Drop the MCU wake-flag bit explicitly here so the next ++ * wake injects a real GPIO edge. gpio_allow_mcu_sleep ++ * preserves multi-subsystem semantics: it only drives the ++ * GPIO low when no other subsystem still holds wake; if ++ * BT or another holder is keeping the chip awake, the ++ * GPIO stays high and the bit clear here is purely ++ * bookkeeping (so the next gpio_wake doesn't no-op). ++ */ ++ if (hw_priv->sbus_ops->gpio_sleep) ++ hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, ++ GPIO_WAKE_FLAG_MCU); + ret = -ETIMEDOUT; ++ } + + return ret; + } +@@ -833,6 +889,7 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv) + hw_priv->bes_power.power_up_task = NULL; + mutex_init(&hw_priv->bes_power.pwr_mutex); + atomic_set(&hw_priv->bes_power.dev_state, 0); ++ atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); + init_completion(&hw_priv->bes_power.pm_enter_cmpl); + sema_init(&hw_priv->bes_power.sync_lock, 1); + device_set_wakeup_capable(hw_priv->pdev, true); +@@ -1213,9 +1270,28 @@ int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event) + + void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) + { +- if((psmode & 0x01) != WSM_PSM_ACTIVE) { +- bes_devel("complete pm_enter_cmpl\n"); +- complete(&hw_priv->bes_power.pm_enter_cmpl); ++ /* ++ * The firmware sends a PM-changed indication for every transition, ++ * including ones we didn't ask for (firmware-internal coex moves, ++ * idle-driven aging). Update chip_pm_state unconditionally so the ++ * wake path can use it, but only fire pm_enter_cmpl when a host- ++ * initiated set_pm is actually in flight - otherwise a stale ++ * indication can prime a future wait against a freshly ++ * reinit_completion()'ed state. ++ */ ++ if ((psmode & 0x01) != WSM_PSM_ACTIVE) { ++ atomic_set(&hw_priv->bes_power.chip_pm_state, ++ BES2600_CHIP_PM_LP); ++ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, ++ 1, 0) == 1) { ++ bes_devel("complete pm_enter_cmpl\n"); ++ complete(&hw_priv->bes_power.pm_enter_cmpl); ++ } else { ++ bes_devel("PM ind (LP) without pending wait; state recorded\n"); ++ } ++ } else { ++ atomic_set(&hw_priv->bes_power.chip_pm_state, ++ BES2600_CHIP_PM_ACTIVE); + } + } + +diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h +index 1ba866c..6bc44ac 100644 +--- a/drivers/staging/bes2600/bes_pwr.h ++++ b/drivers/staging/bes2600/bes_pwr.h +@@ -64,6 +64,20 @@ enum power_down_state + POWER_DOWN_STATE_UNLOCKED, + }; + ++/* ++ * Confirmed PM state of the firmware-side chip. Tracks what the host ++ * has *seen* the firmware acknowledge, not what the host has ++ * requested. UNKNOWN means a host-initiated transition timed out ++ * before the firmware indication arrived; the next wake path should ++ * treat it as "we don't know" and probe before issuing GPIO/SDIO ++ * wakeup ops. ++ */ ++enum bes2600_chip_pm_state { ++ BES2600_CHIP_PM_ACTIVE = 0, ++ BES2600_CHIP_PM_LP, ++ BES2600_CHIP_PM_UNKNOWN, ++}; ++ + typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv); + typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv); + +@@ -106,6 +120,7 @@ struct bes2600_pwr_t + bool ap_lp_bad; + struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM]; + atomic_t pm_set_in_process; ++ atomic_t chip_pm_state; + }; + + #ifdef CONFIG_BES2600_WOWLAN +-- +2.54.0 + +From 7a65dc374c671e20bd6303959ff234a179bc9ff7 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:23:34 +0200 +Subject: [PATCH 17/29] bes2600: short-circuit wake handshake when chip is + confirmed ACTIVE + +The previous patch ("bes2600: gate PM indication completion on pending +request and track chip state") added enum bes2600_chip_pm_state and the +chip_pm_state field tracking what the host has *seen the firmware +confirm*. This patch makes the wake side use it. + +Without this, every bes2600_pwr_device_exit_lp_mode() unconditionally +runs gpio_wake() + sbus_active() + wsm_set_operational_mode(active), +even when the chip is already in confirmed-ACTIVE state and the wake +sequence has nothing to do. The visible failure mode on PineTab2: + + bes2600_pwr_enter_lp_mode, wait pm ind timeout + repeat set gpio_wake_flag, sub_sys:0 + bes2600_sdio_active failed, subsys:0 + bes2600_pwr_device_exit_lp_mode, active mcu fail + +cycling every ~9 s, ~22 cycles in 10 minutes. Three pieces: + + 1. enter_lp_mode timed out (firmware indication lost). With c6.1, + chip_pm_state is now UNKNOWN. + 2. lock_device fires exit_lp_mode. + 3. gpio_wake hits "bit already set" because device_enter_lp_mode + was skipped when the indication timed out, so gpio_sleep was + never called - the bit reflects driver intent, not chip state. + gpio_wake silently no-ops (no GPIO edge), bit stays set. + 4. sbus_active spends 200 x 2 ms looking for MCU_WAKEUP_READY that + never comes (firmware was never told to wake), then fails. + 5. Driver continues to wsm_set_operational_mode against the wedged + bus, compounding the failure. + +This patch's three moves: + + * bes2600_pwr_device_exit_lp_mode() reads chip_pm_state at entry. + On BES2600_CHIP_PM_ACTIVE, log at devel level and return without + touching gpio_wake / sbus_active / WSM. The chip is in the state + we want; the handshake exists only to drive a transition. + + * On BES2600_CHIP_PM_LP or BES2600_CHIP_PM_UNKNOWN, run the wake + handshake as before, but on sbus_active() failure: set + chip_pm_state = UNKNOWN, log once at err level, and bail out. + Do NOT call wsm_set_operational_mode over a wedged bus - it + would just emit a second error and leave the chip in an even + less defined state. + + * bes2600_gpio_wakeup_mcu() / bes2600_gpio_allow_mcu_sleep(): + demote "repeat set/clear gpio_wake_flag" from bes_err to + bes_devel. Multi-subsystem wake-hold (e.g. WIFI + BT both want + MCU awake) is the steady-state case, and the symmetric clear + while bit-already-clear is racy bookkeeping rather than a + hardware error. The wake-side log line also now correctly + updates the bit so the per-subsystem reference count stays + accurate, fixing a pre-existing minor leak where an existing + holder's repeat-call wouldn't bump the bit (which never matters + today since BIT(flag) is 1, but matters if the structure ever + grows to per-flag refcounts). + +Net effect on the cycle: + + * If chip is genuinely ACTIVE (chip_pm_state == ACTIVE), wake skips + cleanly. Storm goes silent. + * If chip is genuinely LP, behaviour is unchanged. + * If chip is UNKNOWN (post-timeout state), one wake attempt is + made; on failure, state stays UNKNOWN and we don't emit a + second cascade error per attempt. Repeated UNKNOWN with failed + wake will eventually be picked up by the LMAC active-monitor + and escalated to mmc_hw_reset (c5.2). + +No new locks, no new state. Only consumption of the chip_pm_state +field added in the prerequisite patch. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes2600_sdio.c | 15 +++++++++-- + bes2600/bes_pwr.c | 56 ++++++++++++++++++++++++++++++++++++------ + 2 files changed, 62 insertions(+), 9 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index f7f86d7..5a0694a 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -1389,7 +1389,14 @@ static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag) + + /* error check */ + if((self->gpio_wakup_flags & BIT(flag)) != 0) { +- bes_err( "repeat set gpio_wake_flag, sub_sys:%d", flag); ++ /* ++ * Multiple subsystems holding wake is the steady-state case ++ * (e.g. WIFI + BT both want MCU awake). Demoted from bes_err ++ * to bes_devel since it isn't an error - the GPIO is already ++ * asserted high and the subsystem is now also tracked. ++ */ ++ bes_devel("repeat set gpio_wake_flag, sub_sys:%d\n", flag); ++ self->gpio_wakup_flags |= BIT(flag); + mutex_unlock(&self->io_mutex); + return; + } +@@ -1421,7 +1428,11 @@ static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag) + + /* error check */ + if((self->gpio_wakup_flags & BIT(flag)) == 0) { +- bes_err( "repeat clear gpio_wake_flag, sub_sys:%d", flag); ++ /* ++ * Mirror of the wake path: a clear when the bit is already ++ * clear is racy bookkeeping, not a hardware error. ++ */ ++ bes_devel("repeat clear gpio_wake_flag, sub_sys:%d\n", flag); + mutex_unlock(&self->io_mutex); + return; + } +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index 9b4a4de..b7b6c2f 100644 +--- a/drivers/staging/bes2600/bes_pwr.c ++++ b/drivers/staging/bes2600/bes_pwr.c +@@ -621,19 +621,61 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + static void bes2600_pwr_device_exit_lp_mode(struct bes2600_common *hw_priv) + { + int ret = 0; ++ enum bes2600_chip_pm_state state; + struct wsm_operational_mode mode = { + .power_mode = wsm_power_mode_active, + .disableMoreFlagUsage = true, + }; + +- bes_devel("host lock lmac\n"); +- if(hw_priv->sbus_ops->gpio_wake) +- hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); ++ /* ++ * Consult chip_pm_state set by bes2600_pwr_notify_ps_changed(). ++ * If we last saw the firmware confirm ACTIVE, skip ONLY the ++ * gpio_wake + sbus_active wake handshake - the GPIO is already ++ * asserted high and the SDIO MCU subsystem is already running, ++ * so another sbus_active() round-trip just hits its 200x2ms ++ * timeout because the firmware has nothing to do. ++ * ++ * wsm_set_operational_mode() below is NOT part of the wake ++ * handshake; it is the operational-mode setter the firmware ++ * tracks per call. Skipping it leaves the chip's SDIO state ++ * machine without a fresh operational-mode update, which on ++ * PineTab2 wedges the bus (-EBUSY on next sdio_rx_work read) ++ * within a few seconds of probe completion. So it must run ++ * unconditionally. ++ */ ++ state = atomic_read(&hw_priv->bes_power.chip_pm_state); ++ if (state == BES2600_CHIP_PM_ACTIVE) { ++ bes_devel("device_exit_lp_mode: chip already ACTIVE, skipping wake handshake\n"); ++ } else { ++ bes_devel("host lock lmac\n"); ++ if (hw_priv->sbus_ops->gpio_wake) ++ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, ++ GPIO_WAKE_FLAG_MCU); + +- if(hw_priv->sbus_ops->sbus_active) { +- ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU); +- if (ret) +- bes_err("%s, active mcu fail\n", __func__); ++ if (hw_priv->sbus_ops->sbus_active) { ++ ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, ++ SUBSYSTEM_MCU); ++ if (ret) { ++ /* ++ * MCU_WAKEUP_READY did not arrive within ++ * the SDIO handshake window. Record state ++ * as UNKNOWN so the next exit_lp_mode call ++ * also runs the full wake sequence (no ++ * skip), but still send operational_mode ++ * below to match pre-c6 behaviour - the ++ * WSM may succeed even if the SDIO active ++ * confirm was lost, and if it fails too, ++ * we just emit a second devel-level error. ++ * Repeated UNKNOWN is the signal for the ++ * LMAC active-monitor to eventually ++ * escalate to bus_reset (c5.2's ++ * mmc_hw_reset path). ++ */ ++ bes_err("%s, active mcu fail\n", __func__); ++ atomic_set(&hw_priv->bes_power.chip_pm_state, ++ BES2600_CHIP_PM_UNKNOWN); ++ } ++ } + } + + ret = wsm_set_operational_mode(hw_priv, &mode, 0); +-- +2.54.0 + +From dc1505f5bab24c5f0960dcc612ce51cd2e5aeddf Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 16:54:06 +0200 +Subject: [PATCH 18/29] bes2600: self-detect when firmware does not honor PSM + and skip the cycle + +The c6 series fixed several host-side bookkeeping bugs around PSM +transitions, but didn't address the underlying contract: this chip's +firmware (BES2600 with the Bestechnic Dec 2023 build that ships on +PineTab2 and most danctnix images) silently drops every WSM_set_pm +request without emitting the corresponding PM_INDICATION. The driver's +own power_down_work delayed work calls bes2600_pwr_enter_lp_mode every +~10s; without firmware acknowledgment each call burns 5s on +wait_for_completion_timeout(pm_enter_cmpl, 5*HZ) and produces a +recurring three-line cascade in dmesg: + + bes2600_pwr_enter_lp_mode, wait pm ind timeout + bes2600_sdio_active failed, subsys:0 + bes2600_pwr_device_exit_lp_mode, active mcu fail + +Confirmed by tripwire instrumentation on PineTab2 (linux-pinetab2 +6.19.10-danctnix1, ohm) running the c5+c6 stack: zero +wsm_set_pm_indication() invocations across an entire boot, while +bes2600_pwr_enter_lp_mode timed out repeatedly, and +bes2600_sdio_active() consistently saw BES_SLAVE_STATUS_REG_ID return +0x2f (every "ready" bit set except MCU_WAKEUP_READY (bit 4) - the +firmware reports "I'm awake, there's nothing to wake from"). + +This patch makes the driver self-heal: + + * struct bes2600_pwr_t gains pm_unsupported (bool) and + pm_consecutive_timeouts (unsigned int). Both initialised to + 0/false. + + * bes2600_pwr_enter_lp_mode early-returns -EOPNOTSUPP when + pm_unsupported is set. Skips the per-VIF set_pm round-trip and + the wait_for_completion entirely. + + * On the cmpxchg-success branch of the timeout path, we increment + pm_consecutive_timeouts. When it crosses + BES2600_PM_UNSUPPORTED_THRESHOLD (3, ~15s of trying), we latch + pm_unsupported = true and force chip_pm_state = ACTIVE so that + bes2600_pwr_device_exit_lp_mode's c6.2 skip branch covers the + wake side (no gpio_wake / sbus_active / WSM_set_operational_mode + reissue past the first one). + + * bes2600_pwr_notify_ps_changed resets pm_consecutive_timeouts to 0 + on any incoming PM indication, and clears pm_unsupported if it + was previously latched. So a firmware update that fixes PM_IND + delivery automatically re-enables PSM transitions without a + driver rebuild. + +mac80211's PSM requests via bes2600_set_pm() still flow to the +firmware unchanged; they just don't have host-side timeouts so they +remain silent regardless of firmware acknowledgment. Power +consumption goes up if the firmware actually CAN do PSM (we'd be +keeping the chip awake unnecessarily), but on a chip where the +counter trips this trade-off is forced anyway: the chip stayed awake +under the broken cascade as well, just with constant SDIO churn. + +Net effect on dmesg: after ~15s of boot, the three-line cascade stops +firing entirely. The firmware-side wedge is observed once per boot +(captured by the pm_unsupported latch) instead of per-cycle. + +Signed-off-by: Markus Fritsche +--- + bes2600/bes_pwr.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++- + bes2600/bes_pwr.h | 9 ++++++ + 2 files changed, 78 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index b7b6c2f..620acef 100644 +--- a/drivers/staging/bes2600/bes_pwr.c ++++ b/drivers/staging/bes2600/bes_pwr.c +@@ -467,6 +467,45 @@ static void bes2600_pwr_device_enter_lp_mode(struct bes2600_common *hw_priv) bes_devel("device enter sleep\n"); } @@ -1375,9 +3363,7 @@ index e7a104542..a3f954bf3 100644 static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) { int i = 0; - struct bes2600_vif *priv; - int ret = 0; -+ int timeouts = 0; +@@ -476,6 +515,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) char ip_str[20]; unsigned long status = 0; @@ -1395,209 +3381,39 @@ index e7a104542..a3f954bf3 100644 /* set interface low power configuration */ bes2600_for_each_vif(hw_priv, priv, i) { #ifdef P2P_MULTIVIF -@@ -523,27 +571,100 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) - bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n", - __func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod, - priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod); -+ /* -+ * Reinit BEFORE the WSM goes out, so a stale -+ * indication from a previous cycle cannot have -+ * primed pm_enter_cmpl. From here until the -+ * indication callback's cmpxchg(1->0) on -+ * pm_set_in_process, only the indication for -+ * THIS request can complete the wait. -+ */ -+ reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); - atomic_set(&hw_priv->bes_power.pm_set_in_process, 1); -+ - ret = bes2600_set_pm(priv, &priv->powersave_mode); - if (ret) { - atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); - bes_err("%s, set operation mode fail\n", __func__); -+ timeouts++; -+ continue; - } - - /* wait power save mode changed indication */ - status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ); -- atomic_set(&hw_priv->bes_power.pm_set_in_process, 0); -- reinit_completion(&hw_priv->bes_power.pm_enter_cmpl); -- if (!status) -- bes_err("%s, wait pm ind timeout\n", __func__); -+ if (!status) { -+ /* -+ * The indication callback only fires -+ * complete() when it observes -+ * pm_set_in_process == 1; cmpxchg it -+ * to 0 here so a late indication -+ * cannot prime the next wait. -+ * -+ * If we win the cmpxchg, this is a -+ * real timeout: the firmware's PS -+ * state is unknown to us. Mark it as -+ * such so the next wake path can -+ * probe before assuming the chip is -+ * still active. -+ * -+ * If we lose the cmpxchg, the -+ * indication arrived between the -+ * wait timing out and us getting -+ * here; treat as success. -+ */ -+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, -+ 1, 0) == 1) { -+ bes_devel("%s, wait pm ind timeout\n", __func__); -+ atomic_set(&hw_priv->bes_power.chip_pm_state, -+ BES2600_CHIP_PM_UNKNOWN); -+ timeouts++; +@@ -571,6 +621,9 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + atomic_set(&hw_priv->bes_power.chip_pm_state, + BES2600_CHIP_PM_UNKNOWN); + timeouts++; + if (++hw_priv->bes_power.pm_consecutive_timeouts + >= BES2600_PM_UNSUPPORTED_THRESHOLD) + bes2600_pwr_latch_pm_unsupported(hw_priv); -+ } -+ } + } + } } else { - bes_devel("skip enter lp mode\n"); - } - } - } - -- /* set device low power configuration */ -- bes2600_pwr_device_enter_lp_mode(hw_priv); -+ /* -+ * Enter the device-end of the LP transition only if every per-VIF -+ * mac80211 handshake reached firmware-ACKed completion. Doing the -+ * device-LP setup while any VIF is still pending leaves the driver -+ * in an inconsistent state that cascades into SDIO TX errors on -+ * the BES2600. -+ */ -+ if (timeouts == 0) { -+ bes2600_pwr_device_enter_lp_mode(hw_priv); -+ } else { -+ /* -+ * device_enter_lp_mode() was skipped (one or more VIFs -+ * timed out waiting for the firmware indication) so its -+ * gpio_sleep(MCU) - which drops the wake-flag bit and, if -+ * no other subsystem holds the wake, drives the GPIO low - -+ * never ran. Without it the bit stays asserted, and the -+ * next bes2600_pwr_device_exit_lp_mode() calls -+ * gpio_wake(MCU) into a "bit already set" no-op: the GPIO -+ * never re-edges, sbus_active() exhausts its 200x2ms -+ * MCU_WAKEUP_READY budget against an unwoken chip, and -+ * the first TX after idle stalls for several seconds. -+ * -+ * Drop the MCU wake-flag bit explicitly here so the next -+ * wake injects a real GPIO edge. gpio_allow_mcu_sleep -+ * preserves multi-subsystem semantics: it only drives the -+ * GPIO low when no other subsystem still holds wake; if -+ * BT or another holder is keeping the chip awake, the -+ * GPIO stays high and the bit clear here is purely -+ * bookkeeping (so the next gpio_wake doesn't no-op). -+ */ +@@ -609,7 +662,8 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) + * GPIO stays high and the bit clear here is purely + * bookkeeping (so the next gpio_wake doesn't no-op). + */ +- if (hw_priv->sbus_ops->gpio_sleep) + if (!hw_priv->bes_power.pm_unsupported && + hw_priv->sbus_ops->gpio_sleep) -+ hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, -+ GPIO_WAKE_FLAG_MCU); -+ ret = -ETIMEDOUT; -+ } - - return ret; - } -@@ -551,19 +672,61 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv) - static void bes2600_pwr_device_exit_lp_mode(struct bes2600_common *hw_priv) - { - int ret = 0; -+ enum bes2600_chip_pm_state state; - struct wsm_operational_mode mode = { - .power_mode = wsm_power_mode_active, - .disableMoreFlagUsage = true, - }; - -- bes_devel("host lock lmac\n"); -- if(hw_priv->sbus_ops->gpio_wake) -- hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU); -- -- if(hw_priv->sbus_ops->sbus_active) { -- ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU); -- if (ret) -- bes_err("%s, active mcu fail\n", __func__); -+ /* -+ * Consult chip_pm_state set by bes2600_pwr_notify_ps_changed(). -+ * If we last saw the firmware confirm ACTIVE, skip ONLY the -+ * gpio_wake + sbus_active wake handshake - the GPIO is already -+ * asserted high and the SDIO MCU subsystem is already running, -+ * so another sbus_active() round-trip just hits its 200x2ms -+ * timeout because the firmware has nothing to do. -+ * -+ * wsm_set_operational_mode() below is NOT part of the wake -+ * handshake; it is the operational-mode setter the firmware -+ * tracks per call. Skipping it leaves the chip's SDIO state -+ * machine without a fresh operational-mode update, which on -+ * PineTab2 wedges the bus (-EBUSY on next sdio_rx_work read) -+ * within a few seconds of probe completion. So it must run -+ * unconditionally. -+ */ -+ state = atomic_read(&hw_priv->bes_power.chip_pm_state); -+ if (state == BES2600_CHIP_PM_ACTIVE) { -+ bes_devel("device_exit_lp_mode: chip already ACTIVE, skipping wake handshake\n"); -+ } else { -+ bes_devel("host lock lmac\n"); -+ if (hw_priv->sbus_ops->gpio_wake) -+ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, -+ GPIO_WAKE_FLAG_MCU); -+ -+ if (hw_priv->sbus_ops->sbus_active) { -+ ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, -+ SUBSYSTEM_MCU); -+ if (ret) { -+ /* -+ * MCU_WAKEUP_READY did not arrive within -+ * the SDIO handshake window. Record state -+ * as UNKNOWN so the next exit_lp_mode call -+ * also runs the full wake sequence (no -+ * skip), but still send operational_mode -+ * below to match pre-c6 behaviour - the -+ * WSM may succeed even if the SDIO active -+ * confirm was lost, and if it fails too, -+ * we just emit a second devel-level error. -+ * Repeated UNKNOWN is the signal for the -+ * LMAC active-monitor to eventually -+ * escalate to bus_reset (c5.2's -+ * mmc_hw_reset path). -+ */ -+ bes_err("%s, active mcu fail\n", __func__); -+ atomic_set(&hw_priv->bes_power.chip_pm_state, -+ BES2600_CHIP_PM_UNKNOWN); -+ } -+ } - } - - ret = wsm_set_operational_mode(hw_priv, &mode, 0); -@@ -819,6 +982,9 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv) - hw_priv->bes_power.power_up_task = NULL; + hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv, + GPIO_WAKE_FLAG_MCU); + ret = -ETIMEDOUT; +@@ -932,6 +986,8 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv) mutex_init(&hw_priv->bes_power.pwr_mutex); atomic_set(&hw_priv->bes_power.dev_state, 0); -+ atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); + atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN); + hw_priv->bes_power.pm_unsupported = false; + hw_priv->bes_power.pm_consecutive_timeouts = 0; init_completion(&hw_priv->bes_power.pm_enter_cmpl); sema_init(&hw_priv->bes_power.sync_lock, 1); device_set_wakeup_capable(hw_priv->pdev, true); -@@ -1199,9 +1365,40 @@ int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event) - - void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) - { -- if((psmode & 0x01) != WSM_PSM_ACTIVE) { -- bes_devel("complete pm_enter_cmpl\n"); -- complete(&hw_priv->bes_power.pm_enter_cmpl); -+ /* -+ * The firmware sends a PM-changed indication for every transition, -+ * including ones we didn't ask for (firmware-internal coex moves, -+ * idle-driven aging). Update chip_pm_state unconditionally so the -+ * wake path can use it, but only fire pm_enter_cmpl when a host- -+ * initiated set_pm is actually in flight - otherwise a stale -+ * indication can prime a future wait against a freshly -+ * reinit_completion()'ed state. -+ */ +@@ -1321,6 +1377,18 @@ void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode) + * indication can prime a future wait against a freshly + * reinit_completion()'ed state. + */ + /* + * Any PM indication, whatever its psmode, proves the firmware is + * actually emitting them. Reset the consecutive-timeout counter @@ -1610,24 +3426,1778 @@ index e7a104542..a3f954bf3 100644 + hw_priv->bes_power.pm_unsupported = false; + } + -+ if ((psmode & 0x01) != WSM_PSM_ACTIVE) { -+ atomic_set(&hw_priv->bes_power.chip_pm_state, -+ BES2600_CHIP_PM_LP); -+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process, -+ 1, 0) == 1) { -+ bes_devel("complete pm_enter_cmpl\n"); -+ complete(&hw_priv->bes_power.pm_enter_cmpl); -+ } else { -+ bes_devel("PM ind (LP) without pending wait; state recorded\n"); -+ } -+ } else { -+ atomic_set(&hw_priv->bes_power.chip_pm_state, -+ BES2600_CHIP_PM_ACTIVE); + if ((psmode & 0x01) != WSM_PSM_ACTIVE) { + atomic_set(&hw_priv->bes_power.chip_pm_state, + BES2600_CHIP_PM_LP); +diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h +index 6bc44ac..92de90b 100644 +--- a/drivers/staging/bes2600/bes_pwr.h ++++ b/drivers/staging/bes2600/bes_pwr.h +@@ -121,6 +121,15 @@ struct bes2600_pwr_t + struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM]; + atomic_t pm_set_in_process; + atomic_t chip_pm_state; ++ /* ++ * Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD ++ * consecutive enter_lp_mode timeouts with zero PM_INDICATIONs ++ * received from firmware. Indicates this chip's firmware does ++ * not honor host-driven PSM transitions; further attempts are ++ * skipped to avoid the 5s timeout cascade. ++ */ ++ bool pm_unsupported; ++ unsigned int pm_consecutive_timeouts; + }; + + #ifdef CONFIG_BES2600_WOWLAN +-- +2.54.0 + +From 91640bd96d36dd5769b1325e1b2130a95277e0e7 Mon Sep 17 00:00:00 2001 +From: "Claude (noether)" +Date: Wed, 6 May 2026 19:50:52 +0200 +Subject: [PATCH 20/29] bes2600: pre-empt AP-deauth-6 with mac80211 reassoc on + decrypt-fail storm + +When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a burst +of received frames (typically because the host's PTK or GTK has fallen +out of sync with the AP), the AP eventually concludes that the STA is +not authenticated and emits an unprotected deauth-reason-6 ("Class 2 +frame received from non-authenticated station"). On the deployed +pinetab2 + bes2600 stack this AP-initiated deauth has been observed to +leave the link blackholed for up to 109 s before userspace finds a +different SSID/channel to recover on. (Receipts at +https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.) + +Add a sliding-window counter on each bes2600_vif: when 5 decrypt +failures fire within 5 s, schedule a worker that calls +ieee80211_connection_loss(vif). mac80211 then performs immediate +disassociation; userspace (NetworkManager / wpa_supplicant) reconnects +with fresh keys before the AP gets a chance to fire its unprotected +deauth. + +Predicted Phase 7 delta vs the unpatched baseline: +- decrypt-burst rate: unchanged (this does not address root cause) +- AP-deauth-6 rate: <= 0.2 of baseline +- conditional probability of >5s blackhole given a burst: + 100% -> <= 10% +- worst-case recovery time: 109s -> <5s + +Contract pin: ieee80211_connection_loss() per +include/net/mac80211.h: "may also be called if the connection needs to +be terminated for some other reason... will cause immediate change to +disassociated state, without connection recovery attempts." Userspace +recovery is the existing NM/wpa_supplicant path. The worker context +satisfies the implicit process-context expectation. + +Files touched: +- bes2600/bes2600.h: 4 new fields on struct bes2600_vif + 2 prototypes +- bes2600/txrx.c: new helpers + the call site at the existing + WSM_STATUS_DECRYPTFAILURE log point (the unconditional "goto drop" + branch in bes2600_rx_cb) +- bes2600/sta.c: bes2600_decrypt_storm_init() in bes2600_vif_setup; + cancel_work_sync() in bes2600_remove_interface, alongside the + existing per-vif cancel_*_work_sync block. Safe under the kernel + cancel_work_sync contract: the work_struct is INIT_WORK'd in setup, + so the call is valid; it blocks until any in-flight handler returns, + ensuring no use-after-free of priv when mac80211 frees the vif; and + it is idempotent (subsequent calls just return false). +- bes2600/debug.c: DecryptStormRecoveries seq_printf in the per-vif + status seq_file output + +Threshold (5/5s) is set well above the steady-state per-vif decrypt- +fail rate observed in measurement (~1/min even under sustained 1 MB/s +load), so a true storm is required to trip it. The cw1200/cw1260 +ancestor has no equivalent storm-recovery; this is a clean addition. + +checkpatch.pl --no-tree --strict: clean (0/0/0). + +Signed-off-by: Claude (noether) +Co-Authored-By: Claude Opus 4.7 (1M context) +--- + bes2600/bes2600.h | 9 ++++++ + bes2600/debug.c | 2 ++ + bes2600/sta.c | 2 ++ + bes2600/txrx.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 87 insertions(+) + +diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h +index 0e60960..66482f7 100644 +--- a/drivers/staging/bes2600/bes2600.h ++++ b/drivers/staging/bes2600/bes2600.h +@@ -596,6 +596,11 @@ struct bes2600_vif { + unsigned long rx_timestamp; + u32 cipherType; + ++ /* Decrypt-storm fast-recover (Trigger B). See txrx.c. */ ++ unsigned long decrypt_storm_window_start; ++ unsigned int decrypt_storm_count; ++ unsigned int decrypt_storm_recoveries; ++ struct work_struct decrypt_storm_recover_work; + + /* AP powersave */ + u32 link_id_map; +@@ -856,4 +861,8 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv); + void bes2600_btusb_uninit(struct usb_interface *interface); + #endif + ++/* Decrypt-storm fast-recover helpers — see txrx.c. */ ++void bes2600_decrypt_storm_init(struct bes2600_vif *priv); ++void bes2600_decrypt_storm_account(struct bes2600_vif *priv); ++ + #endif /* BES2600_H */ +diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c +index 5228b22..ca223dd 100644 +--- a/drivers/staging/bes2600/debug.c ++++ b/drivers/staging/bes2600/debug.c +@@ -542,6 +542,8 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v) + priv->listening ? " (listening)" : ""); + seq_printf(seq, "Assoc: %s\n", + bes2600_debug_join_status[priv->join_status]); ++ seq_printf(seq, "DecryptStormRecoveries: %u\n", ++ priv->decrypt_storm_recoveries); + if (priv->rx_filter.promiscuous) + seq_puts(seq, "Filter: promisc\n"); + else if (priv->rx_filter.fcs) +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index bc6d483..139bdae 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -464,6 +464,7 @@ void bes2600_remove_interface(struct ieee80211_hw *dev, + cancel_delayed_work_sync(&priv->join_timeout); + cancel_delayed_work_sync(&priv->set_cts_work); + cancel_delayed_work_sync(&priv->pending_offchanneltx_work); ++ cancel_work_sync(&priv->decrypt_storm_recover_work); + + timer_delete_sync(&priv->mcast_timeout); + /* TODO:COMBO: May be reset of these variables "delayed_link_loss and +@@ -2639,6 +2640,7 @@ int bes2600_vif_setup(struct bes2600_vif *priv) + + /* Setup per vif workitems and locks */ + spin_lock_init(&priv->vif_lock); ++ bes2600_decrypt_storm_init(priv); + INIT_WORK(&priv->join_work, bes2600_join_work); + INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout); + INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work); +diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c +index 017f0d8..f6a66d6 100644 +--- a/drivers/staging/bes2600/txrx.c ++++ b/drivers/staging/bes2600/txrx.c +@@ -26,6 +26,78 @@ + + #define BES2600_INVALID_RATE_ID (0xFF) + ++/* ++ * Decrypt-storm fast-recover (Trigger B). ++ * ++ * When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a ++ * burst of received frames (typically because the host's PTK or GTK ++ * has fallen out of sync with the AP), the AP eventually concludes that ++ * the STA is not authenticated and emits an unprotected deauth-reason-6 ++ * ("Class 2 frame received from non-authenticated station"). On the ++ * deployed pinetab2 + bes2600 stack this AP-initiated deauth has been ++ * observed to leave the link blackholed for up to 109 s before ++ * userspace finds a different SSID/channel to recover on. (Receipts at ++ * https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.) ++ * ++ * Recovery here pre-empts the AP: when we see THRESHOLD decrypt ++ * failures within WINDOW, we ask mac80211 for a clean reassoc via ++ * ieee80211_connection_loss(), which causes immediate disassociation ++ * and lets userspace auto-reconnect with fresh keys. ++ * ++ * mac80211 contract: ieee80211_connection_loss() may be called ++ * regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate ++ * disassociation without driver-side recovery attempts. See ++ * include/net/mac80211.h for the canonical doc-comment. ++ * ++ * The threshold is set well above the steady-state per-vif ++ * decrypt-fail rate observed in measurement (~1/min even under ++ * sustained 1 MB/s load), so a true storm is required to trip it. ++ */ ++#define BES2600_DECRYPT_STORM_THRESHOLD 5 ++#define BES2600_DECRYPT_STORM_WINDOW_MS 5000 ++ ++static void bes2600_decrypt_storm_recover_work(struct work_struct *work) ++{ ++ struct bes2600_vif *priv = container_of(work, struct bes2600_vif, ++ decrypt_storm_recover_work); ++ ++ if (!priv->vif) ++ return; ++ ++ bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n"); ++ ieee80211_connection_loss(priv->vif); ++ priv->decrypt_storm_recoveries++; ++} ++ ++void bes2600_decrypt_storm_init(struct bes2600_vif *priv) ++{ ++ INIT_WORK(&priv->decrypt_storm_recover_work, ++ bes2600_decrypt_storm_recover_work); ++ priv->decrypt_storm_window_start = 0; ++ priv->decrypt_storm_count = 0; ++ priv->decrypt_storm_recoveries = 0; ++} ++ ++void bes2600_decrypt_storm_account(struct bes2600_vif *priv) ++{ ++ unsigned long now = jiffies; ++ unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS); ++ ++ if (priv->decrypt_storm_window_start == 0 || ++ time_after(now, priv->decrypt_storm_window_start + window)) { ++ priv->decrypt_storm_window_start = now; ++ priv->decrypt_storm_count = 1; ++ return; ++ } ++ ++ if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) { ++ priv->decrypt_storm_count = 0; ++ /* Skew the window so we don't re-fire on the same storm. */ ++ priv->decrypt_storm_window_start = now + window; ++ schedule_work(&priv->decrypt_storm_recover_work); ++ } ++} ++ + #ifdef CONFIG_BES2600_TESTMODE + #include "bes_nl80211_testmode_msg.h" + #endif /* CONFIG_BES2600_TESTMODE */ +@@ -1694,6 +1766,8 @@ void bes2600_rx_cb(struct bes2600_vif *priv, + goto drop; + } else { + bes_warn("[RX] Receive failure: %d.\n", arg->status); ++ if (arg->status == WSM_STATUS_DECRYPTFAILURE) ++ bes2600_decrypt_storm_account(priv); + goto drop; + } + } +-- +2.54.0 + +From 06fab777454d36ec5178730d8423285c2457d3ba Mon Sep 17 00:00:00 2001 +From: "Claude (noether)" +Date: Thu, 7 May 2026 11:30:09 +0200 +Subject: [PATCH 21/29] 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) +--- + bes2600/bes2600.h | 12 +++++++ + bes2600/bes_chardev.c | 12 +++++++ + bes2600/bes_chardev.h | 1 + + bes2600/debug.c | 2 ++ + bes2600/main.c | 2 ++ + bes2600/sta.c | 82 +++++++++++++++++++++++++++++++++++++++++-- + 6 files changed, 109 insertions(+), 2 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h +index 66482f7..ec41141 100644 +--- a/drivers/staging/bes2600/bes2600.h ++++ b/drivers/staging/bes2600/bes2600.h +@@ -511,6 +511,9 @@ struct bes2600_common { + struct list_head coex_event_list; + spinlock_t coex_event_lock; + ++ /* Connection-loss-storm fast-recover (Trigger A). See sta.c. */ ++ struct work_struct connection_loss_storm_recover_work; ++ + /* member for low power */ + struct bes2600_pwr_t bes_power; + +@@ -627,6 +630,10 @@ struct bes2600_vif { + /* CQM Implementation */ + struct delayed_work bss_loss_work; + struct delayed_work connection_loss_work; ++ /* Connection-loss-storm fast-recover (Trigger A). See sta.c. */ ++ unsigned long connection_loss_storm_window_start; ++ unsigned int connection_loss_storm_count; ++ unsigned int connection_loss_storm_recoveries; + struct work_struct tx_failure_work; + int delayed_link_loss; + spinlock_t bss_loss_lock; +@@ -865,4 +872,9 @@ void bes2600_btusb_uninit(struct usb_interface *interface); + void bes2600_decrypt_storm_init(struct bes2600_vif *priv); + void bes2600_decrypt_storm_account(struct bes2600_vif *priv); + ++/* Connection-loss-storm fast-recover helpers — see sta.c. */ ++void bes2600_connection_loss_storm_init(struct bes2600_vif *priv); ++bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv); ++void bes2600_connection_loss_storm_recover(struct work_struct *work); ++ + #endif /* BES2600_H */ +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index d1375bc..224c62d 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -484,6 +484,18 @@ int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_pri + 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 wifi_opened = false; +diff --git a/drivers/staging/bes2600/bes_chardev.h b/drivers/staging/bes2600/bes_chardev.h +index ca8419e..2a7cad7 100644 +--- a/drivers/staging/bes2600/bes_chardev.h ++++ b/drivers/staging/bes2600/bes_chardev.h +@@ -61,6 +61,7 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(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_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_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev); + void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv); +diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c +index ca223dd..0d68392 100644 +--- a/drivers/staging/bes2600/debug.c ++++ b/drivers/staging/bes2600/debug.c +@@ -544,6 +544,8 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v) + bes2600_debug_join_status[priv->join_status]); + seq_printf(seq, "DecryptStormRecoveries: %u\n", + priv->decrypt_storm_recoveries); ++ seq_printf(seq, "ConnectionLossStormRecoveries: %u\n", ++ priv->connection_loss_storm_recoveries); + if (priv->rx_filter.promiscuous) + seq_puts(seq, "Filter: promisc\n"); + else if (priv->rx_filter.fcs) +diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c +index 7cbb3a9..ff82f4d 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -489,6 +489,8 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) + spin_lock_init(&hw_priv->rtsvalue_lock); + INIT_WORK(&hw_priv->dynamic_opt_txrx_work, bes2600_dynamic_opt_txrx_work); + INIT_WORK(&hw_priv->tx_policy_upload_work, tx_policy_upload_work); ++ INIT_WORK(&hw_priv->connection_loss_storm_recover_work, ++ bes2600_connection_loss_storm_recover); + spin_lock_init(&hw_priv->event_queue_lock); + INIT_LIST_HEAD(&hw_priv->event_queue); + INIT_WORK(&hw_priv->event_handler, bes2600_event_handler); +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index 139bdae..5868757 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -268,6 +268,7 @@ void bes2600_stop(struct ieee80211_hw *dev, bool suspend) + cancel_work_sync(&hw_priv->coex_work); + coex_stop(hw_priv); + #endif ++ cancel_work_sync(&hw_priv->connection_loss_storm_recover_work); + + bes2600_wifi_stop(hw_priv); + +@@ -1675,6 +1676,70 @@ report: + spin_unlock(&priv->bss_loss_lock); + } + ++/* ++ * Connection-loss-storm fast-recover (Trigger A). ++ * ++ * bes2600_connection_loss_work below is the driver's own decision-point ++ * to give up on a BSS (after bss-loss detection accumulates beyond ++ * tolerance) and tell mac80211 via ieee80211_connection_loss(). On the ++ * deployed pinetab2 stack a single ieee80211_connection_loss() event ++ * sometimes triggers a userspace reauth blackhole (assoc-comeback ++ * timeouts followed by AP unprotected-deauth-reason-6) that ends only ++ * via cross-channel/cross-SSID fallback and can take 80+ s. Receipts at ++ * https://git.reauktion.de/marfrit/besser, notes/phase4-2026-05-07.md. ++ * ++ * When N connection-loss decisions land within WINDOW on the same vif, ++ * skip the ieee80211_connection_loss() path and trigger a chip-level ++ * bus_reset (the c5.2-introduced bes2600_chrdev_do_bus_reset). The chip ++ * is removed and re-probed; userspace re-associates from a fresh state, ++ * dodging the assoc-comeback loop. ++ * ++ * Threshold (3 / 60 s) is chosen 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. ++ * ++ * The recover work_struct lives on bes2600_common (hw_priv) so that ++ * scheduling it does not race with vif teardown after bus_reset frees ++ * the per-vif state. ++ */ ++#define BES2600_CONNECTION_LOSS_STORM_THRESHOLD 3 ++#define BES2600_CONNECTION_LOSS_STORM_WINDOW_MS 60000 ++ ++void bes2600_connection_loss_storm_recover(struct work_struct *work) ++{ ++ bes_warn("[bes2600] connection-loss-storm fast-recover: bus_reset\n"); ++ bes2600_chrdev_trigger_bus_reset(); ++ /* ++ * After bes2600_chrdev_do_bus_reset() returns, the SDIO core has ++ * scheduled a remove + rescan; per-vif state may already be gone. ++ * Do not dereference any per-vif pointer here. ++ */ ++} ++ ++void bes2600_connection_loss_storm_init(struct bes2600_vif *priv) ++{ ++ priv->connection_loss_storm_window_start = 0; ++ priv->connection_loss_storm_count = 0; ++ priv->connection_loss_storm_recoveries = 0; ++} ++ ++bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv) ++{ ++ unsigned long now = jiffies; ++ unsigned long window = ++ msecs_to_jiffies(BES2600_CONNECTION_LOSS_STORM_WINDOW_MS); ++ ++ if (priv->connection_loss_storm_window_start == 0 || ++ time_after(now, priv->connection_loss_storm_window_start + window)) { ++ priv->connection_loss_storm_window_start = now; ++ priv->connection_loss_storm_count = 1; ++ return false; ++ } ++ ++ return ++priv->connection_loss_storm_count >= ++ BES2600_CONNECTION_LOSS_STORM_THRESHOLD; ++} ++ + void bes2600_connection_loss_work(struct work_struct *work) + { + struct bes2600_vif *priv = +@@ -1684,9 +1749,21 @@ void bes2600_connection_loss_work(struct work_struct *work) + + bes_devel("[CQM] Reporting connection loss.\n"); + bes2600_pwr_clear_busy_event(priv->hw_priv, BES_PWR_LOCK_ON_BSS_LOST); +- if(bes2600_suspend_status_get(hw_priv)) { ++ ++ if (bes2600_connection_loss_storm_account(priv)) { ++ bes_warn("[bes2600] connection-loss storm: %u in %u s, scheduling bus reset\n", ++ priv->connection_loss_storm_count, ++ BES2600_CONNECTION_LOSS_STORM_WINDOW_MS / 1000); ++ priv->connection_loss_storm_count = 0; ++ priv->connection_loss_storm_recoveries++; ++ schedule_work(&hw_priv->connection_loss_storm_recover_work); ++ /* bus_reset will tear the chip down; skip the mac80211 path. */ ++ return; ++ } ++ ++ if (bes2600_suspend_status_get(hw_priv)) + bes2600_pending_unjoin_set(hw_priv, priv->if_id); +- } else ++ else + ieee80211_connection_loss(priv->vif); + #ifdef WIFI_BT_COEXIST_EPTA_ENABLE + // set disconnected in BSS_CHANGED_ASSOC +@@ -2641,6 +2718,7 @@ int bes2600_vif_setup(struct bes2600_vif *priv) + /* Setup per vif workitems and locks */ + spin_lock_init(&priv->vif_lock); + bes2600_decrypt_storm_init(priv); ++ bes2600_connection_loss_storm_init(priv); + INIT_WORK(&priv->join_work, bes2600_join_work); + INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout); + INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work); +-- +2.54.0 + +From 737f28e29c4b8253939e24b1d6b97d5605bb7ac4 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 7 May 2026 21:19:49 +0200 +Subject: [PATCH 22/29] bes2600: replace a set of atomic_add() + +Backport of cw1200 mainline commit 07f995ca1951 ("cw1200: replace a set +of atomic_add()", 2020-11-10). atomic_inc() reads more naturally than +atomic_add(1, &x). Mechanical change, no functional impact. + +7 sites: 6 in bh.c (bh_term, bh_rx x2, bh_tx x3) and 1 in itp.c +(awaiting_confirm). Two of the bh_rx and three of the bh_tx sites are +inside the cw1200-ancestor #if 0 block; replaced anyway to keep the +file consistent with cw1200 mainline source style. + +Cherry-picked from upstream Linux: + 07f995ca1951 cw1200: replace a set of atomic_add() + Author: Yejune Deng + Signed-off-by: Kalle Valo + Link: https://lore.kernel.org/r/1604991491-27908-1-git-send-email-yejune.deng@gmail.com +--- + bes2600/bh.c | 12 ++++++------ + bes2600/itp.c | 2 +- + 2 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/drivers/staging/bes2600/bh.c b/drivers/staging/bes2600/bh.c +index 175ab5e..fab3bf0 100644 +--- a/drivers/staging/bes2600/bh.c ++++ b/drivers/staging/bes2600/bh.c +@@ -102,7 +102,7 @@ void bes2600_unregister_bh(struct bes2600_common *hw_priv) + coex_deinit_mode(hw_priv); + #endif + +- atomic_add(1, &hw_priv->bh_term); ++ atomic_inc(&hw_priv->bh_term); + wake_up(&hw_priv->bh_wq); + + flush_workqueue(hw_priv->bh_workqueue); +@@ -591,7 +591,7 @@ static int bes2600_bh(void *arg) + bes_devel("[BH] Device resume.\n"); + atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); + wake_up(&hw_priv->bh_evt_wq); +- atomic_add(1, &hw_priv->bh_rx); ++ atomic_inc(&hw_priv->bh_rx); + continue; + } + +@@ -759,9 +759,9 @@ tx: + + #if 0 /* count is not implemented */ + if (ret > 1) +- atomic_add(1, &hw_priv->bh_tx); ++ atomic_inc(&hw_priv->bh_tx); + #else +- atomic_add(1, &hw_priv->bh_tx); ++ atomic_inc(&hw_priv->bh_tx); + #endif + + #if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES) +@@ -1135,7 +1135,7 @@ static int bes2600_bh_tx_helper(struct bes2600_common *hw_priv, + tx_len += 4; + #endif + +- atomic_add(1, &hw_priv->bh_tx); ++ atomic_inc(&hw_priv->bh_tx); + + tx_len = hw_priv->sbus_ops->align_size( + hw_priv->sbus_priv, tx_len); +@@ -1442,7 +1442,7 @@ static int bes2600_bh(void *arg) + bes_devel("[BH] Device resume.\n"); + atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); + wake_up(&hw_priv->bh_evt_wq); +- atomic_add(1, &hw_priv->bh_rx); ++ atomic_inc(&hw_priv->bh_rx); + goto done; + } + +diff --git a/drivers/staging/bes2600/itp.c b/drivers/staging/bes2600/itp.c +index e5c2958..c50b29c 100644 +--- a/drivers/staging/bes2600/itp.c ++++ b/drivers/staging/bes2600/itp.c +@@ -570,7 +570,7 @@ int bes2600_itp_get_tx(struct bes2600_common *priv, u8 **data, + *burst = 2; + atomic_set(&priv->bh_tx, 1); + ktime_get_ts(&itp->last_sent); +- atomic_add(1, &itp->awaiting_confirm); ++ atomic_inc(&itp->awaiting_confirm); + spin_unlock_bh(&itp->tx_lock); + return 1; + +-- +2.54.0 + +From 2fb72f06e54172479662257ae4ef9a61d6ba7092 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 7 May 2026 21:20:46 +0200 +Subject: [PATCH 23/29] bes2600: fix missing destroy_workqueue() on error in + init_common +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Two error paths between create_singlethread_workqueue() (~main.c:489) +and the success-path destroy_workqueue() in unregister_common (~609) +return without cleaning up the workqueue, leaking it on probe failure: + + 1. bes2600_queue_stats_init() failure + 2. bes2600_queue_init() failure (any of the 4 TID queues) + +Both call ieee80211_free_hw(hw); return NULL — without first +destroy_workqueue(hw_priv->workqueue). Add it. + +Backport of cw1200 mainline commit 7ec8a926188e ("cw1200: fix missing +destroy_workqueue() on error in cw1200_init_common", 2020-11-19), +which fixed the identical bug in the same code shape we inherited. +Reported on cw1200 by Hulk Robot. + +Cherry-picked from upstream Linux: + 7ec8a926188e cw1200: fix missing destroy_workqueue() on error + Author: Qinglang Miao + Reported-by: Hulk Robot + Signed-off-by: Kalle Valo + Link: https://lore.kernel.org/r/20201119070842.1011-1-miaoqinglang@huawei.com + Fixes: a910e4a94f69 ("cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets") +--- + bes2600/main.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c +index ff82f4d..89b5e2d 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -502,6 +502,7 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) + WLAN_LINK_ID_MAX, + bes2600_skb_dtor, + hw_priv))) { ++ destroy_workqueue(hw_priv->workqueue); + ieee80211_free_hw(hw); + return NULL; + } +@@ -513,6 +514,7 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) + for (; i > 0; i--) + bes2600_queue_deinit(&hw_priv->tx_queue[i - 1]); + bes2600_queue_stats_deinit(&hw_priv->tx_queue_stats); ++ destroy_workqueue(hw_priv->workqueue); + ieee80211_free_hw(hw); + return NULL; + } +-- +2.54.0 + +From d9e6361cf0c273f07aee94f24533a5f19e7ed4c0 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 7 May 2026 21:24:01 +0200 +Subject: [PATCH 24/29] bes2600: fix concurrency UAF in bes2600_hw_scan and + sched_scan +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +bes2600_bss_info_changed() and bes2600_hw_scan() can run concurrently. +The probe-request SKB allocated by ieee80211_probereq_get() before +scan.lock + conf_lock are taken can be touched by a concurrent +bss_info_changed (via wsm_set_template_frame's path) while we hold no +lock. Reorder to acquire both locks BEFORE the SKB allocation. + +Also reorder cleanup paths so dev_kfree_skb() runs BEFORE up() — +otherwise a small window exists where the SKB has been touched but the +lock has been released, allowing concurrent code to also touch it. + +Three sites fixed: + - bes2600_hw_scan: lock-take + ENOMEM cleanup + wsm_set_template_frame + error cleanup + success-path SKB free + lock release order + - bes2600_sched_scan_start (#ifdef ROAM_OFFLOAD): same three sub-fixes + (compiled-out at default build, fixed for consistency) + - All success/error paths: dev_kfree_skb before up() + +Backport of cw1200 mainline commit 86760e0dfe36 ("cw1200: Fix +concurrency use-after-free bugs in cw1200_hw_scan()", 2018-12-14), +which fixed the identical bug in the same code shape we inherited. +That commit was merged from upstream 4f68ef64cd7f. + +Cherry-picked from upstream Linux: + 86760e0dfe36 cw1200: Fix concurrency use-after-free bugs in cw1200_hw_scan() + Author: Jia-Ju Bai + Link: https://lore.kernel.org/r/20181214035521.7575-1-baijiaju1990@gmail.com +--- + bes2600/scan.c | 37 ++++++++++++++++++++++--------------- + 1 file changed, 22 insertions(+), 15 deletions(-) + +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index b944adc..3cd7b64 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -257,18 +257,21 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, + + 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, + req->ie_len); +- if (!frame.skb) ++ if (!frame.skb) { ++ up(&hw_priv->conf_lock); ++ up(&hw_priv->scan.lock); + return -ENOMEM; ++ } + + if (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) { + int ret; + //if (priv->if_id == 0) +@@ -286,9 +289,9 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, + } + #endif + if (ret) { ++ dev_kfree_skb(frame.skb); + up(&hw_priv->conf_lock); + up(&hw_priv->scan.lock); +- dev_kfree_skb(frame.skb); + return ret; + } + } +@@ -318,10 +321,10 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, + ++hw_priv->scan.n_ssids; + } + +- up(&hw_priv->conf_lock); +- + if (frame.skb) + dev_kfree_skb(frame.skb); ++ ++ up(&hw_priv->conf_lock); + #ifdef WIFI_BT_COEXIST_EPTA_ENABLE + bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING); + #endif +@@ -362,14 +365,18 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, + if (req->n_ssids > hw->wiphy->max_scan_ssids) + return -EINVAL; + ++ /* 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, + req->ie_len); +- if (!frame.skb) ++ if (!frame.skb) { ++ up(&hw_priv->conf_lock); ++ up(&hw_priv->scan.lock); + return -ENOMEM; ++ } + +- /* will be unlocked in bes2600_scan_work() */ +- down(&hw_priv->scan.lock); +- down(&hw_priv->conf_lock); + if (frame.skb) { + int ret; + if (priv->if_id == 0) +@@ -380,9 +387,9 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, + ret = wsm_set_probe_responder(priv, true); + } + if (ret) { ++ dev_kfree_skb(frame.skb); + up(&hw_priv->conf_lock); + up(&hw_priv->scan.lock); +- dev_kfree_skb(frame.skb); + return ret; + } + } +@@ -414,10 +421,10 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, + } + } + +- up(&hw_priv->conf_lock); +- + if (frame.skb) + dev_kfree_skb(frame.skb); ++ ++ up(&hw_priv->conf_lock); + queue_work(hw_priv->workqueue, &hw_priv->scan.swork); + wiphy_warn(hw->wiphy, "<--[SCAN] Scheduled scan request.\n"); + return 0; +-- +2.54.0 + +From 0f185172b020818faec9572fd800867db623a40e Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 7 May 2026 22:34:11 +0200 +Subject: [PATCH 25/29] =?UTF-8?q?bes2600:=20drop=20sdio=5Frx=5Fwork=20rela?= + =?UTF-8?q?y,=20IRQ=E2=86=92bh-direct=20(no-relay=20architecture)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Patch C v3 — match cw1200 mainline architecture +(drivers/net/wireless/st/cw1200/). Eliminates the +sdio_rx_work workqueue relay that introduced a thread-safety +race on hw_priv->hw_bufs_used in v1 (PR #3 closed) and that +v2's atomic_t prep was a workaround for (PR #10 superseded by +v3 plan PR #11). + +Architectural changes: + + - bes2600_gpio_irq_handler: now calls self->irq_handler() + directly instead of queue_work(self->sdio_wq, &self->rx_work). + Bumps bh_rx atomic + wakes bh_wq. + - bes2600_bh_rx_helper (BES_SDIO_RX_MULTIPLE_ENABLE branch): + now calls priv->sbus_ops->bus_rx_batch() to do the SDIO read + inline. No pipe_read, no skb_dequeue. + - bes2600_sdio_read_rx_batch (new): the SDIO read sequence + extracted from sdio_rx_work, registered as + sbus_ops->bus_rx_batch. Runs in bh thread context. + - bes2600_sdio_extract_packets: calls + bes2600_bh_handle_rx_skb() directly per parsed SKB. No + skb_queue_tail, no rx_queue. + - bes2600_bh_handle_rx_skb (new in bh.c): the per-SKB + bookkeeping that bh_rx_helper used to do post-pipe_read + (seq# check, exception, confirm-condition, wsm_handle_rx). + Wakes bh thread for tx-burst via atomic_inc(&priv->bh_tx) + instead of bes2600_bh_wakeup() — we ARE the bh thread. + - Post-tx queue_work(rx_work) site: replaced with + self->irq_handler() to wake bh for piggyback RX check. + +Deleted infrastructure: + + - struct sbus_priv: rx_queue, rx_queue_lock, rx_work fields + - bes2600_sdio_pipe_read: function deleted (unused) + - sdio_rx_work: function deleted (unused) + - sbus_ops->pipe_read assignment: removed for SDIO bus + - skb_queue_head_init(&self->rx_queue), spin_lock_init(...), + INIT_WORK(rx_work): probe-time setup removed + - cancel_work_sync(rx_work) + drain loop in empty_work: removed + - flush_work(rx_work) in drain helper: replaced with msleep(2) + - work_pending(rx_work) check in suspend predicate: removed + +Concurrency invariant restored: + + - hw_priv->hw_bufs_used: single-writer (bh thread only) + by construction. No atomic_t needed. + - hw_priv->hw_bufs_used_vif[]: ditto. + - hw_priv->wsm_tx_pending[]: ditto. + - All other shared state: unchanged or already protected. + +Phase 7 partial verification (rep 1, 2026-05-07): + + - Module loads clean, srcversion 371C6606B73AF19299228CA + - Link associates, no WARN/BUG/oops + - sdio_rx_work dispatches: 0 (function deleted) + - bes2600_bh_work redispatches: 0 (single long-lived + invariant preserved) + - Chip handled stress traffic without wedge + +Phase 7 full N=3 stress ramp deferred to follow-up rep series +(rep 2 had a TCP-level nc race; not a bes2600 issue but +invalidated rep 2's throughput number). +--- + bes2600/bes2600_sdio.c | 144 ++++++++++++++++++++++++----------------- + bes2600/bh.c | 129 ++++++++++++++++++++++++++++++++++-- + bes2600/bh.h | 9 +++ + bes2600/sbus.h | 8 +++ + 4 files changed, 226 insertions(+), 64 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index c81c244..3834032 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -29,6 +29,7 @@ + #include + + #include "bes2600.h" ++#include "bh.h" + #include "sbus.h" + #include "bes2600_plat.h" + #include "bes2600_factory.h" +@@ -72,10 +73,12 @@ struct sbus_priv { + int rx_data_toggle; + #endif + #ifdef BES_SDIO_RX_MULTIPLE_ENABLE +- spinlock_t rx_queue_lock; +- struct sk_buff_head rx_queue; ++ /* ++ * Patch C v3: rx_queue, rx_queue_lock, rx_work removed (no relay). ++ * The bh thread now reads RX inline; the rx_buffer scratch area ++ * stays. Counters/timestamps stay for debugfs visibility. ++ */ + u8 *rx_buffer; +- struct work_struct rx_work; + u32 rx_last_ctrl; + u32 rx_valid_ctrl; + u32 rx_total_ctrl_cnt; +@@ -412,10 +415,19 @@ static void bes2600_sdio_irq_handler(struct sdio_func *func) + + bes_devel("%s called, fw_started:%d \n", + __func__, self->fw_started); +- if (likely(self->fw_started && self->core)) { +- queue_work(self->sdio_wq, &self->rx_work); ++ /* ++ * Patch C v3: no more sdio_rx_work relay. Wake the bh thread ++ * directly via self->irq_handler (bes2600_irq_handler in bh.c ++ * which bumps bh_rx atomic + wakes bh_wq). The bh thread will ++ * then call sbus_ops->bus_rx_batch() to do the SDIO read inline. ++ * Matches cw1200 mainline IRQ → bh-direct architecture. ++ */ ++ if (likely(self->fw_started && self->core && self->irq_handler)) { ++ spin_lock_irqsave(&self->lock, flags); ++ self->irq_handler(self->irq_priv); ++ spin_unlock_irqrestore(&self->lock, flags); + self->last_irq_timestamp = jiffies; +- } else if(self->irq_handler) { ++ } else if (self->irq_handler) { + spin_lock_irqsave(&self->lock, flags); + self->irq_handler(self->irq_priv); + spin_unlock_irqrestore(&self->lock, flags); +@@ -812,10 +824,15 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8 + skb_put(skb, packet_len); + memcpy(skb->data, &data[pos], packet_len); + bes_devel("%s, %d,%d\n", __func__, packet_len, pos); +- spin_lock(&self->rx_queue_lock); +- skb_queue_tail(&self->rx_queue, skb); + self->rx_data_cnt++; +- spin_unlock(&self->rx_queue_lock); ++ /* ++ * Patch C v3: deliver the SKB directly into the WSM/mac80211 ++ * stack from the bh thread. No rx_queue, no inter-thread ++ * handoff, no atomic_t needed on the counters that ++ * wsm_release_tx_buffer touches — single-writer-from-bh is ++ * preserved by construction. See bh.c for the contract block. ++ */ ++ bes2600_bh_handle_rx_skb(self->core, skb); + packet_len = (packet_len + 3) & (~0x3); + pos += packet_len; + #ifdef BES_SDIO_OPTIMIZED_LEN +@@ -826,17 +843,31 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8 + return 0; + } + +-static void sdio_rx_work(struct work_struct *work) ++/* ++ * Patch C v3: bh thread calls this directly via sbus_ops->bus_rx_batch. ++ * No more sdio_rx_work workqueue. SDIO read sequence (lock → ++ * read_ctrl → memcpy_fromio → packets_check → extract_packets) runs ++ * inline in bh-thread context. Each parsed SKB is delivered via ++ * bes2600_bh_handle_rx_skb() from extract_packets — no rx_queue, no ++ * second worker, no inter-thread handoff. ++ * ++ * Architecture matches cw1200 mainline. Single-writer-from-bh ++ * invariant on hw_bufs_used preserved by construction. ++ * ++ * Returns 0 on success (caller's bh outer loop decides whether to ++ * continue), negative on bus read error. On error: triggers ++ * wifi_force_close (same as the old sdio_rx_work). ++ */ ++static int bes2600_sdio_read_rx_batch(struct sbus_priv *self) + { +- int ret, again = 0, retry = 0, crc_retry = 0; ++ int ret = 0, again = 0, retry = 0, crc_retry = 0; + u32 ctrl_reg = 0; + int total_len; +- struct sbus_priv *self = container_of(work, struct sbus_priv, rx_work); + u8 *buf = self->rx_buffer; + + /* don't read/write sdio when sdio error */ + if (bes2600_chrdev_is_bus_error()) +- return; ++ return 0; + + bes2600_gpio_wakeup_mcu(self, GPIO_WAKE_FLAG_SDIO_RX); + +@@ -891,6 +922,10 @@ static void sdio_rx_work(struct work_struct *work) + goto failed; + } + ++ /* ++ * extract_packets parses the multi-RX buffer and calls ++ * bes2600_bh_handle_rx_skb() per SKB. No queueing. ++ */ + if ((ret = bes2600_sdio_extract_packets(self, ctrl_reg, buf))) { + bes_err("%s,%d error=%d\n", __func__, __LINE__, ret); + goto failed; +@@ -898,22 +933,16 @@ static void sdio_rx_work(struct work_struct *work) + + ctrl_reg = 0; + +- if (likely(self->irq_handler)) { +- self->irq_handler(self->irq_priv); +- } else { +- bes_err("%s,%d\n", __func__, __LINE__); +- goto failed; +- } +- + } while (again); + + bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX); +- return; ++ return 0; + + failed: + bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX); + bes2600_chrdev_wifi_force_close(self->core, false); + WARN_ON(1); ++ return -1; + } + + static void sdio_scan_work(struct work_struct *work) +@@ -921,26 +950,11 @@ static void sdio_scan_work(struct work_struct *work) + bes_warn("%s: this function does nothing\n", __FUNCTION__); + } + +-static void *bes2600_sdio_pipe_read(struct sbus_priv *self) +-{ +- struct sk_buff *skb; +- +- if (bes2600_chrdev_is_bus_error()) { +- return bes2600_tx_loop_read(self->core); +- } +- +- spin_lock(&self->rx_queue_lock); +- skb = skb_dequeue(&self->rx_queue); +- if (skb) +- self->rx_proc_cnt++; +- spin_unlock(&self->rx_queue_lock); +- if (likely(self->fw_started == true && +- !bes2600_pwr_device_is_idle(self->core) && +- self->core->hw_bufs_used > 0)) +- if (!skb) +- queue_work(self->sdio_wq, &self->rx_work); +- return skb; +-} ++/* Patch C v3: bes2600_sdio_pipe_read deleted. bh thread reads the ++ * SDIO bus inline via bes2600_sdio_read_rx_batch (sbus_ops->bus_rx_batch). ++ * No rx_queue, no skb_dequeue, no relay. bes2600_tx_loop_read remains ++ * for the test bus error-fallback path but is now invoked at higher ++ * level. */ + + #endif + +@@ -1196,7 +1210,14 @@ flush_previous: + } + } while (crc_retry <= 10); + sdio_release_host(self->func); +- queue_work(self->sdio_wq, &self->rx_work); ++ /* ++ * Patch C v3: wake the bh thread to check for any RX ++ * that piggybacked on this TX window. Bumps bh_rx ++ * atomic; bh's wait_event will pick it up and call ++ * sbus_ops->bus_rx_batch(). ++ */ ++ if (likely(self->irq_handler)) ++ self->irq_handler(self->irq_priv); + if (ret) { + bes_err("%s,%d err=%d,%d,%d\n", __func__, __LINE__, ret, scatters, cur_blk); + sdio_work_debug(self); +@@ -1247,12 +1268,11 @@ static int bes2600_sdio_misc_init(struct sbus_priv *self, struct bes2600_common + self->next_toggle = 0; + #endif + #ifdef BES_SDIO_RX_MULTIPLE_ENABLE +- spin_lock_init(&self->rx_queue_lock); +- skb_queue_head_init(&self->rx_queue); ++ /* Patch C v3: rx_queue / rx_queue_lock removed (no relay). */ + self->rx_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM)); + if (!self->rx_buffer) + return -ENOMEM; +- INIT_WORK(&self->rx_work, sdio_rx_work); ++ /* Patch C v3: sdio_rx_work removed; bh thread does the read. */ + #endif + #ifdef BES_SDIO_TX_MULTIPLE_ENABLE + INIT_LIST_HEAD(&self->tx_bufferlist); +@@ -1581,22 +1601,15 @@ err: + + static void bes2600_sdio_empty_work(struct sbus_priv *self) + { +-#ifdef BES_SDIO_RX_MULTIPLE_ENABLE +- struct sk_buff *skb; +-#endif + #ifdef BES_SDIO_TX_MULTIPLE_ENABLE + struct bes_sdio_tx_list_t *tx_buffer, *temp; + #endif + + #ifdef BES_SDIO_RX_MULTIPLE_ENABLE +- cancel_work_sync(&self->rx_work); +- while (1) { +- skb = skb_dequeue(&self->rx_queue); +- if (skb) +- dev_kfree_skb(skb); +- else +- break; +- } ++ /* ++ * Patch C v3: rx_work and rx_queue removed. Counters still ++ * reset for the next attach cycle. ++ */ + self->rx_last_ctrl = 0; + self->rx_total_ctrl_cnt = 0; + self->rx_continuous_ctrl_cnt = 0; +@@ -1864,7 +1877,8 @@ static struct sbus_ops bes2600_sdio_sbus_ops = { + .sbus_reg_write = bes2600_sdio_reg_write, + .init = bes2600_sdio_misc_init, + #ifdef BES_SDIO_RX_MULTIPLE_ENABLE +- .pipe_read = bes2600_sdio_pipe_read, ++ /* Patch C v3: .pipe_read removed; bus_rx_batch replaces it. */ ++ .bus_rx_batch = bes2600_sdio_read_rx_batch, + #endif + #ifdef BES_SDIO_TX_MULTIPLE_ENABLE + .pipe_send = bes2600_sdio_pipe_send, +@@ -1884,9 +1898,15 @@ static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv) + long unsigned int old_ts, new_ts; + struct sbus_priv *self = hw_priv->sbus_priv; + ++ /* ++ * Patch C v3: rx_work removed. Wait for IRQ-timestamp activity ++ * to settle by polling self->last_irq_timestamp via msleep ++ * (best-effort). The caller already knows the bh thread will ++ * process pending bh_rx during its next wait_event round. ++ */ + do { + old_ts = self->last_irq_timestamp; +- flush_work(&self->rx_work); ++ msleep(2); + new_ts = self->last_irq_timestamp; + } while(old_ts != new_ts); + } +@@ -2243,8 +2263,12 @@ static int bes2600_sdio_suspend_noirq(struct device *dev) + if (func->num > 1) + return 0; + +- if(self->core && +- (work_pending(&self->rx_work) || atomic_read(&self->core->bh_rx))) { ++ /* ++ * Patch C v3: work_pending(&self->rx_work) check dropped (no ++ * relay). bh_rx atomic alone tells us whether the bh thread ++ * has un-processed RX events queued. ++ */ ++ if (self->core && atomic_read(&self->core->bh_rx)) { + bes_devel("%s: Suspend interrupted.\n", __func__); + return -EAGAIN; + } +diff --git a/drivers/staging/bes2600/bh.c b/drivers/staging/bes2600/bh.c +index fab3bf0..febcaf4 100644 +--- a/drivers/staging/bes2600/bh.c ++++ b/drivers/staging/bes2600/bh.c +@@ -959,6 +959,119 @@ static void bes2600_bh_parse_wakeup_event(struct bes2600_common *hw_priv, struct } } ++/* ++ * Direct-deliver an RX SKB into the WSM/mac80211 stack. ++ * ++ * Patch C v3 (no-relay architecture, matches cw1200): the bh thread ++ * calls bes2600_sdio_read_rx_batch which calls ++ * bes2600_sdio_extract_packets which calls THIS function per parsed ++ * SKB. No rx_queue, no sdio_rx_work, no inter-thread handoff. ++ * ++ * Single-writer-from-bh invariant on hw_priv->hw_bufs_used, ++ * hw_priv->hw_bufs_used_vif[] and hw_priv->wsm_tx_pending[] is ++ * preserved BY CONSTRUCTION — there is now only one writer (the bh ++ * thread itself), same as cw1200's design. No atomic_t conversion ++ * needed. ++ * ++ * Contract: ++ * - process context, sleepable. wsm_handle_rx (wsm.c, EXPORT_SYMBOL) ++ * acquires wsm_cmd.lock and may sleep on wait_event_timeout. ++ * - caller holds no bes2600 spinlock. bes2600_sdio_unlock(self) is ++ * called inside read_rx_batch before extract_packets is invoked. ++ * - SKB ownership: function frees on every path (success + error). ++ * - No need to wake the bh thread on TX-confirm — we ARE the bh ++ * thread; tx_burst is signalled by returning *tx_out = 1 to the ++ * caller (bh_rx_helper), which propagates it to bh's outer loop. ++ */ ++int bes2600_bh_handle_rx_skb(struct bes2600_common *priv, struct sk_buff *skb) ++{ ++ struct wsm_hdr *wsm; ++ size_t wsm_len; ++ u16 wsm_id; ++ u8 wsm_seq; ++ int tx = 0; ++ u32 confirm_label = 0x0; ++ ++ if (!skb) ++ return 0; ++ ++ wsm = (struct wsm_hdr *)skb->data; ++ wsm_len = __le16_to_cpu(wsm->len); ++ if (WARN_ON(wsm_len > skb->len)) { ++ bes_err("wsm_len err %d %d\n", (int)wsm_len, (int)skb->len); ++ dev_kfree_skb(skb); ++ return -1; ++ } ++ ++ if (priv->wsm_enable_wsm_dumps) ++ print_hex_dump(KERN_DEBUG, "<-- ", DUMP_PREFIX_NONE, 16, 1, ++ skb->data, wsm_len, false); ++ ++ wsm_id = __le16_to_cpu(wsm->id) & 0xFFF; ++ wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7; ++ bes_devel("bes2600_bh_handle_rx_skb wsm_id:0x%04x seq:%d\n", ++ wsm_id, wsm_seq); ++ ++ skb_trim(skb, wsm_len); ++ ++ if (wsm_id == 0x0800) { ++ wsm_handle_exception(priv, ++ &skb->data[sizeof(*wsm)], ++ wsm_len - sizeof(*wsm)); ++ bes_err("wsm exception\n"); ++ dev_kfree_skb(skb); ++ return -1; ++ } else if ((wsm_seq != priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)])) { ++ bes_err("seq error! %u. %u. 0x%x.", wsm_seq, ++ priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)], wsm_id); ++ dev_kfree_skb(skb); ++ return -1; ++ } ++ ++ bes2600_bh_parse_wakeup_event(priv, skb); ++ ++ priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)] = (wsm_seq + 1) & 7; ++ ++ if (IS_DRIVER_TO_MCU_CMD(wsm_id)) ++ confirm_label = __le32_to_cpu(((struct wsm_mcu_hdr *)wsm)->handle_label); ++ ++ if (WSM_CONFIRM_CONDITION(wsm_id, confirm_label)) { ++ int rc = wsm_release_tx_buffer(priv, 1); ++ bes2600_bh_dec_pending_count(priv, WSM_TXRX_SEQ_IDX(wsm->id)); ++ ++ if (rc < 0) { ++ bes_err("wsm_release_tx_buffer failed: %d\n", rc); ++ dev_kfree_skb(skb); ++ return rc; ++ } else if (rc > 0) { ++ tx = 1; ++ } ++ } ++ ++ /* wsm_handle_rx takes care of SKB lifetime: zeroes *skb_p if consumed. */ ++ if (wsm_handle_rx(priv, wsm_id, wsm, &skb)) { ++ bes_err("wsm_handle_rx failed (id=0x%04x)\n", wsm_id); ++ if (skb) ++ dev_kfree_skb(skb); ++ return -1; ++ } ++ ++ if (skb) ++ dev_kfree_skb(skb); ++ ++ /* ++ * Signal "tx side has new headroom" via atomic so the bh outer ++ * loop's wait_event predicate notices on its next wait. No ++ * cross-thread wake needed because we are the bh thread; the ++ * outer loop will pick this up after read_rx_batch returns. ++ */ ++ if (tx) ++ atomic_inc(&priv->bh_tx); ++ ++ return 0; ++} ++EXPORT_SYMBOL(bes2600_bh_handle_rx_skb); ++ + static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx) + { + struct sk_buff *skb = NULL; +@@ -970,10 +1083,18 @@ static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx) + u32 confirm_label = 0x0; /* wsm to mcu cmd cnfirm label */ + + #if defined(BES_SDIO_RX_MULTIPLE_ENABLE) +- skb = (struct sk_buff *)priv->sbus_ops->pipe_read(priv->sbus_priv); +- if (!skb) +- return 0; +- rx = 1; // always consider rx pipe not empty ++ /* ++ * Patch C v3: the bh thread does the SDIO read inline via ++ * sbus_ops->bus_rx_batch. bes2600_sdio_read_rx_batch reads the ++ * multi-RX coalesced frames out of the chip and delivers each ++ * one inline via bes2600_bh_handle_rx_skb (no rx_queue, no ++ * pipe_read, no inter-thread handoff). Return value: 0 on ++ * success (bh outer loop will check whether to continue), ++ * negative on read error. ++ */ ++ if (priv->sbus_ops->bus_rx_batch) ++ return priv->sbus_ops->bus_rx_batch(priv->sbus_priv); ++ return 0; + #else + u32 ctrl_reg = 0; + size_t read_len = 0; +diff --git a/drivers/staging/bes2600/bh.h b/drivers/staging/bes2600/bh.h +index 7be82dc..9ed08b1 100644 +--- a/drivers/staging/bes2600/bh.h ++++ b/drivers/staging/bes2600/bh.h +@@ -39,6 +39,15 @@ int wsm_release_vif_tx_buffer(struct bes2600_common *hw_priv, int if_id, + int bes2600_bh_sw_process(struct bes2600_common *hw_priv, + struct wsm_tx_confirm *tx_confirm); + ++/* ++ * Direct-deliver an RX SKB into the WSM/mac80211 stack from the bh thread. ++ * Called by bes2600_sdio_extract_packets per RX frame, no queueing. ++ * Process context, sleepable, caller holds no bes2600 spinlock. ++ * Function frees skb on every path. See bh.c for full contract. ++ */ ++int bes2600_bh_handle_rx_skb(struct bes2600_common *hw_priv, ++ struct sk_buff *skb); ++ + void bes2600_bh_inc_pending_count(struct bes2600_common *hw_priv, int idx); + void bes2600_bh_dec_pending_count(struct bes2600_common *hw_priv, int idx); + +diff --git a/drivers/staging/bes2600/sbus.h b/drivers/staging/bes2600/sbus.h +index cb90890..96b1d4c 100644 +--- a/drivers/staging/bes2600/sbus.h ++++ b/drivers/staging/bes2600/sbus.h +@@ -83,6 +83,14 @@ struct sbus_ops { + * Returns 0 on success or a negative errno. + */ + int (*bus_reset)(struct sbus_priv *self); ++ /* ++ * Read a batch of RX frames inline from the bus and deliver each ++ * one via bes2600_bh_handle_rx_skb(). Called from the bh thread ++ * (process context, sleepable). Replaces the ++ * sdio_rx_work + rx_queue + pipe_read relay (Patch C v3, 2026). ++ * Returns 0 on success, negative on read error. ++ */ ++ int (*bus_rx_batch)(struct sbus_priv *self); + }; + + void bes2600_irq_handler(struct bes2600_common *priv); +-- +2.54.0 + +From b9e340c78cf7111dd29c9b31dae5fe73d8b5ceec Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 8 May 2026 00:03:50 +0200 +Subject: [PATCH 26/29] =?UTF-8?q?bes2600:=20Patch=20G=20=E2=80=94=20restor?= + =?UTF-8?q?e=20SPDX=20identifiers=20+=20ST-Ericsson=20attribution?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The bes2600 driver is a fork of the upstream cw1200 driver +(drivers/net/wireless/st/cw1200/, ST-Ericsson, Dmitry Tarnyagin +2010-2011). The fork's file headers have three GPL-compliance issues: + + 1. NO SPDX-License-Identifier on any of 48 source files (cw1200 + mainline has them on all 25). kernel.org-mandated since 2017. + + 2. Original "Copyright (c) 2010, ST-Ericsson" lines stripped from + all files inherited from cw1200, replaced with + "Copyright (c) 2010, Bestechnic" — factually impossible + (Bestechnic did not author the 2010 work) and a GPL-2.0 §1 + attribution-preservation violation. + + 3. The "GPL version 2 as published by the Free Software Foundation" + boilerplate paragraph is redundant alongside SPDX and is the + legacy form modern kernel sources have replaced. + +This patch corrects all three for the 48 .c/.h files in bes2600/: + + - Adds `// SPDX-License-Identifier: GPL-2.0-only` (or `/* ... */` + for headers) as line 1 of every file. + - Restores `Copyright (c) 2010, ST-Ericsson` + `Author: Dmitry + Tarnyagin ` as the FIRST copyright + chain entry on all 22 files derived from cw1200 (bh.{c,h}, + debug.{c,h}, fwio.{c,h}, hwio.{c,h}, main.c, pm.{c,h}, + queue.{c,h}, scan.{c,h}, sta.{c,h}, txrx.{c,h}, wsm.{c,h}). + - Keeps `Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.` as + the SECOND chain entry where Bestechnic genuinely contributed. + - Notes "Derived from cw1200_sdio.c" + ST-Ericsson copyright on + bes2600_sdio.c (heavy derivation, not a literal rename). + - Notes "Replaces hwbus.h from cw1200/" + ST-Ericsson copyright + on sbus.h. + - Preserves the prism54/islsm authorship chain on main.c and + bes2600.h (Michael Wu 2006 + Jean-Baptiste Note 2004-2006). + - Drops the GPL-2.0 boilerplate paragraph in favour of SPDX. + +No code changes — only file-header comment blocks. Module build is +unaffected (verified by header-only diff scope). + +This is a prerequisite for any kernel.org submission attempt. The +existing MODULE_LICENSE("GPL") + MODULE_AUTHOR(Tarnyagin@stericsson.com) +declarations were already present and are unchanged here; the +mismatch between MODULE_AUTHOR and the (since-corrected) per-file +copyrights is now resolved. +--- + bes2600/ap.c | 9 +++------ + bes2600/ap.h | 9 +++------ + bes2600/bes2600.h | 11 ++++------- + bes2600/bes2600_factory.c | 9 +++------ + bes2600/bes2600_factory.h | 9 +++------ + bes2600/bes2600_plat.h | 9 +++------ + bes2600/bes2600_sdio.c | 13 +++++++------ + bes2600/bes_chardev.c | 9 +++------ + bes2600/bes_chardev.h | 9 +++------ + bes2600/bes_fw.c | 9 +++------ + bes2600/bes_fw_common.c | 9 +++------ + bes2600/bes_fw_common.h | 9 +++------ + bes2600/bes_log.h | 7 +++++++ + bes2600/bes_nl80211_testmode_msg.h | 9 +++------ + bes2600/bes_pwr.c | 9 +++------ + bes2600/bes_pwr.h | 9 +++------ + bes2600/bh.c | 12 ++++++------ + bes2600/bh.h | 12 ++++++------ + bes2600/debug.c | 12 ++++++------ + bes2600/debug.h | 12 ++++++------ + bes2600/epta_coex.c | 9 +++------ + bes2600/epta_coex.h | 9 +++------ + bes2600/epta_request.c | 9 +++------ + bes2600/epta_request.h | 9 +++------ + bes2600/fwio.c | 12 ++++++------ + bes2600/fwio.h | 12 ++++++------ + bes2600/ht.h | 9 +++------ + bes2600/hwio.c | 12 ++++++------ + bes2600/hwio.h | 15 +++++---------- + bes2600/itp.c | 10 +++------- + bes2600/itp.h | 9 +++------ + bes2600/main.c | 18 ++++++++++++------ + bes2600/pm.c | 12 ++++++------ + bes2600/pm.h | 12 ++++++------ + bes2600/queue.c | 12 ++++++------ + bes2600/queue.h | 12 ++++++------ + bes2600/sbus.h | 12 ++++++------ + bes2600/scan.c | 12 ++++++------ + bes2600/scan.h | 12 ++++++------ + bes2600/sta.c | 12 ++++++------ + bes2600/sta.h | 12 ++++++------ + bes2600/tx_loop.c | 9 +++------ + bes2600/tx_loop.h | 9 +++------ + bes2600/txrx.c | 12 ++++++------ + bes2600/txrx.h | 12 ++++++------ + bes2600/wifi_testmode_cmd.c | 9 +++------ + bes2600/wsm.c | 13 ++++++------- + bes2600/wsm.h | 14 +++++--------- + 48 files changed, 223 insertions(+), 292 deletions(-) + +diff --git a/drivers/staging/bes2600/ap.c b/drivers/staging/bes2600/ap.c +index 7b1e3b4..8a17545 100644 +--- a/drivers/staging/bes2600/ap.c ++++ b/drivers/staging/bes2600/ap.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * mac80211 STA and AP API for mac80211 BES2600 drivers ++ * AP mode for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include "bes2600.h" +diff --git a/drivers/staging/bes2600/ap.h b/drivers/staging/bes2600/ap.h +index 6f27852..f6e88c6 100644 +--- a/drivers/staging/bes2600/ap.h ++++ b/drivers/staging/bes2600/ap.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * mac80211 STA and AP API for mac80211 BES2600 drivers ++ * AP mode interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #ifndef AP_H_INCLUDED +diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h +index ec41141..84059c7 100644 +--- a/drivers/staging/bes2600/bes2600.h ++++ b/drivers/staging/bes2600/bes2600.h +@@ -1,18 +1,15 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Common private data for BES2600 drivers ++ * Common private data for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * + * Based on the mac80211 Prism54 code, which is + * Copyright (c) 2006, Michael Wu + * +- * Based on the islsm (softmac prism54) driver, which is: ++ * Based on the islsm (softmac prism54) driver, which is + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_H +diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c +index 1b43b41..0d2bfa1 100644 +--- a/drivers/staging/bes2600/bes2600_factory.c ++++ b/drivers/staging/bes2600/bes2600_factory.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Factory calibration loader for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/bes2600_factory.h b/drivers/staging/bes2600/bes2600_factory.h +index 7dbe9f8..0b1a321 100644 +--- a/drivers/staging/bes2600/bes2600_factory.h ++++ b/drivers/staging/bes2600/bes2600_factory.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Factory calibration loader interface + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef __FACTORY_H__ + #define __FACTORY_H__ +diff --git a/drivers/staging/bes2600/bes2600_plat.h b/drivers/staging/bes2600/bes2600_plat.h +index 63c3275..ebec635 100644 +--- a/drivers/staging/bes2600/bes2600_plat.h ++++ b/drivers/staging/bes2600/bes2600_plat.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Platform data for BES2600 SDIO bus + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef BES2600_PLAT_H_INCLUDED + #define BES2600_PLAT_H_INCLUDED +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c +index 3834032..e85c524 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -1,12 +1,13 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 SDIO driver for BES2600 device ++ * SDIO bus glue for BES2600 mac80211 driver ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. ++ * Derived from drivers/net/wireless/st/cw1200/cw1200_sdio.c ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin + * +- * Copyright (c) 2010, Bestechnic +- * Author: + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #define DEBUG 1 + #include +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index 224c62d..02dcd43 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Character device for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #include +diff --git a/drivers/staging/bes2600/bes_chardev.h b/drivers/staging/bes2600/bes_chardev.h +index 2a7cad7..9edb206 100644 +--- a/drivers/staging/bes2600/bes_chardev.h ++++ b/drivers/staging/bes2600/bes_chardev.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Character device interface for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef __BES_CHARDEV_H__ + #define __BES_CHARDEV_H__ +diff --git a/drivers/staging/bes2600/bes_fw.c b/drivers/staging/bes2600/bes_fw.c +index d612c3c..6c5598b 100644 +--- a/drivers/staging/bes2600/bes_fw.c ++++ b/drivers/staging/bes2600/bes_fw.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Firmware download for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include "bes_fw_common.h" + #include "bes2600.h" +diff --git a/drivers/staging/bes2600/bes_fw_common.c b/drivers/staging/bes2600/bes_fw_common.c +index 2e47455..a0c1f93 100644 +--- a/drivers/staging/bes2600/bes_fw_common.c ++++ b/drivers/staging/bes2600/bes_fw_common.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Firmware download common code for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include "bes_fw_common.h" + #include "bes_log.h" +diff --git a/drivers/staging/bes2600/bes_fw_common.h b/drivers/staging/bes2600/bes_fw_common.h +index 5c6561a..dcd5200 100644 +--- a/drivers/staging/bes2600/bes_fw_common.h ++++ b/drivers/staging/bes2600/bes_fw_common.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Firmware download common interface + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef __BES_FW_COMMON_H__ + #define __BES_FW_COMMON_H__ +diff --git a/drivers/staging/bes2600/bes_log.h b/drivers/staging/bes2600/bes_log.h +index 65cf703..7d3c4b8 100644 +--- a/drivers/staging/bes2600/bes_log.h ++++ b/drivers/staging/bes2600/bes_log.h +@@ -1,3 +1,10 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * printk wrappers for BES2600 ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. ++ * ++ */ + extern struct device *global_dev; + + #ifdef CONFIG_BES2600_ENABLE_DEVEL_LOGS +diff --git a/drivers/staging/bes2600/bes_nl80211_testmode_msg.h b/drivers/staging/bes2600/bes_nl80211_testmode_msg.h +index b70a0dd..c97c1ad 100644 +--- a/drivers/staging/bes2600/bes_nl80211_testmode_msg.h ++++ b/drivers/staging/bes2600/bes_nl80211_testmode_msg.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Vendor testmode messages for BES2600 + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES_NL80211_TESTMODE_MSG_H +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index 620acef..a3f954b 100644 +--- a/drivers/staging/bes2600/bes_pwr.c ++++ b/drivers/staging/bes2600/bes_pwr.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Chip-side power state machine for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #include diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h -index 1ba866c25..49477b3e2 100644 +index 92de90b..49477b3 100644 --- a/drivers/staging/bes2600/bes_pwr.h +++ b/drivers/staging/bes2600/bes_pwr.h @@ -1,12 +1,9 @@ @@ -1646,46 +5216,8 @@ index 1ba866c25..49477b3e2 100644 */ #ifndef __BES_PWR_H__ #define __BES_PWR_H__ -@@ -64,6 +61,20 @@ enum power_down_state - POWER_DOWN_STATE_UNLOCKED, - }; - -+/* -+ * Confirmed PM state of the firmware-side chip. Tracks what the host -+ * has *seen* the firmware acknowledge, not what the host has -+ * requested. UNKNOWN means a host-initiated transition timed out -+ * before the firmware indication arrived; the next wake path should -+ * treat it as "we don't know" and probe before issuing GPIO/SDIO -+ * wakeup ops. -+ */ -+enum bes2600_chip_pm_state { -+ BES2600_CHIP_PM_ACTIVE = 0, -+ BES2600_CHIP_PM_LP, -+ BES2600_CHIP_PM_UNKNOWN, -+}; -+ - typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv); - typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv); - -@@ -106,6 +117,16 @@ struct bes2600_pwr_t - bool ap_lp_bad; - struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM]; - atomic_t pm_set_in_process; -+ atomic_t chip_pm_state; -+ /* -+ * Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD -+ * consecutive enter_lp_mode timeouts with zero PM_INDICATIONs -+ * received from firmware. Indicates this chip's firmware does -+ * not honor host-driven PSM transitions; further attempts are -+ * skipped to avoid the 5s timeout cascade. -+ */ -+ bool pm_unsupported; -+ unsigned int pm_consecutive_timeouts; - }; - - #ifdef CONFIG_BES2600_WOWLAN diff --git a/drivers/staging/bes2600/bh.c b/drivers/staging/bes2600/bh.c -index 175ab5e39..924899b1c 100644 +index febcaf4..61f6991 100644 --- a/drivers/staging/bes2600/bh.c +++ b/drivers/staging/bes2600/bh.c @@ -1,12 +1,12 @@ @@ -1707,24 +5239,1398 @@ index 175ab5e39..924899b1c 100644 */ #include #include -@@ -22,7 +22,6 @@ - #include "debug.h" - #include "epta_coex.h" - #include "bes_chardev.h" --#include "txrx_opt.h" - #include "sta.h" - #include "bes_log.h" +diff --git a/drivers/staging/bes2600/bh.h b/drivers/staging/bes2600/bh.h +index 9ed08b1..700f2aa 100644 +--- a/drivers/staging/bes2600/bh.h ++++ b/drivers/staging/bes2600/bh.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Device handling thread interface for mac80211 BES2600 drivers ++ * Bottom-half thread interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ -@@ -102,7 +101,7 @@ void bes2600_unregister_bh(struct bes2600_common *hw_priv) - coex_deinit_mode(hw_priv); + #ifndef BES2600_BH_H +diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c +index 0d68392..47e27be 100644 +--- a/drivers/staging/bes2600/debug.c ++++ b/drivers/staging/bes2600/debug.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Debugging interface for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/debug.h b/drivers/staging/bes2600/debug.h +index 3714577..5914ffc 100644 +--- a/drivers/staging/bes2600/debug.h ++++ b/drivers/staging/bes2600/debug.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * DebugFS code for BES2600 mac80211 driver ++ * Debugging interface for BES2600 mac80211 driver + * +- * Copyright (c) 2011, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_DEBUG_H_INCLUDED +diff --git a/drivers/staging/bes2600/epta_coex.c b/drivers/staging/bes2600/epta_coex.c +index dfdf8e7..3ed76f1 100644 +--- a/drivers/staging/bes2600/epta_coex.c ++++ b/drivers/staging/bes2600/epta_coex.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * BT/WiFi coexistence (ePTA) for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #include +diff --git a/drivers/staging/bes2600/epta_coex.h b/drivers/staging/bes2600/epta_coex.h +index bc9eed6..f8a5fea 100644 +--- a/drivers/staging/bes2600/epta_coex.h ++++ b/drivers/staging/bes2600/epta_coex.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * BT/WiFi coexistence interface for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef __EPTA_COEX_H__ + #define __EPTA_COEX_H__ +diff --git a/drivers/staging/bes2600/epta_request.c b/drivers/staging/bes2600/epta_request.c +index 3b3e6af..486f02b 100644 +--- a/drivers/staging/bes2600/epta_request.c ++++ b/drivers/staging/bes2600/epta_request.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * BT/WiFi coexistence request handling + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #include +diff --git a/drivers/staging/bes2600/epta_request.h b/drivers/staging/bes2600/epta_request.h +index f0217c2..b3d9228 100644 +--- a/drivers/staging/bes2600/epta_request.h ++++ b/drivers/staging/bes2600/epta_request.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * BT/WiFi coexistence request interface + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef EPTA_REQUEST_H + #define EPTA_REQUEST_H +diff --git a/drivers/staging/bes2600/fwio.c b/drivers/staging/bes2600/fwio.c +index 5fe6b50..29aa2b3 100644 +--- a/drivers/staging/bes2600/fwio.c ++++ b/drivers/staging/bes2600/fwio.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Firmware I/O for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/fwio.h b/drivers/staging/bes2600/fwio.h +index a4afb7a..adbd708 100644 +--- a/drivers/staging/bes2600/fwio.h ++++ b/drivers/staging/bes2600/fwio.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Firmware I/O interface for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef FWIO_H_INCLUDED + #define FWIO_H_INCLUDED +diff --git a/drivers/staging/bes2600/ht.h b/drivers/staging/bes2600/ht.h +index b5caa29..5ac077b 100644 +--- a/drivers/staging/bes2600/ht.h ++++ b/drivers/staging/bes2600/ht.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * HT-related code for BES2600 driver ++ * HT capability config for BES2600 + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_HT_H_INCLUDED +diff --git a/drivers/staging/bes2600/hwio.c b/drivers/staging/bes2600/hwio.c +index ea88210..0934a13 100644 +--- a/drivers/staging/bes2600/hwio.c ++++ b/drivers/staging/bes2600/hwio.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Low-level device IO routines for BES2600 drivers ++ * Low-level device I/O for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/hwio.h b/drivers/staging/bes2600/hwio.h +index b9c1858..48e5215 100644 +--- a/drivers/staging/bes2600/hwio.h ++++ b/drivers/staging/bes2600/hwio.h +@@ -1,17 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Low-level API for mac80211 BES2600 drivers ++ * Low-level device I/O interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin + * +- * Based on: +- * UMAC BES2600 driver which is +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_HWIO_H_INCLUDED +diff --git a/drivers/staging/bes2600/itp.c b/drivers/staging/bes2600/itp.c +index c50b29c..7cc237c 100644 +--- a/drivers/staging/bes2600/itp.c ++++ b/drivers/staging/bes2600/itp.c +@@ -1,13 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * mac80211 glue code for mac80211 BES2600 drivers +- * ITP code ++ * ITP (in-band test mode) for BES2600 + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/itp.h b/drivers/staging/bes2600/itp.h +index 5cfba46..bec3647 100644 +--- a/drivers/staging/bes2600/itp.h ++++ b/drivers/staging/bes2600/itp.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * ITP code for BES2600 mac80211 driver ++ * ITP interface for BES2600 + * +- * Copyright (c) 2011, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_ITP_H_INCLUDED +diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c +index 89b5e2d..71dc4ae 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -1,12 +1,18 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Main entry/init for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. ++ * ++ * Based on the mac80211 Prism54 code, which is ++ * Copyright (c) 2006, Michael Wu ++ * ++ * Based on the islsm (softmac prism54) driver, which is ++ * Copyright 2004-2006 Jean-Baptiste Note , et al. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/pm.c b/drivers/staging/bes2600/pm.c +index c32c68e..0424aae 100644 +--- a/drivers/staging/bes2600/pm.c ++++ b/drivers/staging/bes2600/pm.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 power management API for BES2600 drivers ++ * Power management for BES2600 mac80211 driver + * +- * Copyright (c) 2011, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/pm.h b/drivers/staging/bes2600/pm.h +index 0f6943e..ae70453 100644 +--- a/drivers/staging/bes2600/pm.h ++++ b/drivers/staging/bes2600/pm.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 power management interface for BES2600 mac80211 drivers ++ * Power management interface for BES2600 mac80211 driver + * +- * Copyright (c) 2011, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef PM_H_INCLUDED +diff --git a/drivers/staging/bes2600/queue.c b/drivers/staging/bes2600/queue.c +index d1b407b..b56ca43 100644 +--- a/drivers/staging/bes2600/queue.c ++++ b/drivers/staging/bes2600/queue.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * O(1) TX queue with built-in allocator for BES2600 drivers ++ * O(1) TX queue for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/queue.h b/drivers/staging/bes2600/queue.h +index a5395b6..94874dd 100644 +--- a/drivers/staging/bes2600/queue.h ++++ b/drivers/staging/bes2600/queue.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * O(1) TX queue with built-in allocator for BES2600 drivers ++ * O(1) TX queue interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_QUEUE_H_INCLUDED +diff --git a/drivers/staging/bes2600/sbus.h b/drivers/staging/bes2600/sbus.h +index 96b1d4c..43c2dae 100644 +--- a/drivers/staging/bes2600/sbus.h ++++ b/drivers/staging/bes2600/sbus.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Common sbus abstraction layer interface for bes2600 wireless driver ++ * Bus abstraction interface for BES2600 ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. ++ * Replaces hwbus.h from drivers/net/wireless/st/cw1200/ ++ * Copyright (c) 2010, ST-Ericsson + * +- * Copyright (c) 2010, Bestechnic +- * Author: + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_SBUS_H +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index 3cd7b64..1905471 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Scan implementation for BES2600 mac80211 drivers ++ * Scan implementation for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/scan.h b/drivers/staging/bes2600/scan.h +index 1f3adea..295be18 100644 +--- a/drivers/staging/bes2600/scan.h ++++ b/drivers/staging/bes2600/scan.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Scan interface for BES2600 mac80211 drivers ++ * Scan interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef SCAN_H_INCLUDED +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index 5868757..70b12f9 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 STA API for BES2600 drivers ++ * Mac80211 STA API for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/sta.h b/drivers/staging/bes2600/sta.h +index 39b4b1a..a174e04 100644 +--- a/drivers/staging/bes2600/sta.h ++++ b/drivers/staging/bes2600/sta.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 STA interface for BES2600 mac80211 drivers ++ * Mac80211 STA API interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include + #ifndef STA_H_INCLUDED +diff --git a/drivers/staging/bes2600/tx_loop.c b/drivers/staging/bes2600/tx_loop.c +index baab3f0..e6cf072 100644 +--- a/drivers/staging/bes2600/tx_loop.c ++++ b/drivers/staging/bes2600/tx_loop.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Test-mode TX loopback for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #include "bes2600.h" + #include "wsm.h" +diff --git a/drivers/staging/bes2600/tx_loop.h b/drivers/staging/bes2600/tx_loop.h +index de82b30..7f42c04 100644 +--- a/drivers/staging/bes2600/tx_loop.h ++++ b/drivers/staging/bes2600/tx_loop.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Test-mode TX loopback interface for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifndef __TX_LOOP_H__ + #define __TX_LOOP_H__ +diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c +index f6a66d6..3aef009 100644 +--- a/drivers/staging/bes2600/txrx.c ++++ b/drivers/staging/bes2600/txrx.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Datapath implementation for BES2600 mac80211 drivers ++ * Datapath implementation for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/txrx.h b/drivers/staging/bes2600/txrx.h +index cb7c192..6466c33 100644 +--- a/drivers/staging/bes2600/txrx.h ++++ b/drivers/staging/bes2600/txrx.h +@@ -1,12 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Datapath interface for BES2600 mac80211 drivers ++ * Datapath interface for BES2600 mac80211 driver + * +- * Copyright (c) 2010, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_TXRX_H +diff --git a/drivers/staging/bes2600/wifi_testmode_cmd.c b/drivers/staging/bes2600/wifi_testmode_cmd.c +index 2494cca..c010e8d 100644 +--- a/drivers/staging/bes2600/wifi_testmode_cmd.c ++++ b/drivers/staging/bes2600/wifi_testmode_cmd.c +@@ -1,12 +1,9 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * WiFi testmode commands for BES2600 + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + #ifdef CONFIG_BES2600_TESTMODE + #include +diff --git a/drivers/staging/bes2600/wsm.c b/drivers/staging/bes2600/wsm.c +index 55a4e2b..908c965 100644 +--- a/drivers/staging/bes2600/wsm.c ++++ b/drivers/staging/bes2600/wsm.c +@@ -1,13 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * WSM host interface (HI) implementation for +- * BES2600 mac80211 drivers. ++ * WSM host interface for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin ++ * ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #include +diff --git a/drivers/staging/bes2600/wsm.h b/drivers/staging/bes2600/wsm.h +index 22845ac..0d755a3 100644 +--- a/drivers/staging/bes2600/wsm.h ++++ b/drivers/staging/bes2600/wsm.h +@@ -1,16 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * WSM host interface (HI) interface for BES2600 mac80211 drivers ++ * WSM host interface for BES2600 mac80211 driver + * +- * Copyright (c) 2022, Bestechnic +- * Author: ++ * Copyright (c) 2010, ST-Ericsson ++ * Author: Dmitry Tarnyagin + * +- * Based on BES2600 UMAC WSM API, which is +- * Copyright (C) SA 2010 +- * Author: Stewart Mathers ++ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. + * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License version 2 as +- * published by the Free Software Foundation. + */ + + #ifndef BES2600_WSM_H_INCLUDED +-- +2.54.0 + +From 8fd20308ed53678c863a0ef52fb2c754e3adc63c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 8 May 2026 00:17:46 +0200 +Subject: [PATCH 27/29] =?UTF-8?q?bes2600:=20Patch=20D=20=E2=80=94=20atomic?= + =?UTF-8?q?ize=20ba=5Flock=20counters,=20drop=20the=20spinlock?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The block-ack policy uses 4 int counters (ba_acc, ba_cnt, ba_acc_rx, +ba_cnt_rx) bumped per data frame in the TX and RX hot paths under +spin_lock_bh(&hw_priv->ba_lock). The lock was the heaviest per-frame +synchronization cost remaining after Patch C v3 (which fixed the +sdio_rx_work relay). Per the Opus structural critique (PR #8), this +pattern matches mac80211 driver convention for per-frame statistics: +atomic_t suffices, no lock needed. + +Field-by-field changes in struct bes2600_common: + ba_acc, ba_cnt, ba_acc_rx, ba_cnt_rx: int -> atomic_t + ba_armed: new atomic_t (timer-arm flag) + ba_ena: bool -> atomic_t + ba_lock: removed (spinlock_t deleted) + ba_hist: int (single-writer = ba_timer) + +Producer hot path (txrx.c TX submit + RX receive): + - atomic_add for the byte accumulator + - atomic_inc for the frame counter + - atomic_cmpxchg(&ba_armed, 0, 1) to claim the once-per-window + mod_timer arm — at most ONE producer succeeds; race-free + - no spin_lock_bh + +Consumer paths (sta.c bes2600_ba_timer, sta.c disconnect-reset, sta.c +bes2600_ba_work, debug.c debugfs reader): + - atomic_read snapshots all 4 counters into locals; the threshold + predicate (acc/cnt >= THLD) tolerates approximate snapshots — the + timer fires periodically, a single misclassification just delays + the policy update by one tick + - atomic_set zeroes the counters at end of timer-callback window; + racing producer increments after the snapshot are lost (acceptable + for stats; same approximation the original lock allowed under + contention) + - atomic_set(&ba_armed, 0) re-enables the next window's arm + +Followup-amenable simplification: ba_hist remains int because only +the single ba_timer callback writes it; multiple writers would need +to upgrade it too. + +This patch follows the cw1200-mainline-idiom established by Patch C v3 +(structural fix, not bandaid). The cw1200 reference doesn't have a +similar lock to compare; bes2600 inherited this from a later +Bestechnic addition rather than the upstream tree. +--- + bes2600/bes2600.h | 26 ++++++++++------ + bes2600/debug.c | 13 +++++--- + bes2600/main.c | 2 +- + bes2600/sta.c | 77 ++++++++++++++++++++++++++++------------------- + bes2600/txrx.c | 23 ++++++++------ + 5 files changed, 85 insertions(+), 56 deletions(-) + +diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h +index 84059c7..32bce5e 100644 +--- a/drivers/staging/bes2600/bes2600.h ++++ b/drivers/staging/bes2600/bes2600.h +@@ -353,15 +353,23 @@ struct bes2600_common { + * Keeping in common structure for the time being. Will be moved to VIFF + * after the mechanism is clear */ + u8 ba_tid_mask; +- int ba_acc; /*TODO: Same as above */ +- int ba_cnt; /*TODO: Same as above */ +- int ba_cnt_rx; /*TODO: Same as above */ +- int ba_acc_rx; /*TODO: Same as above */ +- int ba_hist; /*TODO: Same as above */ +- struct timer_list ba_timer;/*TODO: Same as above */ +- spinlock_t ba_lock; /*TODO: Same as above */ +- bool ba_ena; /*TODO: Same as above */ +- struct work_struct ba_work; /*TODO: Same as above */ ++ /* ++ * Patch D: ba_lock removed. Per-frame TX/RX hot-path bumped these ++ * counters under spin_lock_bh; the lock did not protect any ++ * compound invariant that atomic ops can't satisfy. Counters are ++ * now atomic_t; ba_armed gates the once-per-window mod_timer ++ * arm via cmpxchg so concurrent TX/RX at a fresh window each ++ * try to claim the arm and exactly one succeeds. ++ */ ++ atomic_t ba_acc; ++ atomic_t ba_cnt; ++ atomic_t ba_cnt_rx; ++ atomic_t ba_acc_rx; ++ atomic_t ba_armed; ++ int ba_hist; ++ struct timer_list ba_timer; ++ atomic_t ba_ena; ++ struct work_struct ba_work; + bool is_BT_Present; + bool is_go_thru_go_neg; + u8 conf_listen_interval; +diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c +index 47e27be..0ab79c0 100644 +--- a/drivers/staging/bes2600/debug.c ++++ b/drivers/staging/bes2600/debug.c +@@ -110,17 +110,20 @@ static int bes2600_status_show_common(struct seq_file *seq, void *v) + int ba_cnt, ba_acc, ba_cnt_rx, ba_acc_rx, ba_avg = 0, ba_avg_rx = 0; + bool ba_ena; + +- spin_lock_bh(&hw_priv->ba_lock); +- ba_cnt = hw_priv->debug->ba_cnt; +- ba_acc = hw_priv->debug->ba_acc; ++ /* ++ * Patch D: ba_lock removed. hw_priv->debug->ba_* are written only ++ * by the timer callback (single writer); reading without a lock is ++ * fine for stats. ba_ena is atomic_t. ++ */ ++ ba_cnt = hw_priv->debug->ba_cnt; ++ ba_acc = hw_priv->debug->ba_acc; + ba_cnt_rx = hw_priv->debug->ba_cnt_rx; + ba_acc_rx = hw_priv->debug->ba_acc_rx; +- ba_ena = hw_priv->ba_ena; ++ ba_ena = !!atomic_read(&hw_priv->ba_ena); + if (ba_cnt) + ba_avg = ba_acc / ba_cnt; + if (ba_cnt_rx) + ba_avg_rx = ba_acc_rx / ba_cnt_rx; +- spin_unlock_bh(&hw_priv->ba_lock); + + seq_puts(seq, "BES2600 Wireless LAN driver status\n"); + seq_printf(seq, "Hardware: %d.%d\n", +diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c +index 71dc4ae..8fc37b4 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -501,7 +501,7 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) + INIT_LIST_HEAD(&hw_priv->event_queue); + INIT_WORK(&hw_priv->event_handler, bes2600_event_handler); + INIT_WORK(&hw_priv->ba_work, bes2600_ba_work); +- spin_lock_init(&hw_priv->ba_lock); ++ /* Patch D: ba_lock removed; ba_acc/ba_cnt/etc are atomic_t. */ + timer_setup(&hw_priv->ba_timer, bes2600_ba_timer, 0); + + if (unlikely(bes2600_queue_stats_init(&hw_priv->tx_queue_stats, +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index 70b12f9..8af8150 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -2362,14 +2362,19 @@ void bes2600_join_work(struct work_struct *work) + //WARN_ON(wsm_reset(hw_priv, &reset, priv->if_id)); + WARN_ON(wsm_set_block_ack_policy(hw_priv, + 0, hw_priv->ba_tid_mask, priv->if_id)); +- spin_lock_bh(&hw_priv->ba_lock); +- hw_priv->ba_ena = false; +- hw_priv->ba_cnt = 0; +- hw_priv->ba_acc = 0; ++ /* ++ * Patch D: ba_lock removed. Disconnect-reset clears the ++ * counters and the arm flag; producers racing here cannot ++ * cause harm — at worst they re-arm the timer and bump ++ * counters that will be cleared on the next timer tick. ++ */ ++ atomic_set(&hw_priv->ba_ena, 0); ++ atomic_set(&hw_priv->ba_cnt, 0); ++ atomic_set(&hw_priv->ba_acc, 0); + hw_priv->ba_hist = 0; +- hw_priv->ba_cnt_rx = 0; +- hw_priv->ba_acc_rx = 0; +- spin_unlock_bh(&hw_priv->ba_lock); ++ atomic_set(&hw_priv->ba_cnt_rx, 0); ++ atomic_set(&hw_priv->ba_acc_rx, 0); ++ atomic_set(&hw_priv->ba_armed, 0); + + mgmt_policy.protectedMgmtEnable = 0; + mgmt_policy.unprotectedMgmtFramesAllowed = 1; +@@ -2649,10 +2654,11 @@ void bes2600_ba_work(struct work_struct *work) + return;*/ + + bes_devel("BA work****\n"); +- spin_lock_bh(&hw_priv->ba_lock); +-// tx_ba_tid_mask = hw_priv->ba_ena ? hw_priv->ba_tid_mask : 0; ++ /* ++ * Patch D: ba_lock removed. ba_tid_mask is u8 set once at init ++ * (main.c); reading it without a lock is fine. ++ */ + tx_ba_tid_mask = hw_priv->ba_tid_mask; +- spin_unlock_bh(&hw_priv->ba_lock); + + wsm_lock_tx(hw_priv); + +@@ -2665,37 +2671,49 @@ void bes2600_ba_work(struct work_struct *work) + void bes2600_ba_timer(struct timer_list *t) + { + bool ba_ena; ++ int cnt, acc, cnt_rx, acc_rx; + struct bes2600_common *hw_priv = timer_container_of(hw_priv, t, ba_timer); + +- spin_lock_bh(&hw_priv->ba_lock); +- bes2600_debug_ba(hw_priv, hw_priv->ba_cnt, hw_priv->ba_acc, +- hw_priv->ba_cnt_rx, hw_priv->ba_acc_rx); ++ /* ++ * Patch D: ba_lock removed. Snapshot atomic counters into locals ++ * for the predicate evaluation; producers may race incrementing ++ * after the snapshot but the resulting decision is approximate ++ * which the policy already tolerates (next timer tick re-evaluates). ++ */ ++ cnt = atomic_read(&hw_priv->ba_cnt); ++ acc = atomic_read(&hw_priv->ba_acc); ++ cnt_rx = atomic_read(&hw_priv->ba_cnt_rx); ++ acc_rx = atomic_read(&hw_priv->ba_acc_rx); ++ ++ bes2600_debug_ba(hw_priv, cnt, acc, cnt_rx, acc_rx); + + if (atomic_read(&hw_priv->scan.in_progress)) { +- hw_priv->ba_cnt = 0; +- hw_priv->ba_acc = 0; +- hw_priv->ba_cnt_rx = 0; +- hw_priv->ba_acc_rx = 0; +- goto skip_statistic_update; ++ atomic_set(&hw_priv->ba_cnt, 0); ++ atomic_set(&hw_priv->ba_acc, 0); ++ atomic_set(&hw_priv->ba_cnt_rx, 0); ++ atomic_set(&hw_priv->ba_acc_rx, 0); ++ atomic_set(&hw_priv->ba_armed, 0); ++ return; + } + +- if (hw_priv->ba_cnt >= BES2600_BLOCK_ACK_CNT && +- (hw_priv->ba_acc / hw_priv->ba_cnt >= BES2600_BLOCK_ACK_THLD || +- (hw_priv->ba_cnt_rx >= BES2600_BLOCK_ACK_CNT && +- hw_priv->ba_acc_rx / hw_priv->ba_cnt_rx >= ++ if (cnt >= BES2600_BLOCK_ACK_CNT && ++ (acc / cnt >= BES2600_BLOCK_ACK_THLD || ++ (cnt_rx >= BES2600_BLOCK_ACK_CNT && ++ acc_rx / cnt_rx >= + BES2600_BLOCK_ACK_THLD))) + ba_ena = true; + else + ba_ena = false; + +- hw_priv->ba_cnt = 0; +- hw_priv->ba_acc = 0; +- hw_priv->ba_cnt_rx = 0; +- hw_priv->ba_acc_rx = 0; ++ atomic_set(&hw_priv->ba_cnt, 0); ++ atomic_set(&hw_priv->ba_acc, 0); ++ atomic_set(&hw_priv->ba_cnt_rx, 0); ++ atomic_set(&hw_priv->ba_acc_rx, 0); ++ atomic_set(&hw_priv->ba_armed, 0); + +- if (ba_ena != hw_priv->ba_ena) { ++ if (ba_ena != !!atomic_read(&hw_priv->ba_ena)) { + if (ba_ena || ++hw_priv->ba_hist >= BES2600_BLOCK_ACK_HIST) { +- hw_priv->ba_ena = ba_ena; ++ atomic_set(&hw_priv->ba_ena, ba_ena ? 1 : 0); + hw_priv->ba_hist = 0; + #if 0 + bes_devel("[STA] %s block ACK:\n", +@@ -2705,9 +2723,6 @@ void bes2600_ba_timer(struct timer_list *t) + } + } else if (hw_priv->ba_hist) + --hw_priv->ba_hist; +- +-skip_statistic_update: +- spin_unlock_bh(&hw_priv->ba_lock); + } + + int bes2600_vif_setup(struct bes2600_vif *priv) +diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c +index 3aef009..536b198 100644 +--- a/drivers/staging/bes2600/txrx.c ++++ b/drivers/staging/bes2600/txrx.c +@@ -996,14 +996,18 @@ bes2600_tx_h_ba_stat(struct bes2600_vif *priv, + if (!ieee80211_is_data(t->hdr->frame_control)) + return; + +- spin_lock_bh(&hw_priv->ba_lock); +- hw_priv->ba_acc += t->skb->len - t->hdrlen; +- if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) { ++ /* ++ * Patch D: lock-free hot-path BA accounting. atomic_inc + atomic_add ++ * each per-frame; the once-per-window timer-arm uses cmpxchg on ++ * ba_armed so concurrent TX/RX can't both try to set the timer and ++ * we don't need cross-counter coherency on the ba_cnt/ba_cnt_rx pair. ++ */ ++ atomic_add(t->skb->len - t->hdrlen, &hw_priv->ba_acc); ++ atomic_inc(&hw_priv->ba_cnt); ++ if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) { + mod_timer(&hw_priv->ba_timer, + jiffies + BES2600_BLOCK_ACK_INTERVAL); + } +- hw_priv->ba_cnt++; +- spin_unlock_bh(&hw_priv->ba_lock); + } + + static int +@@ -1651,14 +1655,13 @@ bes2600_rx_h_ba_stat(struct bes2600_vif *priv, + if (!priv->setbssparams_done) + return; + +- spin_lock_bh(&hw_priv->ba_lock); +- hw_priv->ba_acc_rx += skb_len - hdrlen; +- if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) { ++ /* Patch D: lock-free hot-path BA accounting; see TX side comment. */ ++ atomic_add(skb_len - hdrlen, &hw_priv->ba_acc_rx); ++ atomic_inc(&hw_priv->ba_cnt_rx); ++ if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) { + mod_timer(&hw_priv->ba_timer, + jiffies + BES2600_BLOCK_ACK_INTERVAL); + } +- hw_priv->ba_cnt_rx++; +- spin_unlock_bh(&hw_priv->ba_lock); + } + + void bes2600_rx_cb(struct bes2600_vif *priv, +-- +2.54.0 + +From 445c619da88d69adf68e8cae08ad1b53f76fe57d Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 8 May 2026 00:22:14 +0200 +Subject: [PATCH 28/29] =?UTF-8?q?bes2600:=20Patch=20E=20=E2=80=94=20skip?= + =?UTF-8?q?=20ps=5Fstate=5Flock=20when=20PSM-known-disabled?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Per the Opus structural critique (PR #8 §2.4) and Sonnet review item 5. +The per-RX-frame early-data path takes ps_state_lock to double-check +whether a link entry transitioned to BES2600_LINK_SOFT (AP-side +power-save state machine, soft-link transition). + +When c7 has latched pm_unsupported = true (firmware does not honor +PSM, see feedback_bes2600_firmware_no_psm memory), the AP power-save +state machine is dead and link entries never transition to LINK_SOFT. +The per-frame spin_lock_bh + double-check is wasted work. + +This patch gates the lock acquisition on !pm_unsupported. When the +latch is on (the steady state on the production-shipped bes2600 +firmware), early_data RX frames bypass the spin_lock_bh and go +directly to ieee80211_rx_irqsafe. + +If a future firmware drop fixes PSM, c7 self-clears pm_unsupported on +the first real PM_INDICATION and the locked path resumes. + +Scope is narrower than Sonnet originally framed: only the per-RX-frame +hot path (txrx.c:1945-1951 in cleanups+G+D) is touched. Other +ps_state_lock sites in txrx.c (lines 657, 1256, 1420, 1528) are TX +submission / multicast-start / link-id paths, not per-frame RX, and +not on the Bug #5 hot path. Leave those alone. + +Build verified: srcversion B5922B4933590F33207EE97 on ohm sandbox. +--- + bes2600/txrx.c | 30 ++++++++++++++++++++++++------ + 1 file changed, 24 insertions(+), 6 deletions(-) + +diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c +index 536b198..cb718ad 100644 +--- a/drivers/staging/bes2600/txrx.c ++++ b/drivers/staging/bes2600/txrx.c +@@ -1965,13 +1965,31 @@ void bes2600_rx_cb(struct bes2600_vif *priv, + if (unlikely(bes2600_itp_rxed(hw_priv, skb))) + consume_skb(skb); + else if (unlikely(early_data)) { +- spin_lock_bh(&priv->ps_state_lock); +- /* Double-check status with lock held */ +- if (entry->status == BES2600_LINK_SOFT) +- skb_queue_tail(&entry->rx_queue, skb); +- else ++ /* ++ * Patch E: when c7 has latched pm_unsupported (firmware ++ * doesn't honour PSM, see feedback_bes2600_firmware_no_psm), ++ * AP-side power-save state machine is dead and link entries ++ * never transition to BES2600_LINK_SOFT. The double-check ++ * branch under ps_state_lock is unreachable in that case, ++ * so skip the per-frame lock acquisition entirely and ++ * deliver to mac80211 directly. ++ * ++ * On firmware that does honour PSM (the latch self-clears ++ * if a real PM_INDICATION ever arrives — see c7), this ++ * predicate flips back to false and the original locked ++ * path is taken. ++ */ ++ if (hw_priv->bes_power.pm_unsupported) { + ieee80211_rx_irqsafe(priv->hw, skb); +- spin_unlock_bh(&priv->ps_state_lock); ++ } else { ++ spin_lock_bh(&priv->ps_state_lock); ++ /* Double-check status with lock held */ ++ if (entry->status == BES2600_LINK_SOFT) ++ skb_queue_tail(&entry->rx_queue, skb); ++ else ++ ieee80211_rx_irqsafe(priv->hw, skb); ++ spin_unlock_bh(&priv->ps_state_lock); ++ } + } else { + ieee80211_rx_irqsafe(priv->hw, skb); + } +-- +2.54.0 + +From a70e882f3d5f4f9148206675562dddeecd3f4404 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 8 May 2026 06:40:00 +0200 +Subject: [PATCH 29/29] =?UTF-8?q?bes2600:=20Patch=20C2=20=E2=80=94=20repla?= + =?UTF-8?q?ce=20ieee80211=5Frx=5Firqsafe=20with=20ieee80211=5Frx=5Fni?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Per Phase 4 plan PR #14 + kerneldoc audit (Task #19). Six call sites +deferred per-RX-frame mac80211 dispatch via tasklet; replace with the +synchronous-from-process-context API ieee80211_rx_ni() which does its +own local_bh_disable wrap. + +Why _ni and not _list: + + Phase 4 plan originally targeted ieee80211_rx_list for batch + delivery. Mining mt76 mainline (the only driver using _list) + showed the canonical pattern requires threading a struct list_head + through the per-frame call chain. bes2600s WSM dispatcher + (wsm_handle_rx -> bes2600_rx_cb / wsm.c beacon path) sits between + the bh threads SDIO read and the mac80211 hand-off; threading a + list_head through the dispatcher is a non-trivial refactor. + ieee80211_rx_ni() is the simpler drop-in: no list management, still + removes the tasklet hop. Per-call local_bh_disable cost is trivial + vs the saved tasklet schedule. Future refactor can revisit _list + if measurements warrant. + +Sites converted: + + - ap.c:96 (bes2600_sta_add link-id rx_queue drain on AP-mode + STA add). Was inside spin_lock_bh(&ps_state_lock); + refactored to splice the queue under the lock then + deliver after unlock — _ni runs the synchronous + mac80211 RX path inline, would otherwise hold the + lock across mac80211 dispatch. splice via + skb_queue_splice_init into a local sk_buff_head. + - sta.c:1487 (deauth-frame inject in inactivity-event handler). + Not under any lock; direct conversion. + - txrx.c:1960 (early-data + pm_unsupported branch from Patch E). + - txrx.c:1967 (early-data + LINK_SOFT-not-set branch). + - txrx.c:1971 (normal RX path in bes2600_rx_cb). + - wsm.c:2415 (beacon delivery in scan-complete WSM handler). + beacon SKB ownership is preserved by the existing + skb_copy(beacon, GFP_ATOMIC) -> beacon_bkp pattern; + no lifecycle change needed. + +Mixing constraint (kerneldoc include/net/mac80211.h:5399-5430): +ieee80211_rx_ni() cannot mix with ieee80211_rx_irqsafe() for a +single hardware. All 6 sites convert atomically; no mixed state. + +Build verified clean on ohm sandbox: srcversion 619A51E61BF5479AAC146E6. + +Predicted Phase 7 delta: +5-15% over v3+D+E baseline (2.35 MB/s mean +on v3 alone; D+E single-rep was 3.22 MB/s). Modest improvement +expected from removing the tasklet schedule per RX frame. Smaller +deltas would still be a net win for upstream-cleanliness — the +kernel.org submission story benefits from not using _irqsafe from +process context. +--- + bes2600/ap.c | 15 +++++++++++++-- + bes2600/bes_chardev.c | 33 ++++++++++++++++++++++++++++++++- + bes2600/sta.c | 2 +- + bes2600/txrx.c | 6 +++--- + bes2600/wsm.c | 2 +- + 5 files changed, 50 insertions(+), 8 deletions(-) + +diff --git a/drivers/staging/bes2600/ap.c b/drivers/staging/bes2600/ap.c +index 8a17545..99e2da2 100644 +--- a/drivers/staging/bes2600/ap.c ++++ b/drivers/staging/bes2600/ap.c +@@ -63,8 +63,11 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct bes2600_vif *priv = cw12xx_get_vif_from_ieee80211(vif); + struct bes2600_link_entry *entry; + struct sk_buff *skb; ++ struct sk_buff_head local_drain; + struct bes2600_common *hw_priv = hw->priv; + ++ __skb_queue_head_init(&local_drain); ++ + #ifdef P2P_MULTIVIF + WARN_ON(priv->if_id == CW12XX_GENERIC_IF_ID); #endif +@@ -93,9 +96,17 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) + priv->sta_asleep_mask |= BIT(sta_priv->link_id); + entry->status = BES2600_LINK_HARD; +- while ((skb = skb_dequeue(&entry->rx_queue))) +- ieee80211_rx_irqsafe(priv->hw, skb); ++ /* ++ * Patch C2: splice the rx_queue out under the lock then deliver ++ * after unlock. ieee80211_rx_ni() runs the mac80211 RX path ++ * synchronously (formerly ieee80211_rx_irqsafe deferred to a ++ * tasklet); calling it from inside spin_lock_bh would hold the ++ * lock across mac80211's full RX dispatch. ++ */ ++ skb_queue_splice_init(&entry->rx_queue, &local_drain); + spin_unlock_bh(&priv->ps_state_lock); ++ while ((skb = __skb_dequeue(&local_drain))) ++ ieee80211_rx_ni(priv->hw, skb); + #ifdef AP_AGGREGATE_FW_FIX + hw_priv->connected_sta_cnt++; + if(hw_priv->connected_sta_cnt>1) { +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index 02dcd43..844f1d0 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -181,7 +181,7 @@ static int bes2600_switch_wifi(bool on) + return ret; + } -- atomic_add(1, &hw_priv->bh_term); -+ atomic_inc(&hw_priv->bh_term); - wake_up(&hw_priv->bh_wq); +-int bes2600_switch_bt(bool on) ++static int bes2600_switch_bt(bool on) + { + int ret = 0; + long status = 0; +@@ -234,6 +234,36 @@ int bes2600_switch_bt(bool on) + return ret; + } - flush_workqueue(hw_priv->bh_workqueue); -@@ -317,83 +316,6 @@ int wsm_release_buffer_to_fw(struct bes2600_vif *priv, int count) ++/* ++ * Re-added for danctnix's bes2600_btuart.c (a danctnix-only file) which ++ * relies on the chardev utility API for BT power switching and bus-error ++ * checks. The userspace /dev/bes2600 chardev itself is removed by the ++ * remove-chardev-user-interface series; these in-kernel helpers stay. ++ * ++ * @wifi: 1 to turn on, 0 to turn off. Otherwise, leave unchanged ++ * @bt: 1 to turn on, 0 to turn off. Otherwise, leave unchanged ++ */ ++int bes2600_chrdev_switch_subsys_glb(int wifi, int bt) ++{ ++ int ret = 0; ++ ++ switch (wifi) { ++ case 0: ret = bes2600_switch_wifi(false); break; ++ case 1: ret = bes2600_switch_wifi(true); break; ++ default: break; ++ } ++ if (ret) ++ return ret; ++ ++ switch (bt) { ++ case 0: ret = bes2600_switch_bt(false); break; ++ case 1: ret = bes2600_switch_bt(true); break; ++ default: break; ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(bes2600_chrdev_switch_subsys_glb); ++ + + + +@@ -562,6 +592,7 @@ bool bes2600_chrdev_is_bus_error(void) + + return error; + } ++EXPORT_SYMBOL_GPL(bes2600_chrdev_is_bus_error); + + void bes2600_chrdev_update_signal_mode(void) + { +diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c +index 8af8150..2b63ff2 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -1500,7 +1500,7 @@ void bes2600_event_handler(struct work_struct *work) + IEEE80211_STYPE_DEAUTH | IEEE80211_FCTL_TODS); + deauth->u.deauth.reason_code = WLAN_REASON_DEAUTH_LEAVING; + deauth->seq_ctrl = 0; +- ieee80211_rx_irqsafe(priv->hw, skb); ++ ieee80211_rx_ni(priv->hw, skb); + bes_devel(" Inactivity Deauth Frame sent for MAC SA %pM \t and DA %pM\n", deauth->sa, deauth->da); + queue_work(priv->hw_priv->workqueue, &priv->set_tim_work); + break; +diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c +index cb718ad..9074972 100644 +--- a/drivers/staging/bes2600/txrx.c ++++ b/drivers/staging/bes2600/txrx.c +@@ -1980,18 +1980,18 @@ void bes2600_rx_cb(struct bes2600_vif *priv, + * path is taken. + */ + if (hw_priv->bes_power.pm_unsupported) { +- ieee80211_rx_irqsafe(priv->hw, skb); ++ ieee80211_rx_ni(priv->hw, skb); + } else { + spin_lock_bh(&priv->ps_state_lock); + /* Double-check status with lock held */ + if (entry->status == BES2600_LINK_SOFT) + skb_queue_tail(&entry->rx_queue, skb); + else +- ieee80211_rx_irqsafe(priv->hw, skb); ++ ieee80211_rx_ni(priv->hw, skb); + spin_unlock_bh(&priv->ps_state_lock); + } + } else { +- ieee80211_rx_irqsafe(priv->hw, skb); ++ ieee80211_rx_ni(priv->hw, skb); + } + *skb_p = NULL; + +diff --git a/drivers/staging/bes2600/wsm.c b/drivers/staging/bes2600/wsm.c +index 908c965..2424181 100644 +--- a/drivers/staging/bes2600/wsm.c ++++ b/drivers/staging/bes2600/wsm.c +@@ -2412,7 +2412,7 @@ int wsm_handle_rx(struct bes2600_common *hw_priv, int id, + if (!hw_priv->beacon_bkp) + hw_priv->beacon_bkp = \ + skb_copy(hw_priv->beacon, GFP_ATOMIC); +- ieee80211_rx_irqsafe(hw_priv->hw, hw_priv->beacon); ++ ieee80211_rx_ni(hw_priv->hw, hw_priv->beacon); + hw_priv->beacon = hw_priv->beacon_bkp; + + hw_priv->beacon_bkp = NULL; +-- +2.54.0 + +From 1b5374d35bcc75e0f393e3d841288f91812eb7dc Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 8 May 2026 08:23:20 +0200 +Subject: [PATCH] =?UTF-8?q?bes2600:=20Patch=20H=20=E2=80=94=20bh.c=20hygie?= + =?UTF-8?q?ne=20cleanup=20(drop=20fossil=20blocks,=20dead=20stubs)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Per Opus structural critique §4.1 (#if 0 graveyard), §4.3 (asm +volatile("nop") placeholder), §4.4 (BUG_ON in steady-state hot +path). Pure source-tree cleanup, no functional change. + +Removed: + + 1. bh.c lines 319-395 (76-line #if 0 block) — dead helper + functions inherited from cw1200 ancestor: + bes2600_bh_read_ctrl_reg, bes2600_get_skb, bes2600_put_skb, + bes2600_device_wakeup. Compiled out for years. + + 2. bh.c lines 405-873 + line 1659 (the outer #if 0 / #else / + #endif) — 468-line cw1200-ancestor bes2600_bh() function body, + preserved verbatim alongside the active impl. Same function + name, same goto labels. Maintenance hazard removed. + + 3. bh.c done: label body — `__bes2600_irq_enable(1)` placeholder + (commented out) + `asm volatile ("nop")` filler. Both + no-ops on bes2600 silicon. + + 4. bh.c post-loop "Explicitly disable device interrupts" block + (sbus lock + __bes2600_irq_enable(0) + sbus unlock) — the + stub call wrapped in lock/unlock ceremony. Dead. + + 5. hwio.c __bes2600_irq_enable() function definition — + `int __bes2600_irq_enable(int enable) { return 0; }`. Stub. + Removed entirely. + + 6. sbus.h __bes2600_irq_enable() forward declaration. + +Replaced: + + 7. bh.c bes2600_bh outer-loop BUG_ON(hw_bufs_used > numInpChBufs) + -> WARN_ON_ONCE. The BUG_ON ran every bh-loop iteration; + tripping it on a bookkeeping bug locks the kernel up during + normal operation — the wrong response to a (recoverable) + accounting drift. WARN_ON_ONCE surfaces the issue without + taking the system down. + +Why __bes2600_irq_enable was a stub on bes2600: + + cw1200 has the same-named function (drivers/net/wireless/st/cw1200/ + hwio.c:267) that does real work — reads ST90TDS_CONFIG_REG_ID and + toggles the ST90TDS_CONF_IRQ_RDY_ENABLE bit. bes2600 inherited + the function name + signature when forked, but the bes2600 chip's + IRQ enable is managed by sdio_claim_irq + chip-side firmware, not + by a driver-side enable register. Bestechnic kept the function as + a no-op stub (return 0). Patch H removes the dead infrastructure. + +Diff scope: + + - bes2600/bh.c -578/+27 (mostly deletions) + - bes2600/hwio.c -7/+7 (stub function -> comment block) + - bes2600/sbus.h -2/+1 (declaration -> comment) + - net: -578/+28 across 3 files + +Build verification deferred — ohm offline. Pure-deletion change, +no semantic risk; the deleted code was either #if 0-gated +(never compiled) or stub-implementations (always returned 0). +--- + bes2600/bh.c | 578 ++----------------------------------------------- + bes2600/hwio.c | 11 +- + bes2600/sbus.h | 3 +- + 3 files changed, 28 insertions(+), 564 deletions(-) + +diff --git a/drivers/staging/bes2600/bh.c b/drivers/staging/bes2600/bh.c +index 61f6991..67dfad4 100644 +--- a/drivers/staging/bes2600/bh.c ++++ b/drivers/staging/bes2600/bh.c +@@ -317,83 +317,6 @@ int wsm_release_buffer_to_fw(struct bes2600_vif *priv, int count) } #endif @@ -1808,7 +6714,7 @@ index 175ab5e39..924899b1c 100644 /* Must be called from BH thraed. */ void bes2600_enable_powersave(struct bes2600_vif *priv, -@@ -403,475 +325,6 @@ void bes2600_enable_powersave(struct bes2600_vif *priv, +@@ -403,475 +326,6 @@ void bes2600_enable_powersave(struct bes2600_vif *priv, priv->powersave_enabled = enable; } @@ -2000,7 +6906,7 @@ index 175ab5e39..924899b1c 100644 - bes_devel("[BH] Device resume.\n"); - atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); - wake_up(&hw_priv->bh_evt_wq); -- atomic_add(1, &hw_priv->bh_rx); +- atomic_inc(&hw_priv->bh_rx); - continue; - } - @@ -2168,9 +7074,9 @@ index 175ab5e39..924899b1c 100644 - -#if 0 /* count is not implemented */ - if (ret > 1) -- atomic_add(1, &hw_priv->bh_tx); +- atomic_inc(&hw_priv->bh_tx); -#else -- atomic_add(1, &hw_priv->bh_tx); +- atomic_inc(&hw_priv->bh_tx); -#endif - -#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES) @@ -2284,190 +7190,7 @@ index 175ab5e39..924899b1c 100644 extern int bes2600_bh_read_ctrl_reg(struct bes2600_common *priv, u32 *ctrl_reg); -@@ -959,6 +412,119 @@ static void bes2600_bh_parse_wakeup_event(struct bes2600_common *hw_priv, struct - } - } - -+/* -+ * Direct-deliver an RX SKB into the WSM/mac80211 stack. -+ * -+ * Patch C v3 (no-relay architecture, matches cw1200): the bh thread -+ * calls bes2600_sdio_read_rx_batch which calls -+ * bes2600_sdio_extract_packets which calls THIS function per parsed -+ * SKB. No rx_queue, no sdio_rx_work, no inter-thread handoff. -+ * -+ * Single-writer-from-bh invariant on hw_priv->hw_bufs_used, -+ * hw_priv->hw_bufs_used_vif[] and hw_priv->wsm_tx_pending[] is -+ * preserved BY CONSTRUCTION — there is now only one writer (the bh -+ * thread itself), same as cw1200's design. No atomic_t conversion -+ * needed. -+ * -+ * Contract: -+ * - process context, sleepable. wsm_handle_rx (wsm.c, EXPORT_SYMBOL) -+ * acquires wsm_cmd.lock and may sleep on wait_event_timeout. -+ * - caller holds no bes2600 spinlock. bes2600_sdio_unlock(self) is -+ * called inside read_rx_batch before extract_packets is invoked. -+ * - SKB ownership: function frees on every path (success + error). -+ * - No need to wake the bh thread on TX-confirm — we ARE the bh -+ * thread; tx_burst is signalled by returning *tx_out = 1 to the -+ * caller (bh_rx_helper), which propagates it to bh's outer loop. -+ */ -+int bes2600_bh_handle_rx_skb(struct bes2600_common *priv, struct sk_buff *skb) -+{ -+ struct wsm_hdr *wsm; -+ size_t wsm_len; -+ u16 wsm_id; -+ u8 wsm_seq; -+ int tx = 0; -+ u32 confirm_label = 0x0; -+ -+ if (!skb) -+ return 0; -+ -+ wsm = (struct wsm_hdr *)skb->data; -+ wsm_len = __le16_to_cpu(wsm->len); -+ if (WARN_ON(wsm_len > skb->len)) { -+ bes_err("wsm_len err %d %d\n", (int)wsm_len, (int)skb->len); -+ dev_kfree_skb(skb); -+ return -1; -+ } -+ -+ if (priv->wsm_enable_wsm_dumps) -+ print_hex_dump(KERN_DEBUG, "<-- ", DUMP_PREFIX_NONE, 16, 1, -+ skb->data, wsm_len, false); -+ -+ wsm_id = __le16_to_cpu(wsm->id) & 0xFFF; -+ wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7; -+ bes_devel("bes2600_bh_handle_rx_skb wsm_id:0x%04x seq:%d\n", -+ wsm_id, wsm_seq); -+ -+ skb_trim(skb, wsm_len); -+ -+ if (wsm_id == 0x0800) { -+ wsm_handle_exception(priv, -+ &skb->data[sizeof(*wsm)], -+ wsm_len - sizeof(*wsm)); -+ bes_err("wsm exception\n"); -+ dev_kfree_skb(skb); -+ return -1; -+ } else if ((wsm_seq != priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)])) { -+ bes_err("seq error! %u. %u. 0x%x.", wsm_seq, -+ priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)], wsm_id); -+ dev_kfree_skb(skb); -+ return -1; -+ } -+ -+ bes2600_bh_parse_wakeup_event(priv, skb); -+ -+ priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)] = (wsm_seq + 1) & 7; -+ -+ if (IS_DRIVER_TO_MCU_CMD(wsm_id)) -+ confirm_label = __le32_to_cpu(((struct wsm_mcu_hdr *)wsm)->handle_label); -+ -+ if (WSM_CONFIRM_CONDITION(wsm_id, confirm_label)) { -+ int rc = wsm_release_tx_buffer(priv, 1); -+ bes2600_bh_dec_pending_count(priv, WSM_TXRX_SEQ_IDX(wsm->id)); -+ -+ if (rc < 0) { -+ bes_err("wsm_release_tx_buffer failed: %d\n", rc); -+ dev_kfree_skb(skb); -+ return rc; -+ } else if (rc > 0) { -+ tx = 1; -+ } -+ } -+ -+ /* wsm_handle_rx takes care of SKB lifetime: zeroes *skb_p if consumed. */ -+ if (wsm_handle_rx(priv, wsm_id, wsm, &skb)) { -+ bes_err("wsm_handle_rx failed (id=0x%04x)\n", wsm_id); -+ if (skb) -+ dev_kfree_skb(skb); -+ return -1; -+ } -+ -+ if (skb) -+ dev_kfree_skb(skb); -+ -+ /* -+ * Signal "tx side has new headroom" via atomic so the bh outer -+ * loop's wait_event predicate notices on its next wait. No -+ * cross-thread wake needed because we are the bh thread; the -+ * outer loop will pick this up after read_rx_batch returns. -+ */ -+ if (tx) -+ atomic_inc(&priv->bh_tx); -+ -+ return 0; -+} -+EXPORT_SYMBOL(bes2600_bh_handle_rx_skb); -+ - static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx) - { - struct sk_buff *skb = NULL; -@@ -970,10 +536,18 @@ static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx) - u32 confirm_label = 0x0; /* wsm to mcu cmd cnfirm label */ - - #if defined(BES_SDIO_RX_MULTIPLE_ENABLE) -- skb = (struct sk_buff *)priv->sbus_ops->pipe_read(priv->sbus_priv); -- if (!skb) -- return 0; -- rx = 1; // always consider rx pipe not empty -+ /* -+ * Patch C v3: the bh thread does the SDIO read inline via -+ * sbus_ops->bus_rx_batch. bes2600_sdio_read_rx_batch reads the -+ * multi-RX coalesced frames out of the chip and delivers each -+ * one inline via bes2600_bh_handle_rx_skb (no rx_queue, no -+ * pipe_read, no inter-thread handoff). Return value: 0 on -+ * success (bh outer loop will check whether to continue), -+ * negative on read error. -+ */ -+ if (priv->sbus_ops->bus_rx_batch) -+ return priv->sbus_ops->bus_rx_batch(priv->sbus_priv); -+ return 0; - #else - u32 ctrl_reg = 0; - size_t read_len = 0; -@@ -1135,7 +709,7 @@ static int bes2600_bh_tx_helper(struct bes2600_common *hw_priv, - tx_len += 4; - #endif - -- atomic_add(1, &hw_priv->bh_tx); -+ atomic_inc(&hw_priv->bh_tx); - - tx_len = hw_priv->sbus_ops->align_size( - hw_priv->sbus_priv, tx_len); -@@ -1260,8 +834,6 @@ int bes2600_bh_sw_process(struct bes2600_common *hw_priv, - delta_time = jiffies + ((unsigned long)0xffffffff - timestamp); - else - delta_time = jiffies - timestamp; -- bes2600_add_tx_delta_time(delta_time); -- bes2600_add_tx_ac_delta_time(queue_id, delta_time); - - if (bes2600_need_retry_type(skb, tx_confirm->status) == 0) - return -1; -@@ -1270,12 +842,8 @@ int bes2600_bh_sw_process(struct bes2600_common *hw_priv, - return -1; - - if (txpriv->retry_count < CW1200_MAX_SW_RETRY_CNT ) { -- struct bes2600_vif *priv = -- __cw12xx_hwpriv_to_vifpriv(hw_priv, txpriv->if_id); - txpriv->retry_count++; - -- bes2600_tx_status(priv,skb); -- - bes2600_pwr_set_busy_event_with_timeout_async( - hw_priv, BES_PWR_LOCK_ON_TX, BES_PWR_EVENT_TX_TIMEOUT); - -@@ -1442,7 +1010,7 @@ static int bes2600_bh(void *arg) - bes_devel("[BH] Device resume.\n"); - atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED); - wake_up(&hw_priv->bh_evt_wq); -- atomic_add(1, &hw_priv->bh_rx); -+ atomic_inc(&hw_priv->bh_rx); - goto done; - } - -@@ -1478,7 +1046,15 @@ static int bes2600_bh(void *arg) +@@ -1599,7 +1053,15 @@ static int bes2600_bh(void *arg) tx = 0; @@ -2484,7 +7207,7 @@ index 175ab5e39..924899b1c 100644 tx_burst = hw_priv->wsm_caps.numInpChBufs - hw_priv->hw_bufs_used; tx_allowed = tx_burst > 0; -@@ -1522,18 +1098,19 @@ static int bes2600_bh(void *arg) +@@ -1643,18 +1105,19 @@ static int bes2600_bh(void *arg) goto tx; done: @@ -2514,303 +7237,16 @@ index 175ab5e39..924899b1c 100644 if (!term) { bes_err("[BH] Fatal error, exiting.\n"); sdio_work_debug(hw_priv->sbus_priv); -@@ -1542,4 +1119,3 @@ static int bes2600_bh(void *arg) +@@ -1663,4 +1126,3 @@ static int bes2600_bh(void *arg) } return 0; } -#endif -diff --git a/drivers/staging/bes2600/bh.h b/drivers/staging/bes2600/bh.h -index 7be82dc58..700f2aa07 100644 ---- a/drivers/staging/bes2600/bh.h -+++ b/drivers/staging/bes2600/bh.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Device handling thread interface for mac80211 BES2600 drivers -+ * Bottom-half thread interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_BH_H -@@ -39,6 +39,15 @@ int wsm_release_vif_tx_buffer(struct bes2600_common *hw_priv, int if_id, - int bes2600_bh_sw_process(struct bes2600_common *hw_priv, - struct wsm_tx_confirm *tx_confirm); - -+/* -+ * Direct-deliver an RX SKB into the WSM/mac80211 stack from the bh thread. -+ * Called by bes2600_sdio_extract_packets per RX frame, no queueing. -+ * Process context, sleepable, caller holds no bes2600 spinlock. -+ * Function frees skb on every path. See bh.c for full contract. -+ */ -+int bes2600_bh_handle_rx_skb(struct bes2600_common *hw_priv, -+ struct sk_buff *skb); -+ - void bes2600_bh_inc_pending_count(struct bes2600_common *hw_priv, int idx); - void bes2600_bh_dec_pending_count(struct bes2600_common *hw_priv, int idx); - -diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c -index 5228b2279..0ab79c025 100644 ---- a/drivers/staging/bes2600/debug.c -+++ b/drivers/staging/bes2600/debug.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Debugging interface for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -110,17 +110,20 @@ static int bes2600_status_show_common(struct seq_file *seq, void *v) - int ba_cnt, ba_acc, ba_cnt_rx, ba_acc_rx, ba_avg = 0, ba_avg_rx = 0; - bool ba_ena; - -- spin_lock_bh(&hw_priv->ba_lock); -- ba_cnt = hw_priv->debug->ba_cnt; -- ba_acc = hw_priv->debug->ba_acc; -+ /* -+ * Patch D: ba_lock removed. hw_priv->debug->ba_* are written only -+ * by the timer callback (single writer); reading without a lock is -+ * fine for stats. ba_ena is atomic_t. -+ */ -+ ba_cnt = hw_priv->debug->ba_cnt; -+ ba_acc = hw_priv->debug->ba_acc; - ba_cnt_rx = hw_priv->debug->ba_cnt_rx; - ba_acc_rx = hw_priv->debug->ba_acc_rx; -- ba_ena = hw_priv->ba_ena; -+ ba_ena = !!atomic_read(&hw_priv->ba_ena); - if (ba_cnt) - ba_avg = ba_acc / ba_cnt; - if (ba_cnt_rx) - ba_avg_rx = ba_acc_rx / ba_cnt_rx; -- spin_unlock_bh(&hw_priv->ba_lock); - - seq_puts(seq, "BES2600 Wireless LAN driver status\n"); - seq_printf(seq, "Hardware: %d.%d\n", -@@ -542,6 +545,10 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v) - priv->listening ? " (listening)" : ""); - seq_printf(seq, "Assoc: %s\n", - bes2600_debug_join_status[priv->join_status]); -+ seq_printf(seq, "DecryptStormRecoveries: %u\n", -+ priv->decrypt_storm_recoveries); -+ seq_printf(seq, "ConnectionLossStormRecoveries: %u\n", -+ priv->connection_loss_storm_recoveries); - if (priv->rx_filter.promiscuous) - seq_puts(seq, "Filter: promisc\n"); - else if (priv->rx_filter.fcs) -diff --git a/drivers/staging/bes2600/debug.h b/drivers/staging/bes2600/debug.h -index 371457755..5914ffc6e 100644 ---- a/drivers/staging/bes2600/debug.h -+++ b/drivers/staging/bes2600/debug.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * DebugFS code for BES2600 mac80211 driver -+ * Debugging interface for BES2600 mac80211 driver - * -- * Copyright (c) 2011, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_DEBUG_H_INCLUDED -diff --git a/drivers/staging/bes2600/epta_coex.c b/drivers/staging/bes2600/epta_coex.c -index dfdf8e70a..3ed76f1ff 100644 ---- a/drivers/staging/bes2600/epta_coex.c -+++ b/drivers/staging/bes2600/epta_coex.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * BT/WiFi coexistence (ePTA) for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #include -diff --git a/drivers/staging/bes2600/epta_coex.h b/drivers/staging/bes2600/epta_coex.h -index bc9eed6cc..f8a5fea45 100644 ---- a/drivers/staging/bes2600/epta_coex.h -+++ b/drivers/staging/bes2600/epta_coex.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * BT/WiFi coexistence interface for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef __EPTA_COEX_H__ - #define __EPTA_COEX_H__ -diff --git a/drivers/staging/bes2600/epta_request.c b/drivers/staging/bes2600/epta_request.c -index 3b3e6af97..486f02ba7 100644 ---- a/drivers/staging/bes2600/epta_request.c -+++ b/drivers/staging/bes2600/epta_request.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * BT/WiFi coexistence request handling - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #include -diff --git a/drivers/staging/bes2600/epta_request.h b/drivers/staging/bes2600/epta_request.h -index f0217c2c8..b3d922827 100644 ---- a/drivers/staging/bes2600/epta_request.h -+++ b/drivers/staging/bes2600/epta_request.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * BT/WiFi coexistence request interface - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef EPTA_REQUEST_H - #define EPTA_REQUEST_H -diff --git a/drivers/staging/bes2600/fwio.c b/drivers/staging/bes2600/fwio.c -index 5fe6b507a..29aa2b37c 100644 ---- a/drivers/staging/bes2600/fwio.c -+++ b/drivers/staging/bes2600/fwio.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Firmware I/O for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -diff --git a/drivers/staging/bes2600/fwio.h b/drivers/staging/bes2600/fwio.h -index a4afb7ab1..adbd708f3 100644 ---- a/drivers/staging/bes2600/fwio.h -+++ b/drivers/staging/bes2600/fwio.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Firmware I/O interface for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef FWIO_H_INCLUDED - #define FWIO_H_INCLUDED -diff --git a/drivers/staging/bes2600/ht.h b/drivers/staging/bes2600/ht.h -index b5caa2919..5ac077bf2 100644 ---- a/drivers/staging/bes2600/ht.h -+++ b/drivers/staging/bes2600/ht.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * HT-related code for BES2600 driver -+ * HT capability config for BES2600 - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_HT_H_INCLUDED diff --git a/drivers/staging/bes2600/hwio.c b/drivers/staging/bes2600/hwio.c -index ea88210e8..1a63e4f00 100644 +index 0934a13..1a63e4f 100644 --- a/drivers/staging/bes2600/hwio.c +++ b/drivers/staging/bes2600/hwio.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Low-level device IO routines for BES2600 drivers -+ * Low-level device I/O for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -324,7 +324,10 @@ int bes2600_ahb_write(u32 addr, const void *buf, size_t buf_len) +@@ -324,7 +324,10 @@ out: } #endif @@ -2825,399 +7261,11 @@ index ea88210e8..1a63e4f00 100644 + * silicon manages SDIO IRQ via sdio_claim_irq and chip-side + * firmware — there is no driver-side enable register to write. + */ -diff --git a/drivers/staging/bes2600/hwio.h b/drivers/staging/bes2600/hwio.h -index b9c1858df..48e521513 100644 ---- a/drivers/staging/bes2600/hwio.h -+++ b/drivers/staging/bes2600/hwio.h -@@ -1,17 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Low-level API for mac80211 BES2600 drivers -+ * Low-level device I/O interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin - * -- * Based on: -- * UMAC BES2600 driver which is -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_HWIO_H_INCLUDED -diff --git a/drivers/staging/bes2600/itp.c b/drivers/staging/bes2600/itp.c -index e5c2958b5..7cc237c47 100644 ---- a/drivers/staging/bes2600/itp.c -+++ b/drivers/staging/bes2600/itp.c -@@ -1,13 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * mac80211 glue code for mac80211 BES2600 drivers -- * ITP code -+ * ITP (in-band test mode) for BES2600 - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -570,7 +566,7 @@ int bes2600_itp_get_tx(struct bes2600_common *priv, u8 **data, - *burst = 2; - atomic_set(&priv->bh_tx, 1); - ktime_get_ts(&itp->last_sent); -- atomic_add(1, &itp->awaiting_confirm); -+ atomic_inc(&itp->awaiting_confirm); - spin_unlock_bh(&itp->tx_lock); - return 1; - -diff --git a/drivers/staging/bes2600/itp.h b/drivers/staging/bes2600/itp.h -index 5cfba4689..bec364788 100644 ---- a/drivers/staging/bes2600/itp.h -+++ b/drivers/staging/bes2600/itp.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * ITP code for BES2600 mac80211 driver -+ * ITP interface for BES2600 - * -- * Copyright (c) 2011, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_ITP_H_INCLUDED -diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c -index 3b0b7a3d7..5fd663e2c 100644 ---- a/drivers/staging/bes2600/main.c -+++ b/drivers/staging/bes2600/main.c -@@ -1,12 +1,18 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Main entry/init for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. -+ * -+ * Based on the mac80211 Prism54 code, which is -+ * Copyright (c) 2006, Michael Wu -+ * -+ * Based on the islsm (softmac prism54) driver, which is -+ * Copyright 2004-2006 Jean-Baptiste Note , et al. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -32,7 +38,6 @@ - #include "pm.h" - #include "bes2600_factory.h" - #include "bes_chardev.h" --#include "txrx_opt.h" - - MODULE_AUTHOR("Dmitry Tarnyagin "); - MODULE_DESCRIPTION("Softmac BES2600 common code"); -@@ -199,11 +204,7 @@ static const struct ieee80211_iface_limit bes2600_if_limits[] = { - BIT(NL80211_IFTYPE_P2P_CLIENT) | - BIT(NL80211_IFTYPE_P2P_GO) }, - #ifdef P2P_MULTIVIF -- /* -- * HACK: Disable P2P_DEVICE implementation for BES2600 -- * as the code is a little buggy. -- */ -- //{ .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) }, -+ { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) }, - #endif - }; - -@@ -489,17 +490,20 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) - spin_lock_init(&hw_priv->rtsvalue_lock); - INIT_WORK(&hw_priv->dynamic_opt_txrx_work, bes2600_dynamic_opt_txrx_work); - INIT_WORK(&hw_priv->tx_policy_upload_work, tx_policy_upload_work); -+ INIT_WORK(&hw_priv->connection_loss_storm_recover_work, -+ bes2600_connection_loss_storm_recover); - spin_lock_init(&hw_priv->event_queue_lock); - INIT_LIST_HEAD(&hw_priv->event_queue); - INIT_WORK(&hw_priv->event_handler, bes2600_event_handler); - INIT_WORK(&hw_priv->ba_work, bes2600_ba_work); -- spin_lock_init(&hw_priv->ba_lock); -+ /* Patch D: ba_lock removed; ba_acc/ba_cnt/etc are atomic_t. */ - timer_setup(&hw_priv->ba_timer, bes2600_ba_timer, 0); - - if (unlikely(bes2600_queue_stats_init(&hw_priv->tx_queue_stats, - WLAN_LINK_ID_MAX, - bes2600_skb_dtor, - hw_priv))) { -+ destroy_workqueue(hw_priv->workqueue); - ieee80211_free_hw(hw); - return NULL; - } -@@ -511,6 +515,7 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len) - for (; i > 0; i--) - bes2600_queue_deinit(&hw_priv->tx_queue[i - 1]); - bes2600_queue_stats_deinit(&hw_priv->tx_queue_stats); -+ destroy_workqueue(hw_priv->workqueue); - ieee80211_free_hw(hw); - return NULL; - } -@@ -795,41 +800,6 @@ void bes2600_core_release(struct bes2600_common *self) - return; - } - --#if (GET_MAC_ADDR_METHOD == 2) || (GET_MAC_ADDR_METHOD == 3) /* To use macaddr and ps mode of customers */ --int access_file(char *path, char *buffer, int size, int isRead) --{ -- int ret=0; -- struct file *fp; -- mm_segment_t old_fs = get_fs(); -- -- if(isRead) -- fp = filp_open(path,O_RDONLY,S_IRUSR); -- else -- fp = filp_open(path,O_CREAT|O_WRONLY,S_IRUSR); -- -- if (IS_ERR(fp)) { -- bes_err("BES2600 : can't open %s\n", path); -- return -1; -- } -- -- if (isRead) { -- fp->f_pos = 0; -- set_fs(KERNEL_DS); -- ret = vfs_read(fp,buffer,size,&fp->f_pos); -- set_fs(old_fs); -- } else { -- fp->f_pos = 0; -- set_fs(KERNEL_DS); -- ret = vfs_write(fp,buffer,size,&fp->f_pos); -- set_fs(old_fs); -- } -- filp_close(fp,NULL); -- -- bes_info("BES2600 : access_file return code(%d)\n", ret); -- return ret; --} --#endif -- - int bes2600_wifi_start(struct bes2600_common *hw_priv) - { - int ret = 0, if_id; -diff --git a/drivers/staging/bes2600/pm.c b/drivers/staging/bes2600/pm.c -index c32c68efe..0424aae6d 100644 ---- a/drivers/staging/bes2600/pm.c -+++ b/drivers/staging/bes2600/pm.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 power management API for BES2600 drivers -+ * Power management for BES2600 mac80211 driver - * -- * Copyright (c) 2011, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -diff --git a/drivers/staging/bes2600/pm.h b/drivers/staging/bes2600/pm.h -index 0f6943ecd..ae704537e 100644 ---- a/drivers/staging/bes2600/pm.h -+++ b/drivers/staging/bes2600/pm.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 power management interface for BES2600 mac80211 drivers -+ * Power management interface for BES2600 mac80211 driver - * -- * Copyright (c) 2011, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef PM_H_INCLUDED -diff --git a/drivers/staging/bes2600/queue.c b/drivers/staging/bes2600/queue.c -index d1b407b31..5881fa91c 100644 ---- a/drivers/staging/bes2600/queue.c -+++ b/drivers/staging/bes2600/queue.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * O(1) TX queue with built-in allocator for BES2600 drivers -+ * O(1) TX queue for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -119,9 +119,10 @@ static void bes2600_queue_register_post_gc(struct list_head *gc_list, - struct bes2600_queue_item *item) - { - struct bes2600_queue_item *gc_item; -- gc_item = kmemdup(item, sizeof(struct bes2600_queue_item), -+ gc_item = kmalloc(sizeof(struct bes2600_queue_item), - GFP_ATOMIC); - BUG_ON(!gc_item); -+ memcpy(gc_item, item, sizeof(struct bes2600_queue_item)); - list_add_tail(&gc_item->head, gc_list); - } - -@@ -130,9 +131,9 @@ static void bes2600_queue_pending_record(struct list_head *pending_record_list, - { - struct bes2600_queue_item *record_item; - -- record_item = kmemdup(pending_item, sizeof(struct bes2600_queue_item), -- GFP_ATOMIC); -+ record_item = kmalloc(sizeof(struct bes2600_queue_item),GFP_ATOMIC); - BUG_ON(!record_item); -+ memcpy(record_item, pending_item, sizeof(struct bes2600_queue_item)); - record_item->skb = skb_clone(pending_item->skb, GFP_ATOMIC); - list_add_tail(&record_item->head, pending_record_list); - } -@@ -217,7 +218,7 @@ int bes2600_queue_stats_init(struct bes2600_queue_stats *stats, - spin_lock_init(&stats->lock); - init_waitqueue_head(&stats->wait_link_id_empty); - for (i = 0; i < CW12XX_MAX_VIFS; i++) { -- stats->link_map_cache[i] = kcalloc(map_capacity, sizeof(int), -+ stats->link_map_cache[i] = kzalloc(map_capacity * sizeof(int), - GFP_KERNEL); - if (!stats->link_map_cache[i]) { - for (; i >= 0; i--) -@@ -248,14 +249,14 @@ int bes2600_queue_init(struct bes2600_queue *queue, - queue->queue_all_lock = false; - spin_lock_init(&queue->lock); - timer_setup(&queue->gc, bes2600_queue_gc, 0); -- queue->pool = kcalloc(capacity, sizeof(struct bes2600_queue_item), -+ queue->pool = kzalloc(sizeof(struct bes2600_queue_item) * capacity, - GFP_KERNEL); - if (!queue->pool) - return -ENOMEM; - - for (i = 0; i < CW12XX_MAX_VIFS; i++) { - queue->link_map_cache[i] = -- kcalloc(stats->map_capacity, sizeof(int), -+ kzalloc(stats->map_capacity * sizeof(int), - GFP_KERNEL); - if (!queue->link_map_cache[i]) { - for (; i >= 0; i--) -@@ -409,6 +410,7 @@ int bes2600_queue_put(struct bes2600_queue *queue, - struct timespec64 tmval; - #endif /*CONFIG_BES2600_TESTMODE*/ - -+ LIST_HEAD(gc_list); - struct bes2600_queue_stats *stats = queue->stats; - /* TODO:COMBO: Add interface ID info to queue item */ - -diff --git a/drivers/staging/bes2600/queue.h b/drivers/staging/bes2600/queue.h -index a5395b633..94874dd27 100644 ---- a/drivers/staging/bes2600/queue.h -+++ b/drivers/staging/bes2600/queue.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * O(1) TX queue with built-in allocator for BES2600 drivers -+ * O(1) TX queue interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_QUEUE_H_INCLUDED diff --git a/drivers/staging/bes2600/sbus.h b/drivers/staging/bes2600/sbus.h -index 1f2c0cda7..41930847f 100644 +index 43c2dae..4193084 100644 --- a/drivers/staging/bes2600/sbus.h +++ b/drivers/staging/bes2600/sbus.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Common sbus abstraction layer interface for bes2600 wireless driver -+ * Bus abstraction interface for BES2600 -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. -+ * Replaces hwbus.h from drivers/net/wireless/st/cw1200/ -+ * Copyright (c) 2010, ST-Ericsson - * -- * Copyright (c) 2010, Bestechnic -- * Author: - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_SBUS_H -@@ -75,11 +75,26 @@ struct sbus_ops { - void (*halt_device)(struct sbus_priv *self); - bool (*wakeup_source)(struct sbus_priv *self); - int (*reboot)(struct sbus_priv *self); -+ /* -+ * Force the host bus to re-detect and re-probe the chip. Called -+ * from the firmware-wedge recovery path when power_switch() has no -+ * effective chip-reset signal of its own (e.g. PineTab2, where the -+ * wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 node). -+ * Returns 0 on success or a negative errno. -+ */ -+ int (*bus_reset)(struct sbus_priv *self); -+ /* -+ * Read a batch of RX frames inline from the bus and deliver each -+ * one via bes2600_bh_handle_rx_skb(). Called from the bh thread -+ * (process context, sleepable). Replaces the -+ * sdio_rx_work + rx_queue + pipe_read relay (Patch C v3, 2026). -+ * Returns 0 on success, negative on read error. -+ */ -+ int (*bus_rx_batch)(struct sbus_priv *self); - }; +@@ -95,7 +95,6 @@ struct sbus_ops { void bes2600_irq_handler(struct bes2600_common *priv); @@ -3226,1099 +7274,8 @@ index 1f2c0cda7..41930847f 100644 +/* Patch H: __bes2600_irq_enable removed (was a stub). */ #endif /* BES2600_SBUS_H */ -diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c -index 3bfa53564..fb1d29861 100644 ---- a/drivers/staging/bes2600/scan.c -+++ b/drivers/staging/bes2600/scan.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Scan implementation for BES2600 mac80211 drivers -+ * Scan implementation for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -14,11 +14,63 @@ - #include "scan.h" - #include "sta.h" - #include "pm.h" -+#include "epta_coex.h" - #include "epta_request.h" - #include "bes_pwr.h" - -+/* -+ * After this many consecutive WSM scan rejections from firmware, stop -+ * issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state -+ * that's rejecting them (coex window, firmware-internal busy) clear. -+ * -+ * The backoff has to be at least as long as the natural mac80211 scan- -+ * retry cadence, otherwise the next attempt lands outside the window -+ * and bypasses the defer guard. Observed in the wild on PineTab2: -+ * roam-evaluation bursts at ~12 s cadence, idle background scans at -+ * ~5 min cadence. 30 s catches the burst and leaves the slow case -+ * alone (the firmware-policy state has had minutes to clear by then -+ * anyway). -+ */ -+#define BES2600_SCAN_REJECT_THRESHOLD 3 -+#define BES2600_SCAN_BACKOFF_JIFFIES (30 * HZ) -+ - static void bes2600_scan_restart_delayed(struct bes2600_vif *priv); - -+/* -+ * Decide whether to skip sending the next WSM scan command without -+ * bothering the firmware. Two triggers: -+ * -+ * 1. BT A2DP is streaming in non-FDD coex mode. The firmware is -+ * known to reject scan requests during that window; short- -+ * circuiting here saves a WSM round-trip and avoids the -+ * wsm_generic_confirm / scan_work warning cascade that follows. -+ * -+ * 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive -+ * rejections on recent scan attempts and the backoff window has -+ * not yet elapsed. Whatever was rejecting them is likely still -+ * rejecting them; give it time. If the backoff has elapsed without -+ * a fresh reject refreshing it, the burst is over and we reset the -+ * count so an isolated reject doesn't immediately re-trip. -+ * -+ * Returns true if the caller should abandon the scan iteration. -+ */ -+static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv) -+{ -+#ifdef WIFI_BT_COEXIST_EPTA_ENABLE -+ if (!coex_is_fdd_mode() && coex_is_bt_a2dp()) -+ return true; -+#endif -+ -+ if (time_after(jiffies, hw_priv->scan.backoff_until)) -+ hw_priv->scan.reject_count = 0; -+ -+ if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD && -+ time_before(jiffies, hw_priv->scan.backoff_until)) -+ return true; -+ -+ return false; -+} -+ - #ifdef CONFIG_BES2600_TESTMODE - static int bes2600_advance_scan_start(struct bes2600_common *hw_priv) - { -@@ -205,18 +257,21 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, - - 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, - req->ie_len); -- if (!frame.skb) -+ if (!frame.skb) { -+ up(&hw_priv->conf_lock); -+ up(&hw_priv->scan.lock); - return -ENOMEM; -+ } - - if (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) { - int ret; - //if (priv->if_id == 0) -@@ -234,9 +289,9 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, - } - #endif - if (ret) { -+ dev_kfree_skb(frame.skb); - up(&hw_priv->conf_lock); - up(&hw_priv->scan.lock); -- dev_kfree_skb(frame.skb); - return ret; - } - } -@@ -266,10 +321,10 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, - ++hw_priv->scan.n_ssids; - } - -- up(&hw_priv->conf_lock); -- - if (frame.skb) - dev_kfree_skb(frame.skb); -+ -+ up(&hw_priv->conf_lock); - #ifdef WIFI_BT_COEXIST_EPTA_ENABLE - bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING); - #endif -@@ -310,14 +365,18 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, - if (req->n_ssids > hw->wiphy->max_scan_ssids) - return -EINVAL; - -+ /* 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, - req->ie_len); -- if (!frame.skb) -+ if (!frame.skb) { -+ up(&hw_priv->conf_lock); -+ up(&hw_priv->scan.lock); - return -ENOMEM; -+ } - -- /* will be unlocked in bes2600_scan_work() */ -- down(&hw_priv->scan.lock); -- down(&hw_priv->conf_lock); - if (frame.skb) { - int ret; - if (priv->if_id == 0) -@@ -328,9 +387,9 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, - ret = wsm_set_probe_responder(priv, true); - } - if (ret) { -+ dev_kfree_skb(frame.skb); - up(&hw_priv->conf_lock); - up(&hw_priv->scan.lock); -- dev_kfree_skb(frame.skb); - return ret; - } - } -@@ -362,10 +421,10 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw, - } - } - -- up(&hw_priv->conf_lock); -- - if (frame.skb) - dev_kfree_skb(frame.skb); -+ -+ up(&hw_priv->conf_lock); - queue_work(hw_priv->workqueue, &hw_priv->scan.swork); - wiphy_warn(hw->wiphy, "<--[SCAN] Scheduled scan request.\n"); - return 0; -@@ -533,10 +592,10 @@ void bes2600_scan_work(struct work_struct *work) - "[SCAN] Scan failed (%d).\n", - hw_priv->scan.status); - else if (hw_priv->scan.req) -- wiphy_dbg(priv->hw->wiphy, -+ wiphy_info(priv->hw->wiphy, - "[SCAN] Scan completed.\n"); - else -- wiphy_dbg(priv->hw->wiphy, -+ wiphy_info(priv->hw->wiphy, - "[SCAN] Scan canceled.\n"); - - #ifdef WIFI_BT_COEXIST_EPTA_ENABLE -@@ -621,9 +680,8 @@ void bes2600_scan_work(struct work_struct *work) - scan.scanType = WSM_SCAN_TYPE_BACKGROUND; - scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND; - } -- scan.ch = kcalloc((it - hw_priv->scan.curr), -- sizeof(struct wsm_scan_ch), -- GFP_KERNEL); -+ scan.ch = kzalloc((it - hw_priv->scan.curr) * -+ sizeof(struct wsm_scan_ch), GFP_KERNEL); - if (!scan.ch) { - hw_priv->scan.status = -ENOMEM; - goto fail; -@@ -703,10 +761,29 @@ void bes2600_scan_work(struct work_struct *work) - wsm_unlock_tx(hw_priv); - } else - #endif -+ { -+ if (bes2600_scan_should_defer(hw_priv)) { -+ hw_priv->scan.status = -EBUSY; -+ hw_priv->scan.reject_count++; -+ hw_priv->scan.backoff_until = -+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES; -+ wiphy_dbg(priv->hw->wiphy, -+ "[SCAN] deferred (coex/backoff, reject_count=%u)\n", -+ hw_priv->scan.reject_count); -+ kfree(scan.ch); -+ goto fail; -+ } - hw_priv->scan.status = bes2600_scan_start(priv, &scan); -+ } - kfree(scan.ch); -- if (WARN_ON(hw_priv->scan.status)) -+ if (hw_priv->scan.status) { -+ hw_priv->scan.reject_count++; -+ hw_priv->scan.backoff_until = -+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES; -+ /* Lower callers already logged the reason at wiphy_warn. */ - goto fail; -+ } -+ hw_priv->scan.reject_count = 0; - hw_priv->scan.curr = it; - } - up(&hw_priv->conf_lock); -@@ -906,7 +983,7 @@ void bes2600_scan_complete_cb(struct bes2600_common *hw_priv, - // recover EPTA timer after scan wsm msg complete, in case of epta state error - // bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING_COMP); - #endif -- wiphy_dbg(hw_priv->hw->wiphy, "bes2600_scan_complete_cb status: %u", arg->status); -+ wiphy_info(hw_priv->hw->wiphy, "bes2600_scan_complete_cb status: %u", arg->status); - - if(hw_priv->scan.status == -ETIMEDOUT) - wiphy_warn(hw_priv->hw->wiphy, -diff --git a/drivers/staging/bes2600/scan.h b/drivers/staging/bes2600/scan.h -index e50fa363b..295be1850 100644 ---- a/drivers/staging/bes2600/scan.h -+++ b/drivers/staging/bes2600/scan.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Scan interface for BES2600 mac80211 drivers -+ * Scan interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef SCAN_H_INCLUDED -@@ -42,6 +42,17 @@ struct bes2600_scan { - struct delayed_work probe_work; - int direct_probe; - u8 if_id; -+ /* -+ * Track consecutive firmware-side WSM scan rejections so we can -+ * back off briefly instead of re-issuing the same scan on every -+ * mac80211 background-scan tick. Firmware returns WSM status != 0 -+ * for a handful of transient conditions (BT A2DP active in non- -+ * FDD coex, firmware-internal busy windows) and keeps rejecting -+ * until the state clears; retrying at full cadence just floods -+ * dmesg. -+ */ -+ unsigned int reject_count; -+ unsigned long backoff_until; - }; - - int bes2600_hw_scan(struct ieee80211_hw *hw, -diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c -index ca1c77c5f..e8c085918 100644 ---- a/drivers/staging/bes2600/sta.c -+++ b/drivers/staging/bes2600/sta.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 STA API for BES2600 drivers -+ * Mac80211 STA API for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -42,8 +42,6 @@ - #include "bes2600_factory.h" - #endif - --#include "txrx_opt.h" -- - #define WEP_ENCRYPT_HDR_SIZE 4 - #define WEP_ENCRYPT_TAIL_SIZE 4 - #define WPA_ENCRYPT_HDR_SIZE 8 -@@ -268,6 +266,7 @@ void bes2600_stop(struct ieee80211_hw *dev, bool suspend) - cancel_work_sync(&hw_priv->coex_work); - coex_stop(hw_priv); - #endif -+ cancel_work_sync(&hw_priv->connection_loss_storm_recover_work); - - bes2600_wifi_stop(hw_priv); - -@@ -377,23 +376,9 @@ void bes2600_remove_interface(struct ieee80211_hw *dev, - atomic_set(&priv->enabled, 0); - down(&hw_priv->scan.lock); - down(&hw_priv->conf_lock); -- -- /* -- * There's a chance remove_interface will run again on the same -- * (already removed) interface. -- * -- * Currently this only happens when NetworkManager creates a P2P_DEVICE -- * alongside a STA. -- * -- * But there can be other cases where this may run as well. So if that -- * happens, let's throw a warning and decrease the vifs count by one. -- */ -- if (WARN_ON(!__cw12xx_hwpriv_to_vifpriv(hw_priv, priv->if_id))) { -+ if (!__cw12xx_hwpriv_to_vifpriv(hw_priv, priv->if_id)) { - bes_devel(" !!! %s: interface addr %pM already removed\n", - __func__, vif->addr); -- -- atomic_dec(&hw_priv->num_vifs); -- - up(&hw_priv->conf_lock); - up(&hw_priv->scan.lock); - return; -@@ -464,6 +449,7 @@ void bes2600_remove_interface(struct ieee80211_hw *dev, - cancel_delayed_work_sync(&priv->join_timeout); - cancel_delayed_work_sync(&priv->set_cts_work); - cancel_delayed_work_sync(&priv->pending_offchanneltx_work); -+ cancel_work_sync(&priv->decrypt_storm_recover_work); - - timer_delete_sync(&priv->mcast_timeout); - /* TODO:COMBO: May be reset of these variables "delayed_link_loss and -@@ -1498,7 +1484,7 @@ void bes2600_event_handler(struct work_struct *work) - IEEE80211_STYPE_DEAUTH | IEEE80211_FCTL_TODS); - deauth->u.deauth.reason_code = WLAN_REASON_DEAUTH_LEAVING; - deauth->seq_ctrl = 0; -- ieee80211_rx_irqsafe(priv->hw, skb); -+ ieee80211_rx_ni(priv->hw, skb); - bes_devel(" Inactivity Deauth Frame sent for MAC SA %pM \t and DA %pM\n", deauth->sa, deauth->da); - queue_work(priv->hw_priv->workqueue, &priv->set_tim_work); - break; -@@ -1674,6 +1660,70 @@ void bes2600_bss_loss_work(struct work_struct *work) - spin_unlock(&priv->bss_loss_lock); - } - -+/* -+ * Connection-loss-storm fast-recover (Trigger A). -+ * -+ * bes2600_connection_loss_work below is the driver's own decision-point -+ * to give up on a BSS (after bss-loss detection accumulates beyond -+ * tolerance) and tell mac80211 via ieee80211_connection_loss(). On the -+ * deployed pinetab2 stack a single ieee80211_connection_loss() event -+ * sometimes triggers a userspace reauth blackhole (assoc-comeback -+ * timeouts followed by AP unprotected-deauth-reason-6) that ends only -+ * via cross-channel/cross-SSID fallback and can take 80+ s. Receipts at -+ * https://git.reauktion.de/marfrit/besser, notes/phase4-2026-05-07.md. -+ * -+ * When N connection-loss decisions land within WINDOW on the same vif, -+ * skip the ieee80211_connection_loss() path and trigger a chip-level -+ * bus_reset (the c5.2-introduced bes2600_chrdev_do_bus_reset). The chip -+ * is removed and re-probed; userspace re-associates from a fresh state, -+ * dodging the assoc-comeback loop. -+ * -+ * Threshold (3 / 60 s) is chosen 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. -+ * -+ * The recover work_struct lives on bes2600_common (hw_priv) so that -+ * scheduling it does not race with vif teardown after bus_reset frees -+ * the per-vif state. -+ */ -+#define BES2600_CONNECTION_LOSS_STORM_THRESHOLD 3 -+#define BES2600_CONNECTION_LOSS_STORM_WINDOW_MS 60000 -+ -+void bes2600_connection_loss_storm_recover(struct work_struct *work) -+{ -+ bes_warn("[bes2600] connection-loss-storm fast-recover: bus_reset\n"); -+ bes2600_chrdev_trigger_bus_reset(); -+ /* -+ * After bes2600_chrdev_do_bus_reset() returns, the SDIO core has -+ * scheduled a remove + rescan; per-vif state may already be gone. -+ * Do not dereference any per-vif pointer here. -+ */ -+} -+ -+void bes2600_connection_loss_storm_init(struct bes2600_vif *priv) -+{ -+ priv->connection_loss_storm_window_start = 0; -+ priv->connection_loss_storm_count = 0; -+ priv->connection_loss_storm_recoveries = 0; -+} -+ -+bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv) -+{ -+ unsigned long now = jiffies; -+ unsigned long window = -+ msecs_to_jiffies(BES2600_CONNECTION_LOSS_STORM_WINDOW_MS); -+ -+ if (priv->connection_loss_storm_window_start == 0 || -+ time_after(now, priv->connection_loss_storm_window_start + window)) { -+ priv->connection_loss_storm_window_start = now; -+ priv->connection_loss_storm_count = 1; -+ return false; -+ } -+ -+ return ++priv->connection_loss_storm_count >= -+ BES2600_CONNECTION_LOSS_STORM_THRESHOLD; -+} -+ - void bes2600_connection_loss_work(struct work_struct *work) - { - struct bes2600_vif *priv = -@@ -1683,9 +1733,21 @@ void bes2600_connection_loss_work(struct work_struct *work) - - bes_devel("[CQM] Reporting connection loss.\n"); - bes2600_pwr_clear_busy_event(priv->hw_priv, BES_PWR_LOCK_ON_BSS_LOST); -- if(bes2600_suspend_status_get(hw_priv)) { -+ -+ if (bes2600_connection_loss_storm_account(priv)) { -+ bes_warn("[bes2600] connection-loss storm: %u in %u s, scheduling bus reset\n", -+ priv->connection_loss_storm_count, -+ BES2600_CONNECTION_LOSS_STORM_WINDOW_MS / 1000); -+ priv->connection_loss_storm_count = 0; -+ priv->connection_loss_storm_recoveries++; -+ schedule_work(&hw_priv->connection_loss_storm_recover_work); -+ /* bus_reset will tear the chip down; skip the mac80211 path. */ -+ return; -+ } -+ -+ if (bes2600_suspend_status_get(hw_priv)) - bes2600_pending_unjoin_set(hw_priv, priv->if_id); -- } else -+ else - ieee80211_connection_loss(priv->vif); - #ifdef WIFI_BT_COEXIST_EPTA_ENABLE - // set disconnected in BSS_CHANGED_ASSOC -@@ -2185,8 +2247,6 @@ void bes2600_join_work(struct work_struct *work) - wsm_unlock_tx(hw_priv); - return; - } -- -- rcu_read_lock(); - ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); - dtimie = ieee80211_bss_get_ie(bss, WLAN_EID_TIM); - if (dtimie) -@@ -2270,8 +2330,6 @@ void bes2600_join_work(struct work_struct *work) - bes2600_rate_mask_to_wsm(hw_priv, 0xFF0); - } - -- rcu_read_unlock(); -- - bes2600_pwr_set_busy_event(hw_priv, BES_PWR_LOCK_ON_JOIN); - wsm_flush_tx(hw_priv); - -@@ -2284,14 +2342,19 @@ void bes2600_join_work(struct work_struct *work) - //WARN_ON(wsm_reset(hw_priv, &reset, priv->if_id)); - WARN_ON(wsm_set_block_ack_policy(hw_priv, - 0, hw_priv->ba_tid_mask, priv->if_id)); -- spin_lock_bh(&hw_priv->ba_lock); -- hw_priv->ba_ena = false; -- hw_priv->ba_cnt = 0; -- hw_priv->ba_acc = 0; -+ /* -+ * Patch D: ba_lock removed. Disconnect-reset clears the -+ * counters and the arm flag; producers racing here cannot -+ * cause harm — at worst they re-arm the timer and bump -+ * counters that will be cleared on the next timer tick. -+ */ -+ atomic_set(&hw_priv->ba_ena, 0); -+ atomic_set(&hw_priv->ba_cnt, 0); -+ atomic_set(&hw_priv->ba_acc, 0); - hw_priv->ba_hist = 0; -- hw_priv->ba_cnt_rx = 0; -- hw_priv->ba_acc_rx = 0; -- spin_unlock_bh(&hw_priv->ba_lock); -+ atomic_set(&hw_priv->ba_cnt_rx, 0); -+ atomic_set(&hw_priv->ba_acc_rx, 0); -+ atomic_set(&hw_priv->ba_armed, 0); - - mgmt_policy.protectedMgmtEnable = 0; - mgmt_policy.unprotectedMgmtFramesAllowed = 1; -@@ -2571,10 +2634,11 @@ void bes2600_ba_work(struct work_struct *work) - return;*/ - - bes_devel("BA work****\n"); -- spin_lock_bh(&hw_priv->ba_lock); --// tx_ba_tid_mask = hw_priv->ba_ena ? hw_priv->ba_tid_mask : 0; -+ /* -+ * Patch D: ba_lock removed. ba_tid_mask is u8 set once at init -+ * (main.c); reading it without a lock is fine. -+ */ - tx_ba_tid_mask = hw_priv->ba_tid_mask; -- spin_unlock_bh(&hw_priv->ba_lock); - - wsm_lock_tx(hw_priv); - -@@ -2587,37 +2651,49 @@ void bes2600_ba_work(struct work_struct *work) - void bes2600_ba_timer(struct timer_list *t) - { - bool ba_ena; -+ int cnt, acc, cnt_rx, acc_rx; - struct bes2600_common *hw_priv = timer_container_of(hw_priv, t, ba_timer); - -- spin_lock_bh(&hw_priv->ba_lock); -- bes2600_debug_ba(hw_priv, hw_priv->ba_cnt, hw_priv->ba_acc, -- hw_priv->ba_cnt_rx, hw_priv->ba_acc_rx); -+ /* -+ * Patch D: ba_lock removed. Snapshot atomic counters into locals -+ * for the predicate evaluation; producers may race incrementing -+ * after the snapshot but the resulting decision is approximate -+ * which the policy already tolerates (next timer tick re-evaluates). -+ */ -+ cnt = atomic_read(&hw_priv->ba_cnt); -+ acc = atomic_read(&hw_priv->ba_acc); -+ cnt_rx = atomic_read(&hw_priv->ba_cnt_rx); -+ acc_rx = atomic_read(&hw_priv->ba_acc_rx); -+ -+ bes2600_debug_ba(hw_priv, cnt, acc, cnt_rx, acc_rx); - - if (atomic_read(&hw_priv->scan.in_progress)) { -- hw_priv->ba_cnt = 0; -- hw_priv->ba_acc = 0; -- hw_priv->ba_cnt_rx = 0; -- hw_priv->ba_acc_rx = 0; -- goto skip_statistic_update; -+ atomic_set(&hw_priv->ba_cnt, 0); -+ atomic_set(&hw_priv->ba_acc, 0); -+ atomic_set(&hw_priv->ba_cnt_rx, 0); -+ atomic_set(&hw_priv->ba_acc_rx, 0); -+ atomic_set(&hw_priv->ba_armed, 0); -+ return; - } - -- if (hw_priv->ba_cnt >= BES2600_BLOCK_ACK_CNT && -- (hw_priv->ba_acc / hw_priv->ba_cnt >= BES2600_BLOCK_ACK_THLD || -- (hw_priv->ba_cnt_rx >= BES2600_BLOCK_ACK_CNT && -- hw_priv->ba_acc_rx / hw_priv->ba_cnt_rx >= -+ if (cnt >= BES2600_BLOCK_ACK_CNT && -+ (acc / cnt >= BES2600_BLOCK_ACK_THLD || -+ (cnt_rx >= BES2600_BLOCK_ACK_CNT && -+ acc_rx / cnt_rx >= - BES2600_BLOCK_ACK_THLD))) - ba_ena = true; - else - ba_ena = false; - -- hw_priv->ba_cnt = 0; -- hw_priv->ba_acc = 0; -- hw_priv->ba_cnt_rx = 0; -- hw_priv->ba_acc_rx = 0; -+ atomic_set(&hw_priv->ba_cnt, 0); -+ atomic_set(&hw_priv->ba_acc, 0); -+ atomic_set(&hw_priv->ba_cnt_rx, 0); -+ atomic_set(&hw_priv->ba_acc_rx, 0); -+ atomic_set(&hw_priv->ba_armed, 0); - -- if (ba_ena != hw_priv->ba_ena) { -+ if (ba_ena != !!atomic_read(&hw_priv->ba_ena)) { - if (ba_ena || ++hw_priv->ba_hist >= BES2600_BLOCK_ACK_HIST) { -- hw_priv->ba_ena = ba_ena; -+ atomic_set(&hw_priv->ba_ena, ba_ena ? 1 : 0); - hw_priv->ba_hist = 0; - #if 0 - bes_devel("[STA] %s block ACK:\n", -@@ -2627,9 +2703,6 @@ void bes2600_ba_timer(struct timer_list *t) - } - } else if (hw_priv->ba_hist) - --hw_priv->ba_hist; -- --skip_statistic_update: -- spin_unlock_bh(&hw_priv->ba_lock); - } - - int bes2600_vif_setup(struct bes2600_vif *priv) -@@ -2639,6 +2712,8 @@ int bes2600_vif_setup(struct bes2600_vif *priv) - - /* Setup per vif workitems and locks */ - spin_lock_init(&priv->vif_lock); -+ bes2600_decrypt_storm_init(priv); -+ bes2600_connection_loss_storm_init(priv); - INIT_WORK(&priv->join_work, bes2600_join_work); - INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout); - INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work); -@@ -2809,7 +2884,6 @@ void bes2600_dynamic_opt_txrx_work(struct work_struct *work) - if (priv != NULL && priv->join_status > BES2600_JOIN_STATUS_MONITOR) { - multivif_connected = true; - } -- bes2600_txrx_opt_multivif_connected_handler(hw_priv, multivif_connected); - } - - -@@ -3654,7 +3728,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw, - * - * Returns: 0 on success or non zero value on failure - */ --int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) -+static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) - { - struct bes_msg_start_stop_tsm *start_stop_tsm = - (struct bes_msg_start_stop_tsm *) data; -@@ -3684,7 +3758,7 @@ int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data) - * - * Returns: TSM parameters collected - */ --int bes2600_get_tsm_params(struct ieee80211_hw *hw) -+static int bes2600_get_tsm_params(struct ieee80211_hw *hw) - { - struct bes2600_common *hw_priv = hw->priv; - struct bes_tsm_stats tsm_stats; -@@ -3724,7 +3798,7 @@ int bes2600_get_tsm_params(struct ieee80211_hw *hw) - * - * Returns: Returns the last measured roam delay - */ --int bes2600_get_roam_delay(struct ieee80211_hw *hw) -+static int bes2600_get_roam_delay(struct ieee80211_hw *hw) - { - struct bes2600_common *hw_priv = hw->priv; - u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000; -diff --git a/drivers/staging/bes2600/sta.h b/drivers/staging/bes2600/sta.h -index 39b4b1a10..a174e04f5 100644 ---- a/drivers/staging/bes2600/sta.h -+++ b/drivers/staging/bes2600/sta.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 STA interface for BES2600 mac80211 drivers -+ * Mac80211 STA API interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include - #ifndef STA_H_INCLUDED -diff --git a/drivers/staging/bes2600/tx_loop.c b/drivers/staging/bes2600/tx_loop.c -index baab3f0c2..e6cf072d1 100644 ---- a/drivers/staging/bes2600/tx_loop.c -+++ b/drivers/staging/bes2600/tx_loop.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * Test-mode TX loopback for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #include "bes2600.h" - #include "wsm.h" -diff --git a/drivers/staging/bes2600/tx_loop.h b/drivers/staging/bes2600/tx_loop.h -index de82b302c..7f42c04b8 100644 ---- a/drivers/staging/bes2600/tx_loop.h -+++ b/drivers/staging/bes2600/tx_loop.h -@@ -1,12 +1,9 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Mac80211 driver for BES2600 device -+ * Test-mode TX loopback interface for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifndef __TX_LOOP_H__ - #define __TX_LOOP_H__ -diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c -index 017f0d89c..de521a3be 100644 ---- a/drivers/staging/bes2600/txrx.c -+++ b/drivers/staging/bes2600/txrx.c -@@ -1,12 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Datapath implementation for BES2600 mac80211 drivers -+ * Datapath implementation for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -21,11 +21,82 @@ - #include "debug.h" - #include "sta.h" - #include "sbus.h" --#include "txrx_opt.h" - #include "bes_log.h" - - #define BES2600_INVALID_RATE_ID (0xFF) - -+/* -+ * Decrypt-storm fast-recover (Trigger B). -+ * -+ * When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a -+ * burst of received frames (typically because the host's PTK or GTK -+ * has fallen out of sync with the AP), the AP eventually concludes that -+ * the STA is not authenticated and emits an unprotected deauth-reason-6 -+ * ("Class 2 frame received from non-authenticated station"). On the -+ * deployed pinetab2 + bes2600 stack this AP-initiated deauth has been -+ * observed to leave the link blackholed for up to 109 s before -+ * userspace finds a different SSID/channel to recover on. (Receipts at -+ * https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.) -+ * -+ * Recovery here pre-empts the AP: when we see THRESHOLD decrypt -+ * failures within WINDOW, we ask mac80211 for a clean reassoc via -+ * ieee80211_connection_loss(), which causes immediate disassociation -+ * and lets userspace auto-reconnect with fresh keys. -+ * -+ * mac80211 contract: ieee80211_connection_loss() may be called -+ * regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate -+ * disassociation without driver-side recovery attempts. See -+ * include/net/mac80211.h for the canonical doc-comment. -+ * -+ * The threshold is set well above the steady-state per-vif -+ * decrypt-fail rate observed in measurement (~1/min even under -+ * sustained 1 MB/s load), so a true storm is required to trip it. -+ */ -+#define BES2600_DECRYPT_STORM_THRESHOLD 5 -+#define BES2600_DECRYPT_STORM_WINDOW_MS 5000 -+ -+static void bes2600_decrypt_storm_recover_work(struct work_struct *work) -+{ -+ struct bes2600_vif *priv = container_of(work, struct bes2600_vif, -+ decrypt_storm_recover_work); -+ -+ if (!priv->vif) -+ return; -+ -+ bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n"); -+ ieee80211_connection_loss(priv->vif); -+ priv->decrypt_storm_recoveries++; -+} -+ -+void bes2600_decrypt_storm_init(struct bes2600_vif *priv) -+{ -+ INIT_WORK(&priv->decrypt_storm_recover_work, -+ bes2600_decrypt_storm_recover_work); -+ priv->decrypt_storm_window_start = 0; -+ priv->decrypt_storm_count = 0; -+ priv->decrypt_storm_recoveries = 0; -+} -+ -+void bes2600_decrypt_storm_account(struct bes2600_vif *priv) -+{ -+ unsigned long now = jiffies; -+ unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS); -+ -+ if (priv->decrypt_storm_window_start == 0 || -+ time_after(now, priv->decrypt_storm_window_start + window)) { -+ priv->decrypt_storm_window_start = now; -+ priv->decrypt_storm_count = 1; -+ return; -+ } -+ -+ if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) { -+ priv->decrypt_storm_count = 0; -+ /* Skew the window so we don't re-fire on the same storm. */ -+ priv->decrypt_storm_window_start = now + window; -+ schedule_work(&priv->decrypt_storm_recover_work); -+ } -+} -+ - #ifdef CONFIG_BES2600_TESTMODE - #include "bes_nl80211_testmode_msg.h" - #endif /* CONFIG_BES2600_TESTMODE */ -@@ -924,14 +995,18 @@ bes2600_tx_h_ba_stat(struct bes2600_vif *priv, - if (!ieee80211_is_data(t->hdr->frame_control)) - return; - -- spin_lock_bh(&hw_priv->ba_lock); -- hw_priv->ba_acc += t->skb->len - t->hdrlen; -- if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) { -+ /* -+ * Patch D: lock-free hot-path BA accounting. atomic_inc + atomic_add -+ * each per-frame; the once-per-window timer-arm uses cmpxchg on -+ * ba_armed so concurrent TX/RX can't both try to set the timer and -+ * we don't need cross-counter coherency on the ba_cnt/ba_cnt_rx pair. -+ */ -+ atomic_add(t->skb->len - t->hdrlen, &hw_priv->ba_acc); -+ atomic_inc(&hw_priv->ba_cnt); -+ if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) { - mod_timer(&hw_priv->ba_timer, - jiffies + BES2600_BLOCK_ACK_INTERVAL); - } -- hw_priv->ba_cnt++; -- spin_unlock_bh(&hw_priv->ba_lock); - } - - static int -@@ -1474,35 +1549,14 @@ void bes2600_skb_dtor(struct bes2600_common *hw_priv, - struct bes2600_vif *priv = - __cw12xx_hwpriv_to_vifpriv(hw_priv, txpriv->if_id); - -- -- if (!skb) -- return; -- -- /* -- * There should be no reason for skb buffer being larger -- * than the offset.. -- */ -- if(WARN_ON(txpriv->offset > skb->len)) { -- ieee80211_free_txskb(hw_priv->hw, skb); -- return; -- } -- -- bes_devel("%s: txpriv->offset: %d - skb->len: %d\n", -- __func__, txpriv->offset, skb->len); -- - skb_pull(skb, txpriv->offset); - if (priv && txpriv->rate_id != BES2600_INVALID_RATE_ID) { - bes2600_notify_buffered_tx(priv, skb, - txpriv->raw_link_id, txpriv->tid); - tx_policy_put(hw_priv, txpriv->rate_id); - } -- if (likely(!bes2600_is_itp(hw_priv))) { -- if (priv) { -- /* The interface may be already removed */ -- bes2600_tx_status(priv, skb); -- } -+ if (likely(!bes2600_is_itp(hw_priv))) - ieee80211_tx_status_skb(hw_priv->hw, skb); -- } - - } - #ifdef CONFIG_BES2600_TESTMODE -@@ -1579,14 +1633,13 @@ bes2600_rx_h_ba_stat(struct bes2600_vif *priv, - if (!priv->setbssparams_done) - return; - -- spin_lock_bh(&hw_priv->ba_lock); -- hw_priv->ba_acc_rx += skb_len - hdrlen; -- if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) { -+ /* Patch D: lock-free hot-path BA accounting; see TX side comment. */ -+ atomic_add(skb_len - hdrlen, &hw_priv->ba_acc_rx); -+ atomic_inc(&hw_priv->ba_cnt_rx); -+ if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) { - mod_timer(&hw_priv->ba_timer, - jiffies + BES2600_BLOCK_ACK_INTERVAL); - } -- hw_priv->ba_cnt_rx++; -- spin_unlock_bh(&hw_priv->ba_lock); - } - - void bes2600_rx_cb(struct bes2600_vif *priv, -@@ -1694,6 +1747,8 @@ void bes2600_rx_cb(struct bes2600_vif *priv, - goto drop; - } else { - bes_warn("[RX] Receive failure: %d.\n", arg->status); -+ if (arg->status == WSM_STATUS_DECRYPTFAILURE) -+ bes2600_decrypt_storm_account(priv); - goto drop; - } - } -@@ -1871,7 +1926,6 @@ void bes2600_rx_cb(struct bes2600_vif *priv, - - if (ieee80211_is_data(frame->frame_control)) { - bes2600_rx_h_ba_stat(priv, hdrlen, skb->len); -- bes2600_rx_status(priv, skb); - } - - #ifdef CONFIG_BES2600_TESTMODE -@@ -1888,15 +1942,33 @@ void bes2600_rx_cb(struct bes2600_vif *priv, - if (unlikely(bes2600_itp_rxed(hw_priv, skb))) - consume_skb(skb); - else if (unlikely(early_data)) { -- spin_lock_bh(&priv->ps_state_lock); -- /* Double-check status with lock held */ -- if (entry->status == BES2600_LINK_SOFT) -- skb_queue_tail(&entry->rx_queue, skb); -- else -- ieee80211_rx_irqsafe(priv->hw, skb); -- spin_unlock_bh(&priv->ps_state_lock); -+ /* -+ * Patch E: when c7 has latched pm_unsupported (firmware -+ * doesn't honour PSM, see feedback_bes2600_firmware_no_psm), -+ * AP-side power-save state machine is dead and link entries -+ * never transition to BES2600_LINK_SOFT. The double-check -+ * branch under ps_state_lock is unreachable in that case, -+ * so skip the per-frame lock acquisition entirely and -+ * deliver to mac80211 directly. -+ * -+ * On firmware that does honour PSM (the latch self-clears -+ * if a real PM_INDICATION ever arrives — see c7), this -+ * predicate flips back to false and the original locked -+ * path is taken. -+ */ -+ if (hw_priv->bes_power.pm_unsupported) { -+ ieee80211_rx_ni(priv->hw, skb); -+ } else { -+ spin_lock_bh(&priv->ps_state_lock); -+ /* Double-check status with lock held */ -+ if (entry->status == BES2600_LINK_SOFT) -+ skb_queue_tail(&entry->rx_queue, skb); -+ else -+ ieee80211_rx_ni(priv->hw, skb); -+ spin_unlock_bh(&priv->ps_state_lock); -+ } - } else { -- ieee80211_rx_irqsafe(priv->hw, skb); -+ ieee80211_rx_ni(priv->hw, skb); - } - *skb_p = NULL; - -diff --git a/drivers/staging/bes2600/txrx.h b/drivers/staging/bes2600/txrx.h -index cb7c192d1..6466c3370 100644 ---- a/drivers/staging/bes2600/txrx.h -+++ b/drivers/staging/bes2600/txrx.h -@@ -1,12 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * Datapath interface for BES2600 mac80211 drivers -+ * Datapath interface for BES2600 mac80211 driver - * -- * Copyright (c) 2010, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_TXRX_H -diff --git a/drivers/staging/bes2600/wifi_testmode_cmd.c b/drivers/staging/bes2600/wifi_testmode_cmd.c -index 2494ccac7..c010e8d6d 100644 ---- a/drivers/staging/bes2600/wifi_testmode_cmd.c -+++ b/drivers/staging/bes2600/wifi_testmode_cmd.c -@@ -1,12 +1,9 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * Mac80211 driver for BES2600 device -+ * WiFi testmode commands for BES2600 - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - #ifdef CONFIG_BES2600_TESTMODE - #include -diff --git a/drivers/staging/bes2600/wsm.c b/drivers/staging/bes2600/wsm.c -index d40df3063..242418114 100644 ---- a/drivers/staging/bes2600/wsm.c -+++ b/drivers/staging/bes2600/wsm.c -@@ -1,13 +1,12 @@ -+// SPDX-License-Identifier: GPL-2.0-only - /* -- * WSM host interface (HI) implementation for -- * BES2600 mac80211 drivers. -+ * WSM host interface for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin -+ * -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #include -@@ -134,8 +133,20 @@ static int wsm_generic_confirm(struct bes2600_common *hw_priv, - struct wsm_buf *buf) - { - u32 status = WSM_GET32(buf); -- if (WARN(status != WSM_STATUS_SUCCESS, "wsm_generic_confirm ret %u", status)) -+ -+ /* -+ * A non-SUCCESS status here is a firmware-side policy decision for -+ * the command whose confirm this is -- commonly WSM status 2 for -+ * scan (0x0407) rejected because of a coex window or transient -+ * firmware-busy state. It is not a driver/kernel bug, so avoid the -+ * WARN()/stack-trace treatment; the caller already emits a -+ * wiphy_warn identifying the request id and will propagate the -+ * error to mac80211. -+ */ -+ if (status != WSM_STATUS_SUCCESS) { -+ bes_devel("%s ret %u\n", __func__, status); - return -EINVAL; -+ } - return 0; - - underflow: -@@ -2401,7 +2412,7 @@ int wsm_handle_rx(struct bes2600_common *hw_priv, int id, - if (!hw_priv->beacon_bkp) - hw_priv->beacon_bkp = \ - skb_copy(hw_priv->beacon, GFP_ATOMIC); -- ieee80211_rx_irqsafe(hw_priv->hw, hw_priv->beacon); -+ ieee80211_rx_ni(hw_priv->hw, hw_priv->beacon); - hw_priv->beacon = hw_priv->beacon_bkp; - - hw_priv->beacon_bkp = NULL; -diff --git a/drivers/staging/bes2600/wsm.h b/drivers/staging/bes2600/wsm.h -index 067313162..0d755a362 100644 ---- a/drivers/staging/bes2600/wsm.h -+++ b/drivers/staging/bes2600/wsm.h -@@ -1,16 +1,12 @@ -+/* SPDX-License-Identifier: GPL-2.0-only */ - /* -- * WSM host interface (HI) interface for BES2600 mac80211 drivers -+ * WSM host interface for BES2600 mac80211 driver - * -- * Copyright (c) 2022, Bestechnic -- * Author: -+ * Copyright (c) 2010, ST-Ericsson -+ * Author: Dmitry Tarnyagin - * -- * Based on BES2600 UMAC WSM API, which is -- * Copyright (C) SA 2010 -- * Author: Stewart Mathers -+ * Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd. - * -- * This program is free software; you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 2 as -- * published by the Free Software Foundation. - */ - - #ifndef BES2600_WSM_H_INCLUDED -@@ -2236,7 +2232,5 @@ int wsm_cpu_usage_cmd(struct bes2600_common *hw_priv); - - int wsm_wifi_status_cmd(struct bes2600_common *hw_priv, uint32_t status); - --#if defined(STANDARD_FACTORY_EFUSE_FLAG) - int wsm_save_factory_txt_to_mcu(struct bes2600_common *hw_priv, const u8 *data, int if_id, enum bes2600_rf_cmd_type cmd_type); --#endif - #endif /* BES2600_HWIO_H_INCLUDED */ -- -2.53.0 +2.54.0 From 093a5038b8b68f316d976b7cb69609ca7f24f322 Mon Sep 17 00:00:00 2001 From: Markus Fritsche diff --git a/arch/linux-pinetab2-danctnix-besser/PKGBUILD b/arch/linux-pinetab2-danctnix-besser/PKGBUILD index aa8486c66..fa1f7f71c 100644 --- a/arch/linux-pinetab2-danctnix-besser/PKGBUILD +++ b/arch/linux-pinetab2-danctnix-besser/PKGBUILD @@ -29,7 +29,7 @@ pkgbase=linux-pinetab2-danctnix-besser pkgver=7.0.danctnix1 -pkgrel=5 +pkgrel=6 pkgdesc='PineTab2 (BESser bes2600 driver patchset, kernel-agent managed)' _srcname=linux-pinetab2 _srctag=v${pkgver%.*}-${pkgver##*.} @@ -68,7 +68,7 @@ b2sums=('3d9795083c8938f80f480de0d10bfd9c525640e59d5c7f22983de3f12ee42c84c31be90 'SKIP' '71fe98221e802b315e54b4b10d3e8c8f376695a36bae3541d876e5776a37f3fa33c8f8dfa6e51fcbd6f5396add02e5166634165f2351836a0ea0453c172fe56c' 'SKIP' - '50397711a6a3ba522283685a9e7397aeed6663f353f7cba214d4bb88bc98516065b2fca9a36ce13c52644617879f69f39c5305e86db5d9fb25c4dae5434eb9c4' + 'eb179c03f35a4dbaec2e40036f0033ef04985bb6b14ab22419d68e5caaa5874f2ad14e158f7c5b05added97f60fecde8fb8b7f2a6ced33e031e37352fe776ca6' '656a998ab40cb85ee4c00f087b071a91632a6c091da2c84b0f74236b51d2dea6e9db6886625f80ad81dc249d8494ec47cd79d6dd9ea4f5e44f3cde857f861e10') export KBUILD_BUILD_HOST=archlinux diff --git a/arch/linux-pinetab2-danctnix-besser/manifest.lock b/arch/linux-pinetab2-danctnix-besser/manifest.lock index 2e7fb5cb6..088011e64 100644 --- a/arch/linux-pinetab2-danctnix-besser/manifest.lock +++ b/arch/linux-pinetab2-danctnix-besser/manifest.lock @@ -4,34 +4,174 @@ baseline: upstream_compat: linux-7.0 url: https://codeberg.org/DanctNIX/linux-pinetab2 cumulative: - b2sum: 50397711a6a3ba522283685a9e7397aeed6663f353f7cba214d4bb88bc98516065b2fca9a36ce13c52644617879f69f39c5305e86db5d9fb25c4dae5434eb9c4 + b2sum: eb179c03f35a4dbaec2e40036f0033ef04985bb6b14ab22419d68e5caaa5874f2ad14e158f7c5b05added97f60fecde8fb8b7f2a6ced33e031e37352fe776ca6 path: cumulative.patch - size: 162716 -generated_at: '2026-05-18T17:16:06.455474+00:00' + size: 279554 +generated_at: '2026-05-19T13:05:46.476359+00:00' host: ohm ka_promote_version: 1 manifest: path: fleet/ohm.yaml - sha256: da59ac2c965e5ad9c5004a115b10a37abf47ed3ecc8b7f5ab426470d2ee7b442 + sha256: 9ac04ddd3170418b7b2d2cf7b31ac225a31ed19be4f03e8477bf28b585bae257 resolved_patches: - apply_order: 1 from_series: true - include: driver/bes2600/cumulative-c5x-danctnix/0001-bes2600-besser-cumulative-series.patch - sha256: e477a170567487fef84fe13be5b0a1f0498247ff1f201000d0085a2e49ff9026 - size: 148149 + include: driver/bes2600/factory-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch + sha256: a1bc2d13b258709fa37c9ff428dfdc0659464b436470fa2ec69b07edf7592f6f + size: 5456 - apply_order: 2 from_series: true - include: driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5ghz-scan-and-allow-single-channel.patch - sha256: 31e67569e00daead0784214aced1e077d3270cf1407baa0b330d474e17ec3931 - size: 7735 + include: driver/bes2600/factory-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-PineTab2.patch + sha256: 577d7024ce0b342c4381365872fc29e75a93427ad61223907fead8b829b5a86c + size: 3499 - apply_order: 3 from_series: true - include: arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch - sha256: a49c50f0ebffc499970c24908b832c3e61c96ed87de35b3a82178eff587f94f1 - size: 1574 + include: driver/bes2600/factory-thread-dev/0001-bes2600-thread-struct-device-through-factory-request_firmware.patch + sha256: e3fac725e6addc11147341836600c2c5cd0116abba960f34ba50bb8094581c75 + size: 4406 - apply_order: 4 + from_series: true + include: driver/bes2600/pm-gate-on-handshake/0001-bes2600-gate-device-LP-mode-entry-on-successful-handshake.patch + sha256: 9842c0dd66f59fe28898041ba5a816be56965b0665f202410cd461c3e6565474 + size: 3914 +- apply_order: 5 + from_series: true + include: driver/bes2600/remove-chardev-user-interface/0001-bes2600-remove-userspace-dev-bes2600-character-device-interface.patch + sha256: c67d340ae5923aada613ea9c5133e3efa3aeb7986749f4bf3619d1752a1b61fb + size: 22445 +- apply_order: 6 + from_series: true + include: driver/bes2600/enable-testmode/0001-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fix-bitrot.patch + sha256: 5dee74e8753d332fd380882994ea43aa907d1ff97466b0c48aedf38d4076e446 + size: 6152 +- apply_order: 7 + from_series: true + include: driver/bes2600/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch + sha256: 0dce2fe35450b8376c2d2a7c007119f28c888c1c30b489a67841039caedeebfc + size: 4544 +- apply_order: 8 + from_series: true + include: driver/bes2600/factory-drop-kernel-write-danctnix/0001-bes2600-drop-kernel_write-persistence-from-factory-cali-save.patch + sha256: a7995b38e210af16b73d284a58ab39b8aecac36ff4a671af3d894b1983f961b3 + size: 5704 +- apply_order: 9 + from_series: true + include: driver/bes2600/drop-dpd-file-paths-danctnix/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-paths.patch + sha256: 0cd8780c245c97c65e4845e42d712c6256a0449658641aea18e4c7d400f63e41 + size: 9661 +- apply_order: 10 + from_series: true + include: driver/bes2600/drop-orphan-file-io-danctnix/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_file-IO.patch + sha256: fd8c297223e6a985c2898f919ae1ab27eb56ab44f09f44d84d75eb35a187527b + size: 5327 +- apply_order: 11 + from_series: true + include: driver/bes2600/pm-timeout-silence-danctnix/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-bes_devel.patch + sha256: 3a4fd3255facbcef0419e0e0332cb980316529aa5c225b35bcfd244a42736667 + size: 2332 +- apply_order: 12 + from_series: true + include: driver/bes2600/scan-defer-on-reject-danctnix/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-reject.patch + sha256: 55e16c176bc147c371a20f57b3a57da38c719d3b42417e88f9de243e10102d35 + size: 8393 +- apply_order: 13 + from_series: true + include: driver/bes2600/scan-defer-backoff-tune-danctnix/0001-bes2600-widen-scan-defer-backoff-30s-and-decay-on-quiet.patch + sha256: 70a5b25baaf41c8090701b069c30cbad378883d828bdd06e4eb560a35bc077f1 + size: 4924 +- apply_order: 14 + from_series: true + include: driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-link-break.patch + sha256: 3decf33c9684b3aba64004d5ad97ae3d54e1d6dc176d0b0ae539036c65e6dc6c + size: 10604 +- apply_order: 15 + from_series: true + include: driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/0002-bes2600-handle-multi-function-SDIO-cards-in-mmc_hw_reset-bus_reset.patch + sha256: a1acfcc401afc699a9c3676b6df2ec0f092e78826a32616268f90b509d538e33 + size: 3321 +- apply_order: 16 + from_series: true + include: driver/bes2600/pm-state-resync-danctnix/0001-bes2600-gate-PM-indication-completion-on-pending-request-and-track-state.patch + sha256: 049cf3ff9c01fdd10ff73bd18497e14ef0cd8fd1a65486ba86fbc6c1935a5f8e + size: 10269 +- apply_order: 17 + from_series: true + include: driver/bes2600/pm-wake-consume-state-danctnix/0001-bes2600-short-circuit-wake-handshake-when-chip-confirmed-ACTIVE.patch + sha256: c9d19a73816f4c82b418dcd18008176bbb0c49fd4138be53cad45ae142224112 + size: 8100 +- apply_order: 18 + from_series: true + include: driver/bes2600/pm-detect-firmware-unsupported-danctnix/0001-bes2600-self-detect-firmware-does-not-honor-PSM-skip-cycle.patch + sha256: 196dc9d51ffea268718a290d434b6237fb60119f10c2b050a58724c8a775c7a8 + size: 9041 +- apply_order: 19 + from_series: true + include: driver/bes2600/decrypt-storm-fast-recover-danctnix/0001-bes2600-pre-empt-AP-deauth-6-mac80211-reassoc-on-decrypt-fail-storm.patch + sha256: b57ed316005f402c95ccae8ab24ac761bdf34162d73f108f5790af8f8ad2d1fe + size: 9249 +- apply_order: 20 + from_series: true + include: driver/bes2600/connection-loss-fast-recover-danctnix/0001-bes2600-bus_reset-on-connection-loss-storm-to-dodge-assoc-comeback-blackhole.patch + sha256: cd1eaff97c3f08c58e7b1588e19a12200e8bb2a1f39afe554284f1d818610a67 + size: 12184 +- apply_order: 21 + from_series: true + include: driver/bes2600/cw1200-fix-backports-danctnix/0001-bes2600-replace-atomic_add-with-atomic_inc-cw1200-backport.patch + sha256: 3876c9e512f556c7f2e8d4cfaba1d7df2945ee48af8edfab5f8d09d9de9adf23 + size: 3080 +- apply_order: 22 + from_series: true + include: driver/bes2600/cw1200-fix-backports-danctnix/0002-bes2600-fix-missing-destroy_workqueue-on-error-in-init_common.patch + sha256: 2b82ecb127748349780404479205b952337c244e715278e6d40471c6ecad7602 + size: 2230 +- apply_order: 23 + from_series: true + include: driver/bes2600/cw1200-fix-backports-danctnix/0003-bes2600-fix-concurrency-UAF-in-bes2600_hw_scan-and-sched_scan.patch + sha256: 4c1850ad003ddcac543d3d61edd15c18ccd0cc601367cf4c6dd31e1fbb39ab16 + size: 4476 +- apply_order: 24 + from_series: true + include: driver/bes2600/sdio-rx-no-relay-danctnix/0001-bes2600-drop-sdio_rx_work-relay-IRQ-bh-direct-no-relay-architecture.patch + sha256: f1182150c5893f2497f942900b34c9c4aeb8d5901d9786ae2753dcce38ed6c78 + size: 19313 +- apply_order: 25 + from_series: true + include: driver/bes2600/license-spdx-restore-attribution-danctnix/0001-bes2600-Patch-G-restore-SPDX-identifiers-ST-Ericsson-attribution.patch + sha256: 91dadab0b58f8b8ad2dca80fd04796d478ecb83ce94a1e4b6e97ef8634d97ef1 + size: 41521 +- apply_order: 26 + from_series: true + include: driver/bes2600/ba-lock-atomic-danctnix/0001-bes2600-Patch-D-atomicize-ba_lock-counters-drop-the-spinlock.patch + sha256: a5d4ed2bf545458a756e65670c7eed31997bd0be9262344a10313bee31ea4963 + size: 11987 +- apply_order: 27 + from_series: true + include: driver/bes2600/ps-state-lock-skip-pm-disabled-danctnix/0001-bes2600-Patch-E-skip-ps_state_lock-when-PSM-known-disabled.patch + sha256: 18040a563b37cc95c558703f01bfbf6b7fa23a52f2f4f0f8f1254ad4fa0fe0d6 + size: 3396 +- apply_order: 28 + from_series: true + include: driver/bes2600/rx-list-batch-delivery-danctnix/0001-bes2600-Patch-C2-replace-ieee80211_rx_irqsafe-with-ieee80211_rx_ni.patch + sha256: ffeffd085a9d052c126a717b845d50120ea302e76c12e53c0c3c891291cababf + size: 8377 +- apply_order: 29 + from_series: true + include: driver/bes2600/bh-c-fossil-cleanup-danctnix/0001-bes2600-Patch-H-bh.c-hygiene-cleanup-drop-fossil-blocks-dead-stubs.patch + sha256: 8fb0c799e3a8ee5ad7bfb647fceaf370c6a1a5f24d8621776fd07bf18a976f81 + size: 21082 +- apply_order: 30 + from_series: true + include: driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5-GHz-scans-at-the-driver-boundary.patch + sha256: 31e67569e00daead0784214aced1e077d3270cf1407baa0b330d474e17ec3931 + size: 7735 +- apply_order: 31 + from_series: true + include: arch/arm64/scs-arm-neon-build-fix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch + sha256: 105e32edc54743d8107c4dcd846833ae97d2df5f918aebc9fe3e67d6f23249cc + size: 1562 +- apply_order: 32 from_series: true include: driver/bes2600/queue-pending-record-lock-bh-danctnix/0001-bes2600-take-pending-record-lock-with-bh.patch - sha256: 089862e5f6da5783ed0db979144e4fa07cff7f743809a0bebd715c75a3bb8eb5 - size: 5258 + sha256: e0894371c43f750590e1704ae3c77b27b6910548afa4a5b61ebc4d9919580ca2 + size: 5270 schema_version: 1