Compare commits

..

2 Commits

Author SHA1 Message Date
marfrit 789a9a4700 Merge pull request 'bes2600: pre-empt AP-deauth-6 with mac80211 reassoc on decrypt-fail storm' (#1) from bes2600/decrypt-storm-fast-recover into mobian
Reviewed-on: #1
2026-05-06 18:48:29 +00:00
claude-noether af4b52affc bes2600: pre-empt AP-deauth-6 with mac80211 reassoc on decrypt-fail storm
When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a burst
of received frames (typically because the host's PTK or GTK has fallen
out of sync with the AP), the AP eventually concludes that the STA is
not authenticated and emits an unprotected deauth-reason-6 ("Class 2
frame received from non-authenticated station"). On the deployed
pinetab2 + bes2600 stack this AP-initiated deauth has been observed to
leave the link blackholed for up to 109 s before userspace finds a
different SSID/channel to recover on. (Receipts at
https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.)

Add a sliding-window counter on each bes2600_vif: when 5 decrypt
failures fire within 5 s, schedule a worker that calls
ieee80211_connection_loss(vif). mac80211 then performs immediate
disassociation; userspace (NetworkManager / wpa_supplicant) reconnects
with fresh keys before the AP gets a chance to fire its unprotected
deauth.

Predicted Phase 7 delta vs the unpatched baseline:
- decrypt-burst rate: unchanged (this does not address root cause)
- AP-deauth-6 rate: <= 0.2 of baseline
- conditional probability of >5s blackhole given a burst:
  100% -> <= 10%
- worst-case recovery time: 109s -> <5s

Contract pin: ieee80211_connection_loss() per
include/net/mac80211.h: "may also be called if the connection needs to
be terminated for some other reason... will cause immediate change to
disassociated state, without connection recovery attempts." Userspace
recovery is the existing NM/wpa_supplicant path. The worker context
satisfies the implicit process-context expectation.

Files touched:
- bes2600/bes2600.h: 4 new fields on struct bes2600_vif + 2 prototypes
- bes2600/txrx.c: new helpers + the call site at the existing
  WSM_STATUS_DECRYPTFAILURE log point (the unconditional "goto drop"
  branch in bes2600_rx_cb)
- bes2600/sta.c: bes2600_decrypt_storm_init() in bes2600_vif_setup;
  cancel_work_sync() in bes2600_remove_interface, alongside the
  existing per-vif cancel_*_work_sync block. Safe under the kernel
  cancel_work_sync contract: the work_struct is INIT_WORK'd in setup,
  so the call is valid; it blocks until any in-flight handler returns,
  ensuring no use-after-free of priv when mac80211 frees the vif; and
  it is idempotent (subsequent calls just return false).
- bes2600/debug.c: DecryptStormRecoveries seq_printf in the per-vif
  status seq_file output

Threshold (5/5s) is set well above the steady-state per-vif decrypt-
fail rate observed in measurement (~1/min even under sustained 1 MB/s
load), so a true storm is required to trip it. The cw1200/cw1260
ancestor has no equivalent storm-recovery; this is a clean addition.

checkpatch.pl --no-tree --strict: clean (0/0/0).

Signed-off-by: Claude (noether) <claude@reauktion.de>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:43:42 +02:00
20 changed files with 914 additions and 314 deletions
+15 -3
View File
@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build
# feature option
BES2600 ?= m
CONFIG_BES2600_TESTMODE ?= y
CONFIG_BES2600_TESTMODE ?= n
CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n
@@ -28,6 +28,7 @@ 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
@@ -64,8 +65,8 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
STANDARD_FACTORY_EFUSE_FLAG ?= n
FACTORY_PATH ?= bes2600/bes2600_factory.txt
STANDARD_FACTORY_EFUSE_FLAG ?= y
FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt
endif
# basic function
@@ -92,6 +93,12 @@ 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
@@ -128,6 +135,9 @@ 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)
@@ -149,6 +159,8 @@ 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)
+9
View File
@@ -596,6 +596,11 @@ struct bes2600_vif {
unsigned long rx_timestamp;
u32 cipherType;
/* Decrypt-storm fast-recover (Trigger B). See txrx.c. */
unsigned long decrypt_storm_window_start;
unsigned int decrypt_storm_count;
unsigned int decrypt_storm_recoveries;
struct work_struct decrypt_storm_recover_work;
/* AP powersave */
u32 link_id_map;
@@ -856,4 +861,8 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv);
void bes2600_btusb_uninit(struct usb_interface *interface);
#endif
/* Decrypt-storm fast-recover helpers — see txrx.c. */
void bes2600_decrypt_storm_init(struct bes2600_vif *priv);
void bes2600_decrypt_storm_account(struct bes2600_vif *priv);
#endif /* BES2600_H */
+65 -43
View File
@@ -12,7 +12,6 @@
#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>
@@ -31,18 +30,6 @@
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
@@ -150,32 +137,66 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data)
*/
static int factory_section_read_file(char *path, void *buffer)
{
const struct firmware *fw;
int ret;
int ret = 0;
struct file *fp;
if (!path || !buffer) {
bes_err("%s NULL pointer err\n", __func__);
return -1;
}
bes_devel("requesting firmware-class %s\n", path);
bes_devel("reading %s \n", path);
ret = request_firmware(&fw, path, bes2600_factory_dev);
if (ret) {
bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
fp = filp_open(path, O_RDONLY, 0); //S_IRUSR
if (IS_ERR(fp)) {
bes_devel("BES2600 : can't open %s\n",path);
return -1;
}
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);
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);
return -1;
}
memcpy(buffer, fw->data, fw->size);
ret = (int)fw->size;
release_firmware(fw);
ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos);
filp_close(fp, NULL);
if (ret != fp->f_inode->i_size) {
bes_err("bes2600_factory.txt read fail\n");
ret = -1;
}
return ret;
}
/**
* factory_section_write_file - Write data of specified length to file
* @path: path of the file
* @buffer: storage of write data
* @size: length of data to write
*
* Return: length on success, negative error code otherwise.
*/
static int factory_section_write_file(char *path, void *buffer, int size)
{
int ret = 0;
struct file *fp;
bes_devel("writing %s \n", path);
fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
if (IS_ERR(fp)) {
bes_devel("BES2600 : can't open %s\n",path);
return -1;
}
ret = kernel_write(fp, buffer, size, &fp->f_pos);
filp_close(fp,NULL);
return ret;
}
@@ -870,22 +891,9 @@ 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
@@ -894,11 +902,13 @@ 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);
@@ -910,10 +920,22 @@ 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! w_size = %d.", __func__, w_size);
bes_err("%s: build failed! ret = %d.", __func__, ret);
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;
}
-3
View File
@@ -199,9 +199,6 @@ 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);
+1 -71
View File
@@ -16,7 +16,6 @@
#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>
@@ -31,7 +30,6 @@
#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"
@@ -96,7 +94,6 @@ 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;
@@ -1138,26 +1135,7 @@ static void sdio_tx_work(struct work_struct *work)
}
}
/*
* 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);
sg_set_buf(&sg[scatters], tx_buffer->buf, align);
total_len += align;
++scatters;
/*del_node:*/
@@ -1778,33 +1756,6 @@ 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();
@@ -1843,7 +1794,6 @@ 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)
@@ -1884,9 +1834,6 @@ 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;
@@ -1906,17 +1853,6 @@ 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
@@ -2045,12 +1981,6 @@ 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);
}
+664 -57
View File
@@ -43,6 +43,12 @@ 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;
@@ -63,6 +69,9 @@ 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 */
@@ -82,6 +91,9 @@ 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);
@@ -237,18 +249,351 @@ 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)
{
@@ -258,13 +603,255 @@ 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)
{
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)
@@ -325,6 +912,14 @@ 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;
}
@@ -442,48 +1037,6 @@ 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;
@@ -571,6 +1124,12 @@ 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");
@@ -582,24 +1141,19 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
/* unregister wifi */
bes2600_switch_wifi(0);
/*
* 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()) {
/* power down device if wifi is only opened */
if (bes2600_chrdev_check_system_close()) {
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");
}
}
@@ -693,6 +1247,46 @@ 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);
@@ -724,6 +1318,15 @@ 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)
@@ -733,5 +1336,9 @@ 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__);
}
-1
View File
@@ -60,7 +60,6 @@ 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);
+34
View File
@@ -125,6 +125,8 @@ 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;
@@ -466,6 +468,14 @@ 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;
@@ -573,6 +583,14 @@ 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
@@ -622,6 +640,17 @@ 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);
@@ -803,6 +832,11 @@ 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);
-23
View File
@@ -8,26 +8,3 @@ 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)
+4 -18
View File
@@ -472,7 +472,6 @@ 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;
@@ -529,35 +528,22 @@ 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) {
bes_devel("%s, wait pm ind timeout\n", __func__);
timeouts++;
}
if (!status)
bes_err("%s, wait pm ind timeout\n", __func__);
} else {
bes_devel("skip enter lp mode\n");
}
}
}
/*
* 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;
/* set device low power configuration */
bes2600_pwr_device_enter_lp_mode(hw_priv);
return ret;
}
+2
View File
@@ -542,6 +542,8 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v)
priv->listening ? " (listening)" : "");
seq_printf(seq, "Assoc: %s\n",
bes2600_debug_join_status[priv->join_status]);
seq_printf(seq, "DecryptStormRecoveries: %u\n",
priv->decrypt_storm_recoveries);
if (priv->rx_filter.promiscuous)
seq_puts(seq, "Filter: promisc\n");
else if (priv->rx_filter.fcs)
+35
View File
@@ -790,6 +790,41 @@ 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;
-8
View File
@@ -75,14 +75,6 @@ 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);
+1 -59
View File
@@ -14,50 +14,11 @@
#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)
{
@@ -741,29 +702,10 @@ 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 (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. */
if (WARN_ON(hw_priv->scan.status))
goto fail;
}
hw_priv->scan.reject_count = 0;
hw_priv->scan.curr = it;
}
up(&hw_priv->conf_lock);
-11
View File
@@ -42,17 +42,6 @@ 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,
+5 -3
View File
@@ -448,6 +448,7 @@ void bes2600_remove_interface(struct ieee80211_hw *dev,
cancel_delayed_work_sync(&priv->join_timeout);
cancel_delayed_work_sync(&priv->set_cts_work);
cancel_delayed_work_sync(&priv->pending_offchanneltx_work);
cancel_work_sync(&priv->decrypt_storm_recover_work);
del_timer_sync(&priv->mcast_timeout);
/* TODO:COMBO: May be reset of these variables "delayed_link_loss and
@@ -2619,6 +2620,7 @@ int bes2600_vif_setup(struct bes2600_vif *priv)
/* Setup per vif workitems and locks */
spin_lock_init(&priv->vif_lock);
bes2600_decrypt_storm_init(priv);
INIT_WORK(&priv->join_work, bes2600_join_work);
INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout);
INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work);
@@ -3633,7 +3635,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw,
*
* Returns: 0 on success or non zero value on failure
*/
static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
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 +3665,7 @@ static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
*
* Returns: TSM parameters collected
*/
static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
int bes2600_get_tsm_params(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
struct bes_tsm_stats tsm_stats;
@@ -3703,7 +3705,7 @@ static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
*
* Returns: Returns the last measured roam delay
*/
static int bes2600_get_roam_delay(struct ieee80211_hw *hw)
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;
+74
View File
@@ -25,6 +25,78 @@
#define BES2600_INVALID_RATE_ID (0xFF)
/*
* Decrypt-storm fast-recover (Trigger B).
*
* When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a
* burst of received frames (typically because the host's PTK or GTK
* has fallen out of sync with the AP), the AP eventually concludes that
* the STA is not authenticated and emits an unprotected deauth-reason-6
* ("Class 2 frame received from non-authenticated station"). On the
* deployed pinetab2 + bes2600 stack this AP-initiated deauth has been
* observed to leave the link blackholed for up to 109 s before
* userspace finds a different SSID/channel to recover on. (Receipts at
* https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.)
*
* Recovery here pre-empts the AP: when we see THRESHOLD decrypt
* failures within WINDOW, we ask mac80211 for a clean reassoc via
* ieee80211_connection_loss(), which causes immediate disassociation
* and lets userspace auto-reconnect with fresh keys.
*
* mac80211 contract: ieee80211_connection_loss() may be called
* regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate
* disassociation without driver-side recovery attempts. See
* include/net/mac80211.h for the canonical doc-comment.
*
* The threshold is set well above the steady-state per-vif
* decrypt-fail rate observed in measurement (~1/min even under
* sustained 1 MB/s load), so a true storm is required to trip it.
*/
#define BES2600_DECRYPT_STORM_THRESHOLD 5
#define BES2600_DECRYPT_STORM_WINDOW_MS 5000
static void bes2600_decrypt_storm_recover_work(struct work_struct *work)
{
struct bes2600_vif *priv = container_of(work, struct bes2600_vif,
decrypt_storm_recover_work);
if (!priv->vif)
return;
bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n");
ieee80211_connection_loss(priv->vif);
priv->decrypt_storm_recoveries++;
}
void bes2600_decrypt_storm_init(struct bes2600_vif *priv)
{
INIT_WORK(&priv->decrypt_storm_recover_work,
bes2600_decrypt_storm_recover_work);
priv->decrypt_storm_window_start = 0;
priv->decrypt_storm_count = 0;
priv->decrypt_storm_recoveries = 0;
}
void bes2600_decrypt_storm_account(struct bes2600_vif *priv)
{
unsigned long now = jiffies;
unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS);
if (priv->decrypt_storm_window_start == 0 ||
time_after(now, priv->decrypt_storm_window_start + window)) {
priv->decrypt_storm_window_start = now;
priv->decrypt_storm_count = 1;
return;
}
if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) {
priv->decrypt_storm_count = 0;
/* Skew the window so we don't re-fire on the same storm. */
priv->decrypt_storm_window_start = now + window;
schedule_work(&priv->decrypt_storm_recover_work);
}
}
#ifdef CONFIG_BES2600_TESTMODE
#include "bes_nl80211_testmode_msg.h"
#endif /* CONFIG_BES2600_TESTMODE */
@@ -1672,6 +1744,8 @@ void bes2600_rx_cb(struct bes2600_vif *priv,
goto drop;
} else {
bes_warn("[RX] Receive failure: %d.\n", arg->status);
if (arg->status == WSM_STATUS_DECRYPTFAILURE)
bes2600_decrypt_storm_account(priv);
goto drop;
}
}
+1 -13
View File
@@ -134,20 +134,8 @@ static int wsm_generic_confirm(struct bes2600_common *hw_priv,
struct wsm_buf *buf)
{
u32 status = WSM_GET32(buf);
/*
* 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);
if (WARN(status != WSM_STATUS_SUCCESS, "wsm_generic_confirm ret %u", status))
return -EINVAL;
}
return 0;
underflow:
+2
View File
@@ -2236,5 +2236,7 @@ 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 -1
View File
@@ -18,7 +18,8 @@ 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, see <https://www.gnu.org/licenses/>.
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
.
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".