patches/driver/bes2600: mirror besser series — closes #2 #17

Merged
marfrit merged 1 commits from claude-noether/kernel-agent:migrate-besser into main 2026-05-16 16:42:50 +00:00
46 changed files with 8636 additions and 0 deletions
+92
View File
@@ -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
@@ -0,0 +1,38 @@
From f31c57adf736df52b3f393f2650920af98b8e8f1 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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 <https://www.gnu.org/licenses/>.
.
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
@@ -0,0 +1,293 @@
From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,293 @@
From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,168 @@
From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,168 @@
From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,143 @@
From 9398d3028bc9d2f4ccbf8e830f8e9799bf065ce4 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,156 @@
From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,156 @@
From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,144 @@
From 1a5d54a3213041262caf1605bb19c66ddded41f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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/<ver>/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 <fritsche.markus@gmail.com>
---
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 <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -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
@@ -0,0 +1,83 @@
From 82ba594a444a855310fbbe2a5c8ff02f211d8e83 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,116 @@
From 8732881c5916106539b9071b51710489c57e8d73 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,251 @@
From 9ea8a8e810ee5eb220de700a5c0a6d1153b15130 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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 <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
#include <linux/mmc/sdio.h>
#include <linux/spinlock.h>
#include <net/mac80211.h>
@@ -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
@@ -0,0 +1,251 @@
From 460495803346f71a9d5dcc634180e5368ff9b1dc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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 <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
#include <linux/mmc/sdio.h>
#include <linux/spinlock.h>
#include <net/mac80211.h>
@@ -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
@@ -0,0 +1,209 @@
From d1de35c62930b1bc035d3863d75901356548b6f0 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,209 @@
From f12e87002576f094c441ac6c945a451c88868592 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,105 @@
From 80178ec9b1f83aed1dcce9ea7ca02bc81341ba01 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,246 @@
From 4ab8c790304206abd134de48c878b637a70f3c59 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,246 @@
From c57c77e446d9a552b537175453b838d0400ff41d Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,53 @@
From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,53 @@
From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,190 @@
From 706a594dab68779294e4fff9705a6e1df46ec1af Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,190 @@
From 822a5f1bab37e3f61b91aaf304ec1c54b42d639a Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,675 @@
From f43bcc5dda0a9120aee62cce0cec1a8c851cb4ef Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <n>', '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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,109 @@
From 3d98404c1a85ef33e9fc1422042c71dc90f3b255 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,109 @@
From db4ea70fb5dae1b2ab9c06dd91f1d7b2b9dcf09c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,226 @@
From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,226 @@
From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,186 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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/<ver>/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
@@ -0,0 +1,147 @@
From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001
Message-ID: <d18aa6a9bc03a03e455434f83577892aa1a60ffe.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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/<ver>/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 <fritsche.markus@gmail.com>
---
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 <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -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
@@ -0,0 +1,86 @@
From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001
Message-ID: <a826f4db7d97a3a872d92079db37dbdaf9a0cdec.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,119 @@
From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001
Message-ID: <c7ba2044b78cc3778763737daea60c9912b710c6.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <n>', '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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,186 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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/<ver>/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
@@ -0,0 +1,147 @@
From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001
Message-ID: <d18aa6a9bc03a03e455434f83577892aa1a60ffe.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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/<ver>/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 <fritsche.markus@gmail.com>
---
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 <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -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
@@ -0,0 +1,86 @@
From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001
Message-ID: <a826f4db7d97a3a872d92079db37dbdaf9a0cdec.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,119 @@
From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001
Message-ID: <c7ba2044b78cc3778763737daea60c9912b710c6.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <n>', '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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -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: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,123 @@
From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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
@@ -0,0 +1,123 @@
From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
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 <fritsche.markus@gmail.com>
---
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