From f203b70f4f10f6a1a29e9a628be2bdaa5eeabf66 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 16:50:41 +0200 Subject: [PATCH 1/8] 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`. From 989b8842fb0c9e5e1b8ea589b15d1998577ea52e Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 16:52:46 +0200 Subject: [PATCH 2/8] 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 + From a840f769077a9d81d58447ce359e98045263e8f7 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 16:58:19 +0200 Subject: [PATCH 3/8] patches/arch/arm64/xor-neon-...: fix malformed @@ hunk counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hunk header @@ -9,6 +9,10 @@ understated both old (actual 7) and new (actual 12) line counts by 1. patch(1) standalone tolerates this via fuzz, but in the concatenated cumulative the wrong counts cause patch to mis-judge the hunk boundary and read the trailing context line ('lib-...uaccess_flushcache.o') as the start of a new patch header — 'malformed patch at line 4526'. Cumulative b2sum: bd42cd39106298879eeb... -> ad9e2cb533957f218058... (size unchanged at 157 458; only the @@ counts in the SCS patch differ) --- .../0001-arm64-xor-neon-ffixed-x18-build-fix.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a838097..3233904 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 @@ -22,7 +22,7 @@ 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) +@@ -9,7 +9,12 @@ 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) From 84734ba5273922996023d10f51ea4d9a0d9d87f2 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 17:00:11 +0200 Subject: [PATCH 4/8] patches/arch/arm64/xor-neon-...: strip trailing git-format-patch sentinel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The '-- \n2.54.0\n\n' trailer added in 989b884 was wrong. The underlying problem was the malformed @@ hunk counts (off by 1 in both old and new), fixed in a840f76. With correct @@ counts, patch(1) processes the hunk fully and then sees the orphan trailer at EOF — which it tries to parse as the start of a new patch header ('malformed patch at line N: 2.54.0'). The original (no-trailer) shape works correctly in the concatenated cumulative as long as the @@ counts are right. Removing the trailer brings the file back to the original 1562-byte size and the cumulative b2sum to 334c37b5d37067982bd9... (size unchanged 157 458 -> 157 446 since the 12 byte trailer is gone). Lesson for ka-promote: when concatenating patches as a stream for patch(1), the LAST patch must not carry a trailing '-- \n\n' sentinel — the previous patches' sentinels are fine because they are followed by 'From ' headers that patch(1) recognises as the next patch boundary. Documented in series-dir README as a gotcha. --- .../0001-arm64-xor-neon-ffixed-x18-build-fix.patch | 3 --- 1 file changed, 3 deletions(-) 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 3233904..b23e7b2 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,6 +34,3 @@ index 1234567..2345678 100644 endif lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o --- -2.54.0 - From 4d98a8169d3319547873e45a0b712ebee992d2a1 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 18:01:41 +0200 Subject: [PATCH 5/8] fleet/ohm + patches/driver/bes2600/queue-pending-record-lock-bh-danctnix: bundle besser#18 fix into the migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls the besser#18 lockdep fix (originally on noether/bes2600-pending-record-lock-bh / PR #30) into this PR so the ohm migration ships a single self-consistent pkgrel that contains all three goal components: kernel-agent flow + Patch I + besser#18 fix (plus the GCC 15 SCS Makefile workaround, no-op while SCS=n). ohm.yaml includes now resolve to 4 patches: 1. driver/bes2600/cumulative-c5x-danctnix/ (148 149 B) 2. driver/bes2600/scan-filter-5ghz-danctnix/ ( 7 735 B) 3. arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/ (1 562 B) 4. driver/bes2600/queue-pending-record-lock-bh-danctnix/ (5 258 B) ---- cumulative.patch (162 704 B) b2sum 0eb091ddaba4a8f1c3c2a78eb8c621cdc6e6dfed6c43f7dac03e508a05b... Trailer-strip applied to the besser#18 patch source for the same reason as the SCS patch — it's now the last in the concatenated cumulative, and patch(1) errors on the orphan '-- \n2.54.0\n' EOF sentinel. Same gotcha documented in 84734ba. PR #30 (the standalone besser#18 mirror PR) becomes superfluous once this lands; close it as 'bundled into #28'. --- fleet/ohm.yaml | 3 + ...600-take-pending-record-lock-with-bh.patch | 118 ++++++++++++++++++ .../README.md | 19 +++ 3 files changed, 140 insertions(+) create mode 100644 patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/0001-bes2600-take-pending-record-lock-with-bh.patch create mode 100644 patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/README.md diff --git a/fleet/ohm.yaml b/fleet/ohm.yaml index edb0cd6..2495aa1 100644 --- a/fleet/ohm.yaml +++ b/fleet/ohm.yaml @@ -49,6 +49,9 @@ includes: # (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/ + # close besser#18 — pending_record_lock SOFTIRQ-safe -> -unsafe inversion. + # Mirror of marfrit/bes2600-dkms#11 (d95453c). 5-site spin_lock -> _bh. + - driver/bes2600/queue-pending-record-lock-bh-danctnix/ # Explicitly NOT included (decision logged): # - debian-copyright-fsf-address: Debian packaging metadata, not kernel diff --git a/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/0001-bes2600-take-pending-record-lock-with-bh.patch b/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/0001-bes2600-take-pending-record-lock-with-bh.patch new file mode 100644 index 0000000..a1d763d --- /dev/null +++ b/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/0001-bes2600-take-pending-record-lock-with-bh.patch @@ -0,0 +1,118 @@ +From d95453c98e31d7a47bc227aef5d0b426ac9e334b Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Mon, 18 May 2026 16:58:49 +0200 +Subject: [PATCH] =?UTF-8?q?bes2600:=20take=20pending=5Frecord=5Flock=20wit?= + =?UTF-8?q?h=20=5Fbh()=20to=20fix=20SOFTIRQ-safe=20=E2=86=92=20-unsafe=20i?= + =?UTF-8?q?nversion=20(besser#18)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +PROVE_LOCKING reports: + + WARNING: SOFTIRQ-safe -> SOFTIRQ-unsafe lock order detected + kworker/u16:1 is trying to acquire: + &hw_priv->tx_loop.pending_record_lock at bes2600_queue_clear+0x80 + and this task is already holding: + &queue->lock at bes2600_queue_clear+0x60 + + which would create a new lock dependency: + (&queue->lock){+.-.} -> (&hw_priv->tx_loop.pending_record_lock){+.+.} + + but this new dependency connects a SOFTIRQ-irq-safe lock: + (&queue->lock){+.-.} + ... which became SOFTIRQ-irq-safe at: + bes2600_tx -> ieee80211_handle_wake_tx_queue -> tasklet_action + to a SOFTIRQ-irq-unsafe lock: + (&hw_priv->tx_loop.pending_record_lock){+.+.} + ... which became SOFTIRQ-irq-unsafe at: + bes2600_queue_get_skb -> bes2600_join_work -> process_one_work + +queue->lock is taken consistently with spin_lock_bh() at 22 sites; +the nested acquisition of pending_record_lock at queue.c:289 (inside +the outer queue->lock_bh held at line 285) had it implicitly BH-safe +via the outer scope. But pending_record_lock is ALSO taken from +non-BH-disabled contexts: + + bes2600_queue_get_skb (queue.c:832) — process context via + bes2600_join_work (workqueue), no outer queue->lock held + bes2600_tx_loop_item_pending_check (tx_loop.c:112) + — TX-loop context, no outer + queue->lock held + +When CPU0 holds pending_record_lock from one of those non-BH paths +and a softirq fires that wants queue->lock, and CPU1 in softirq has +queue->lock and is about to acquire pending_record_lock — classic AB-BA +SOFTIRQ deadlock. + +The fix is the conservative one: take pending_record_lock with _bh() +at every site that's not already inside a queue->lock_bh-held scope. +That makes the lock consistently SOFTIRQ-safe, eliminating the +inversion. queue.c:289/295 stays as plain spin_lock because BH is +already disabled by the outer queue->lock_bh acquired at queue.c:285. + +Five sites converted: + bes2600/queue.c:832 -- spin_lock -> spin_lock_bh + bes2600/queue.c:839 -- spin_unlock -> spin_unlock_bh + bes2600/queue.c:844 -- spin_unlock -> spin_unlock_bh + bes2600/tx_loop.c:112 -- spin_lock -> spin_lock_bh + bes2600/tx_loop.c:114 -- spin_unlock -> spin_unlock_bh + +Contract: + - Documentation/locking/locktypes.rst spelling: spin_lock_bh() is + the canonical way to make a non-IRQ spinlock safe against + softirq preemption that might re-enter the same lock. + - Same shape as queue->lock in this driver and as is_drv->lock + in the cw1200 ancestor. + +Closes: besser#18 +Fixes: +Signed-off-by: Markus Fritsche +--- + bes2600/queue.c | 6 +++--- + bes2600/tx_loop.c | 4 ++-- + 2 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/staging/bes2600/queue.c b/drivers/staging/bes2600/queue.c +index cc606c1..4016b76 100644 +--- a/drivers/staging/bes2600/queue.c ++++ b/drivers/staging/bes2600/queue.c +@@ -829,19 +829,19 @@ int bes2600_queue_get_skb(struct bes2600_queue *queue, u32 packetID, + bes2600_queue_parse_id(packetID, &queue_generation, &queue_id, + &item_generation, &item_id, &if_id, &link_id); + +- spin_lock(&queue->stats->hw_priv->tx_loop.pending_record_lock); ++ spin_lock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock); + if (!list_empty(&queue->stats->hw_priv->tx_loop.pending_record_list)) { + list_for_each_entry_safe(record_item, temp_record_item, &queue->stats->hw_priv->tx_loop.pending_record_list, head) { + if (record_item->packetID == packetID) { + list_del(&record_item->head); + dev_kfree_skb(record_item->skb); + kfree(record_item); +- spin_unlock(&queue->stats->hw_priv->tx_loop.pending_record_lock); ++ spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock); + return -EINVAL; + } + } + } +- spin_unlock(&queue->stats->hw_priv->tx_loop.pending_record_lock); ++ spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock); + + item = &queue->pool[item_id]; + +diff --git a/drivers/staging/bes2600/tx_loop.c b/drivers/staging/bes2600/tx_loop.c +index e6cf072..0cf7ce1 100644 +--- a/drivers/staging/bes2600/tx_loop.c ++++ b/drivers/staging/bes2600/tx_loop.c +@@ -109,9 +109,9 @@ void bes2600_tx_loop_set_enable(struct bes2600_common *hw_priv, bool need_warn) + bes2600_queue_iterate_pending_packet(&hw_priv->tx_queue[i], + bes2600_tx_loop_item_pending_item); + } +- spin_lock(&hw_priv->tx_loop.pending_record_lock); ++ spin_lock_bh(&hw_priv->tx_loop.pending_record_lock); + bes2600_queue_iterate_record_pending_packet(hw_priv, bes2600_tx_loop_item_pending_item); +- spin_unlock(&hw_priv->tx_loop.pending_record_lock); ++ spin_unlock_bh(&hw_priv->tx_loop.pending_record_lock); + + if (atomic_read(&hw_priv->bh_rx) > 0) + wake_up(&hw_priv->bh_wq); diff --git a/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/README.md b/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/README.md new file mode 100644 index 0000000..28f809f --- /dev/null +++ b/patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/README.md @@ -0,0 +1,19 @@ +# queue-pending-record-lock-bh-danctnix — close besser#18 + +Converts `pending_record_lock` to `spin_lock_bh()` at the 5 sites +where it is taken in non-BH-disabled contexts (`bes2600_queue_get_skb` +called from `bes2600_join_work`, and `bes2600_tx_loop_item_pending_check`). + +Eliminates the PROVE_LOCKING SOFTIRQ-safe → SOFTIRQ-unsafe warning +reported in besser#18: `&queue->lock` (taken with `_bh` everywhere, +including the nested acquisition at `queue.c:289` that holds +`pending_record_lock` as inner) was registered SOFTIRQ-irq-safe by +lockdep, but `pending_record_lock` was sometimes taken without BH +disable, creating an AB-BA deadlock window. + +Provenance: +- Source-of-truth commit on `marfrit/bes2600-dkms` branch + `bes2600/queue-pending-record-lock-bh-fix`, commit `d95453c`. +- This file is the same commit's `git format-patch` output with + the DKMS-style `bes2600/foo.c` paths rewritten to in-tree + `drivers/staging/bes2600/foo.c` paths via sed. From 878e86f103ab16c66bac7379e730484f5a66f3ad Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 18:15:45 +0200 Subject: [PATCH 6/8] patches/arch/arm64/xor-neon-...: restore trailer (SCS is no longer last) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the SCS patch was the LAST patch in ohm's cumulative, the trailing '-- \n2.54.0\n' git-format-patch sentinel was an orphan that patch(1) read as a malformed header — fixed in 84734ba by stripping the trailer. Now besser#18 (queue-pending-record-lock-bh-danctnix) is added at the end of ohm.yaml's includes. SCS is no longer last. Without its trailer to mark end-of-patch, patch(1) reads straight into besser#18's 'From d95453c... Mon Sep 17 00:00:00 2001' line and errors as 'malformed patch at line N: From ...'. Restoring the trailer makes the separator unambiguous again. Cumulative b2sum: 0eb091ddaba4a8f1c3c2a78... -> ceec602afa8574c74354... Size: 162 704 -> 162 716 (+12 = the trailer bytes). This rule — 'only the LAST patch must lack a trailer; all others must keep theirs' — is sensitive to ohm.yaml include ordering, which is brittle. Filed as a kernel-agent followup: ka-promote should rewrite trailers automatically (always add to non-last, always strip from last) so source patches don't need to be ordering-aware. --- .../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 b23e7b2..3233904 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 + From 3ee0ef7d86e5edd8adc7fffaae03333c99263882 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 19:17:22 +0200 Subject: [PATCH 7/8] patches/arch/arm64/xor-neon-...: correct @@ hunk counts (overcorrected in a840f76) a840f76 changed @@ from -9,6 +9,10 to -9,7 +9,12 but overshot by 1. Actual hunk is 6 context + 5 add = -9,6 +9,11. Wrong counts were silently masked in pkgrel=4 build #4 by the trailer-stripped EOF letting patch fuzz recover. pkgrel=5 with besser#18 after SCS exposes it as 'malformed patch at line N: 2.54.0'. Cumulative b2sum: ceec602afa8574c74354... -> 50397711a6a3ba522283... Size unchanged 162 716. --- .../0001-arm64-xor-neon-ffixed-x18-build-fix.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3233904..f6aa1bb 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 @@ -22,7 +22,7 @@ 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,7 +9,12 @@ ifeq ($(CONFIG_KERNEL_MODE_NEON), y) +@@ -9,6 +9,11 @@ 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) From 731e98e07901de6f9a1d7f95a5994517dc663c7f Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Mon, 18 May 2026 20:52:59 +0000 Subject: [PATCH 8/8] fleet/ohm.yaml: fix arch/arm64 include path after merge rename The merge commit renamed arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/ to arch/arm64/scs-arm-neon-build-fix/ (= main's canonical name) but the include reference in ohm.yaml didn't get updated atomically. Update the include path to match the renamed dir; ka-promote would have exit-2'd on this manifest otherwise. Co-Authored-By: Claude Opus 4.7 (1M context) --- fleet/ohm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet/ohm.yaml b/fleet/ohm.yaml index 2495aa1..5456d01 100644 --- a/fleet/ohm.yaml +++ b/fleet/ohm.yaml @@ -48,7 +48,7 @@ includes: # 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/ + - arch/arm64/scs-arm-neon-build-fix/ # close besser#18 — pending_record_lock SOFTIRQ-safe -> -unsafe inversion. # Mirror of marfrit/bes2600-dkms#11 (d95453c). 5-site spin_lock -> _bh. - driver/bes2600/queue-pending-record-lock-bh-danctnix/