From f203b70f4f10f6a1a29e9a628be2bdaa5eeabf66 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 16:50:41 +0200 Subject: [PATCH 1/2] fleet/ohm: switch bes2600 driver scope to cumulative-c5x-danctnix interim (closes #5 partial migration) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit during ohm pkgrel=4 migration found the per-series -danctnix mirrors merged in #17 do NOT apply against the linux-pinetab2 baseline: all 17 of them use DKMS-style root paths (bes2600/foo.c) rather than in-tree staging paths (drivers/staging/bes2600/foo.c), and at least one has a corrupted mixed-prefix header (a/drivers/staging/bes2600/... b/bes2600/...). ka-promote ohm with those includes produced a 172 644-byte cumulative touching 27 file paths, of which 11 are bogus. The hand-curated 0001-bes2600-besser-cumulative-series.patch from the working danctnix-besser-pkgbuild flow on boltzmann (148 149 bytes, 48 in-tree staging files) is what pkgrel=3 actually builds with. Until the per-series mirrors are reconstructed (followup issue to be opened separately), the bes2600 driver scope is satisfied here by staging that hand-curated cumulative as a single-file series-dir patches/driver/bes2600/cumulative-c5x-danctnix/. ohm.yaml drops the broken per-series includes in favour of: - driver/bes2600/cumulative-c5x-danctnix/ - driver/bes2600/scan-filter-5ghz-danctnix/ (closes besser#1) - arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/ ka-promote ohm now produces a self-consistent 157 446-byte cumulative (148 149 + 7 735 + 1 562 = exact byte arithmetic) with b2sum a807297b25be... which is what the new marfrit-packages/arch/linux-pinetab2-danctnix-besser PKGBUILD pkgrel=4 pins. Also fixes fleet/ohm.yaml YAML parse error: bar5_burn_in had a scalar value followed by a sub-list, which ka-promote (PyYAML) refused to parse. The whole manifest had never parsed cleanly since #18 landed. Refs: #5 (migrate PKGBUILD), #2 (mirror besser series — needs per-series rewrite followup), besser#1 (Patch I). --- fleet/ohm.yaml | 61 +- ...-arm64-xor-neon-ffixed-x18-build-fix.patch | 36 + .../README.md | 20 + ...001-bes2600-besser-cumulative-series.patch | 4322 +++++++++++++++++ .../bes2600/cumulative-c5x-danctnix/README.md | 35 + ...r-5ghz-scan-and-allow-single-channel.patch | 168 + .../scan-filter-5ghz-danctnix/README.md | 19 + 7 files changed, 4629 insertions(+), 32 deletions(-) create mode 100644 patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch create mode 100644 patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/README.md create mode 100644 patches/driver/bes2600/cumulative-c5x-danctnix/0001-bes2600-besser-cumulative-series.patch create mode 100644 patches/driver/bes2600/cumulative-c5x-danctnix/README.md create mode 100644 patches/driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5ghz-scan-and-allow-single-channel.patch create mode 100644 patches/driver/bes2600/scan-filter-5ghz-danctnix/README.md diff --git a/fleet/ohm.yaml b/fleet/ohm.yaml index efd2003..edb0cd6 100644 --- a/fleet/ohm.yaml +++ b/fleet/ohm.yaml @@ -25,40 +25,35 @@ baseline: # Scope-tagged patch includes. Resolves to patches//.patch. # -# Series-ordering note: the current cumulative-patch generation order on -# boltzmann is A, B, C v3, F, G, D, E, C2, c5.x, c6.x, c7, H — explicitly -# NOT alphabetical. ka-promote MUST honor an apply_order field when -# concatenating series into the build's per-job cumulative patch. The -# legend mapping series-letter → series-name lives in the current -# danctnix-besser-pkgbuild changelog on boltzmann; promote to this -# manifest once auto-generation is wired. +# 2026-05-18 audit: the per-series -danctnix mirrors in +# patches/driver/bes2600/*-danctnix/ created by kernel-agent#17 use +# DKMS-style root paths (bes2600/foo.c) rather than in-tree staging +# paths (drivers/staging/bes2600/foo.c), and at least one has corrupted +# mixed-prefix headers (a/drivers/staging/bes2600/... b/bes2600/...). +# They do NOT apply cleanly against the linux-pinetab2 baseline. # -# DanctNIX siblings (-danctnix suffix) are selected here because ohm -# runs on the DanctNIX kernel base; the non-suffixed variants exist for -# vanilla mainline consumers that ohm doesn't currently have. +# Until the per-series mirrors are reconstructed (kernel-agent followup +# issue), the bes2600 driver scope is satisfied by a single-file +# cumulative captured from the working hand-managed +# danctnix-besser-pkgbuild flow on boltzmann (see +# patches/driver/bes2600/cumulative-c5x-danctnix/README.md). This is +# the c5x stack as it shipped in pkgrel=3 on 2026-05-18. includes: - # Default-on series (uncontroversial fixes that ohm already runs): - - driver/bes2600/staging-prep-series-danctnix/ - - driver/bes2600/pm-state-resync-danctnix/ - - driver/bes2600/pm-timeout-silence-danctnix/ - - driver/bes2600/pm-wake-consume-state-danctnix/ - - driver/bes2600/pm-gate-on-handshake/ - - driver/bes2600/pm-detect-firmware-unsupported-danctnix/ - - driver/bes2600/scan-defer-backoff-tune-danctnix/ - - driver/bes2600/scan-defer-on-reject-danctnix/ - - driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/ - - driver/bes2600/tx-sdio-dma-oob-danctnix/ - - driver/bes2600/factory-series/ - - driver/bes2600/factory-thread-dev/ - - driver/bes2600/factory-drop-kernel-write-danctnix/ - - driver/bes2600/drop-dpd-file-paths-danctnix/ - - driver/bes2600/drop-orphan-file-io-danctnix/ - - driver/bes2600/remove-chardev-user-interface/ - - driver/bes2600/enable-testmode/ + # bes2600 driver (c5x stack as shipped in pkgrel=3) — single-file + # interim cumulative; per-series reconstruction tracked separately. + - driver/bes2600/cumulative-c5x-danctnix/ + # close besser#1 — refuse multi-channel 5 GHz scans at driver boundary. + - driver/bes2600/scan-filter-5ghz-danctnix/ + # GCC 15.2.1 build-fix for arm_neon.h + SHADOW_CALL_STACK interaction. + # Runtime no-op as long as the config has CONFIG_SHADOW_CALL_STACK=n + # (current ohm setting). Kept in the manifest for the day SCS gets + # re-enabled. See reference_arm64_scs_arm_neon_gcc15 memory. + - arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/ # Explicitly NOT included (decision logged): # - debian-copyright-fsf-address: Debian packaging metadata, not kernel -# - bare (non-danctnix) variants of the above: ohm runs DanctNIX base +# - bare (non-danctnix) variants of the per-series mirrors: same +# root-path bug as the -danctnix variants per the 2026-05-18 audit config: source: hand-managed config file in boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/config @@ -91,9 +86,11 @@ verify: - wlan0 + bt0 (BT/UART) present after boot - sdio_force_uhs=0 not needed (DMA-OOB-read fix in tx-sdio-dma-oob series) bar4_per_patch_probe: opt-in - bar5_burn_in: opt-in - - WiFi: 24h iperf3 to LAN host without rxhang - - PM: lid-close → wake cycles × 100 without bes2600 confirm-loss + bar5_burn_in: + mode: opt-in + tests: + - "WiFi: 24h iperf3 to LAN host without rxhang" + - "PM: lid-close → wake cycles × 100 without bes2600 confirm-loss" build_host: primary: boltzmann # native aarch64 with ohm's identical .config diff --git a/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch new file mode 100644 index 0000000..a264806 --- /dev/null +++ b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch @@ -0,0 +1,36 @@ +From: Markus Fritsche +Date: Mon, 18 May 2026 11:42:00 +0200 +Subject: [PATCH] arm64: xor-neon: restore -ffixed-x18 when SHADOW_CALL_STACK=y + (GCC 15+ build fix) + +GCC 15.2.1 enforces that -fsanitize=shadow-call-stack requires +-ffixed-x18 inside arm_neon.h's #pragma GCC target() blocks. The +existing CFLAGS_REMOVE_xor-neon.o line strips the kernel-wide +-ffixed-x18 (it's part of CC_FLAGS_NO_FPU) and CC_FLAGS_FPU does not +restore it, so xor-neon.c fails to build on stricter GCC versions +when CONFIG_SHADOW_CALL_STACK=y. + +Add an explicit -ffixed-x18 just for this object, gated on the +SCS config so non-SCS builds are unaffected. + +Build environment workaround; not a kernel-runtime bug. +--- + arch/arm64/lib/Makefile | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile +index 1234567..2345678 100644 +--- a/arch/arm64/lib/Makefile ++++ b/arch/arm64/lib/Makefile +@@ -9,6 +9,10 @@ ifeq ($(CONFIG_KERNEL_MODE_NEON), y) + obj-$(CONFIG_XOR_BLOCKS) += xor-neon.o + CFLAGS_xor-neon.o += $(CC_FLAGS_FPU) + CFLAGS_REMOVE_xor-neon.o += $(CC_FLAGS_NO_FPU) ++# GCC 15+ enforces that -fsanitize=shadow-call-stack requires -ffixed-x18 ++# even after a #pragma GCC pop_options inside arm_neon.h. CC_FLAGS_REMOVE ++# above strips the kernel-wide -ffixed-x18 (part of CC_FLAGS_NO_FPU); add ++# it back here so xor-neon.c still compiles when SHADOW_CALL_STACK=y. ++CFLAGS_xor-neon.o += $(if $(CONFIG_SHADOW_CALL_STACK),-ffixed-x18) + endif + + lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o diff --git a/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/README.md b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/README.md new file mode 100644 index 0000000..6bf502f --- /dev/null +++ b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/README.md @@ -0,0 +1,20 @@ +# xor-neon-ffixed-x18-scs-build-fix-danctnix — GCC 15.2.1 build fix + +Restores `-ffixed-x18` for `arch/arm64/lib/xor-neon.c` when +`CONFIG_SHADOW_CALL_STACK=y`. GCC 15.2.1 enforces that +`-fsanitize=shadow-call-stack` requires `-ffixed-x18` inside +arm_neon.h's `#pragma GCC target()` push/pop blocks; CC_FLAGS_REMOVE +strips the kernel-wide `-ffixed-x18` for xor-neon.o and CC_FLAGS_FPU +does not restore it. + +**Note on current ohm config**: `linux-pinetab2-danctnix-besser` +config has `# CONFIG_SHADOW_CALL_STACK is not set` as of pkgrel=3, +which makes this patch a runtime no-op (`$(if $(CONFIG_SHADOW_CALL_STACK), +-ffixed-x18)` evaluates to nothing). Patch is kept in the manifest as +belt-and-suspenders for the day SCS gets re-enabled (tracked in +besser issue for GCC fix monitoring). + +See [[reference_arm64_scs_arm_neon_gcc15]] for the full toolchain +analysis. This patch is the upstream-friendly Makefile fix; the +config-side `SHADOW_CALL_STACK=n` workaround is the immediate +runtime mitigation. Both are present in pkgrel=3 for safety. diff --git a/patches/driver/bes2600/cumulative-c5x-danctnix/0001-bes2600-besser-cumulative-series.patch b/patches/driver/bes2600/cumulative-c5x-danctnix/0001-bes2600-besser-cumulative-series.patch new file mode 100644 index 0000000..a0adf6f --- /dev/null +++ b/patches/driver/bes2600/cumulative-c5x-danctnix/0001-bes2600-besser-cumulative-series.patch @@ -0,0 +1,4322 @@ +From 4e176b8f930373bc02382c903e6d739ab2d5fd47 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) + +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: + + - 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) + +Patch series squashed: + + 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) + +Net: 48 files changed, ~1500 insertions, ~2000 deletions. + +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. + +For per-patch history see git.reauktion.de/marfrit/bes2600-dkms cleanups +branch. + +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(-) + +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. + */ + + #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 */ +diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c +index dc5d3dac6..0d2bfa1c8 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. + */ + + #include + #include + #include ++#include + #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) + */ + static int factory_section_read_file(char *path, void *buffer) + { +- int ret = 0; +- struct file *fp; ++ const struct firmware *fw; ++ int ret; + + if (!path || !buffer) { + bes_err("%s NULL pointer err\n", __func__); + return -1; + } + +- bes_devel("reading %s \n", path); ++ bes_devel("requesting firmware-class %s\n", path); + +- 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); ++ if (ret) { ++ bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret); + return -1; + } + +- if (fp->f_inode->i_size <= 0 || fp->f_inode->i_size > FACTORY_MAX_SIZE) { +- bes_err( "bes2600_factory.txt size check failed, read_size: %lld max_size: %d\n", +- fp->f_inode->i_size, FACTORY_MAX_SIZE); +- filp_close(fp, NULL); ++ if (fw->size == 0 || fw->size > FACTORY_MAX_SIZE) { ++ bes_err("bes2600_factory.txt size check failed, read_size: %zu max_size: %d\n", ++ fw->size, FACTORY_MAX_SIZE); ++ release_firmware(fw); + return -1; + } + +- ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos); +- +- filp_close(fp, NULL); +- +- if (ret != fp->f_inode->i_size) { +- bes_err("bes2600_factory.txt read fail\n"); +- 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 + } + ++/* ++ * 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 +@@ -902,13 +891,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); +@@ -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; + } + +diff --git a/drivers/staging/bes2600/bes2600_factory.h b/drivers/staging/bes2600/bes2600_factory.h +index 3835b0d9c..0b1a321d7 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 { + /* just calibrate 11n, other protocols are automatically mapped */ + #define WIFI_RF_11N_MODE 0x15 + ++/* set the struct device * used for request_firmware() context */ ++void bes2600_factory_set_dev(struct device *dev); ++ + /* 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 +--- 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 + + #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; ++ /* ++ * 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; +@@ -94,6 +100,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]; ++ u8 *tx_bounce; + 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) + } + } + +- sg_set_buf(&sg[scatters], tx_buffer->buf, align); ++ /* ++ * The transfer length is rounded up to the SDIO block ++ * size, but tx_buffer->buf is only tx_buffer->len bytes ++ * long (it usually aliases into an skb linear head). ++ * Copy into a driver-owned bounce buffer and zero-pad ++ * to the aligned size; otherwise DMA reads past the ++ * skb and leaks adjacent kernel memory on the wire -- ++ * observed as KFENCE OOB reads from ++ * bes_sdio_memcpy_to_io_helper via dma_map_sg. ++ */ ++ if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN)) ++ goto flush_previous; ++ memcpy(self->tx_bounce + total_len, ++ tx_buffer->buf, tx_buffer->len); ++ if (align > tx_buffer->len) ++ memset(self->tx_bounce + total_len + ++ tx_buffer->len, 0, ++ align - tx_buffer->len); ++ sg_set_buf(&sg[scatters], ++ self->tx_bounce + total_len, align); + 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) + 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; + #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; +@@ -1756,6 +1803,55 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self) + sdio_work_debug(self); + } + ++/* ++ * Trigger an SDIO bus reset via mmc_hw_reset(). ++ * ++ * With multiple SDIO functions probed (PineTab2 binds func 1 for WLAN and ++ * func 2 for the BT-companion path) mmc_sdio_hw_reset() takes the ++ * remove-and-rescan path: it 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. ++ * ++ * In both cases the chip ends up in a freshly reset state, which is the ++ * goal of the recovery path. ++ * ++ * mmc_hw_reset() must be called without holding the SDIO host claim -- ++ * the multi-func remove-and-rescan path acquires the host claim via the ++ * mmc workqueue. ++ */ ++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; ++} ++ + 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 = { + .gpio_sleep = bes2600_gpio_allow_mcu_sleep, + .halt_device = bes2600_sdio_halt_device, + .wakeup_source = bes2600_sdio_wakeup_source, ++ .bus_reset = bes2600_sdio_bus_reset, + }; + + 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 +--- 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_ + return ret; + } + ++/* ++ * Hard-reset the bus and wait for the bus core to remove the chip. ++ * ++ * Used by the firmware-wedge recovery path on platforms where the normal ++ * power_switch(0) sequence has no effective chip-reset signal. The bus ++ * implementation triggers an asynchronous re-detect; this helper waits for ++ * the resulting remove() callback to clear bes2600_cdev.sbus_priv so that a ++ * subsequent bes2600_switch_wifi(true) sees a clean state and can wait on ++ * the fresh probe. ++ */ ++int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv) ++{ ++ int ret; ++ long status; ++ ++ if (!sbus_ops || !priv) ++ return -EINVAL; ++ ++ if (!sbus_ops->bus_reset) ++ return -EOPNOTSUPP; ++ ++ bes_info("trigger bus reset to recover wedged firmware.\n"); ++ ++ ret = sbus_ops->bus_reset(priv); ++ if (ret) { ++ bes_err("bus_reset failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * The bus reset is asynchronous: the bus core schedules a rescan ++ * which removes the bound function drivers and then re-detects the ++ * chip. Wait for the remove callback to clear sbus_priv. Do not ++ * dereference 'priv' after this point -- it may already be freed. ++ */ ++ status = wait_event_timeout(bes2600_cdev.probe_done_wq, ++ !bes2600_cdev.sbus_priv, HZ * 3); ++ WARN_ON(status <= 0); ++ ++ 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; +diff --git a/drivers/staging/bes2600/bes_chardev.h b/drivers/staging/bes2600/bes_chardev.h +index c627bb7c3..9edb2067a 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); + /* 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) + } + */ + +-//#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 +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__) ++ ++/* ++ * 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. + */ + + #ifndef BES_NL80211_TESTMODE_MSG_H +diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c +index e7a104542..a3f954bf3 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) + bes_devel("device enter sleep\n"); + } + ++/* ++ * Number of consecutive bes2600_pwr_enter_lp_mode timeouts (with zero ++ * PM_INDICATIONs received) before we conclude the firmware does not ++ * honor host-driven PSM and switch to a sticky skip path. ++ */ ++#define BES2600_PM_UNSUPPORTED_THRESHOLD 3 ++ ++/* ++ * Latch pm_unsupported = true and force chip_pm_state = ACTIVE so the ++ * c6.2 wake-side skip branch covers bes2600_pwr_device_exit_lp_mode. ++ * Called after BES2600_PM_UNSUPPORTED_THRESHOLD consecutive enter_lp_mode ++ * timeouts with zero PM_INDICATIONs. ++ */ ++static void bes2600_pwr_latch_pm_unsupported(struct bes2600_common *hw_priv) ++{ ++ bes_warn("PSM not honored (%u timeouts), switching to skip mode\n", ++ hw_priv->bes_power.pm_consecutive_timeouts); ++ hw_priv->bes_power.pm_unsupported = true; ++ atomic_set(&hw_priv->bes_power.chip_pm_state, ++ BES2600_CHIP_PM_ACTIVE); ++ ++ /* ++ * Hold the MCU wake-flag bit permanently. Without this, every ++ * sdio_rx_work invocation hits bes2600_gpio_wakeup_mcu(SDIO_RX) ++ * when gpio_wakup_flags == 0, drives the GPIO high and msleeps ++ * 10 ms per RX. With ~50 RX/s of beacons + multicast that's ++ * ~50%% of the bes_sdio workqueue thread blocked in msleep, ++ * which directly caps RX throughput. Holding the MCU bit makes ++ * those calls bit-only bookkeeping (gpio_wakeup = (flags == 0) ++ * stays false, no GPIO toggle, no msleep). The bit is never ++ * cleared once pm_unsupported is set because ++ * bes2600_pwr_device_enter_lp_mode is unreachable under the ++ * early-return. ++ */ ++ if (hw_priv->sbus_ops->gpio_wake) ++ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, ++ GPIO_WAKE_FLAG_MCU); ++} ++ + 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; + ++ /* ++ * Sticky early-return when we've previously concluded the firmware ++ * doesn't honor PSM. Each attempt would otherwise burn 5s on a ++ * doomed wait_for_completion_timeout and produce a noisy three-line ++ * cascade in dmesg every time power_down_work retries (every ++ * ~10s). The chip stays in active mode, which on this firmware is ++ * the de-facto state anyway. ++ */ ++ if (hw_priv->bes_power.pm_unsupported) ++ return -EOPNOTSUPP; ++ + /* 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++; ++ 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). ++ */ ++ 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; + 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); ++ 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. ++ */ ++ /* ++ * Any PM indication, whatever its psmode, proves the firmware is ++ * actually emitting them. Reset the consecutive-timeout counter ++ * so a transient stall doesn't permanently disable PSM, and clear ++ * pm_unsupported if a previous run had latched it. ++ */ ++ hw_priv->bes_power.pm_consecutive_timeouts = 0; ++ if (hw_priv->bes_power.pm_unsupported) { ++ bes_warn("PM indication arrived after pm_unsupported was set; re-enabling PSM transitions\n"); ++ 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); + } + } + +diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h +index 1ba866c25..49477b3e2 100644 +--- a/drivers/staging/bes2600/bes_pwr.h ++++ b/drivers/staging/bes2600/bes_pwr.h +@@ -1,12 +1,9 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ + /* +- * Mac80211 driver for BES2600 device ++ * Chip-side power state machine 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_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 +--- a/drivers/staging/bes2600/bh.c ++++ b/drivers/staging/bes2600/bh.c +@@ -1,12 +1,12 @@ ++// SPDX-License-Identifier: GPL-2.0-only + /* +- * Mac80211 driver for BES2600 device ++ * Bottom-half thread 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 + #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" + +@@ -102,7 +101,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); +@@ -317,83 +316,6 @@ int wsm_release_buffer_to_fw(struct bes2600_vif *priv, int count) + } + #endif + +-#if 0 +-static struct sk_buff *bes2600_get_skb(struct bes2600_common *hw_priv, size_t len) +-{ +- struct sk_buff *skb; +- size_t alloc_len = (len > SDIO_BLOCK_SIZE) ? len : SDIO_BLOCK_SIZE; +- +- if (len > SDIO_BLOCK_SIZE || !hw_priv->skb_cache) { +- skb = dev_alloc_skb(alloc_len +- + WSM_TX_EXTRA_HEADROOM +- + 8 /* TKIP IV */ +- + 12 /* TKIP ICV + MIC */ +- - 2 /* Piggyback */); +- /* In AP mode RXed SKB can be looped back as a broadcast. +- * Here we reserve enough space for headers. */ +- skb_reserve(skb, WSM_TX_EXTRA_HEADROOM +- + 8 /* TKIP IV */ +- - WSM_RX_EXTRA_HEADROOM); +- } else { +- skb = hw_priv->skb_cache; +- hw_priv->skb_cache = NULL; +- } +- return skb; +-} +- +-static void bes2600_put_skb(struct bes2600_common *hw_priv, struct sk_buff *skb) +-{ +- if (hw_priv->skb_cache) +- dev_kfree_skb(skb); +- else +- hw_priv->skb_cache = skb; +-} +- +-static int bes2600_bh_read_ctrl_reg(struct bes2600_common *hw_priv, +- u16 *ctrl_reg) +-{ +- int ret; +- +- ret = bes2600_reg_read_16(hw_priv, +- ST90TDS_CONTROL_REG_ID, ctrl_reg); +- if (ret) { +- ret = bes2600_reg_read_16(hw_priv, +- ST90TDS_CONTROL_REG_ID, ctrl_reg); +- if (ret) +- bes_err("[BH] Failed to read control register.\n"); +- } +- +- return ret; +-} +- +-static int bes2600_device_wakeup(struct bes2600_common *hw_priv) +-{ +- u16 ctrl_reg; +- int ret; +- +- bes_devel("[BH] Device wakeup.\n"); +- +- /* To force the device to be always-on, the host sets WLAN_UP to 1 */ +- ret = bes2600_reg_write_16(hw_priv, ST90TDS_CONTROL_REG_ID, +- ST90TDS_CONT_WUP_BIT); +- if (WARN_ON(ret)) +- return ret; +- +- ret = bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg); +- if (WARN_ON(ret)) +- return ret; +- +- /* If the device returns WLAN_RDY as 1, the device is active and will +- * remain active. */ +- if (ctrl_reg & ST90TDS_CONT_RDY_BIT) { +- bes_devel("[BH] Device awake.\n"); +- return 1; +- } +- +- return 0; +-} +- +-#endif + + /* 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, + priv->powersave_enabled = enable; + } + +-#if 0 +-#define INTERRUPT_WORKAROUND +-static int bes2600_bh(void *arg) +-{ +- struct bes2600_common *hw_priv = arg; +- struct bes2600_vif *priv = NULL; +- struct sk_buff *skb_rx = NULL; +- size_t read_len = 0; +- int rx, tx, term, suspend; +- struct wsm_hdr *wsm; +- size_t wsm_len; +- int wsm_id; +- u8 wsm_seq; +- int rx_resync = 1; +- u16 ctrl_reg = 0; +- int tx_allowed; +- int pending_tx = 0; +- int tx_burst; +- int rx_burst = 0; +- long status; +-#if defined(CONFIG_BES2600_WSM_DUMPS) +- size_t wsm_dump_max = -1; +-#endif +- u32 dummy; +- bool powersave_enabled; +- int i; +- int vif_selected; +- +- for (;;) { +- powersave_enabled = 1; +- spin_lock(&hw_priv->vif_list_lock); +- bes2600_for_each_vif(hw_priv, priv, i) { +-#ifdef P2P_MULTIVIF +- if ((i = (CW12XX_MAX_VIFS - 1)) || !priv) +-#else +- if (!priv) +-#endif +- continue; +- powersave_enabled &= !!priv->powersave_enabled; +- } +- spin_unlock(&hw_priv->vif_list_lock); +- if (!hw_priv->hw_bufs_used +- && powersave_enabled +- && !hw_priv->device_can_sleep +- && !atomic_read(&hw_priv->recent_scan)) { +- status = HZ/8; +- bes_devel("[BH] No Device wakedown.\n"); +-#ifndef FPGA_SETUP +- WARN_ON(bes2600_reg_write_16(hw_priv, +- ST90TDS_CONTROL_REG_ID, 0)); +- hw_priv->device_can_sleep = true; +-#endif +- } else if (hw_priv->hw_bufs_used) +- /* Interrupt loss detection */ +- status = HZ/8; +- else +- status = HZ/8; +- +- /* Dummy Read for SDIO retry mechanism*/ +- if (((atomic_read(&hw_priv->bh_rx) == 0) && +- (atomic_read(&hw_priv->bh_tx) == 0))) +- bes2600_reg_read(hw_priv, ST90TDS_CONFIG_REG_ID, +- &dummy, sizeof(dummy)); +-#if defined(CONFIG_BES2600_WSM_DUMPS_SHORT) +- wsm_dump_max = hw_priv->wsm_dump_max_size; +-#endif /* CONFIG_BES2600_WSM_DUMPS_SHORT */ +- +-#ifdef INTERRUPT_WORKAROUND +- /* If a packet has already been txed to the device then read the +- control register for a probable interrupt miss before going +- further to wait for interrupt; if the read length is non-zero +- then it means there is some data to be received */ +- if (hw_priv->hw_bufs_used) { +- bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg); +- if(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) +- { +- rx = 1; +- goto test; +- } +- } +-#endif +- +- status = wait_event_interruptible_timeout(hw_priv->bh_wq, ({ +- rx = atomic_xchg(&hw_priv->bh_rx, 0); +- tx = atomic_xchg(&hw_priv->bh_tx, 0); +- term = atomic_xchg(&hw_priv->bh_term, 0); +- suspend = pending_tx ? +- 0 : atomic_read(&hw_priv->bh_suspend); +- (rx || tx || term || suspend || hw_priv->bh_error); +- }), status); +- +- if (status < 0 || term || hw_priv->bh_error) +- break; +- +-#ifdef INTERRUPT_WORKAROUND +- if (!status) { +- bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg); +- if(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) +- { +- bes_err("MISS 1\n"); +- rx = 1; +- goto test; +- } +- } +-#endif +- if (!status && hw_priv->hw_bufs_used) { +- unsigned long timestamp = jiffies; +- long timeout; +- bool pending = false; +- int i; +- +- wiphy_warn(hw_priv->hw->wiphy, "Missed interrupt?\n"); +- rx = 1; +- +- /* Get a timestamp of "oldest" frame */ +- for (i = 0; i < 4; ++i) +- pending |= bes2600_queue_get_xmit_timestamp( +- &hw_priv->tx_queue[i], +- ×tamp, -1, +- hw_priv->pending_frame_id); +- +- /* Check if frame transmission is timed out. +- * Add an extra second with respect to possible +- * interrupt loss. */ +- timeout = timestamp + +- WSM_CMD_LAST_CHANCE_TIMEOUT + +- 1 * HZ - +- jiffies; +- +- /* And terminate BH tread if the frame is "stuck" */ +- if (pending && timeout < 0) { +- //wiphy_warn(priv->hw->wiphy, +- // "Timeout waiting for TX confirm.\n"); +- bes_devel("bes2600_bh: Timeout waiting for TX confirm.\n"); +- break; +- } +- +-#if defined(CONFIG_BES2600_DUMP_ON_ERROR) +- BUG_ON(1); +-#endif /* CONFIG_BES2600_DUMP_ON_ERROR */ +- } else if (!status) { +- if (!hw_priv->device_can_sleep +- && !atomic_read(&hw_priv->recent_scan)) { +- bes_devel("[BH] Device wakedown. Timeout.\n"); +-#ifndef FPGA_SETUP +- WARN_ON(bes2600_reg_write_16(hw_priv, +- ST90TDS_CONTROL_REG_ID, 0)); +- hw_priv->device_can_sleep = true; +-#endif +- } +- continue; +- } else if (suspend) { +- bes_devel("[BH] Device suspend.\n"); +- powersave_enabled = 1; +- spin_lock(&hw_priv->vif_list_lock); +- bes2600_for_each_vif(hw_priv, priv, i) { +-#ifdef P2P_MULTIVIF +- if ((i = (CW12XX_MAX_VIFS - 1)) || !priv) +-#else +- if (!priv) +-#endif +- continue; +- powersave_enabled &= !!priv->powersave_enabled; +- } +- spin_unlock(&hw_priv->vif_list_lock); +- if (powersave_enabled) { +- bes_devel("[BH] No Device wakedown. Suspend.\n"); +-#ifndef FPGA_SETUP +- WARN_ON(bes2600_reg_write_16(hw_priv, +- ST90TDS_CONTROL_REG_ID, 0)); +- hw_priv->device_can_sleep = true; +-#endif +- } +- +- atomic_set(&hw_priv->bh_suspend, BES2600_BH_SUSPENDED); +- wake_up(&hw_priv->bh_evt_wq); +- status = wait_event_interruptible(hw_priv->bh_wq, +- BES2600_BH_RESUME == atomic_read( +- &hw_priv->bh_suspend)); +- if (status < 0) { +- wiphy_err(hw_priv->hw->wiphy, +- "%s: Failed to wait for resume: %ld.\n", +- __func__, status); +- break; +- } +- 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); +- continue; +- } +- +-test: +- tx += pending_tx; +- pending_tx = 0; +- +- if (rx) { +- size_t alloc_len; +- u8 *data; +- +-#ifdef INTERRUPT_WORKAROUND +- if(!(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)) +-#endif +- if (WARN_ON(bes2600_bh_read_ctrl_reg( +- hw_priv, &ctrl_reg))) +- break; +-rx: +- read_len = (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2; +- if (!read_len) { +- rx_burst = 0; +- goto tx; +- } +- +- if (WARN_ON((read_len < sizeof(struct wsm_hdr)) || +- (read_len > EFFECTIVE_BUF_SIZE))) { +- bes_devel("Invalid read len: %d", read_len); +- break; +- } +- +- /* Add SIZE of PIGGYBACK reg (CONTROL Reg) +- * to the NEXT Message length + 2 Bytes for SKB */ +- read_len = read_len + 2; +- +-#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES) +- alloc_len = hw_priv->sbus_ops->align_size( +- hw_priv->sbus_priv, read_len); +-#else /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */ +- /* Platform's SDIO workaround */ +- alloc_len = read_len & ~(SDIO_BLOCK_SIZE - 1); +- if (read_len & (SDIO_BLOCK_SIZE - 1)) +- alloc_len += SDIO_BLOCK_SIZE; +-#endif /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */ +- +- /* Check if not exceeding BES2600 capabilities */ +- if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) +- bes_devel("Read aligned len: %d\n", alloc_len); +- +- skb_rx = bes2600_get_skb(hw_priv, alloc_len); +- if (WARN_ON(!skb_rx)) +- break; +- +- skb_trim(skb_rx, 0); +- skb_put(skb_rx, read_len); +- data = skb_rx->data; +- if (WARN_ON(!data)) +- break; +- +- if (WARN_ON(bes2600_data_read(hw_priv, data, alloc_len))) +- break; +- +- /* Piggyback */ +- ctrl_reg = __le16_to_cpu( +- ((__le16 *)data)[alloc_len / 2 - 1]); +- +- wsm = (struct wsm_hdr *)data; +- wsm_len = __le32_to_cpu(wsm->len); +- if (WARN_ON(wsm_len > read_len)) +- break; +- +-#if defined(CONFIG_BES2600_WSM_DUMPS) +- if (unlikely(hw_priv->wsm_enable_wsm_dumps)) { +- u16 msgid, ifid; +- u16 *p = (u16 *)data; +- msgid = (*(p + 1)) & 0xC3F; +- ifid = (*(p + 1)) >> 6; +- ifid &= 0xF; +- bes_devel("[DUMP] <<< msgid 0x%.4X ifid %d len %d\n", msgid, ifid, *p); +- print_hex_dump(KERN_DEBUG, "<-- ", DUMP_PREFIX_NONE, data, min(wsm_len, wsm_dump_max)); +- } +-#endif /* CONFIG_BES2600_WSM_DUMPS */ +- +- wsm_id = __le32_to_cpu(wsm->id) & 0xFFF; +- wsm_seq = (__le32_to_cpu(wsm->id) >> 13) & 7; +- +- skb_trim(skb_rx, wsm_len); +- +- if (unlikely(wsm_id == 0x0800)) { +- wsm_handle_exception(hw_priv, +- &data[sizeof(*wsm)], +- wsm_len - sizeof(*wsm)); +- break; +- } else if (unlikely(!rx_resync)) { +- if (WARN_ON(wsm_seq != hw_priv->wsm_rx_seq)) { +-#if defined(CONFIG_BES2600_DUMP_ON_ERROR) +- BUG_ON(1); +-#endif /* CONFIG_BES2600_DUMP_ON_ERROR */ +- break; +- } +- } +- hw_priv->wsm_rx_seq = (wsm_seq + 1) & 7; +- rx_resync = 0; +- +- if (wsm_id & 0x0400) { +- int rc = wsm_release_tx_buffer(hw_priv, 1); +- if (WARN_ON(rc < 0)) +- break; +- else if (rc > 0) +- tx = 1; +- } +- +- /* bes2600_wsm_rx takes care on SKB livetime */ +- if (WARN_ON(wsm_handle_rx(hw_priv, wsm_id, wsm, +- &skb_rx))) +- break; +- +- if (skb_rx) { +- bes2600_put_skb(hw_priv, skb_rx); +- skb_rx = NULL; +- } +- +- read_len = 0; +- +- if (rx_burst) { +- bes2600_debug_rx_burst(hw_priv); +- --rx_burst; +- goto rx; +- } +- } +- +-tx: +- BUG_ON(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs); +- tx_burst = hw_priv->wsm_caps.numInpChBufs - +- hw_priv->hw_bufs_used; +- tx_allowed = tx_burst > 0; +- if (tx && tx_allowed) { +- size_t tx_len; +- u8 *data; +- int ret; +- +- if (hw_priv->device_can_sleep) { +- ret = bes2600_device_wakeup(hw_priv); +- if (WARN_ON(ret < 0)) +- break; +- else if (ret) +- hw_priv->device_can_sleep = false; +- else { +- /* Wait for "awake" interrupt */ +- pending_tx = tx; +- continue; +- } +- } +- +- wsm_alloc_tx_buffer(hw_priv); +- ret = wsm_get_tx(hw_priv, &data, &tx_len, &tx_burst, +- &vif_selected); +- if (ret <= 0) { +- wsm_release_tx_buffer(hw_priv, 1); +- if (WARN_ON(ret < 0)) +- break; +- } else { +- wsm = (struct wsm_hdr *)data; +- BUG_ON(tx_len < sizeof(*wsm)); +- BUG_ON(__le32_to_cpu(wsm->len) != tx_len); +- +-#if 0 /* count is not implemented */ +- if (ret > 1) +- atomic_add(1, &hw_priv->bh_tx); +-#else +- atomic_add(1, &hw_priv->bh_tx); +-#endif +- +-#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES) +- if (tx_len <= 8) +- tx_len = 16; +- tx_len = hw_priv->sbus_ops->align_size( +- hw_priv->sbus_priv, tx_len); +-#else /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */ +- /* HACK!!! Platform limitation. +- * It is also supported by upper layer: +- * there is always enough space at the +- * end of the buffer. */ +- if (tx_len & (SDIO_BLOCK_SIZE - 1)) { +- tx_len &= ~(SDIO_BLOCK_SIZE - 1); +- tx_len += SDIO_BLOCK_SIZE; +- } +-#endif /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */ +- +- /* Check if not exceeding BES2600 +- capabilities */ +- if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE)) +- bes_devel("Write aligned len: %d\n", tx_len); +- +- wsm->id &= __cpu_to_le32( +- ~WSM_TX_SEQ(WSM_TX_SEQ_MAX)); +- wsm->id |= cpu_to_le32(WSM_TX_SEQ( +- hw_priv->wsm_tx_seq)); +- +- if (WARN_ON(bes2600_data_write(hw_priv, +- data, tx_len))) { +- wsm_release_tx_buffer(hw_priv, 1); +- break; +- } +- +- if (vif_selected != -1) { +- hw_priv->hw_bufs_used_vif[ +- vif_selected]++; +- } +- +-#if defined(CONFIG_BES2600_WSM_DUMPS) +- if (unlikely(hw_priv->wsm_enable_wsm_dumps)) { +- u16 msgid, ifid; +- u16 *p = (u16 *)data; +- msgid = (*(p + 1)) & 0x3F; +- ifid = (*(p + 1)) >> 6; +- ifid &= 0xF; +- if (msgid == 0x0006) +- bes_devel("[DUMP] >>> msgid 0x%.4X ifid %d len %d MIB 0x%.4X\n", msgid, ifid, *p, *(p + 2)); +- else +- bes_devel("[DUMP] >>> msgid 0x%.4X ifid %d len %d\n", msgid, ifid, *p); +- print_hex_dump(KERN_DEBUG, "--> ", DUMP_PREFIX_NONE, data, min(__le32_to_cpu(wsm->len), wsm_dump_max)); +- } +-#endif /* CONFIG_BES2600_WSM_DUMPS */ +- +- wsm_txed(hw_priv, data); +- hw_priv->wsm_tx_seq = (hw_priv->wsm_tx_seq + 1) +- & WSM_TX_SEQ_MAX; +- +- if (tx_burst > 1) { +- bes2600_debug_tx_burst(hw_priv); +- ++rx_burst; +- goto tx; +- } +- } +- } +- +- if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) +- goto rx; +- } +- +- if (skb_rx) { +- bes2600_put_skb(hw_priv, skb_rx); +- skb_rx = NULL; +- } +- +- +- if (!term) { +- bes_devel("[BH] Fatal error, exitting.\n"); +-#if defined(CONFIG_BES2600_DUMP_ON_ERROR) +- BUG_ON(1); +-#endif /* CONFIG_BES2600_DUMP_ON_ERROR */ +- hw_priv->bh_error = 1; +-#if defined(CONFIG_BES2600_USE_STE_EXTENSIONS) +- spin_lock(&hw_priv->vif_list_lock); +- bes2600_for_each_vif(hw_priv, priv, i) { +- if (!priv) +- continue; +- ieee80211_driver_hang_notify(priv->vif, GFP_KERNEL); +- } +- spin_unlock(&hw_priv->vif_list_lock); +- bes2600_pm_stay_awake(&hw_priv->pm_state, 3*HZ); +-#endif +- /* TODO: schedule_work(recovery) */ +-#ifndef HAS_PUT_TASK_STRUCT +- /* The only reason of having this stupid code here is +- * that __put_task_struct is not exported by kernel. */ +- for (;;) { +- int status = wait_event_interruptible(hw_priv->bh_wq, ({ +- term = atomic_xchg(&hw_priv->bh_term, 0); +- (term); +- })); +- +- if (status || term) +- break; +- } +-#endif +- } +- return 0; +-} +-#else + + 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) + + tx = 0; + +- BUG_ON(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs); ++ /* ++ * Patch H: BUG_ON -> WARN_ON_ONCE in the steady-state ++ * hot path. The original BUG_ON ran every bh-loop ++ * iteration; tripping it on a bookkeeping bug locks ++ * the kernel up during normal operation, which is ++ * the wrong response. WARN_ON_ONCE surfaces the ++ * issue without taking the system down. ++ */ ++ WARN_ON_ONCE(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs); + 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) + goto tx; + + done: +- /* Re-enable device interrupts */ +- //hw_priv->sbus_ops->lock(hw_priv->sbus_priv); +- //__bes2600_irq_enable(1); +- //hw_priv->sbus_ops->unlock(hw_priv->sbus_priv); +- asm volatile ("nop"); ++ /* ++ * Patch H: dropped the dead `__bes2600_irq_enable(1)` / ++ * `asm volatile("nop")` placeholder that used to sit here. ++ * `__bes2600_irq_enable()` is a stub that returns 0 on ++ * bes2600 silicon — the IRQ is managed by sdio_claim_irq ++ * and chip-side firmware, not by a driver-side enable bit. ++ * (cw1200 inherited the function from a different chip ++ * shape; bes2600 kept the stub but the call sites are ++ * meaningless.) ++ */ ++ ; + } + +- /* Explicitly disable device interrupts */ +- hw_priv->sbus_ops->lock(hw_priv->sbus_priv); +- __bes2600_irq_enable(0); +- hw_priv->sbus_ops->unlock(hw_priv->sbus_priv); +- + 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) + } + 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 +--- 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) + } + #endif + +-int __bes2600_irq_enable(int enable) +-{ +- return 0; +-} ++/* ++ * Patch H: __bes2600_irq_enable stub removed. It was a no-op ++ * (always returned 0) inherited from cw1200 where the analogous ++ * function manipulates the chip's IRQ-enable register. bes2600 ++ * 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 +--- 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); + }; + + void bes2600_irq_handler(struct bes2600_common *priv); + +-/* This MUST be wrapped with hwbus_ops->lock/unlock! */ +-int __bes2600_irq_enable(int enable); ++/* 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 + diff --git a/patches/driver/bes2600/cumulative-c5x-danctnix/README.md b/patches/driver/bes2600/cumulative-c5x-danctnix/README.md new file mode 100644 index 0000000..25541b3 --- /dev/null +++ b/patches/driver/bes2600/cumulative-c5x-danctnix/README.md @@ -0,0 +1,35 @@ +# cumulative-c5x-danctnix — interim single-file cumulative + +**Series role**: ohm manifest's bes2600 driver patchset, c5x stack as +shipped in `linux-pinetab2-danctnix-besser` pkgrel=3 on 2026-05-18. + +## Why this is a single .patch and not split per-fix + +The 12-ish per-series mirror PR (kernel-agent#17) of the boltzmann-side +`marfrit/besser` series produced patches with DKMS-style paths +(`bes2600/*` at root) rather than in-tree staging paths +(`drivers/staging/bes2600/*`), and at least one entry has corrupted +mixed-prefix headers (`a/drivers/staging/bes2600/foo.c b/bes2600/foo.c`). +Those series do NOT apply cleanly to the linux-pinetab2 baseline. + +Audit performed 2026-05-18 during ohm migration: +- ka-promote ohm (using the per-series includes) produces a + 172 644-byte cumulative touching 27 file paths, of which 11 are + bogus DKMS-style or mixed-prefix. +- The hand-curated `0001-bes2600-besser-cumulative-series.patch` from + the working `danctnix-besser-pkgbuild` (boltzmann) is 148 149 bytes + touching 48 distinct in-tree staging files — and is what pkgrel=3 + actually builds with. + +This single-file cumulative is staged here so the ohm migration can +ship through the kernel-agent flow today without first reconstructing +12 series-dirs. The proper per-series split is tracked separately — +see kernel-agent issue (TBD) for the rewrite. + +## Provenance + +- Source file: `boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/0001-bes2600-besser-cumulative-series.patch` +- Reflects c5x driver state in `marfrit/bes2600-dkms-mobian` branch as + of 2026-05-08, applied against `drivers/staging/bes2600/` in-tree. +- Series legend (A, B, C v3, F, G, D, E, C2, c5.x, c6.x, c7, H — NOT + alphabetical) per the danctnix-besser-pkgbuild changelog comments. diff --git a/patches/driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5ghz-scan-and-allow-single-channel.patch b/patches/driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5ghz-scan-and-allow-single-channel.patch new file mode 100644 index 0000000..76df117 --- /dev/null +++ b/patches/driver/bes2600/scan-filter-5ghz-danctnix/0001-bes2600-filter-5ghz-scan-and-allow-single-channel.patch @@ -0,0 +1,168 @@ +From 093a5038b8b68f316d976b7cb69609ca7f24f322 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Mon, 18 May 2026 11:27:40 +0200 +Subject: [PATCH 1/2] bes2600: filter 5 GHz scans at the driver boundary + (besser#1) + +The BES2600 firmware refuses WSM start-scan for 5 GHz with status 2 +("rejected by policy"). This shows up in dmesg as the recurring + + wsm_generic_confirm failed for request 0x0007. + [SCAN] Scan failed (-22). + +pattern (besser issue #1, ~14-16/h on ohm/PineTab2 baseline). + +Trace shows every reject is the second of a back-to-back pair: mac80211 +splits multi-band hw_scan requests per band when the driver does not +set IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't), then re-invokes +drv_hw_scan from __ieee80211_scan_completed for each subsequent band. +The 2.4 GHz iteration succeeds; the 5 GHz iteration is what the +firmware rejects. See ieee80211_prep_hw_scan in net/mac80211/scan.c +for the loop, and the existing memory reference_bes2600_5ghz_scan_reject +for the firmware behaviour. + +The 056a71a defer-on-reject patch already in this tree handles the +BT-A2DP-coex branch and the consecutive-reject backoff, but it cannot +prevent the per-band-loop reject: by the time defer_should_scan is +consulted, the per-band call is already in flight, and the reject_count +gets reset on every successful 2.4 GHz scan in between (which is +~36% of attempts), so the threshold never trips. + +The fix: refuse the 5 GHz iteration upfront in bes2600_hw_scan. The +2.4 GHz scan still runs normally. The 5 GHz portion is reported as +aborted to userspace -- same outcome as today, minus the dmesg storm +and the wsm_generic_confirm WARN cascade. + +5 GHz band registration is intentionally left in place: direct-BSSID +association to a known 5 GHz AP still works (no scan is needed for +that path), and a future firmware update that fixes the scan behaviour +should not be foreclosed by changing band advertisement. + +Contract: per include/net/mac80211.h ieee80211_ops.hw_scan, a negative +return aborts the scan without requiring ieee80211_scan_completed(). +-EOPNOTSUPP is the semantically accurate code (operation is legal, +driver can't service it on this band today). + +Phase 3 evidence: +- baseline N=3: rate ~14.3-23.6/h converged at 14.3/h (matches OP) +- back-to-back scan gap: 6/6 rejected pairs <200us, 1/1 successful + pair was 114ms (single-band-only, no 5 GHz leg) +- defer log fires: 0/9 in 30-min window (056a71a structurally bypassed) + +Predicted Phase 7 delta: Pattern A 14/h -> 0/h. +--- + bes2600/scan.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index fb1d298..a81afb6 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -238,6 +238,28 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, + /* Scan when P2P_GO corrupt firmware MiniAP mode */ + if (priv->join_status == BES2600_JOIN_STATUS_AP) + return -EOPNOTSUPP; ++ ++ /* ++ * Firmware refuses WSM start-scan for 5 GHz with status 2 ("rejected ++ * by policy"); see besser issue #1. mac80211 splits multi-band ++ * hw_scan requests per-band when the driver does not set ++ * IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't -- see ++ * ieee80211_hw_set() calls in bes2600_main.c), so each per-band call ++ * has req->channels[] from one band only (see ieee80211_prep_hw_scan ++ * in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver ++ * boundary so userspace gets a clean aborted-scan for that portion ++ * rather than waiting for the firmware reject to cascade up. 5 GHz ++ * band registration stays intact so direct-BSSID association to a ++ * known 5 GHz AP still works (no scan needed for that path). ++ * ++ * Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan ++ * documentation, a negative return aborts the scan without requiring ++ * ieee80211_scan_completed(). ++ */ ++ if (req->n_channels > 0 && ++ req->channels[0]->band == NL80211_BAND_5GHZ) ++ return -EOPNOTSUPP; ++ + #if 0 + if (work_pending(&priv->offchannel_work) || + (hw_priv->roc_if_id != -1)) { +-- +2.54.0 + + +From 8cd10f487c8144d462a510812ba0fa717b3e24df Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Mon, 18 May 2026 15:56:34 +0200 +Subject: [PATCH 2/2] bes2600: scan-filter-5ghz: allow targeted single-channel + scans (besser#1 follow-up) + +The original Patch I refused EVERY 5 GHz scan request unconditionally +(req->n_channels > 0 && band == NL80211_BAND_5GHZ). This eliminated +the Pattern A storm but also broke 5 GHz association entirely: +NM / wpa_supplicant iterates a freq_list when a connection profile +specifies 802-11-wireless.band=a, issuing per-frequency single-channel +scans to find the BSS before associating. Those single-channel scans +were also refused by our guard, so the BSS was never seen and +'Wi-Fi network could not be found' was the only outcome. + +Tighten the guard: refuse only multi-channel 5 GHz scans (n_channels +> 1), which is the per-band-sweep pattern mac80211 issues internally +and the only one that triggers the firmware storm at the per-band +loop boundary. Single-channel 5 GHz scans pass through to firmware, +which generally accepts them -- and when they happen to be rejected, +the failure is isolated and doesn't cascade. + +Verified on ohm with pkgrel=3 (srcversion BEB625FA7443171EA8D55F7): + - Pattern A count since boot: 0 (Phase 7 prediction still holds) + - iw dev wlan0 scan freq 5180 -> allowed + - iw dev wlan0 scan freq 5180 5200 ... -> refused -EOPNOTSUPP + - NM 'nmcli connection up' with band=a -> associated to BSSID + c0:25:06:e6:5b:33 on 5240 MHz / ch.48 in ~1 second + - TX bitrate 150 Mbit/s MCS 7 40MHz short-GI (vs 72.2 Mbit/s + HT20 on 2.4 GHz) -- ~2x throughput recovered + +The change is a single byte (> 0 -> > 1) plus comment update; the +test confirmation above is what motivates it. + +Refs: besser#1 (closed but tracked for follow-up like this), original +Patch I sha 093a503. +--- + bes2600/scan.c | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c +index a81afb6..497523b 100644 +--- a/drivers/staging/bes2600/scan.c ++++ b/drivers/staging/bes2600/scan.c +@@ -248,15 +248,23 @@ int bes2600_hw_scan(struct ieee80211_hw *hw, + * has req->channels[] from one band only (see ieee80211_prep_hw_scan + * in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver + * boundary so userspace gets a clean aborted-scan for that portion +- * rather than waiting for the firmware reject to cascade up. 5 GHz +- * band registration stays intact so direct-BSSID association to a +- * known 5 GHz AP still works (no scan needed for that path). ++ * rather than waiting for the firmware reject to cascade up. ++ * ++ * Only the multi-channel case is refused (n_channels > 1): that's ++ * the per-band-sweep pattern mac80211 issues internally and the ++ * one that triggers the firmware storm at the per-band loop ++ * boundary. Single-channel 5 GHz scans (BSS verification, NM's ++ * per-freq iteration when 802-11-wireless.band=a is set) pass ++ * through to firmware, which generally accepts them since the ++ * storm is the back-to-back per-band issue, not a blanket 5 GHz ++ * reject. This preserves 5 GHz association via the ++ * "wpa_supplicant iterates freq_list per channel" path. + * + * Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan + * documentation, a negative return aborts the scan without requiring + * ieee80211_scan_completed(). + */ +- if (req->n_channels > 0 && ++ if (req->n_channels > 1 && + req->channels[0]->band == NL80211_BAND_5GHZ) + return -EOPNOTSUPP; + +-- +2.54.0 + diff --git a/patches/driver/bes2600/scan-filter-5ghz-danctnix/README.md b/patches/driver/bes2600/scan-filter-5ghz-danctnix/README.md new file mode 100644 index 0000000..1b8be78 --- /dev/null +++ b/patches/driver/bes2600/scan-filter-5ghz-danctnix/README.md @@ -0,0 +1,19 @@ +# scan-filter-5ghz-danctnix — close besser#1 + +Refuses multi-channel 5 GHz scan requests at the driver boundary with +`-EOPNOTSUPP`, eliminating the WSM 0x0007 reject storm. Single-channel +5 GHz scans still pass through (NM `802-11-wireless.band=a` BSS +verification path stays functional). + +Phase 7 baseline on ohm: Pattern A 14.3/h → 0/h (verified 2026-05-18, +30 min window). 5 GHz association achieves 150 Mbit/s MCS 7 HT40 SGI vs +72.2 on 2.4 GHz. + +Single combined patch file because the two commits in the source +(initial filter + `n_channels > 1` refinement) form a 2-commit +follow-up series and git apply concatenation handles both. Splitting +into two .patch files would mean a fragile dependency on cross-file +sequencing inside the same series-dir. + +Provenance: closes besser#1. Mirror of source-of-truth in +`marfrit/bes2600-dkms` branch `bes2600/scan-filter-5ghz`. -- 2.47.3 From 989b8842fb0c9e5e1b8ea589b15d1998577ea52e Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 16:52:46 +0200 Subject: [PATCH 2/2] patches/arch/arm64/xor-neon-...: append git-format-patch trailer The SCS-build-fix patch was missing the standard '-- \n2.54.0\n' trailer that git format-patch emits between patches. Without it, BSD-flavour patch(1) in makepkg's prepare() reads the trailing context line of the @@ hunk as the start of a new patch header and dies with 'malformed patch at line N'. Affects builds where ka-promote concatenates this series with any others. Reproduced 2026-05-18 on the first attempted ohm pkgrel=4 build. Cumulative b2sum changes accordingly: a807297b25be... -> bd42cd39106298879eeb... (size 157446 -> 157458; 12 bytes for the trailer) --- .../0001-arm64-xor-neon-ffixed-x18-build-fix.patch | 3 +++ 1 file changed, 3 insertions(+) diff --git a/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch index a264806..a838097 100644 --- a/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch +++ b/patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/0001-arm64-xor-neon-ffixed-x18-build-fix.patch @@ -34,3 +34,6 @@ index 1234567..2345678 100644 endif lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o +-- +2.54.0 + -- 2.47.3