diff --git a/patches/driver/bes2600/README.md b/patches/driver/bes2600/README.md new file mode 100644 index 0000000..4b456be --- /dev/null +++ b/patches/driver/bes2600/README.md @@ -0,0 +1,92 @@ +# patches/driver/bes2600/ + +BES2600 WiFi driver patches (`drivers/staging/bes2600/*`, mainline-bound). +Mirrored from `marfrit/besser/patches/` on 2026-05-16. + +Scope tag: `driver:bes2600` (see `fleet/ohm.yaml` for the consumer). +Consumer: ohm (PineTab2, RK3566 + BES2600 SDIO). + +## Series taxonomy + +30 series (15 base + 15 `-danctnix` siblings). The `-danctnix` +variants exist because vanilla series don't apply on the DanctNIX +kernel base (slightly different in-tree state for `drivers/staging/bes2600/*`). +Keep both as separate series until BES2600 lands upstream, then +collapse — issue #2 acceptance criterion. + +Each series directory contains numbered `.patch` files plus +optionally a `0000-cover-letter.patch` for multi-patch series. + +## Promotion eligibility (per series) + +Marked here for the kernel-agent CLI (`ka-promote`) to pick up. +Markus to update as series mature. Default UNSET means "ask before +including in a build". + +| Series | promote_eligible | Notes | +|------------------------------------|------------------|-------------------------------------------------------------| +| `debian-copyright-fsf-address` | unset | Debian packaging metadata; not kernel-side | +| `drop-dpd-file-paths` | unset | | +| `drop-dpd-file-paths-danctnix` | unset | DanctNIX sibling | +| `drop-orphan-file-io` | unset | | +| `drop-orphan-file-io-danctnix` | unset | DanctNIX sibling | +| `enable-testmode` | unset | | +| `factory-drop-kernel-write` | unset | | +| `factory-drop-kernel-write-danctnix` | unset | DanctNIX sibling | +| `factory-series` | unset | | +| `factory-thread-dev` | unset | | +| `lmac-recover-via-mmc-hw-reset` | unset | | +| `lmac-recover-via-mmc-hw-reset-danctnix` | unset | DanctNIX sibling | +| `pm-detect-firmware-unsupported` | unset | | +| `pm-detect-firmware-unsupported-danctnix` | unset | DanctNIX sibling | +| `pm-gate-on-handshake` | unset | | +| `pm-state-resync` | unset | | +| `pm-state-resync-danctnix` | unset | DanctNIX sibling | +| `pm-timeout-silence` | unset | | +| `pm-timeout-silence-danctnix` | unset | DanctNIX sibling | +| `pm-wake-consume-state` | unset | | +| `pm-wake-consume-state-danctnix` | unset | DanctNIX sibling | +| `remove-chardev-user-interface` | unset | Cross-ref `bes_chardev` merge regression (besser #17) | +| `scan-defer-backoff-tune` | unset | | +| `scan-defer-backoff-tune-danctnix` | unset | DanctNIX sibling | +| `scan-defer-on-reject` | unset | | +| `scan-defer-on-reject-danctnix` | unset | DanctNIX sibling | +| `staging-prep-series` | unset | 7-patch cover-letter series; upstream-staging-prep work | +| `staging-prep-series-danctnix` | unset | DanctNIX sibling | +| `tx-sdio-dma-oob` | unset | | +| `tx-sdio-dma-oob-danctnix` | unset | DanctNIX sibling | + +## DKMS-to-in-tree transition path + +`bes2600-dkms` (Mobian fork, in `marfrit/bes2600-dkms`) is the +out-of-tree shim that ohm currently uses for the BES2600 wifi+BT. +Once these `driver/bes2600/` series land in mainline (or at least in +DanctNIX's PineTab2 kernel base): + +1. ohm's manifest drops the `bes2600-dkms` package dependency +2. `kernel-agent` builds the in-tree variant via the series listed here +3. `marfrit/bes2600-dkms` repo gets archived (kept as history) +4. PineTab2 buyers from then on get bes2600 directly out of the kernel + +Track the dropdown in `fleet/ohm.yaml` (`replaces_dkms: bes2600-dkms` +once the cumulative series is enough to replace it). + +## Cumulative-patch generation order + +The current single-patch cumulative (`0001-bes2600-besser-cumulative-series.patch` +in the existing PKGBUILD) is generated in this order on boltzmann: + + A, B, C v3, F, G, D, E, C2, c5.x, c6.x, c7, H + +This is NOT alphabetical — `C2` follows `E` rather than coming after +`C v3`. `ka-promote` MUST honor an explicit series-ordering field +when concatenating, not sort by series name. Field name TBD; suggest +adding `apply_order:` to `fleet/ohm.yaml` (issue #5 will surface this +when the cumulative gets regenerated). + +## References + +- Issue: `git.reauktion.de/marfrit/kernel-agent/issues/2` +- Source repo: `git.reauktion.de/marfrit/besser/patches/` +- Consumer: ohm (`fleet/ohm.yaml`) +- Related: `bes2600-dkms`, `linux-pinetab2-danctnix-besser` PKGBUILD diff --git a/patches/driver/bes2600/debian-copyright-fsf-address/0001-debian-copyright-drop-obsolete-FSF-street-address.patch b/patches/driver/bes2600/debian-copyright-fsf-address/0001-debian-copyright-drop-obsolete-FSF-street-address.patch new file mode 100644 index 0000000..ea08e46 --- /dev/null +++ b/patches/driver/bes2600/debian-copyright-fsf-address/0001-debian-copyright-drop-obsolete-FSF-street-address.patch @@ -0,0 +1,38 @@ +From f31c57adf736df52b3f393f2650920af98b8e8f1 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 24 Apr 2026 09:40:44 +0200 +Subject: [PATCH] debian/copyright: drop obsolete FSF street address + +The 'You should have received a copy ... write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301 USA' paragraph flags the lintian tag +'old-fsf-address-in-copyright-file'. Debian prefers either no +address at all or an https://www.gnu.org/licenses/ reference; +in this file /usr/share/common-licenses/LGPL-2.1 is already +cited a few lines below, so the address is redundant. Replace +with the gnu.org URL per current FSF boilerplate. + +Pre-existing text, no change to the licence terms. + +Signed-off-by: Markus Fritsche +--- + debian/copyright | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/debian/copyright b/debian/copyright +index 961fc90..3228eec 100644 +--- a/debian/copyright ++++ b/debian/copyright +@@ -18,8 +18,7 @@ License: LGPL-2.1 + License for more details. + . + You should have received a copy of the GNU Lesser General Public License +- along with this library; if not, write to the Free Software Foundation, Inc., +- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ along with this library; if not, see . + . + On Debian systems, the full text of the GNU Lesser General Public License + version 2.1 can be found in the file "/usr/share/common-licenses/LGPL-2.1". +-- +2.53.0 + diff --git a/patches/driver/bes2600/drop-dpd-file-paths-danctnix/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch b/patches/driver/bes2600/drop-dpd-file-paths-danctnix/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch new file mode 100644 index 0000000..76f6100 --- /dev/null +++ b/patches/driver/bes2600/drop-dpd-file-paths-danctnix/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch @@ -0,0 +1,293 @@ +From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:04:11 +0200 +Subject: [PATCH] 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.53.0 + diff --git a/patches/driver/bes2600/drop-dpd-file-paths/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch b/patches/driver/bes2600/drop-dpd-file-paths/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch new file mode 100644 index 0000000..8339996 --- /dev/null +++ b/patches/driver/bes2600/drop-dpd-file-paths/0001-bes2600-drop-BES2600_WRITE_DPD_TO_FILE-kernel-file-p.patch @@ -0,0 +1,293 @@ +From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:04:11 +0200 +Subject: [PATCH] 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/bes2600/Makefile b/bes2600/Makefile +index 2c1a850..0dd3606 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/bes_chardev.c b/bes2600/bes_chardev.c +index e2e4f1b..a02d6d9 100644 +--- a/bes2600/bes_chardev.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/drop-orphan-file-io-danctnix/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch b/patches/driver/bes2600/drop-orphan-file-io-danctnix/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch new file mode 100644 index 0000000..38fef50 --- /dev/null +++ b/patches/driver/bes2600/drop-orphan-file-io-danctnix/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch @@ -0,0 +1,168 @@ +From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:19:27 +0200 +Subject: [PATCH] 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 6ed6b15..9d2aac5 100644 +--- a/drivers/staging/bes2600/main.c ++++ b/drivers/staging/bes2600/main.c +@@ -790,41 +790,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.53.0 + diff --git a/patches/driver/bes2600/drop-orphan-file-io/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch b/patches/driver/bes2600/drop-orphan-file-io/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch new file mode 100644 index 0000000..0cea073 --- /dev/null +++ b/patches/driver/bes2600/drop-orphan-file-io/0001-bes2600-drop-orphan-DATA_DUMP_OBSERVE-and-access_fil.patch @@ -0,0 +1,168 @@ +From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:19:27 +0200 +Subject: [PATCH] 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/bes2600/bes_fw.c b/bes2600/bes_fw.c +index 133c945..d612c3c 100644 +--- a/bes2600/bes_fw.c ++++ b/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/bes2600/main.c b/bes2600/main.c +index 6ed6b15..9d2aac5 100644 +--- a/bes2600/main.c ++++ b/bes2600/main.c +@@ -790,41 +790,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.53.0 + diff --git a/patches/driver/bes2600/enable-testmode/0001-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch b/patches/driver/bes2600/enable-testmode/0001-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch new file mode 100644 index 0000000..276d176 --- /dev/null +++ b/patches/driver/bes2600/enable-testmode/0001-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch @@ -0,0 +1,143 @@ +From 9398d3028bc9d2f4ccbf8e830f8e9799bf065ce4 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:04:27 +0200 +Subject: [PATCH] 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/bes2600/Makefile b/bes2600/Makefile +index 300912b..39150e0 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/bes_log.h b/bes2600/bes_log.h +index 605cea8..65cf703 100644 +--- a/bes2600/bes_log.h ++++ b/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/bes2600/sta.c b/bes2600/sta.c +index aa69eb8..5f1a456 100644 +--- a/bes2600/sta.c ++++ b/bes2600/sta.c +@@ -3633,7 +3633,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; +@@ -3663,7 +3663,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; +@@ -3703,7 +3703,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.53.0 + diff --git a/patches/driver/bes2600/factory-drop-kernel-write-danctnix/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch b/patches/driver/bes2600/factory-drop-kernel-write-danctnix/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch new file mode 100644 index 0000000..e1555cb --- /dev/null +++ b/patches/driver/bes2600/factory-drop-kernel-write-danctnix/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch @@ -0,0 +1,156 @@ +From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 19:31:25 +0200 +Subject: [PATCH] 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.53.0 + diff --git a/patches/driver/bes2600/factory-drop-kernel-write/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch b/patches/driver/bes2600/factory-drop-kernel-write/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch new file mode 100644 index 0000000..294ab4d --- /dev/null +++ b/patches/driver/bes2600/factory-drop-kernel-write/0001-bes2600-drop-kernel_write-persistence-from-factory-c.patch @@ -0,0 +1,156 @@ +From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 19:31:25 +0200 +Subject: [PATCH] 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/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c +index 1cda447..1b43b41 100644 +--- a/bes2600/bes2600_factory.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/factory-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch b/patches/driver/bes2600/factory-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch new file mode 100644 index 0000000..7d2a684 --- /dev/null +++ b/patches/driver/bes2600/factory-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch @@ -0,0 +1,144 @@ +From 1a5d54a3213041262caf1605bb19c66ddded41f7 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 10:09:44 +0200 +Subject: [PATCH 1/2] 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/bes2600/Makefile b/bes2600/Makefile +index 300912b..788aee2 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c +index dc5d3da..8d60b7c 100644 +--- a/bes2600/bes2600_factory.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/factory-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch b/patches/driver/bes2600/factory-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch new file mode 100644 index 0000000..c55db93 --- /dev/null +++ b/patches/driver/bes2600/factory-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch @@ -0,0 +1,83 @@ +From 82ba594a444a855310fbbe2a5c8ff02f211d8e83 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:17:56 +0200 +Subject: [PATCH 2/2] 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/bes2600/Makefile b/bes2600/Makefile +index 788aee2..2dcba09 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/wsm.h b/bes2600/wsm.h +index 0673131..22845ac 100644 +--- a/bes2600/wsm.h ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/factory-thread-dev/0001-bes2600-thread-struct-device-through-factory-request.patch b/patches/driver/bes2600/factory-thread-dev/0001-bes2600-thread-struct-device-through-factory-request.patch new file mode 100644 index 0000000..4052ba9 --- /dev/null +++ b/patches/driver/bes2600/factory-thread-dev/0001-bes2600-thread-struct-device-through-factory-request.patch @@ -0,0 +1,116 @@ +From 8732881c5916106539b9071b51710489c57e8d73 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:18:38 +0200 +Subject: [PATCH] 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/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c +index 8d60b7c..1cda447 100644 +--- a/bes2600/bes2600_factory.c ++++ b/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/bes2600/bes2600_factory.h b/bes2600/bes2600_factory.h +index 3835b0d..7dbe9f8 100644 +--- a/bes2600/bes2600_factory.h ++++ b/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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..371ef4f 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch b/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch new file mode 100644 index 0000000..c6b3392 --- /dev/null +++ b/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset-danctnix/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch @@ -0,0 +1,251 @@ +From 9ea8a8e810ee5eb220de700a5c0a6d1153b15130 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Mon, 27 Apr 2026 06:32:41 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes2600_sdio.c | 29 +++++++++++++ + drivers/staging/bes2600/bes_chardev.c | 59 +++++++++++++++++++++++++- + drivers/staging/bes2600/bes_chardev.h | 1 + + drivers/staging/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 b9d836fab7af..f7f86d765bba 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 455108a2dd66..b776aab5e062 100644 +--- a/drivers/staging/bes2600/bes_chardev.c ++++ b/drivers/staging/bes2600/bes_chardev.c +@@ -626,6 +626,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; +@@ -726,8 +768,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 c627bb7c3d65..ca8419eead8f 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 1f2c0cda73de..cb9089004041 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.53.0 + diff --git a/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch b/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch new file mode 100644 index 0000000..d2fcf33 --- /dev/null +++ b/patches/driver/bes2600/lmac-recover-via-mmc-hw-reset/0001-bes2600-recover-wedged-firmware-via-mmc_hw_reset-on-.patch @@ -0,0 +1,251 @@ +From 460495803346f71a9d5dcc634180e5368ff9b1dc Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Sun, 26 Apr 2026 22:31:58 +0200 +Subject: [PATCH] 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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index 3e04e8c..e5840c8 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/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/bes2600/bes_chardev.c b/bes2600/bes_chardev.c +index a02d6d9..d1375bc 100644 +--- a/bes2600/bes_chardev.c ++++ b/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/bes2600/bes_chardev.h b/bes2600/bes_chardev.h +index 15602ba..3f0c59b 100644 +--- a/bes2600/bes_chardev.h ++++ b/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/bes2600/sbus.h b/bes2600/sbus.h +index 1f2c0cd..cb90890 100644 +--- a/bes2600/sbus.h ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/pm-detect-firmware-unsupported-danctnix/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch b/patches/driver/bes2600/pm-detect-firmware-unsupported-danctnix/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch new file mode 100644 index 0000000..e26b391 --- /dev/null +++ b/patches/driver/bes2600/pm-detect-firmware-unsupported-danctnix/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch @@ -0,0 +1,209 @@ +From d1de35c62930b1bc035d3863d75901356548b6f0 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 16:54:07 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes_pwr.c | 70 ++++++++++++++++++++++++++++++- + drivers/staging/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 d54e1a0bab0c..ebaa42e3e61e 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 6bc44acd7501..92de90b398c6 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.53.0 + diff --git a/patches/driver/bes2600/pm-detect-firmware-unsupported/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch b/patches/driver/bes2600/pm-detect-firmware-unsupported/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch new file mode 100644 index 0000000..a042abc --- /dev/null +++ b/patches/driver/bes2600/pm-detect-firmware-unsupported/0001-bes2600-self-detect-when-firmware-does-not-honor-PSM.patch @@ -0,0 +1,209 @@ +From f12e87002576f094c441ac6c945a451c88868592 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 16:54:06 +0200 +Subject: [PATCH] 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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index b7b6c2f..620acef 100644 +--- a/bes2600/bes_pwr.c ++++ b/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/bes2600/bes_pwr.h b/bes2600/bes_pwr.h +index 6bc44ac..92de90b 100644 +--- a/bes2600/bes_pwr.h ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/pm-gate-on-handshake/0001-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch b/patches/driver/bes2600/pm-gate-on-handshake/0001-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch new file mode 100644 index 0000000..a538337 --- /dev/null +++ b/patches/driver/bes2600/pm-gate-on-handshake/0001-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch @@ -0,0 +1,105 @@ +From 80178ec9b1f83aed1dcce9ea7ca02bc81341ba01 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:37:45 +0200 +Subject: [PATCH] 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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index e7a1045..f62ae22 100644 +--- a/bes2600/bes_pwr.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/pm-state-resync-danctnix/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch b/patches/driver/bes2600/pm-state-resync-danctnix/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch new file mode 100644 index 0000000..083ba8f --- /dev/null +++ b/patches/driver/bes2600/pm-state-resync-danctnix/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch @@ -0,0 +1,246 @@ +From 4ab8c790304206abd134de48c878b637a70f3c59 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:05:27 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes_pwr.c | 94 ++++++++++++++++++++++++++++--- + drivers/staging/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 f62ae226d295..de46e5826ee7 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_err("%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_err("%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 1ba866c25c42..6bc44acd7501 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.53.0 + diff --git a/patches/driver/bes2600/pm-state-resync/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch b/patches/driver/bes2600/pm-state-resync/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch new file mode 100644 index 0000000..61d83cf --- /dev/null +++ b/patches/driver/bes2600/pm-state-resync/0001-bes2600-gate-PM-indication-completion-on-pending-req.patch @@ -0,0 +1,246 @@ +From c57c77e446d9a552b537175453b838d0400ff41d Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:05:27 +0200 +Subject: [PATCH] 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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index 474b6f1..9b4a4de 100644 +--- a/bes2600/bes_pwr.c ++++ b/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/bes2600/bes_pwr.h b/bes2600/bes_pwr.h +index 1ba866c..6bc44ac 100644 +--- a/bes2600/bes_pwr.h ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/pm-timeout-silence-danctnix/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch b/patches/driver/bes2600/pm-timeout-silence-danctnix/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch new file mode 100644 index 0000000..77291f2 --- /dev/null +++ b/patches/driver/bes2600/pm-timeout-silence-danctnix/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch @@ -0,0 +1,53 @@ +From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:35:17 +0200 +Subject: [PATCH] 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.53.0 + diff --git a/patches/driver/bes2600/pm-timeout-silence/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch b/patches/driver/bes2600/pm-timeout-silence/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch new file mode 100644 index 0000000..4ad3ceb --- /dev/null +++ b/patches/driver/bes2600/pm-timeout-silence/0001-bes2600-demote-wait-pm-ind-timeout-from-bes_err-to-b.patch @@ -0,0 +1,53 @@ +From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 20:35:17 +0200 +Subject: [PATCH] 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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index f62ae22..474b6f1 100644 +--- a/bes2600/bes_pwr.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/pm-wake-consume-state-danctnix/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch b/patches/driver/bes2600/pm-wake-consume-state-danctnix/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch new file mode 100644 index 0000000..0655857 --- /dev/null +++ b/patches/driver/bes2600/pm-wake-consume-state-danctnix/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch @@ -0,0 +1,190 @@ +From 706a594dab68779294e4fff9705a6e1df46ec1af Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:23:35 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes2600_sdio.c | 15 ++++++- + drivers/staging/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 b9d836fab7af..929503547cfd 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -1388,7 +1388,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; + } +@@ -1420,7 +1427,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 de46e5826ee7..d54e1a0bab0c 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.53.0 + diff --git a/patches/driver/bes2600/pm-wake-consume-state/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch b/patches/driver/bes2600/pm-wake-consume-state/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch new file mode 100644 index 0000000..e32646c --- /dev/null +++ b/patches/driver/bes2600/pm-wake-consume-state/0001-bes2600-short-circuit-wake-handshake-when-chip-is-co.patch @@ -0,0 +1,190 @@ +From 822a5f1bab37e3f61b91aaf304ec1c54b42d639a Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 15:23:34 +0200 +Subject: [PATCH] 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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index 3e04e8c..acc0f19 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/bes2600/bes2600_sdio.c +@@ -1388,7 +1388,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; + } +@@ -1420,7 +1427,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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index 9b4a4de..b7b6c2f 100644 +--- a/bes2600/bes_pwr.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/remove-chardev-user-interface/0001-bes2600-remove-userspace-dev-bes2600-character-devic.patch b/patches/driver/bes2600/remove-chardev-user-interface/0001-bes2600-remove-userspace-dev-bes2600-character-devic.patch new file mode 100644 index 0000000..af1d781 --- /dev/null +++ b/patches/driver/bes2600/remove-chardev-user-interface/0001-bes2600-remove-userspace-dev-bes2600-character-devic.patch @@ -0,0 +1,675 @@ +From f43bcc5dda0a9120aee62cce0cec1a8c851cb4ef Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:55:18 +0200 +Subject: [PATCH] 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 | 519 ------------------------------------------ + 1 file changed, 519 deletions(-) + +diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c +index 9038e48..e2e4f1b 100644 +--- a/bes2600/bes_chardev.c ++++ b/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; +@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on) + return ret; + } + +-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) + { +@@ -603,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) +@@ -1124,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"); + +@@ -1146,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"); + } + } + +@@ -1247,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); +@@ -1318,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) +@@ -1336,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.53.0 + diff --git a/patches/driver/bes2600/scan-defer-backoff-tune-danctnix/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch b/patches/driver/bes2600/scan-defer-backoff-tune-danctnix/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch new file mode 100644 index 0000000..0d4c7d3 --- /dev/null +++ b/patches/driver/bes2600/scan-defer-backoff-tune-danctnix/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch @@ -0,0 +1,109 @@ +From 3d98404c1a85ef33e9fc1422042c71dc90f3b255 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 14:32:18 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/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 5f6af3bc81ba..b944adcaa08c 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.53.0 + diff --git a/patches/driver/bes2600/scan-defer-backoff-tune/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch b/patches/driver/bes2600/scan-defer-backoff-tune/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch new file mode 100644 index 0000000..da8fd04 --- /dev/null +++ b/patches/driver/bes2600/scan-defer-backoff-tune/0001-bes2600-widen-scan-defer-backoff-to-30s-and-decay-co.patch @@ -0,0 +1,109 @@ +From db4ea70fb5dae1b2ab9c06dd91f1d7b2b9dcf09c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Tue, 28 Apr 2026 14:32:18 +0200 +Subject: [PATCH] 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/bes2600/scan.c b/bes2600/scan.c +index faa1c90..ad5033b 100644 +--- a/bes2600/scan.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/scan-defer-on-reject-danctnix/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch b/patches/driver/bes2600/scan-defer-on-reject-danctnix/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch new file mode 100644 index 0000000..20b93b0 --- /dev/null +++ b/patches/driver/bes2600/scan-defer-on-reject-danctnix/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch @@ -0,0 +1,226 @@ +From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 24 Apr 2026 21:31:45 +0200 +Subject: [PATCH] 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 b2c22e7..faa1c90 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) + { +@@ -702,10 +741,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.53.0 + diff --git a/patches/driver/bes2600/scan-defer-on-reject/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch b/patches/driver/bes2600/scan-defer-on-reject/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch new file mode 100644 index 0000000..42471c1 --- /dev/null +++ b/patches/driver/bes2600/scan-defer-on-reject/0001-bes2600-defer-scan-and-soften-WARN-on-firmware-rejec.patch @@ -0,0 +1,226 @@ +From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Fri, 24 Apr 2026 21:31:45 +0200 +Subject: [PATCH] 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/bes2600/scan.c b/bes2600/scan.c +index b2c22e7..faa1c90 100644 +--- a/bes2600/scan.c ++++ b/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) + { +@@ -702,10 +741,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/bes2600/scan.h b/bes2600/scan.h +index e50fa36..1f3adea 100644 +--- a/bes2600/scan.h ++++ b/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/bes2600/wsm.c b/bes2600/wsm.c +index d40df30..55a4e2b 100644 +--- a/bes2600/wsm.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0000-cover-letter.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0000-cover-letter.patch new file mode 100644 index 0000000..870126a --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0000-cover-letter.patch @@ -0,0 +1,186 @@ +From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001 +Message-ID: +From: Markus Fritsche +Date: Thu, 23 Apr 2026 12:35:28 +0200 +Subject: [PATCH 0/7] bes2600: staging-prep cleanup for PineTab2 (BES2600WM) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This series is a staging-prep cleanup for the out-of-tree Bestechnic +BES2600WM Wi-Fi/BT combo-chip driver as shipped by Mobian's bes2600-dkms +package (and in-tree at drivers/staging/bes2600/ in the danctnix +linux-pinetab2 fork). Target hardware is the Pine64 PineTab2 (RK3566 ++ BES2600WM, SDIO vendor 0xBE57 / device 0x2002). + +The driver descends from the ST-Ericsson CW1200 (drivers/net/wireless/ +st/cw1200/) -- same author, Dmitry Tarnyagin, shared WSM host<->firmware +protocol, shared SDIO bus backend. Kconfig ancestry markers survive in +this tree today: CONFIG_BES2600_USE_STE_EXTENSIONS (STE = ST-Ericsson), +CONFIG_BES2600_WSM_DEBUG (WSM). ST-Ericsson wound down in 2013; +Bestechnic (founded 2015) appears to have inherited or licensed the +CW1200 IP. No linux-wireless RFC has ever linked the two chips. + +The series fixes observable defects on a PineTab2 running linux-pinetab2 +6.19.10-danctnix1-1 and removes two upstream blockers. Each patch is +independently testable and bisectable; the order below preserves +dependencies. + +## What the series does + +* 1/7 -- Replace filp_open() + kernel_read() in the factory-calibration + read path with request_firmware(). Repoint the FACTORY_PATH macro to + the firmware-class name (bes2600/bes2600_factory.txt, matching the + /lib/firmware/ layout). Kills a kernel-mainline anti-pattern and the + '(NULL device *): read and check /lib/firmware/bes2600_factory.txt + error' boot spam on PineTab2. + +* 2/7 -- Default STANDARD_FACTORY_EFUSE_FLAG from y to n. The shipped + bes2600_factory.txt on PineTab2 contains 30 calibration fields; the + driver was expecting 31 (including a ##select_efuse_flag section + absent from this firmware). Also unguards the + wsm_save_factory_txt_to_mcu() prototype in wsm.h which was + inconsistently wrapped in '#if defined(STANDARD_FACTORY_EFUSE_FLAG)' + while its definition in wsm.c and its call site in sta.c were + ungated -- gcc -Werror=missing-prototypes broke the build with the + new default until this is fixed. + +* 3/7 -- Thread struct device * through factory_section_read_file() via + a module-local setter invoked at SDIO probe. request_firmware() now + receives a real device pointer; '(NULL device *):' no longer prefixes + factory-related diagnostics. + +* 4/7 -- Gate the device-end of the low-power transition on successful + per-VIF firmware handshake. Pre-patch bes2600_pwr_enter_lp_mode() + called bes2600_pwr_device_enter_lp_mode() unconditionally even when + wait_for_completion_timeout() returned 0 (firmware never posted the + PM-change indication). On PineTab2 this recurred every 5-10 s + whenever the interface was associated and idle, flooded dmesg, and + cascaded into sdio_tx_work WARN splats / [RX] Receive failure + messages. Post-patch: -ETIMEDOUT returned cleanly, dmesg silent, + SDIO stable. + +* 5/7 -- Remove the custom /dev/bes2600 character-device interface. + file_operations, open/read/write/release, bes2600_op_* + command-dispatch table, bes2600_load_uevent, alloc_chrdev_region / + cdev_init / cdev_add / class_create / device_create in the init + path, and the matching teardown in bes2600_chrdev_free -- 519 lines + deleted. The in-kernel accessor functions (is_signal_mode, + get_fw_type, etc., 13 call sites) and the fw_type module parameter + are preserved; the userspace interface becomes rfkill + module_param + + (with 6/7) nl80211 testmode. + +* 6/7 -- Flip CONFIG_BES2600_TESTMODE default from n to y. The driver + already implements a mac80211 testmode_cmd dispatcher (routing to + the firmware's patch_wifi_testMode path), already gated on the flag; + CONFIG_NL80211_TESTMODE=y is common on target kernels. Enabling the + flag also exposes accumulated bit-rot -- ~41 calls to undefined + bes2600_info/err/warn/dbg/err_with_cond macros, and 3 TSM/roam-delay + helpers with external linkage but no prototype. Add shim macros to + bes_log.h rewiring the legacy calls onto the existing bes_info / + bes_err / bes_warn / bes_devel family, define BES2600_DBG_* subsystem + ids as 0 constants, and mark the 3 helpers static. + +* 7/7 -- Bounce SDIO TX buffers to avoid DMA out-of-bounds reads. + sdio_tx_work() rounded the transfer length up to the SDIO block size + (align = blks * cur_blksize, or 1632) and handed that length to + dma_map_sg() via sg_set_buf(..., tx_buffer->buf, align); tx_buffer->buf + typically aliases into an skb linear head allocated to tx_buffer->len, + not the block-aligned length. The DMA engine therefore read up to one + block past the end of the skb -- KFENCE on PineTab2 fires as + 'out-of-bounds read in __pi_memcpy_generic ... 704B right of + kfence-#...' with sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper in + the stack and pskb_expand_head / validate_xmit_skb / tcp_write_xmit in + the allocator stack. Besides being undefined behavior, the padding + bytes are transmitted to the peer, leaking adjacent kernel memory on + the air. Allocate a driver-owned DMA-pages bounce buffer of + MAX_SDIO_TRANSFER_LEN, memcpy each TX buffer into its slot, zero the + padding tail, and point the SG entries at the bounce. Mirrors the + pattern already used for single_gathered_buffer; kept as a separate + allocation because sdio_tx_work accumulates SG entries before claiming + the bus. + +## Testing + +Reference hardware: Pine64 PineTab2 (BES2600WM + Rockchip RK3566, +8a:2e:77:1f:ec:05, LAN AP newton @ 2.4 GHz ch11, tested also on +TelekomHotspot@ERGO @ 5 GHz ch36). + +Host kernel: linux-pinetab2 6.19.10-danctnix1-1-pinetab2 with +CONFIG_NL80211_TESTMODE=y and CONFIG_KFENCE=y (for 7/7 verification). +Driver installed via /lib/modules//extra/ and verified loaded via +modinfo srcversion. + +Per-patch outcomes in post-reboot dmesg (full-stack applied, 11+ min +soak, 245k RX packets / 365 MB traffic): + +- 1/7: 'read and check /lib/firmware/bes2600_factory.txt error' -- gone +- 2/7: 'bes2600_factory.txt parse fail' / 'factory cali data get + failed.' -- gone +- 3/7: '(NULL device *):' prefix on factory lines -- gone +- 4/7: 'bes2600_pwr_enter_lp_mode, wait pm ind timeout' (pre-patch 20-30 + msgs / 5 min window) -- 0 per 5 min; '[RX] Receive failure: 4.' -- + gone +- 5/7: /dev/bes2600 -- absent; driver continues to associate +- 6/7: 'iw phy0' lists 'testmode' under Supported commands; module + builds cleanly +- 7/7: 'BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic' + (pre-patch ~65 splats per 4 h of real traffic, always via + sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper+0x18c) -- 0; + 'sdio_tx_work+0x2b4' WARN splat residual from 4/7's cascade -- 0 + (previously recurred ~1 per reboot even with 4/7 applied); + 'PS Mode Error, Reason:1' benign handshake notice at T+40s -- + also gone, apparently a downstream effect of no longer DMAing + uninitialised padding bytes into firmware + +Full stack: wifi associates and passes traffic across 3+ reboots. + +## Known limitations / out of scope + +- factory_section_write_file() in bes2600_factory.c still uses + kernel_write() + filp_open(O_CREAT) to persist per-channel + calibration updates back to /lib/firmware/bes2600_factory.txt. + Converting the write-back path to debugfs or nl80211 testmode is a + follow-up. + +- bes_chardev.c still carries DPD file-read/write paths gated by + BES2600_WRITE_DPD_TO_FILE (off by default, so dead code in the + default build). Same treatment needed. + +- bes_fw.c:587 unconditionally creates + /lib/firmware/bes2002_fw_write.bin via filp_open() for debug + observation. Needs to go before drivers/staging/ accepts the driver. + +- bes_cdev global singleton still holds sig_mode and fw_type. Moving + those to per-hw_priv state is blocked by fw_type being a module + parameter (inherently singleton). Migrating fw_type to a per-phy + debugfs knob or nl80211 testmode command is the next step; overlaps + with 6/7's testmode plumbing. + +Markus Fritsche (7): + bes2600: use request_firmware() for factory.txt read + bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for PineTab2 + factory.txt format + bes2600: thread struct device * through factory request_firmware() + call + bes2600: gate device LP-mode entry on successful per-VIF firmware + handshake + bes2600: remove userspace /dev/bes2600 character device interface + bes2600: enable CONFIG_BES2600_TESTMODE by default + fix bit-rotted + testmode plumbing + bes2600: bounce SDIO TX buffers to avoid DMA OOB read + + drivers/staging/bes2600/Makefile | 6 +- + drivers/staging/bes2600/bes2600_factory.c | 45 ++-- + drivers/staging/bes2600/bes2600_factory.h | 3 + + drivers/staging/bes2600/bes2600_sdio.c | 43 +++- + drivers/staging/bes2600/bes_chardev.c | 519 -------------------------------------- + drivers/staging/bes2600/bes_log.h | 23 ++ + drivers/staging/bes2600/bes_pwr.c | 20 +- + drivers/staging/bes2600/sta.c | 6 +- + drivers/staging/bes2600/wsm.h | 2 - + 9 files changed, 117 insertions(+), 550 deletions(-) + +-- +2.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0001-bes2600-use-request_firmware-for-factory.txt-read.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0001-bes2600-use-request_firmware-for-factory.txt-read.patch new file mode 100644 index 0000000..0c43a84 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0001-bes2600-use-request_firmware-for-factory.txt-read.patch @@ -0,0 +1,147 @@ +From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 10:09:44 +0200 +Subject: [PATCH 1/7] 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 +--- + drivers/staging/bes2600/Makefile | 2 +- + drivers/staging/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 ?= drivers/staging/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch new file mode 100644 index 0000000..389ece9 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch @@ -0,0 +1,86 @@ +From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:17:56 +0200 +Subject: [PATCH 2/7] 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 drivers/staging/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 +--- + drivers/staging/bes2600/Makefile | 2 +- + drivers/staging/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 ?= drivers/staging/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0003-bes2600-thread-struct-device-through-factory-request.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0003-bes2600-thread-struct-device-through-factory-request.patch new file mode 100644 index 0000000..dcb58ad --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0003-bes2600-thread-struct-device-through-factory-request.patch @@ -0,0 +1,119 @@ +From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:18:38 +0200 +Subject: [PATCH 3/7] 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 +--- + drivers/staging/bes2600/bes2600_factory.c | 14 +++++++++++++- + drivers/staging/bes2600/bes2600_factory.h | 3 +++ + drivers/staging/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 b595365..371ef4f 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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch new file mode 100644 index 0000000..f583852 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch @@ -0,0 +1,108 @@ +From 108d3967eac4ba3a6e0f508d865a5f221b49079d Mon Sep 17 00:00:00 2001 +Message-ID: <108d3967eac4ba3a6e0f508d865a5f221b49079d.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:37:45 +0200 +Subject: [PATCH 4/7] 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 +--- + drivers/staging/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch new file mode 100644 index 0000000..93808ca --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch @@ -0,0 +1,678 @@ +From 3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f Mon Sep 17 00:00:00 2001 +Message-ID: <3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:55:18 +0200 +Subject: [PATCH 5/7] 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 +--- + drivers/staging/bes2600/bes_chardev.c | 519 ------------------------------------------ + 1 file changed, 519 deletions(-) + +diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c +index 9038e48..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; +@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on) + return ret; + } + +-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) + { +@@ -603,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) +@@ -1124,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"); + +@@ -1146,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"); + } + } + +@@ -1247,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); +@@ -1318,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) +@@ -1336,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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch new file mode 100644 index 0000000..04688a6 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch @@ -0,0 +1,146 @@ +From 6f13e008d21d453db486f707f47340a0a17e650b Mon Sep 17 00:00:00 2001 +Message-ID: <6f13e008d21d453db486f707f47340a0a17e650b.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:04:27 +0200 +Subject: [PATCH 6/7] 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 +--- + drivers/staging/bes2600/Makefile | 2 +- + drivers/staging/bes2600/bes_log.h | 23 +++++++++++++++++++++++ + drivers/staging/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 aa69eb8..5f1a456 100644 +--- a/drivers/staging/bes2600/sta.c ++++ b/drivers/staging/bes2600/sta.c +@@ -3633,7 +3633,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; +@@ -3663,7 +3663,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; +@@ -3703,7 +3703,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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series-danctnix/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/driver/bes2600/staging-prep-series-danctnix/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 0000000..262e4da --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series-danctnix/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,126 @@ +From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001 +Message-ID: <10a05d21bfe4563f963e16d65228fd7a713c143d.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH 7/7] 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 +--- + drivers/staging/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 371ef4f..3e04e8c 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 +@@ -1985,6 +2016,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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0000-cover-letter.patch b/patches/driver/bes2600/staging-prep-series/0000-cover-letter.patch new file mode 100644 index 0000000..d219a84 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0000-cover-letter.patch @@ -0,0 +1,186 @@ +From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001 +Message-ID: +From: Markus Fritsche +Date: Thu, 23 Apr 2026 12:35:28 +0200 +Subject: [PATCH 0/7] bes2600: staging-prep cleanup for PineTab2 (BES2600WM) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This series is a staging-prep cleanup for the out-of-tree Bestechnic +BES2600WM Wi-Fi/BT combo-chip driver as shipped by Mobian's bes2600-dkms +package (and in-tree at drivers/staging/bes2600/ in the danctnix +linux-pinetab2 fork). Target hardware is the Pine64 PineTab2 (RK3566 ++ BES2600WM, SDIO vendor 0xBE57 / device 0x2002). + +The driver descends from the ST-Ericsson CW1200 (drivers/net/wireless/ +st/cw1200/) -- same author, Dmitry Tarnyagin, shared WSM host<->firmware +protocol, shared SDIO bus backend. Kconfig ancestry markers survive in +this tree today: CONFIG_BES2600_USE_STE_EXTENSIONS (STE = ST-Ericsson), +CONFIG_BES2600_WSM_DEBUG (WSM). ST-Ericsson wound down in 2013; +Bestechnic (founded 2015) appears to have inherited or licensed the +CW1200 IP. No linux-wireless RFC has ever linked the two chips. + +The series fixes observable defects on a PineTab2 running linux-pinetab2 +6.19.10-danctnix1-1 and removes two upstream blockers. Each patch is +independently testable and bisectable; the order below preserves +dependencies. + +## What the series does + +* 1/7 -- Replace filp_open() + kernel_read() in the factory-calibration + read path with request_firmware(). Repoint the FACTORY_PATH macro to + the firmware-class name (bes2600/bes2600_factory.txt, matching the + /lib/firmware/ layout). Kills a kernel-mainline anti-pattern and the + '(NULL device *): read and check /lib/firmware/bes2600_factory.txt + error' boot spam on PineTab2. + +* 2/7 -- Default STANDARD_FACTORY_EFUSE_FLAG from y to n. The shipped + bes2600_factory.txt on PineTab2 contains 30 calibration fields; the + driver was expecting 31 (including a ##select_efuse_flag section + absent from this firmware). Also unguards the + wsm_save_factory_txt_to_mcu() prototype in wsm.h which was + inconsistently wrapped in '#if defined(STANDARD_FACTORY_EFUSE_FLAG)' + while its definition in wsm.c and its call site in sta.c were + ungated -- gcc -Werror=missing-prototypes broke the build with the + new default until this is fixed. + +* 3/7 -- Thread struct device * through factory_section_read_file() via + a module-local setter invoked at SDIO probe. request_firmware() now + receives a real device pointer; '(NULL device *):' no longer prefixes + factory-related diagnostics. + +* 4/7 -- Gate the device-end of the low-power transition on successful + per-VIF firmware handshake. Pre-patch bes2600_pwr_enter_lp_mode() + called bes2600_pwr_device_enter_lp_mode() unconditionally even when + wait_for_completion_timeout() returned 0 (firmware never posted the + PM-change indication). On PineTab2 this recurred every 5-10 s + whenever the interface was associated and idle, flooded dmesg, and + cascaded into sdio_tx_work WARN splats / [RX] Receive failure + messages. Post-patch: -ETIMEDOUT returned cleanly, dmesg silent, + SDIO stable. + +* 5/7 -- Remove the custom /dev/bes2600 character-device interface. + file_operations, open/read/write/release, bes2600_op_* + command-dispatch table, bes2600_load_uevent, alloc_chrdev_region / + cdev_init / cdev_add / class_create / device_create in the init + path, and the matching teardown in bes2600_chrdev_free -- 519 lines + deleted. The in-kernel accessor functions (is_signal_mode, + get_fw_type, etc., 13 call sites) and the fw_type module parameter + are preserved; the userspace interface becomes rfkill + module_param + + (with 6/7) nl80211 testmode. + +* 6/7 -- Flip CONFIG_BES2600_TESTMODE default from n to y. The driver + already implements a mac80211 testmode_cmd dispatcher (routing to + the firmware's patch_wifi_testMode path), already gated on the flag; + CONFIG_NL80211_TESTMODE=y is common on target kernels. Enabling the + flag also exposes accumulated bit-rot -- ~41 calls to undefined + bes2600_info/err/warn/dbg/err_with_cond macros, and 3 TSM/roam-delay + helpers with external linkage but no prototype. Add shim macros to + bes_log.h rewiring the legacy calls onto the existing bes_info / + bes_err / bes_warn / bes_devel family, define BES2600_DBG_* subsystem + ids as 0 constants, and mark the 3 helpers static. + +* 7/7 -- Bounce SDIO TX buffers to avoid DMA out-of-bounds reads. + sdio_tx_work() rounded the transfer length up to the SDIO block size + (align = blks * cur_blksize, or 1632) and handed that length to + dma_map_sg() via sg_set_buf(..., tx_buffer->buf, align); tx_buffer->buf + typically aliases into an skb linear head allocated to tx_buffer->len, + not the block-aligned length. The DMA engine therefore read up to one + block past the end of the skb -- KFENCE on PineTab2 fires as + 'out-of-bounds read in __pi_memcpy_generic ... 704B right of + kfence-#...' with sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper in + the stack and pskb_expand_head / validate_xmit_skb / tcp_write_xmit in + the allocator stack. Besides being undefined behavior, the padding + bytes are transmitted to the peer, leaking adjacent kernel memory on + the air. Allocate a driver-owned DMA-pages bounce buffer of + MAX_SDIO_TRANSFER_LEN, memcpy each TX buffer into its slot, zero the + padding tail, and point the SG entries at the bounce. Mirrors the + pattern already used for single_gathered_buffer; kept as a separate + allocation because sdio_tx_work accumulates SG entries before claiming + the bus. + +## Testing + +Reference hardware: Pine64 PineTab2 (BES2600WM + Rockchip RK3566, +8a:2e:77:1f:ec:05, LAN AP newton @ 2.4 GHz ch11, tested also on +TelekomHotspot@ERGO @ 5 GHz ch36). + +Host kernel: linux-pinetab2 6.19.10-danctnix1-1-pinetab2 with +CONFIG_NL80211_TESTMODE=y and CONFIG_KFENCE=y (for 7/7 verification). +Driver installed via /lib/modules//extra/ and verified loaded via +modinfo srcversion. + +Per-patch outcomes in post-reboot dmesg (full-stack applied, 11+ min +soak, 245k RX packets / 365 MB traffic): + +- 1/7: 'read and check /lib/firmware/bes2600_factory.txt error' -- gone +- 2/7: 'bes2600_factory.txt parse fail' / 'factory cali data get + failed.' -- gone +- 3/7: '(NULL device *):' prefix on factory lines -- gone +- 4/7: 'bes2600_pwr_enter_lp_mode, wait pm ind timeout' (pre-patch 20-30 + msgs / 5 min window) -- 0 per 5 min; '[RX] Receive failure: 4.' -- + gone +- 5/7: /dev/bes2600 -- absent; driver continues to associate +- 6/7: 'iw phy0' lists 'testmode' under Supported commands; module + builds cleanly +- 7/7: 'BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic' + (pre-patch ~65 splats per 4 h of real traffic, always via + sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper+0x18c) -- 0; + 'sdio_tx_work+0x2b4' WARN splat residual from 4/7's cascade -- 0 + (previously recurred ~1 per reboot even with 4/7 applied); + 'PS Mode Error, Reason:1' benign handshake notice at T+40s -- + also gone, apparently a downstream effect of no longer DMAing + uninitialised padding bytes into firmware + +Full stack: wifi associates and passes traffic across 3+ reboots. + +## Known limitations / out of scope + +- factory_section_write_file() in bes2600_factory.c still uses + kernel_write() + filp_open(O_CREAT) to persist per-channel + calibration updates back to /lib/firmware/bes2600_factory.txt. + Converting the write-back path to debugfs or nl80211 testmode is a + follow-up. + +- bes_chardev.c still carries DPD file-read/write paths gated by + BES2600_WRITE_DPD_TO_FILE (off by default, so dead code in the + default build). Same treatment needed. + +- bes_fw.c:587 unconditionally creates + /lib/firmware/bes2002_fw_write.bin via filp_open() for debug + observation. Needs to go before drivers/staging/ accepts the driver. + +- bes_cdev global singleton still holds sig_mode and fw_type. Moving + those to per-hw_priv state is blocked by fw_type being a module + parameter (inherently singleton). Migrating fw_type to a per-phy + debugfs knob or nl80211 testmode command is the next step; overlaps + with 6/7's testmode plumbing. + +Markus Fritsche (7): + bes2600: use request_firmware() for factory.txt read + bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for PineTab2 + factory.txt format + bes2600: thread struct device * through factory request_firmware() + call + bes2600: gate device LP-mode entry on successful per-VIF firmware + handshake + bes2600: remove userspace /dev/bes2600 character device interface + bes2600: enable CONFIG_BES2600_TESTMODE by default + fix bit-rotted + testmode plumbing + bes2600: bounce SDIO TX buffers to avoid DMA OOB read + + bes2600/Makefile | 6 +- + bes2600/bes2600_factory.c | 45 ++-- + bes2600/bes2600_factory.h | 3 + + bes2600/bes2600_sdio.c | 43 +++- + bes2600/bes_chardev.c | 519 -------------------------------------- + bes2600/bes_log.h | 23 ++ + bes2600/bes_pwr.c | 20 +- + bes2600/sta.c | 6 +- + bes2600/wsm.h | 2 - + 9 files changed, 117 insertions(+), 550 deletions(-) + +-- +2.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch b/patches/driver/bes2600/staging-prep-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch new file mode 100644 index 0000000..5f65b9c --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0001-bes2600-use-request_firmware-for-factory.txt-read.patch @@ -0,0 +1,147 @@ +From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 10:09:44 +0200 +Subject: [PATCH 1/7] 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/bes2600/Makefile b/bes2600/Makefile +index 300912b..788aee2 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c +index dc5d3da..8d60b7c 100644 +--- a/bes2600/bes2600_factory.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch b/patches/driver/bes2600/staging-prep-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch new file mode 100644 index 0000000..187277d --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch @@ -0,0 +1,86 @@ +From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:17:56 +0200 +Subject: [PATCH 2/7] 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/bes2600/Makefile b/bes2600/Makefile +index 788aee2..2dcba09 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/wsm.h b/bes2600/wsm.h +index 0673131..22845ac 100644 +--- a/bes2600/wsm.h ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0003-bes2600-thread-struct-device-through-factory-request.patch b/patches/driver/bes2600/staging-prep-series/0003-bes2600-thread-struct-device-through-factory-request.patch new file mode 100644 index 0000000..68fcea1 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0003-bes2600-thread-struct-device-through-factory-request.patch @@ -0,0 +1,119 @@ +From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:18:38 +0200 +Subject: [PATCH 3/7] 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/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c +index 8d60b7c..1cda447 100644 +--- a/bes2600/bes2600_factory.c ++++ b/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/bes2600/bes2600_factory.h b/bes2600/bes2600_factory.h +index 3835b0d..7dbe9f8 100644 +--- a/bes2600/bes2600_factory.h ++++ b/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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..371ef4f 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch b/patches/driver/bes2600/staging-prep-series/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch new file mode 100644 index 0000000..cb99f2b --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0004-bes2600-gate-device-LP-mode-entry-on-successful-per-.patch @@ -0,0 +1,108 @@ +From 108d3967eac4ba3a6e0f508d865a5f221b49079d Mon Sep 17 00:00:00 2001 +Message-ID: <108d3967eac4ba3a6e0f508d865a5f221b49079d.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:37:45 +0200 +Subject: [PATCH 4/7] 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/bes2600/bes_pwr.c b/bes2600/bes_pwr.c +index e7a1045..f62ae22 100644 +--- a/bes2600/bes_pwr.c ++++ b/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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch b/patches/driver/bes2600/staging-prep-series/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch new file mode 100644 index 0000000..28ab295 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0005-bes2600-remove-userspace-dev-bes2600-character-devic.patch @@ -0,0 +1,678 @@ +From 3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f Mon Sep 17 00:00:00 2001 +Message-ID: <3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 12:55:18 +0200 +Subject: [PATCH 5/7] 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 | 519 ------------------------------------------ + 1 file changed, 519 deletions(-) + +diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c +index 9038e48..e2e4f1b 100644 +--- a/bes2600/bes_chardev.c ++++ b/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; +@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on) + return ret; + } + +-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) + { +@@ -603,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) +@@ -1124,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"); + +@@ -1146,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"); + } + } + +@@ -1247,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); +@@ -1318,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) +@@ -1336,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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch b/patches/driver/bes2600/staging-prep-series/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch new file mode 100644 index 0000000..8df3e44 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch @@ -0,0 +1,146 @@ +From 6f13e008d21d453db486f707f47340a0a17e650b Mon Sep 17 00:00:00 2001 +Message-ID: <6f13e008d21d453db486f707f47340a0a17e650b.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Wed, 22 Apr 2026 13:04:27 +0200 +Subject: [PATCH 6/7] 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/bes2600/Makefile b/bes2600/Makefile +index 2dcba09..2c1a850 100644 +--- a/bes2600/Makefile ++++ b/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/bes2600/bes_log.h b/bes2600/bes_log.h +index 605cea8..65cf703 100644 +--- a/bes2600/bes_log.h ++++ b/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/bes2600/sta.c b/bes2600/sta.c +index aa69eb8..5f1a456 100644 +--- a/bes2600/sta.c ++++ b/bes2600/sta.c +@@ -3633,7 +3633,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; +@@ -3663,7 +3663,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; +@@ -3703,7 +3703,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.53.0 + diff --git a/patches/driver/bes2600/staging-prep-series/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/driver/bes2600/staging-prep-series/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 0000000..b876ad8 --- /dev/null +++ b/patches/driver/bes2600/staging-prep-series/0007-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,126 @@ +From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001 +Message-ID: <10a05d21bfe4563f963e16d65228fd7a713c143d.1776940528.git.fritsche.markus@gmail.com> +In-Reply-To: +References: +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH 7/7] 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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index 371ef4f..3e04e8c 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/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 +@@ -1985,6 +2016,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.53.0 + diff --git a/patches/driver/bes2600/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/driver/bes2600/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 0000000..0db0eed --- /dev/null +++ b/patches/driver/bes2600/tx-sdio-dma-oob-danctnix/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,123 @@ +From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH] 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 +--- + drivers/staging/bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..7bc922c 100644 +--- a/drivers/staging/bes2600/bes2600_sdio.c ++++ b/drivers/staging/bes2600/bes2600_sdio.c +@@ -94,6 +94,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; +@@ -1135,7 +1136,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:*/ +@@ -1853,6 +1873,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 +@@ -1981,6 +2012,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.53.0 + diff --git a/patches/driver/bes2600/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch b/patches/driver/bes2600/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch new file mode 100644 index 0000000..62867bb --- /dev/null +++ b/patches/driver/bes2600/tx-sdio-dma-oob/0001-bes2600-bounce-SDIO-TX-buffers-to-avoid-DMA-OOB-read.patch @@ -0,0 +1,123 @@ +From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001 +From: Markus Fritsche +Date: Thu, 23 Apr 2026 11:58:31 +0200 +Subject: [PATCH] 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/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c +index b595365..7bc922c 100644 +--- a/bes2600/bes2600_sdio.c ++++ b/bes2600/bes2600_sdio.c +@@ -94,6 +94,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; +@@ -1135,7 +1136,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:*/ +@@ -1853,6 +1873,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 +@@ -1981,6 +2012,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.53.0 +