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