Compare commits

..

14 Commits

Author SHA1 Message Date
test0r db4ea70fb5 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>
2026-04-28 14:33:00 +02:00
test0r aff632ea64 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>
2026-04-24 23:53:05 +02:00
test0r f31c57adf7 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>
2026-04-24 09:40:44 +02:00
test0r 8855718511 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>
2026-04-24 08:55:10 +02:00
test0r ebb5c57988 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>
2026-04-24 08:55:10 +02:00
test0r ef24cdb891 bes2600: drop BES2600_WRITE_DPD_TO_FILE kernel_*() file paths
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>
2026-04-24 08:55:10 +02:00
test0r 64eae76f4e 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>
2026-04-24 08:55:10 +02:00
test0r 315986ea27 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>
2026-04-24 08:55:10 +02:00
test0r 9012b74eea bes2600: enable CONFIG_BES2600_TESTMODE by default + fix bit-rotted testmode plumbing
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>
2026-04-24 08:55:10 +02:00
test0r 8539460bf1 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>
2026-04-24 08:55:10 +02:00
test0r 19feb8181a 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>
2026-04-24 08:55:10 +02:00
test0r 20d349e2b5 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>
2026-04-24 08:55:10 +02:00
test0r 98c6e363f0 bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for PineTab2 factory.txt format
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>
2026-04-24 08:55:10 +02:00
test0r b76c9904f8 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>
2026-04-24 08:55:10 +02:00
54 changed files with 1164 additions and 3462 deletions
+3 -15
View File
@@ -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
@@ -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
@@ -65,8 +64,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 ?= y
FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt
STANDARD_FACTORY_EFUSE_FLAG ?= n
FACTORY_PATH ?= bes2600/bes2600_factory.txt
endif
# basic function
@@ -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)
+11 -20
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AP mode for BES2600 mac80211 driver
* mac80211 STA and AP API for mac80211 BES2600 drivers
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "bes2600.h"
@@ -14,7 +17,6 @@
#include <linux/umh.h>
#include "epta_request.h"
#include "epta_coex.h"
#include "txrx_opt.h"
#ifdef AP_HT_CAP_UPDATE
#define HT_INFO_OFFSET 4
@@ -63,11 +65,8 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct bes2600_vif *priv = cw12xx_get_vif_from_ieee80211(vif);
struct bes2600_link_entry *entry;
struct sk_buff *skb;
struct sk_buff_head local_drain;
struct bes2600_common *hw_priv = hw->priv;
__skb_queue_head_init(&local_drain);
#ifdef P2P_MULTIVIF
WARN_ON(priv->if_id == CW12XX_GENERIC_IF_ID);
#endif
@@ -96,17 +95,9 @@ int bes2600_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
priv->sta_asleep_mask |= BIT(sta_priv->link_id);
entry->status = BES2600_LINK_HARD;
/*
* Patch C2: splice the rx_queue out under the lock then deliver
* after unlock. ieee80211_rx_ni() runs the mac80211 RX path
* synchronously (formerly ieee80211_rx_irqsafe deferred to a
* tasklet); calling it from inside spin_lock_bh would hold the
* lock across mac80211's full RX dispatch.
*/
skb_queue_splice_init(&entry->rx_queue, &local_drain);
while ((skb = skb_dequeue(&entry->rx_queue)))
ieee80211_rx_irqsafe(priv->hw, skb);
spin_unlock_bh(&priv->ps_state_lock);
while ((skb = __skb_dequeue(&local_drain)))
ieee80211_rx_ni(priv->hw, skb);
#ifdef AP_AGGREGATE_FW_FIX
hw_priv->connected_sta_cnt++;
if(hw_priv->connected_sta_cnt>1) {
@@ -1163,7 +1154,7 @@ void bes2600_multicast_stop_work(struct work_struct *work)
container_of(work, struct bes2600_vif, multicast_stop_work);
if (priv->aid0_bit_set) {
timer_delete_sync(&priv->mcast_timeout);
del_timer_sync(&priv->mcast_timeout);
wsm_lock_tx(priv->hw_priv);
priv->aid0_bit_set = false;
bes2600_set_tim_impl(priv, false);
@@ -1173,7 +1164,7 @@ void bes2600_multicast_stop_work(struct work_struct *work)
void bes2600_mcast_timeout(struct timer_list *t)
{
struct bes2600_vif *priv = timer_container_of(priv, t, mcast_timeout);
struct bes2600_vif *priv = from_timer(priv, t, mcast_timeout);
wiphy_warn(priv->hw->wiphy,
"Multicast delivery timeout.\n");
@@ -1247,7 +1238,7 @@ void bes2600_suspend_resume(struct bes2600_vif *priv,
}
spin_unlock_bh(&priv->ps_state_lock);
if (cancel_tmo)
timer_delete_sync(&priv->mcast_timeout);
del_timer_sync(&priv->mcast_timeout);
} else {
spin_lock_bh(&priv->ps_state_lock);
bes2600_ps_notify(priv, arg->link_id, arg->stop);
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AP mode interface for BES2600 mac80211 driver
* mac80211 STA and AP API for mac80211 BES2600 drivers
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/version.h>
#ifndef AP_H_INCLUDED
+16 -42
View File
@@ -1,15 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Common private data for BES2600 mac80211 driver
* Common private data for BES2600 drivers
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* Based on the mac80211 Prism54 code, which is
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
*
* Based on the islsm (softmac prism54) driver, which is
* Based on the islsm (softmac prism54) driver, which is:
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_H
@@ -353,23 +356,15 @@ struct bes2600_common {
* Keeping in common structure for the time being. Will be moved to VIFF
* after the mechanism is clear */
u8 ba_tid_mask;
/*
* Patch D: ba_lock removed. Per-frame TX/RX hot-path bumped these
* counters under spin_lock_bh; the lock did not protect any
* compound invariant that atomic ops can't satisfy. Counters are
* now atomic_t; ba_armed gates the once-per-window mod_timer
* arm via cmpxchg so concurrent TX/RX at a fresh window each
* try to claim the arm and exactly one succeeds.
*/
atomic_t ba_acc;
atomic_t ba_cnt;
atomic_t ba_cnt_rx;
atomic_t ba_acc_rx;
atomic_t ba_armed;
int ba_hist;
struct timer_list ba_timer;
atomic_t ba_ena;
struct work_struct ba_work;
int ba_acc; /*TODO: Same as above */
int ba_cnt; /*TODO: Same as above */
int ba_cnt_rx; /*TODO: Same as above */
int ba_acc_rx; /*TODO: Same as above */
int ba_hist; /*TODO: Same as above */
struct timer_list ba_timer;/*TODO: Same as above */
spinlock_t ba_lock; /*TODO: Same as above */
bool ba_ena; /*TODO: Same as above */
struct work_struct ba_work; /*TODO: Same as above */
bool is_BT_Present;
bool is_go_thru_go_neg;
u8 conf_listen_interval;
@@ -516,9 +511,6 @@ struct bes2600_common {
struct list_head coex_event_list;
spinlock_t coex_event_lock;
/* Connection-loss-storm fast-recover (Trigger A). See sta.c. */
struct work_struct connection_loss_storm_recover_work;
/* member for low power */
struct bes2600_pwr_t bes_power;
@@ -604,11 +596,6 @@ 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;
@@ -635,10 +622,6 @@ struct bes2600_vif {
/* CQM Implementation */
struct delayed_work bss_loss_work;
struct delayed_work connection_loss_work;
/* Connection-loss-storm fast-recover (Trigger A). See sta.c. */
unsigned long connection_loss_storm_window_start;
unsigned int connection_loss_storm_count;
unsigned int connection_loss_storm_recoveries;
struct work_struct tx_failure_work;
int delayed_link_loss;
spinlock_t bss_loss_lock;
@@ -873,13 +856,4 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv);
void bes2600_btusb_uninit(struct usb_interface *interface);
#endif
/* Decrypt-storm fast-recover helpers — see txrx.c. */
void bes2600_decrypt_storm_init(struct bes2600_vif *priv);
void bes2600_decrypt_storm_account(struct bes2600_vif *priv);
/* Connection-loss-storm fast-recover helpers — see sta.c. */
void bes2600_connection_loss_storm_init(struct bes2600_vif *priv);
bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv);
void bes2600_connection_loss_storm_recover(struct work_struct *work);
#endif /* BES2600_H */
-509
View File
@@ -1,509 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Bestechnic BES2600 BT UART driver
*
* Copyright (c) 2025 Dang Huynh <dang.huynh@mainlining.org>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/pm_wakeirq.h>
#include <linux/gpio/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/serdev.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "bes2600.h"
#include "bes_chardev.h"
#include "h4_recv.h"
struct bes2600_btuart_data {
struct serdev_device *serdev;
struct hci_dev *hdev;
struct pinctrl *pinctrl;
struct pinctrl_state *pinctrl_default;
/* Device tree properties */
struct gpio_desc *power;
struct gpio_desc *wakeup;
int speed;
unsigned long state;
int wake_irq;
bool opened;
int err_count; /* workaround for broken firmware */
struct sk_buff_head txq;
struct work_struct tx_work;
rwlock_t lock;
struct sk_buff *rx_skb;
};
static const struct h4_recv_pkt bes2600_btuart_recv_pkts[] = {
{ H4_RECV_ACL, .recv = hci_recv_frame },
{ H4_RECV_SCO, .recv = hci_recv_frame },
{ H4_RECV_EVENT, .recv = hci_recv_frame },
};
static int bes2600_btuart_power(struct bes2600_btuart_data *data, bool enable)
{
int ret, val;
if (enable)
val = 1;
else
val = 0;
ret = gpiod_set_value_cansleep(data->power, val);
if (ret)
return ret;
ret = bes2600_chrdev_switch_subsys_glb(-1, val);
if (ret)
return ret;
return 0;
}
/*
* HACK: Potential DOS issue, not sure if the firmware can filter it but
* there needs to be a better way to detect this. Even better, fix the firmware.
*/
static int bes2600_btuart_bug_recovery(struct bes2600_btuart_data *data)
{
struct hci_dev *hdev = data->hdev;
struct serdev_device *serdev = data->serdev;
bt_dev_info(hdev, "Attempting to recover from Firmware bug (might not work)");
serdev_device_set_flow_control(serdev, false);
bes2600_btuart_power(data, false);
msleep(20);
bes2600_btuart_power(data, true);
serdev_device_set_flow_control(serdev, true);
msleep(100);
hci_reset_dev(hdev);
return 0;
}
static int bes2600_btuart_recv(struct bes2600_btuart_data *data, const void *buffer, int count)
{
struct hci_dev *hdev = data->hdev;
int err = 0;
data->rx_skb = h4_recv_buf(hdev, data->rx_skb, buffer, count,
bes2600_btuart_recv_pkts, ARRAY_SIZE(bes2600_btuart_recv_pkts));
if (IS_ERR(data->rx_skb))
err = PTR_ERR(data->rx_skb);
return err;
}
static size_t bes2600_btuart_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t count)
{
struct bes2600_btuart_data *data = serdev_device_get_drvdata(serdev);
struct hci_dev *hdev = data->hdev;
int ret;
read_lock(&data->lock);
ret = bes2600_btuart_recv(data, buf, count);
if (ret) {
bt_dev_err(hdev, "Failed to receive packet (%d)", ret);
data->err_count++;
ret = 0;
} else {
hdev->stat.byte_rx += count;
ret = count;
}
read_unlock(&data->lock);
/* Attempting to recovery from a bugged firmware */
if (data->err_count > 10) {
bes2600_btuart_bug_recovery(data);
data->err_count = 0;
}
return ret;
}
static const struct serdev_device_ops bes2600_btuart_serdev_client_ops = {
.receive_buf = bes2600_btuart_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static void bes2600_btuart_wake(struct hci_dev *hdev, bool enable)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
if (enable)
gpiod_set_value_cansleep(data->wakeup, 1);
else
gpiod_set_value_cansleep(data->wakeup, 0);
}
static int bes2600_btuart_open(struct hci_dev *hdev)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
struct serdev_device *serdev = data->serdev;
int ret;
ret = serdev_device_open(serdev);
if (ret < 0) {
bt_dev_err(hdev, "Failed to open serial device");
return ret;
}
serdev_device_set_flow_control(serdev, false);
ret = serdev_device_set_baudrate(serdev, data->speed);
if (ret < 0) {
bt_dev_err(hdev, "Failed to set baud rate");
return ret;
}
serdev_device_set_flow_control(serdev, true);
data->opened = true;
bes2600_btuart_wake(hdev, true);
return 0;
}
static int bes2600_btuart_flush(struct hci_dev *hdev)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
struct serdev_device *serdev = data->serdev;
serdev_device_write_flush(serdev);
read_lock(&data->lock);
skb_queue_purge(&data->txq);
cancel_work_sync(&data->tx_work);
read_unlock(&data->lock);
return 0;
}
static int bes2600_btuart_close(struct hci_dev *hdev)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
struct serdev_device *serdev = data->serdev;
bes2600_btuart_wake(hdev, false);
serdev_device_set_flow_control(serdev, false);
serdev_device_close(serdev);
data->opened = false;
return 0;
}
static int bes2600_btuart_setup(struct hci_dev *hdev)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
int ret;
if (bes2600_chrdev_is_bus_error())
return -EFAULT;
/* Set pinctrl state to default */
ret = pinctrl_select_state(data->pinctrl, data->pinctrl_default);
if (ret < 0) {
bt_dev_err(hdev, "Failed to setup pinctrl state");
return ret;
}
/* Set power GPIO to high and tell chardev to switch to BT */
bes2600_btuart_power(data, true);
return 0;
}
static int bes2600_btuart_shutdown(struct hci_dev *hdev)
{
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
if (bes2600_chrdev_is_bus_error())
return -EFAULT;
bes2600_btuart_power(data, false);
return 0;
}
static int bes2600_btuart_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
char *pkt_type = NULL;
struct bes2600_btuart_data *data = hci_get_drvdata(hdev);
switch (hci_skb_pkt_type(skb)) {
case HCI_COMMAND_PKT:
hdev->stat.cmd_tx++;
break;
case HCI_ACLDATA_PKT:
hdev->stat.acl_tx++;
break;
case HCI_SCODATA_PKT:
hdev->stat.sco_tx++;
break;
default:
return -EILSEQ;
}
pkt_type = skb_push(skb, 1);
pkt_type[0] = hci_skb_pkt_type(skb);
read_lock(&data->lock);
skb_queue_tail(&data->txq, skb);
read_unlock(&data->lock);
schedule_work(&data->tx_work);
return 0;
}
static ssize_t bes2600_btuart_send(struct bes2600_btuart_data *data, struct sk_buff *skb)
{
struct hci_dev *hdev = data->hdev;
int len;
len = serdev_device_write_buf(data->serdev, skb->data, skb->len);
if (len < 0) {
bt_dev_err(data->hdev, "Failed to write buffer to TTY");
return -EIO;
}
serdev_device_wait_until_sent(data->serdev, 0);
hdev->stat.byte_tx += len;
skb_pull(skb, len);
return skb->len;
}
static void bes2600_btuart_work(struct work_struct *work)
{
struct bes2600_btuart_data *data = container_of(work, struct bes2600_btuart_data, tx_work);
struct sk_buff *skb;
while ((skb = skb_dequeue(&data->txq))) {
if (bes2600_btuart_send(data, skb) > 0) {
skb_queue_head(&data->txq, skb);
break;
}
kfree_skb(skb);
}
}
static void bes2600_btuart_parse_dt(struct serdev_device *serdev)
{
struct bes2600_btuart_data *data = serdev_device_get_drvdata(serdev);
struct device_node *node = serdev->dev.of_node;
int speed = 1500000;
of_property_read_u32(node, "current-speed", &speed);
data->speed = speed;
}
static irqreturn_t bes2600_btuart_wakeup_handler(int irq, void *priv)
{
struct bes2600_btuart_data *data = (struct bes2600_btuart_data *)priv;
dev_info(&data->serdev->dev, "IRQ wake\n");
return IRQ_HANDLED;
}
#ifdef CONFIG_PM_SLEEP
static int bes2600_btuart_suspend(struct device *dev)
{
struct serdev_device *serdev = to_serdev_device(dev);
struct bes2600_btuart_data *data = serdev_device_get_drvdata(serdev);
if (data->opened) {
bes2600_btuart_wake(data->hdev, false);
if (device_may_wakeup(dev) && data->wake_irq)
enable_irq_wake(data->wake_irq);
}
return 0;
}
static int bes2600_btuart_resume(struct device *dev)
{
struct serdev_device *serdev = to_serdev_device(dev);
struct bes2600_btuart_data *data = serdev_device_get_drvdata(serdev);
if (data->opened) {
bes2600_btuart_wake(data->hdev, true);
if (device_may_wakeup(dev) && data->wake_irq)
disable_irq_wake(data->wake_irq);
}
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(bes2600_btuart_pm_ops,
bes2600_btuart_suspend, bes2600_btuart_resume);
static int bes2600_btuart_probe(struct serdev_device *serdev)
{
struct device *dev = &serdev->dev;
struct bes2600_btuart_data *data;
struct hci_dev *hdev;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
skb_queue_head_init(&data->txq);
data->pinctrl = devm_pinctrl_get(dev);
if (IS_ERR(data->pinctrl))
return dev_err_probe(dev, PTR_ERR(data->pinctrl), "Cannot get pinctrl\n");
data->pinctrl_default = pinctrl_lookup_state(data->pinctrl, PINCTRL_STATE_DEFAULT);
if (IS_ERR(data->pinctrl_default))
return dev_err_probe(dev, PTR_ERR(data->pinctrl_default),
"Cannot get default pinctrl state\n");
data->power = devm_gpiod_get_index(dev, "power", 0, GPIOD_OUT_HIGH);
if (IS_ERR(data->power))
return dev_err_probe(dev, PTR_ERR(data->power), "Failed to get power gpio\n");
data->wakeup = devm_gpiod_get_index_optional(dev, "wakeup", 0, GPIOD_OUT_HIGH);
if (!data->wakeup)
dev_err(dev, "Failed to get wakeup gpio\n");
data->wake_irq = of_irq_get_byname(dev->of_node, "wakeup");
if (data->wake_irq <= 0) {
ret = data->wake_irq;
dev_err(dev, "Failed to get IRQ number, host will not wake up (ret=%d)\n", ret);
}
if (data->wake_irq > 0) {
ret = devm_device_init_wakeup(dev);
if (ret)
return dev_err_probe(dev, ret, "Failed to initialize device wakeup\n");
ret = devm_request_threaded_irq(dev, data->wake_irq, NULL,
bes2600_btuart_wakeup_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
"bes2600_btwakeup_irq", data);
if (ret < 0)
return dev_err_probe(dev, ret, "Cannot request IRQ\n");
ret = devm_pm_set_wake_irq(dev, data->wake_irq);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to set wake irq\n");
}
data->err_count = 0;
hdev = hci_alloc_dev();
if (!hdev)
return -ENOMEM;
rwlock_init(&data->lock);
data->serdev = serdev;
data->hdev = hdev;
serdev_device_set_drvdata(serdev, data);
serdev_device_set_client_ops(serdev, &bes2600_btuart_serdev_client_ops);
bes2600_btuart_parse_dt(serdev);
INIT_WORK(&data->tx_work, bes2600_btuart_work);
hdev->bus = HCI_UART;
hci_set_drvdata(hdev, data);
hdev->open = bes2600_btuart_open;
hdev->close = bes2600_btuart_close;
hdev->flush = bes2600_btuart_flush;
hdev->setup = bes2600_btuart_setup;
hdev->shutdown = bes2600_btuart_shutdown;
hdev->send = bes2600_btuart_send_frame;
hdev->manufacturer = 0x02B0;
SET_HCIDEV_DEV(hdev, &serdev->dev);
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_STORED_LINK_KEY);
/* Firmware claims to support HCI_OP_LE_READ_BUFFER_SIZE_V2 but broken */
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_BUFFER_SIZE_V2);
/*
* Once shutdown() is ran, it turns off the Bluetooth regulator and
* would not power back on unless setup() is run again.
*/
hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP);
ret = hci_register_dev(hdev);
if (ret < 0) {
hci_free_dev(hdev);
return dev_err_probe(dev, ret, "Failed to register HCI device\n");
}
return 0;
}
static void bes2600_btuart_remove(struct serdev_device *serdev)
{
struct bes2600_btuart_data *data = serdev_device_get_drvdata(serdev);
struct hci_dev *hdev = data->hdev;
cancel_work_sync(&data->tx_work);
if (hdev) {
hci_unregister_dev(hdev);
hci_free_dev(hdev);
}
}
static const struct of_device_id bes2600_btuart_of_match[] = {
{ .compatible = "bestechnic,bes2600-btuart", },
{},
};
MODULE_DEVICE_TABLE(of, bes2600_btuart_of_match);
static struct serdev_device_driver bes2600_btuart_driver = {
.probe = bes2600_btuart_probe,
.remove = bes2600_btuart_remove,
.driver = {
.name = "bes2600_btuart",
.pm = &bes2600_btuart_pm_ops,
.of_match_table = of_match_ptr(bes2600_btuart_of_match),
},
};
module_serdev_device_driver(bes2600_btuart_driver);
MODULE_AUTHOR("Dang Huynh <dang.huynh@mainlining.org>");
MODULE_DESCRIPTION("BES2600 BT UART driver");
MODULE_LICENSE("GPL");
+49 -68
View File
@@ -1,14 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Factory calibration loader for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#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>
@@ -27,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
@@ -134,66 +150,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data)
*/
static int factory_section_read_file(char *path, void *buffer)
{
int ret = 0;
struct file *fp;
const struct firmware *fw;
int ret;
if (!path || !buffer) {
bes_err("%s NULL pointer err\n", __func__);
return -1;
}
bes_devel("reading %s \n", path);
bes_devel("requesting firmware-class %s\n", path);
fp = filp_open(path, O_RDONLY, 0); //S_IRUSR
if (IS_ERR(fp)) {
bes_devel("BES2600 : can't open %s\n",path);
ret = request_firmware(&fw, path, bes2600_factory_dev);
if (ret) {
bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
}
if (fp->f_inode->i_size <= 0 || fp->f_inode->i_size > FACTORY_MAX_SIZE) {
bes_err( "bes2600_factory.txt size check failed, read_size: %lld max_size: %d\n",
fp->f_inode->i_size, FACTORY_MAX_SIZE);
filp_close(fp, NULL);
if (fw->size == 0 || fw->size > FACTORY_MAX_SIZE) {
bes_err("bes2600_factory.txt size check failed, read_size: %zu max_size: %d\n",
fw->size, FACTORY_MAX_SIZE);
release_firmware(fw);
return -1;
}
ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos);
filp_close(fp, NULL);
if (ret != fp->f_inode->i_size) {
bes_err("bes2600_factory.txt read fail\n");
ret = -1;
}
return ret;
}
/**
* factory_section_write_file - Write data of specified length to file
* @path: path of the file
* @buffer: storage of write data
* @size: length of data to write
*
* Return: length on success, negative error code otherwise.
*/
static int factory_section_write_file(char *path, void *buffer, int size)
{
int ret = 0;
struct file *fp;
bes_devel("writing %s \n", path);
fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
if (IS_ERR(fp)) {
bes_devel("BES2600 : can't open %s\n",path);
return -1;
}
ret = kernel_write(fp, buffer, size, &fp->f_pos);
filp_close(fp,NULL);
memcpy(buffer, fw->data, fw->size);
ret = (int)fw->size;
release_firmware(fw);
return ret;
}
@@ -888,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
@@ -899,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);
@@ -917,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;
}
+9 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Factory calibration loader interface
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __FACTORY_H__
#define __FACTORY_H__
@@ -196,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);
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Platform data for BES2600 SDIO bus
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_PLAT_H_INCLUDED
#define BES2600_PLAT_H_INCLUDED
+74 -156
View File
@@ -1,13 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SDIO bus glue for BES2600 mac80211 driver
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Derived from drivers/net/wireless/st/cw1200/cw1200_sdio.c
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
* Mac80211 SDIO driver for BES2600 device
*
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define DEBUG 1
#include <linux/version.h>
@@ -17,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>
@@ -30,9 +28,9 @@
#include <linux/of_gpio.h>
#include "bes2600.h"
#include "bh.h"
#include "sbus.h"
#include "bes2600_plat.h"
#include "bes2600_factory.h"
#include "hwio.h"
#include "bes_chardev.h"
#include "bes_log.h"
@@ -73,12 +71,10 @@ struct sbus_priv {
int rx_data_toggle;
#endif
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
/*
* Patch C v3: rx_queue, rx_queue_lock, rx_work removed (no relay).
* The bh thread now reads RX inline; the rx_buffer scratch area
* stays. Counters/timestamps stay for debugfs visibility.
*/
spinlock_t rx_queue_lock;
struct sk_buff_head rx_queue;
u8 *rx_buffer;
struct work_struct rx_work;
u32 rx_last_ctrl;
u32 rx_valid_ctrl;
u32 rx_total_ctrl_cnt;
@@ -415,19 +411,10 @@ static void bes2600_sdio_irq_handler(struct sdio_func *func)
bes_devel("%s called, fw_started:%d \n",
__func__, self->fw_started);
/*
* Patch C v3: no more sdio_rx_work relay. Wake the bh thread
* directly via self->irq_handler (bes2600_irq_handler in bh.c
* which bumps bh_rx atomic + wakes bh_wq). The bh thread will
* then call sbus_ops->bus_rx_batch() to do the SDIO read inline.
* Matches cw1200 mainline IRQ → bh-direct architecture.
*/
if (likely(self->fw_started && self->core && self->irq_handler)) {
spin_lock_irqsave(&self->lock, flags);
self->irq_handler(self->irq_priv);
spin_unlock_irqrestore(&self->lock, flags);
if (likely(self->fw_started && self->core)) {
queue_work(self->sdio_wq, &self->rx_work);
self->last_irq_timestamp = jiffies;
} else if (self->irq_handler) {
} else if(self->irq_handler) {
spin_lock_irqsave(&self->lock, flags);
self->irq_handler(self->irq_priv);
spin_unlock_irqrestore(&self->lock, flags);
@@ -824,15 +811,10 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8
skb_put(skb, packet_len);
memcpy(skb->data, &data[pos], packet_len);
bes_devel("%s, %d,%d\n", __func__, packet_len, pos);
spin_lock(&self->rx_queue_lock);
skb_queue_tail(&self->rx_queue, skb);
self->rx_data_cnt++;
/*
* Patch C v3: deliver the SKB directly into the WSM/mac80211
* stack from the bh thread. No rx_queue, no inter-thread
* handoff, no atomic_t needed on the counters that
* wsm_release_tx_buffer touches — single-writer-from-bh is
* preserved by construction. See bh.c for the contract block.
*/
bes2600_bh_handle_rx_skb(self->core, skb);
spin_unlock(&self->rx_queue_lock);
packet_len = (packet_len + 3) & (~0x3);
pos += packet_len;
#ifdef BES_SDIO_OPTIMIZED_LEN
@@ -843,31 +825,17 @@ static int bes2600_sdio_extract_packets(struct sbus_priv *self, u32 ctrl_reg, u8
return 0;
}
/*
* Patch C v3: bh thread calls this directly via sbus_ops->bus_rx_batch.
* No more sdio_rx_work workqueue. SDIO read sequence (lock →
* read_ctrl → memcpy_fromio → packets_check → extract_packets) runs
* inline in bh-thread context. Each parsed SKB is delivered via
* bes2600_bh_handle_rx_skb() from extract_packets — no rx_queue, no
* second worker, no inter-thread handoff.
*
* Architecture matches cw1200 mainline. Single-writer-from-bh
* invariant on hw_bufs_used preserved by construction.
*
* Returns 0 on success (caller's bh outer loop decides whether to
* continue), negative on bus read error. On error: triggers
* wifi_force_close (same as the old sdio_rx_work).
*/
static int bes2600_sdio_read_rx_batch(struct sbus_priv *self)
static void sdio_rx_work(struct work_struct *work)
{
int ret = 0, again = 0, retry = 0, crc_retry = 0;
int ret, again = 0, retry = 0, crc_retry = 0;
u32 ctrl_reg = 0;
int total_len;
struct sbus_priv *self = container_of(work, struct sbus_priv, rx_work);
u8 *buf = self->rx_buffer;
/* don't read/write sdio when sdio error */
if (bes2600_chrdev_is_bus_error())
return 0;
return;
bes2600_gpio_wakeup_mcu(self, GPIO_WAKE_FLAG_SDIO_RX);
@@ -922,10 +890,6 @@ static int bes2600_sdio_read_rx_batch(struct sbus_priv *self)
goto failed;
}
/*
* extract_packets parses the multi-RX buffer and calls
* bes2600_bh_handle_rx_skb() per SKB. No queueing.
*/
if ((ret = bes2600_sdio_extract_packets(self, ctrl_reg, buf))) {
bes_err("%s,%d error=%d\n", __func__, __LINE__, ret);
goto failed;
@@ -933,16 +897,22 @@ static int bes2600_sdio_read_rx_batch(struct sbus_priv *self)
ctrl_reg = 0;
if (likely(self->irq_handler)) {
self->irq_handler(self->irq_priv);
} else {
bes_err("%s,%d\n", __func__, __LINE__);
goto failed;
}
} while (again);
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
return 0;
return;
failed:
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_RX);
bes2600_chrdev_wifi_force_close(self->core, false);
WARN_ON(1);
return -1;
}
static void sdio_scan_work(struct work_struct *work)
@@ -950,11 +920,26 @@ static void sdio_scan_work(struct work_struct *work)
bes_warn("%s: this function does nothing\n", __FUNCTION__);
}
/* Patch C v3: bes2600_sdio_pipe_read deleted. bh thread reads the
* SDIO bus inline via bes2600_sdio_read_rx_batch (sbus_ops->bus_rx_batch).
* No rx_queue, no skb_dequeue, no relay. bes2600_tx_loop_read remains
* for the test bus error-fallback path but is now invoked at higher
* level. */
static void *bes2600_sdio_pipe_read(struct sbus_priv *self)
{
struct sk_buff *skb;
if (bes2600_chrdev_is_bus_error()) {
return bes2600_tx_loop_read(self->core);
}
spin_lock(&self->rx_queue_lock);
skb = skb_dequeue(&self->rx_queue);
if (skb)
self->rx_proc_cnt++;
spin_unlock(&self->rx_queue_lock);
if (likely(self->fw_started == true &&
!bes2600_pwr_device_is_idle(self->core) &&
self->core->hw_bufs_used > 0))
if (!skb)
queue_work(self->sdio_wq, &self->rx_work);
return skb;
}
#endif
@@ -1210,14 +1195,7 @@ flush_previous:
}
} while (crc_retry <= 10);
sdio_release_host(self->func);
/*
* Patch C v3: wake the bh thread to check for any RX
* that piggybacked on this TX window. Bumps bh_rx
* atomic; bh's wait_event will pick it up and call
* sbus_ops->bus_rx_batch().
*/
if (likely(self->irq_handler))
self->irq_handler(self->irq_priv);
queue_work(self->sdio_wq, &self->rx_work);
if (ret) {
bes_err("%s,%d err=%d,%d,%d\n", __func__, __LINE__, ret, scatters, cur_blk);
sdio_work_debug(self);
@@ -1268,11 +1246,12 @@ static int bes2600_sdio_misc_init(struct sbus_priv *self, struct bes2600_common
self->next_toggle = 0;
#endif
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
/* Patch C v3: rx_queue / rx_queue_lock removed (no relay). */
spin_lock_init(&self->rx_queue_lock);
skb_queue_head_init(&self->rx_queue);
self->rx_buffer = (u8 *)__get_dma_pages(GFP_KERNEL, get_order(1632 * BES_SDIO_RX_MULTIPLE_NUM));
if (!self->rx_buffer)
return -ENOMEM;
/* Patch C v3: sdio_rx_work removed; bh thread does the read. */
INIT_WORK(&self->rx_work, sdio_rx_work);
#endif
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
INIT_LIST_HEAD(&self->tx_bufferlist);
@@ -1409,14 +1388,7 @@ static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) != 0) {
/*
* 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);
bes_err( "repeat set gpio_wake_flag, sub_sys:%d", flag);
mutex_unlock(&self->io_mutex);
return;
}
@@ -1448,11 +1420,7 @@ static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) == 0) {
/*
* 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);
bes_err( "repeat clear gpio_wake_flag, sub_sys:%d", flag);
mutex_unlock(&self->io_mutex);
return;
}
@@ -1601,15 +1569,22 @@ err:
static void bes2600_sdio_empty_work(struct sbus_priv *self)
{
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
struct sk_buff *skb;
#endif
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
struct bes_sdio_tx_list_t *tx_buffer, *temp;
#endif
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
/*
* Patch C v3: rx_work and rx_queue removed. Counters still
* reset for the next attach cycle.
*/
cancel_work_sync(&self->rx_work);
while (1) {
skb = skb_dequeue(&self->rx_queue);
if (skb)
dev_kfree_skb(skb);
else
break;
}
self->rx_last_ctrl = 0;
self->rx_total_ctrl_cnt = 0;
self->rx_continuous_ctrl_cnt = 0;
@@ -1802,55 +1777,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)
{
struct mmc_host *host;
int ret;
if (!self || !self->func || !self->func->card)
return -EINVAL;
host = self->func->card->host;
ret = mmc_hw_reset(self->func->card);
/*
* On multi-function SDIO cards (BES2600 has WLAN func 1 + BT
* companion func 2), mmc_sdio_hw_reset() removes the card and
* returns 1 to signal "remove happened, caller must trigger
* rescan". The kernel does NOT auto-rescan in this case;
* single-function cards take the rescan path inline and return 0.
* Treat any non-negative return as success and force a rescan if
* mmc_hw_reset signalled the multi-function path - otherwise the
* card stays removed indefinitely after a wedge recovery,
* leaving wifi (and the BT companion) silent until reboot.
*/
if (ret > 0) {
bes_info("multi-func mmc_hw_reset removed card; scheduling rescan\n");
mmc_detect_change(host, 0);
ret = 0;
}
return ret;
}
static bool bes2600_sdio_wakeup_source(struct sbus_priv *self)
{
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
@@ -1877,8 +1803,7 @@ static struct sbus_ops bes2600_sdio_sbus_ops = {
.sbus_reg_write = bes2600_sdio_reg_write,
.init = bes2600_sdio_misc_init,
#ifdef BES_SDIO_RX_MULTIPLE_ENABLE
/* Patch C v3: .pipe_read removed; bus_rx_batch replaces it. */
.bus_rx_batch = bes2600_sdio_read_rx_batch,
.pipe_read = bes2600_sdio_pipe_read,
#endif
#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
.pipe_send = bes2600_sdio_pipe_send,
@@ -1890,7 +1815,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)
@@ -1898,15 +1822,9 @@ static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv)
long unsigned int old_ts, new_ts;
struct sbus_priv *self = hw_priv->sbus_priv;
/*
* Patch C v3: rx_work removed. Wait for IRQ-timestamp activity
* to settle by polling self->last_irq_timestamp via msleep
* (best-effort). The caller already knows the bh thread will
* process pending bh_rx during its next wait_event round.
*/
do {
old_ts = self->last_irq_timestamp;
msleep(2);
flush_work(&self->rx_work);
new_ts = self->last_irq_timestamp;
} while(old_ts != new_ts);
}
@@ -1937,6 +1855,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;
@@ -1993,6 +1914,7 @@ static int bes2600_sdio_probe(struct sdio_func *func,
out:
bes2600_chrdev_set_sbus_priv_data(self, false);
bes2600_switch_bt(true);
bes2600_gpio_allow_mcu_sleep(self, GPIO_WAKE_FLAG_SDIO_PROBE);
return 0;
@@ -2027,8 +1949,8 @@ int bes2600_unregister_net_dev(struct sbus_priv *bus_priv)
BUG_ON(!bus_priv);
if (bus_priv->core && !bus_priv->unregister_in_process) {
bus_priv->unregister_in_process = true;
bes2600_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb);
bes2600_core_release(bus_priv->core);
bes2600_pwr_unregister_en_lp_cb(bus_priv->core, bes2600_sdio_en_lp_cb);
bus_priv->core = NULL;
if (bus_priv->sdio_wq) {
@@ -2260,12 +2182,8 @@ static int bes2600_sdio_suspend_noirq(struct device *dev)
if (func->num > 1)
return 0;
/*
* Patch C v3: work_pending(&self->rx_work) check dropped (no
* relay). bh_rx atomic alone tells us whether the bh thread
* has un-processed RX events queued.
*/
if (self->core && atomic_read(&self->core->bh_rx)) {
if(self->core &&
(work_pending(&self->rx_work) || atomic_read(&self->core->bh_rx))) {
bes_devel("%s: Suspend interrupted.\n", __func__);
return -EAGAIN;
}
+11 -782
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Character device for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include<linux/module.h>
#include <linux/init.h>
@@ -40,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;
@@ -66,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 */
@@ -88,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);
@@ -193,7 +184,7 @@ static int bes2600_switch_wifi(bool on)
return ret;
}
static int bes2600_switch_bt(bool on)
int bes2600_switch_bt(bool on)
{
int ret = 0;
long status = 0;
@@ -226,11 +217,11 @@ static int bes2600_switch_bt(bool on)
/* check if there is a error when bootup */
ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0;
} else {
bes_devel("bes2600 activate bt.\n");
bes_info("enable BT\n");
ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_ON, SUBSYSTEM_BT, true);
}
} else {
bes_devel("bes2600 deactivate bt.\n");
bes_info("disable BT\n");
bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_OFF, SUBSYSTEM_BT, false);
}
@@ -246,392 +237,18 @@ static int bes2600_switch_bt(bool on)
return ret;
}
/*
* This is a global function so we don't have to make many changes to
* the driver.
*
* @wifi: 1 to turn on, 0 to turn off. Otherwise, leave unchanged
* @bt: 1 to turn on, 0 to turn off. Otherwise, leave unchanged
*/
int bes2600_chrdev_switch_subsys_glb(int wifi, int bt)
{
int ret = 0;
switch (wifi) {
case 0:
ret = bes2600_switch_wifi(false);
break;
case 1:
ret = bes2600_switch_wifi(true);
break;
default:
break;
}
if (ret)
goto result;
switch (bt) {
case 0:
ret = bes2600_switch_bt(false);
break;
case 1:
ret = bes2600_switch_bt(true);
break;
default:
break;
}
result:
return ret;
}
EXPORT_SYMBOL_GPL(bes2600_chrdev_switch_subsys_glb);
static int bes2600_get_cmd_and_ifname(const char *str, char **result)
{
int cmd_len = 0;
int ifname_len = 0;
char *sp = NULL;
char *tmp_ptr = NULL;
char *cmd_ptr = NULL;
/* check if input arguments is valid */
if (!str || strncmp(str, "ifname:", 7) != 0)
return -1;
sp = strchr(str, ' ');
if (strncmp(sp + 1, "cmd:", 4) != 0)
return -1;
/* extract interface name */
ifname_len = sp - str - 7;
tmp_ptr = kmalloc(ifname_len + 1, GFP_KERNEL);
if (!tmp_ptr) {
return -2;
}
strncpy(tmp_ptr, str+7, ifname_len);
tmp_ptr[ifname_len] = '\0';
result[0] = tmp_ptr;
/* get command length */
cmd_ptr = strstr(str, "cmd:");
cmd_ptr += 4;
sp = strchr(cmd_ptr, ' ');
if (!sp) { /* the command don't have any parameter */
cmd_len = strlen(cmd_ptr);
if (cmd_ptr[cmd_len - 1] == '\n')
--cmd_len;
} else { /* the command have one or more parameter */
cmd_len = sp - cmd_ptr;
}
/* copy command to out buffer */
tmp_ptr = kmalloc( cmd_len + 1, GFP_KERNEL);
if (!tmp_ptr) {
kfree(result[0]);
result[0] = NULL;
return -3;
}
strncpy(tmp_ptr, cmd_ptr, cmd_len);
tmp_ptr[cmd_len] = '\0';
result[1] = tmp_ptr;
return 0;
}
static void bes2600_recyle_cmd_and_ifname_mem(char **info)
{
if (info[0]) {
kfree(info[0]);
info[0] = NULL;
}
if (info[1]) {
kfree(info[1]);
info[1] = NULL;
}
}
static int bes2600_op_default_handler(const char *str)
{
char *info[2] = {0};
if (bes2600_get_cmd_and_ifname(str, info) == 0) {
bes_devel("cmd(%s) on %s not handled\n", info[1], info[0]);
} else {
bes_err("%s get command fail, the origin string is %s\n", __func__, str);
}
bes2600_recyle_cmd_and_ifname_mem(info);
return 0;
}
static int bes2600_op_wifi_bt_on_off(const char *str)
{
char *info[2] = {0};
int ret = 0;
enum wait_state wait_state;
enum bus_probe_state probe_state;
unsigned long status = 0;
spin_lock(&bes2600_cdev.status_lock);
probe_state = bes2600_cdev.bus_probe;
wait_state = bes2600_cdev.wait_state;
spin_unlock(&bes2600_cdev.status_lock);
/* only work for wifi signal mode */
if (bes2600_cdev.fw_type != BES2600_FW_TYPE_WIFI_SIGNAL)
return -EFAULT;
/* wait bus probe operation end */
if (probe_state == BES2600_BUS_PROBE_START) {
bes_devel("wait bus probe operation end\n");
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
(bes2600_cdev.bus_probe > BES2600_BUS_PROBE_START),
HZ);
WARN_ON(status <= 0);
}
/* must wait previous operation end in critical section */
if (wait_state != BES2600_BOOT_WAIT_NONE) {
bes_devel("wait previous operation end\n");
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
(bes2600_cdev.wait_state == BES2600_BOOT_WAIT_NONE),
HZ * 8);
WARN_ON(status <= 0);
}
/* if dpd calibration is doing, modify wifi and bt state directly */
spin_lock(&bes2600_cdev.status_lock);
if (bes2600_cdev.bus_probe == BES2600_BUS_PROBE_OK && !bes2600_cdev.dpd_calied) {
if (bes2600_get_cmd_and_ifname(str, info) == 0) {
if (strncmp(info[1], "WIFI_ON", 7) == 0) {
bes2600_cdev.wifi_opened = true;
} else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
bes2600_cdev.wifi_opened = false;
} else if (strncmp(info[1], "BT_ON", 5) == 0) {
bes2600_cdev.bt_opened = true;
bes2600_cdev.bton_pending = true;
} else if (strncmp(info[1], "BT_OFF", 6) == 0) {
bes2600_cdev.bt_opened = false;
bes2600_cdev.bton_pending = false;
}
}
bes2600_recyle_cmd_and_ifname_mem(info);
spin_unlock(&bes2600_cdev.status_lock);
/* wait probe done event */
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
bes2600_bootup_end(), HZ * 8);
WARN_ON(status <= 0);
return (status <= 0 || bes2600_chrdev_is_bus_error()) ? -EFAULT : 0;
}
spin_unlock(&bes2600_cdev.status_lock);
/* process wifi/bt on/off operation */
if (bes2600_get_cmd_and_ifname(str, info) == 0) {
if (strncmp(info[1], "WIFI_ON", 7) == 0) {
ret = bes2600_switch_wifi(1);
} else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
ret = bes2600_switch_wifi(0);
} else if (strncmp(info[1], "BT_ON", 5) == 0) {
ret = bes2600_switch_bt(1);
} else if (strncmp(info[1], "BT_OFF", 6) == 0) {
ret = bes2600_switch_bt(0);
}
}
if (!ret && bes2600_chrdev_check_system_close())
ret = bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
bes2600_recyle_cmd_and_ifname_mem(info);
return ret ;
}
static int bes2600_op_change_fw_type(const char *str)
{
int ret = 0;
int temp = 0;
long status = 0;
char *cmd_ptr = NULL;
char fw_type[5] = {0};
bool sys_closed = bes2600_chrdev_check_system_close();
bes_devel("%s is called, arg:%s\n", __func__, str);
if (!bes2600_cdev.sbus_ops->power_switch && !bes2600_cdev.sbus_ops->reboot)
return -EPERM;
/* check if user input is valid */
cmd_ptr = strstr(str, "CHANGE_FW_TYPE ");
if (strlen(str) < 16 || !cmd_ptr) {
bes_err("the format of \"%s\" is error\n", str);
return -EINVAL;
}
/* convert fw_type from string to int */
strncpy(fw_type, cmd_ptr + 14, 4);
fw_type[0] = '+';
ret = kstrtoint(fw_type, 10, &temp);
if (ret < 0) {
bes_err("%s parse error\n", __func__);
return -EINVAL;
}
/* no need to realod firmware if new fw_type is equal to the old */
if (temp == bes2600_cdev.fw_type ) {
bes_devel("fw type is equal\n");
return 0;
}
/* close wifi net device */
if (bes2600_cdev.sbus_priv
&& bes2600_is_net_dev_created(bes2600_cdev.sbus_priv)) {
bes2600_unregister_net_dev(bes2600_cdev.sbus_priv);
}
/* update firmware type */
bes2600_cdev.fw_type = temp;
bes2600_chrdev_update_signal_mode();
if (!sys_closed) {
/* close device to call disconnect function */
if (bes2600_cdev.sbus_ops->power_switch)
bes2600_cdev.sbus_ops->power_switch(bes2600_cdev.sbus_priv, 0);
else if (bes2600_cdev.sbus_ops->reboot)
bes2600_cdev.sbus_ops->reboot(bes2600_cdev.sbus_priv);
}
if (bes2600_cdev.sbus_ops->reboot)
bes2600_chrdev_start_bus_probe();
/* wait disconnect event */
status = wait_event_timeout(bes2600_cdev.probe_done_wq, (bes2600_cdev.sbus_priv == NULL), HZ * 10);
WARN_ON(status <= 0);
if (bes2600_cdev.dpd_calied
&& bes2600_chrdev_check_system_close()) {
bes_devel("no need to reload firmware\n");
return 0;
}
bes_devel("reload firmware...\n");
/* power on device to call probe function */
if (bes2600_cdev.sbus_ops->power_switch)
bes2600_cdev.sbus_ops->power_switch(NULL, 1);
/* wait probe done event */
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
bes2600_bootup_end(), HZ * 10);
WARN_ON(status <= 0);
ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0;
return ret;
}
static int bes2600_op_bt_wakeup(const char *str)
{
int ret = 0;
unsigned long status = 0;
spin_lock(&bes2600_cdev.status_lock);
if (!bes2600_cdev.bt_opened) {
spin_unlock(&bes2600_cdev.status_lock);
return -EFAULT;
}
spin_unlock(&bes2600_cdev.status_lock);
/* wait probe done event */
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
bes2600_bootup_end(), HZ * 8);
if (status <= 0 || bes2600_chrdev_is_bus_error())
return -EFAULT;
bes_devel("bes2600 wakeup bt.\n");
ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_ON, SUBSYSTEM_BT_LP, true);
return ret;
}
static int bes2600_op_bt_sleep(const char *str)
{
int ret = 0;
unsigned long status = 0;
spin_lock(&bes2600_cdev.status_lock);
if (!bes2600_cdev.bt_opened) {
spin_unlock(&bes2600_cdev.status_lock);
return -EFAULT;
}
spin_unlock(&bes2600_cdev.status_lock);
/* wait probe done event */
status = wait_event_timeout(bes2600_cdev.probe_done_wq,
bes2600_bootup_end(), HZ * 8);
if (status <= 0 || bes2600_chrdev_is_bus_error())
return -EFAULT;
bes_devel("bes2600 allow bt sleep.\n");
ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_OFF, SUBSYSTEM_BT_LP, false);
return ret;
}
static int bes2600_op_set_wakeup_read_flag(const char *str)
{
bes_devel("%s is called, arg:%s\n", __func__, str);
spin_lock(&bes2600_cdev.status_lock);
bes2600_cdev.read_flag = BES_CDEV_READ_WAKEUP_STATE;
spin_unlock(&bes2600_cdev.status_lock);
return 0;
}
#ifdef FW_DOWNLOAD_UART_DAEMON
int bes2600_load_uevent(char *env[])
{
return kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
}
#endif
static struct bes2600_op_map bes2600_op_map_tab[] ={
/*op op_len handler */
{"P2P_SET_NOA", 11, bes2600_op_default_handler},
{"P2P_SET_PS", 10, bes2600_op_default_handler},
{"SET_AP_WPS_P2P_IE", 17, bes2600_op_default_handler},
{"LINKSPEED", 9, bes2600_op_default_handler},
{"RSSI", 4, bes2600_op_default_handler},
{"GETBAND", 7, bes2600_op_default_handler},
{"WLS_BATCHING", 12, bes2600_op_default_handler},
{"MACADDR", 7, bes2600_op_default_handler},
{"RXFILTER-START", 14, bes2600_op_default_handler},
{"RXFILTER-STOP", 13, bes2600_op_default_handler},
{"RXFILTER-ADD", 12, bes2600_op_default_handler},
{"RXFILTER-REMOVE", 15, bes2600_op_default_handler},
{"BTCOEXMODE", 10, bes2600_op_default_handler},
{"BTCOEXSCAN-START", 16, bes2600_op_default_handler},
{"BTCOEXSCAN-STOP", 15, bes2600_op_default_handler},
{"SETSUSPENDMODE", 14, bes2600_op_default_handler},
{"COUNTRY", 7, bes2600_op_default_handler},
{"WIFI_ON", 7, bes2600_op_wifi_bt_on_off},
{"WIFI_OFF", 8, bes2600_op_wifi_bt_on_off},
{"BT_ON", 5, bes2600_op_wifi_bt_on_off},
{"BT_OFF", 6, bes2600_op_wifi_bt_on_off},
{"CHANGE_FW_TYPE", 14, bes2600_op_change_fw_type},
{"BT_WAKEUP", 9, bes2600_op_bt_wakeup},
{"BT_SLEEP", 8, bes2600_op_bt_sleep},
{"WAKEUP_STATE", 12, bes2600_op_set_wakeup_read_flag},
};
static int bes2600_chrdev_check_system_close_internal(void)
{
@@ -641,255 +258,13 @@ 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)
@@ -950,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;
}
@@ -1075,62 +442,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;
}
EXPORT_SYMBOL_GPL(bes2600_chrdev_do_bus_reset);
/*
* Trigger bes2600_chrdev_do_bus_reset() against the file-global
* bes2600_cdev. Used by host-side recovery paths outside this
* compilation unit (e.g. sta.c connection-loss-storm fast-recover) so
* those callers do not need to reach the static bes2600_cdev directly.
*/
int bes2600_chrdev_trigger_bus_reset(void)
{
return bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
EXPORT_SYMBOL_GPL(bes2600_chrdev_trigger_bus_reset);
bool bes2600_chrdev_is_wifi_opened(void)
{
bool wifi_opened = false;
@@ -1179,7 +490,6 @@ void bes2600_chrdev_wakeup_bt(void)
bes_err("Wakeup BT fail in resume\n");
}
}
EXPORT_SYMBOL_GPL(bes2600_chrdev_wakeup_bt);
int bes2600_chrdev_get_fw_type(void)
{
@@ -1201,7 +511,6 @@ bool bes2600_chrdev_is_bus_error(void)
return error;
}
EXPORT_SYMBOL_GPL(bes2600_chrdev_is_bus_error);
void bes2600_chrdev_update_signal_mode(void)
{
@@ -1220,12 +529,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");
@@ -1237,32 +540,11 @@ 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");
}
}
@@ -1356,46 +638,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);
@@ -1427,15 +669,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)
@@ -1445,9 +678,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__);
}
+8 -6
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Character device interface for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __BES_CHARDEV_H__
#define __BES_CHARDEV_H__
@@ -57,12 +60,9 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void);
/* used to control device power down */
int bes2600_chrdev_check_system_close(void);
int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
int bes2600_chrdev_trigger_bus_reset(void);
void bes2600_chrdev_wakeup_bt(void);
void bes2600_chrdev_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev);
void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv);
int bes2600_chrdev_switch_subsys_glb(int wifi, int bt);
/* get and set internal state */
bool bes2600_chrdev_is_wifi_opened(void);
@@ -91,4 +91,6 @@ u8* bes2600_alloc_dpd_log_buffer(u16 len);
void bes2600_get_dpd_log(char **data, size_t *len);
#endif
int bes2600_switch_bt(bool);
#endif /* __BES_CHARDEV_H__ */
+6 -37
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Firmware download for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "bes_fw_common.h"
#include "bes2600.h"
@@ -122,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;
@@ -465,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;
@@ -580,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
@@ -637,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);
@@ -829,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);
+6 -3
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Firmware download common code for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "bes_fw_common.h"
#include "bes_log.h"
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Firmware download common interface
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __BES_FW_COMMON_H__
#define __BES_FW_COMMON_H__
+23 -7
View File
@@ -1,10 +1,3 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* printk wrappers for BES2600
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
*
*/
extern struct device *global_dev;
#ifdef CONFIG_BES2600_ENABLE_DEVEL_LOGS
@@ -15,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)
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Vendor testmode messages for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES_NL80211_TESTMODE_MSG_H
+24 -205
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Chip-side power state machine for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/list.h>
#include <linux/pm.h>
@@ -464,45 +467,6 @@ 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;
@@ -512,17 +476,6 @@ 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,55 +524,22 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n",
__func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod,
priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod);
/*
* Reinit BEFORE the WSM goes out, so a stale
* indication from a previous cycle cannot have
* primed pm_enter_cmpl. From here until the
* indication callback's cmpxchg(1->0) on
* pm_set_in_process, only the indication for
* THIS request can complete the wait.
*/
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 1);
ret = bes2600_set_pm(priv, &priv->powersave_mode);
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
bes_err("%s, set operation mode fail\n", __func__);
timeouts++;
continue;
}
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
if (!status) {
/*
* The indication callback only fires
* complete() when it observes
* pm_set_in_process == 1; cmpxchg it
* to 0 here so a late indication
* cannot prime the next wait.
*
* If we win the cmpxchg, this is a
* real timeout: the firmware's PS
* state is unknown to us. Mark it as
* such so the next wake path can
* probe before assuming the chip is
* still active.
*
* If we lose the cmpxchg, the
* indication arrived between the
* wait timing out and us getting
* here; treat as success.
*/
if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
1, 0) == 1) {
bes_devel("%s, wait pm ind timeout\n", __func__);
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_UNKNOWN);
timeouts++;
if (++hw_priv->bes_power.pm_consecutive_timeouts
>= BES2600_PM_UNSUPPORTED_THRESHOLD)
bes2600_pwr_latch_pm_unsupported(hw_priv);
}
bes_devel("%s, wait pm ind timeout\n", __func__);
timeouts++;
}
} else {
bes_devel("skip enter lp mode\n");
@@ -634,35 +554,10 @@ 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 {
/*
* device_enter_lp_mode() was skipped (one or more VIFs
* timed out waiting for the firmware indication) so its
* gpio_sleep(MCU) - which drops the wake-flag bit and, if
* no other subsystem holds the wake, drives the GPIO low -
* never ran. Without it the bit stays asserted, and the
* next bes2600_pwr_device_exit_lp_mode() calls
* gpio_wake(MCU) into a "bit already set" no-op: the GPIO
* never re-edges, sbus_active() exhausts its 200x2ms
* MCU_WAKEUP_READY budget against an unwoken chip, and
* the first TX after idle stalls for several seconds.
*
* Drop the MCU wake-flag bit explicitly here so the next
* wake injects a real GPIO edge. gpio_allow_mcu_sleep
* preserves multi-subsystem semantics: it only drives the
* GPIO low when no other subsystem still holds wake; if
* BT or another holder is keeping the chip awake, the
* GPIO stays high and the bit clear here is purely
* bookkeeping (so the next gpio_wake doesn't no-op).
*/
if (!hw_priv->bes_power.pm_unsupported &&
hw_priv->sbus_ops->gpio_sleep)
hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv,
GPIO_WAKE_FLAG_MCU);
else
ret = -ETIMEDOUT;
}
return ret;
}
@@ -670,61 +565,19 @@ 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,
};
/*
* 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);
bes_devel("host lock lmac\n");
if(hw_priv->sbus_ops->gpio_wake)
hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU);
if (hw_priv->sbus_ops->sbus_active) {
ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv,
SUBSYSTEM_MCU);
if (ret) {
/*
* MCU_WAKEUP_READY did not arrive within
* the SDIO handshake window. Record state
* as UNKNOWN so the next exit_lp_mode call
* also runs the full wake sequence (no
* skip), but still send operational_mode
* below to match pre-c6 behaviour - the
* WSM may succeed even if the SDIO active
* confirm was lost, and if it fails too,
* we just emit a second devel-level error.
* Repeated UNKNOWN is the signal for the
* LMAC active-monitor to eventually
* escalate to bus_reset (c5.2's
* mmc_hw_reset path).
*/
bes_err("%s, active mcu fail\n", __func__);
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_UNKNOWN);
}
}
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__);
}
ret = wsm_set_operational_mode(hw_priv, &mode, 0);
@@ -980,9 +833,6 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv)
hw_priv->bes_power.power_up_task = NULL;
mutex_init(&hw_priv->bes_power.pwr_mutex);
atomic_set(&hw_priv->bes_power.dev_state, 0);
atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN);
hw_priv->bes_power.pm_unsupported = false;
hw_priv->bes_power.pm_consecutive_timeouts = 0;
init_completion(&hw_priv->bes_power.pm_enter_cmpl);
sema_init(&hw_priv->bes_power.sync_lock, 1);
device_set_wakeup_capable(hw_priv->pdev, true);
@@ -1363,40 +1213,9 @@ 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)
{
/*
* The firmware sends a PM-changed indication for every transition,
* including ones we didn't ask for (firmware-internal coex moves,
* idle-driven aging). Update chip_pm_state unconditionally so the
* wake path can use it, but only fire pm_enter_cmpl when a host-
* initiated set_pm is actually in flight - otherwise a stale
* indication can prime a future wait against a freshly
* reinit_completion()'ed state.
*/
/*
* Any PM indication, whatever its psmode, proves the firmware is
* actually emitting them. Reset the consecutive-timeout counter
* so a transient stall doesn't permanently disable PSM, and clear
* pm_unsupported if a previous run had latched it.
*/
hw_priv->bes_power.pm_consecutive_timeouts = 0;
if (hw_priv->bes_power.pm_unsupported) {
bes_warn("PM indication arrived after pm_unsupported was set; re-enabling PSM transitions\n");
hw_priv->bes_power.pm_unsupported = false;
}
if ((psmode & 0x01) != WSM_PSM_ACTIVE) {
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_LP);
if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
1, 0) == 1) {
bes_devel("complete pm_enter_cmpl\n");
complete(&hw_priv->bes_power.pm_enter_cmpl);
} else {
bes_devel("PM ind (LP) without pending wait; state recorded\n");
}
} else {
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_ACTIVE);
if((psmode & 0x01) != WSM_PSM_ACTIVE) {
bes_devel("complete pm_enter_cmpl\n");
complete(&hw_priv->bes_power.pm_enter_cmpl);
}
}
+6 -27
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Chip-side power state machine interface
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __BES_PWR_H__
#define __BES_PWR_H__
@@ -61,20 +64,6 @@ 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);
@@ -117,16 +106,6 @@ struct bes2600_pwr_t
bool ap_lp_bad;
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
atomic_t chip_pm_state;
/*
* Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD
* consecutive enter_lp_mode timeouts with zero PM_INDICATIONs
* received from firmware. Indicates this chip's firmware does
* not honor host-driven PSM transitions; further attempts are
* skipped to avoid the 5s timeout cascade.
*/
bool pm_unsupported;
unsigned int pm_consecutive_timeouts;
};
#ifdef CONFIG_BES2600_WOWLAN
+574 -164
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Bottom-half thread for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <net/mac80211.h>
#include <linux/kthread.h>
@@ -22,7 +22,6 @@
#include "debug.h"
#include "epta_coex.h"
#include "bes_chardev.h"
#include "txrx_opt.h"
#include "sta.h"
#include "bes_log.h"
@@ -102,7 +101,7 @@ void bes2600_unregister_bh(struct bes2600_common *hw_priv)
coex_deinit_mode(hw_priv);
#endif
atomic_inc(&hw_priv->bh_term);
atomic_add(1, &hw_priv->bh_term);
wake_up(&hw_priv->bh_wq);
flush_workqueue(hw_priv->bh_workqueue);
@@ -317,6 +316,83 @@ int wsm_release_buffer_to_fw(struct bes2600_vif *priv, int count)
}
#endif
#if 0
static struct sk_buff *bes2600_get_skb(struct bes2600_common *hw_priv, size_t len)
{
struct sk_buff *skb;
size_t alloc_len = (len > SDIO_BLOCK_SIZE) ? len : SDIO_BLOCK_SIZE;
if (len > SDIO_BLOCK_SIZE || !hw_priv->skb_cache) {
skb = dev_alloc_skb(alloc_len
+ WSM_TX_EXTRA_HEADROOM
+ 8 /* TKIP IV */
+ 12 /* TKIP ICV + MIC */
- 2 /* Piggyback */);
/* In AP mode RXed SKB can be looped back as a broadcast.
* Here we reserve enough space for headers. */
skb_reserve(skb, WSM_TX_EXTRA_HEADROOM
+ 8 /* TKIP IV */
- WSM_RX_EXTRA_HEADROOM);
} else {
skb = hw_priv->skb_cache;
hw_priv->skb_cache = NULL;
}
return skb;
}
static void bes2600_put_skb(struct bes2600_common *hw_priv, struct sk_buff *skb)
{
if (hw_priv->skb_cache)
dev_kfree_skb(skb);
else
hw_priv->skb_cache = skb;
}
static int bes2600_bh_read_ctrl_reg(struct bes2600_common *hw_priv,
u16 *ctrl_reg)
{
int ret;
ret = bes2600_reg_read_16(hw_priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret) {
ret = bes2600_reg_read_16(hw_priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret)
bes_err("[BH] Failed to read control register.\n");
}
return ret;
}
static int bes2600_device_wakeup(struct bes2600_common *hw_priv)
{
u16 ctrl_reg;
int ret;
bes_devel("[BH] Device wakeup.\n");
/* To force the device to be always-on, the host sets WLAN_UP to 1 */
ret = bes2600_reg_write_16(hw_priv, ST90TDS_CONTROL_REG_ID,
ST90TDS_CONT_WUP_BIT);
if (WARN_ON(ret))
return ret;
ret = bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
if (WARN_ON(ret))
return ret;
/* If the device returns WLAN_RDY as 1, the device is active and will
* remain active. */
if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
bes_devel("[BH] Device awake.\n");
return 1;
}
return 0;
}
#endif
/* Must be called from BH thraed. */
void bes2600_enable_powersave(struct bes2600_vif *priv,
@@ -326,6 +402,475 @@ void bes2600_enable_powersave(struct bes2600_vif *priv,
priv->powersave_enabled = enable;
}
#if 0
#define INTERRUPT_WORKAROUND
static int bes2600_bh(void *arg)
{
struct bes2600_common *hw_priv = arg;
struct bes2600_vif *priv = NULL;
struct sk_buff *skb_rx = NULL;
size_t read_len = 0;
int rx, tx, term, suspend;
struct wsm_hdr *wsm;
size_t wsm_len;
int wsm_id;
u8 wsm_seq;
int rx_resync = 1;
u16 ctrl_reg = 0;
int tx_allowed;
int pending_tx = 0;
int tx_burst;
int rx_burst = 0;
long status;
#if defined(CONFIG_BES2600_WSM_DUMPS)
size_t wsm_dump_max = -1;
#endif
u32 dummy;
bool powersave_enabled;
int i;
int vif_selected;
for (;;) {
powersave_enabled = 1;
spin_lock(&hw_priv->vif_list_lock);
bes2600_for_each_vif(hw_priv, priv, i) {
#ifdef P2P_MULTIVIF
if ((i = (CW12XX_MAX_VIFS - 1)) || !priv)
#else
if (!priv)
#endif
continue;
powersave_enabled &= !!priv->powersave_enabled;
}
spin_unlock(&hw_priv->vif_list_lock);
if (!hw_priv->hw_bufs_used
&& powersave_enabled
&& !hw_priv->device_can_sleep
&& !atomic_read(&hw_priv->recent_scan)) {
status = HZ/8;
bes_devel("[BH] No Device wakedown.\n");
#ifndef FPGA_SETUP
WARN_ON(bes2600_reg_write_16(hw_priv,
ST90TDS_CONTROL_REG_ID, 0));
hw_priv->device_can_sleep = true;
#endif
} else if (hw_priv->hw_bufs_used)
/* Interrupt loss detection */
status = HZ/8;
else
status = HZ/8;
/* Dummy Read for SDIO retry mechanism*/
if (((atomic_read(&hw_priv->bh_rx) == 0) &&
(atomic_read(&hw_priv->bh_tx) == 0)))
bes2600_reg_read(hw_priv, ST90TDS_CONFIG_REG_ID,
&dummy, sizeof(dummy));
#if defined(CONFIG_BES2600_WSM_DUMPS_SHORT)
wsm_dump_max = hw_priv->wsm_dump_max_size;
#endif /* CONFIG_BES2600_WSM_DUMPS_SHORT */
#ifdef INTERRUPT_WORKAROUND
/* If a packet has already been txed to the device then read the
control register for a probable interrupt miss before going
further to wait for interrupt; if the read length is non-zero
then it means there is some data to be received */
if (hw_priv->hw_bufs_used) {
bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
if(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
{
rx = 1;
goto test;
}
}
#endif
status = wait_event_interruptible_timeout(hw_priv->bh_wq, ({
rx = atomic_xchg(&hw_priv->bh_rx, 0);
tx = atomic_xchg(&hw_priv->bh_tx, 0);
term = atomic_xchg(&hw_priv->bh_term, 0);
suspend = pending_tx ?
0 : atomic_read(&hw_priv->bh_suspend);
(rx || tx || term || suspend || hw_priv->bh_error);
}), status);
if (status < 0 || term || hw_priv->bh_error)
break;
#ifdef INTERRUPT_WORKAROUND
if (!status) {
bes2600_bh_read_ctrl_reg(hw_priv, &ctrl_reg);
if(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
{
bes_err("MISS 1\n");
rx = 1;
goto test;
}
}
#endif
if (!status && hw_priv->hw_bufs_used) {
unsigned long timestamp = jiffies;
long timeout;
bool pending = false;
int i;
wiphy_warn(hw_priv->hw->wiphy, "Missed interrupt?\n");
rx = 1;
/* Get a timestamp of "oldest" frame */
for (i = 0; i < 4; ++i)
pending |= bes2600_queue_get_xmit_timestamp(
&hw_priv->tx_queue[i],
&timestamp, -1,
hw_priv->pending_frame_id);
/* Check if frame transmission is timed out.
* Add an extra second with respect to possible
* interrupt loss. */
timeout = timestamp +
WSM_CMD_LAST_CHANCE_TIMEOUT +
1 * HZ -
jiffies;
/* And terminate BH tread if the frame is "stuck" */
if (pending && timeout < 0) {
//wiphy_warn(priv->hw->wiphy,
// "Timeout waiting for TX confirm.\n");
bes_devel("bes2600_bh: Timeout waiting for TX confirm.\n");
break;
}
#if defined(CONFIG_BES2600_DUMP_ON_ERROR)
BUG_ON(1);
#endif /* CONFIG_BES2600_DUMP_ON_ERROR */
} else if (!status) {
if (!hw_priv->device_can_sleep
&& !atomic_read(&hw_priv->recent_scan)) {
bes_devel("[BH] Device wakedown. Timeout.\n");
#ifndef FPGA_SETUP
WARN_ON(bes2600_reg_write_16(hw_priv,
ST90TDS_CONTROL_REG_ID, 0));
hw_priv->device_can_sleep = true;
#endif
}
continue;
} else if (suspend) {
bes_devel("[BH] Device suspend.\n");
powersave_enabled = 1;
spin_lock(&hw_priv->vif_list_lock);
bes2600_for_each_vif(hw_priv, priv, i) {
#ifdef P2P_MULTIVIF
if ((i = (CW12XX_MAX_VIFS - 1)) || !priv)
#else
if (!priv)
#endif
continue;
powersave_enabled &= !!priv->powersave_enabled;
}
spin_unlock(&hw_priv->vif_list_lock);
if (powersave_enabled) {
bes_devel("[BH] No Device wakedown. Suspend.\n");
#ifndef FPGA_SETUP
WARN_ON(bes2600_reg_write_16(hw_priv,
ST90TDS_CONTROL_REG_ID, 0));
hw_priv->device_can_sleep = true;
#endif
}
atomic_set(&hw_priv->bh_suspend, BES2600_BH_SUSPENDED);
wake_up(&hw_priv->bh_evt_wq);
status = wait_event_interruptible(hw_priv->bh_wq,
BES2600_BH_RESUME == atomic_read(
&hw_priv->bh_suspend));
if (status < 0) {
wiphy_err(hw_priv->hw->wiphy,
"%s: Failed to wait for resume: %ld.\n",
__func__, status);
break;
}
bes_devel("[BH] Device resume.\n");
atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED);
wake_up(&hw_priv->bh_evt_wq);
atomic_add(1, &hw_priv->bh_rx);
continue;
}
test:
tx += pending_tx;
pending_tx = 0;
if (rx) {
size_t alloc_len;
u8 *data;
#ifdef INTERRUPT_WORKAROUND
if(!(ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK))
#endif
if (WARN_ON(bes2600_bh_read_ctrl_reg(
hw_priv, &ctrl_reg)))
break;
rx:
read_len = (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
if (!read_len) {
rx_burst = 0;
goto tx;
}
if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
(read_len > EFFECTIVE_BUF_SIZE))) {
bes_devel("Invalid read len: %d", read_len);
break;
}
/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
* to the NEXT Message length + 2 Bytes for SKB */
read_len = read_len + 2;
#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES)
alloc_len = hw_priv->sbus_ops->align_size(
hw_priv->sbus_priv, read_len);
#else /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */
/* Platform's SDIO workaround */
alloc_len = read_len & ~(SDIO_BLOCK_SIZE - 1);
if (read_len & (SDIO_BLOCK_SIZE - 1))
alloc_len += SDIO_BLOCK_SIZE;
#endif /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */
/* Check if not exceeding BES2600 capabilities */
if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE))
bes_devel("Read aligned len: %d\n", alloc_len);
skb_rx = bes2600_get_skb(hw_priv, alloc_len);
if (WARN_ON(!skb_rx))
break;
skb_trim(skb_rx, 0);
skb_put(skb_rx, read_len);
data = skb_rx->data;
if (WARN_ON(!data))
break;
if (WARN_ON(bes2600_data_read(hw_priv, data, alloc_len)))
break;
/* Piggyback */
ctrl_reg = __le16_to_cpu(
((__le16 *)data)[alloc_len / 2 - 1]);
wsm = (struct wsm_hdr *)data;
wsm_len = __le32_to_cpu(wsm->len);
if (WARN_ON(wsm_len > read_len))
break;
#if defined(CONFIG_BES2600_WSM_DUMPS)
if (unlikely(hw_priv->wsm_enable_wsm_dumps)) {
u16 msgid, ifid;
u16 *p = (u16 *)data;
msgid = (*(p + 1)) & 0xC3F;
ifid = (*(p + 1)) >> 6;
ifid &= 0xF;
bes_devel("[DUMP] <<< msgid 0x%.4X ifid %d len %d\n", msgid, ifid, *p);
print_hex_dump(KERN_DEBUG, "<-- ", DUMP_PREFIX_NONE, data, min(wsm_len, wsm_dump_max));
}
#endif /* CONFIG_BES2600_WSM_DUMPS */
wsm_id = __le32_to_cpu(wsm->id) & 0xFFF;
wsm_seq = (__le32_to_cpu(wsm->id) >> 13) & 7;
skb_trim(skb_rx, wsm_len);
if (unlikely(wsm_id == 0x0800)) {
wsm_handle_exception(hw_priv,
&data[sizeof(*wsm)],
wsm_len - sizeof(*wsm));
break;
} else if (unlikely(!rx_resync)) {
if (WARN_ON(wsm_seq != hw_priv->wsm_rx_seq)) {
#if defined(CONFIG_BES2600_DUMP_ON_ERROR)
BUG_ON(1);
#endif /* CONFIG_BES2600_DUMP_ON_ERROR */
break;
}
}
hw_priv->wsm_rx_seq = (wsm_seq + 1) & 7;
rx_resync = 0;
if (wsm_id & 0x0400) {
int rc = wsm_release_tx_buffer(hw_priv, 1);
if (WARN_ON(rc < 0))
break;
else if (rc > 0)
tx = 1;
}
/* bes2600_wsm_rx takes care on SKB livetime */
if (WARN_ON(wsm_handle_rx(hw_priv, wsm_id, wsm,
&skb_rx)))
break;
if (skb_rx) {
bes2600_put_skb(hw_priv, skb_rx);
skb_rx = NULL;
}
read_len = 0;
if (rx_burst) {
bes2600_debug_rx_burst(hw_priv);
--rx_burst;
goto rx;
}
}
tx:
BUG_ON(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs);
tx_burst = hw_priv->wsm_caps.numInpChBufs -
hw_priv->hw_bufs_used;
tx_allowed = tx_burst > 0;
if (tx && tx_allowed) {
size_t tx_len;
u8 *data;
int ret;
if (hw_priv->device_can_sleep) {
ret = bes2600_device_wakeup(hw_priv);
if (WARN_ON(ret < 0))
break;
else if (ret)
hw_priv->device_can_sleep = false;
else {
/* Wait for "awake" interrupt */
pending_tx = tx;
continue;
}
}
wsm_alloc_tx_buffer(hw_priv);
ret = wsm_get_tx(hw_priv, &data, &tx_len, &tx_burst,
&vif_selected);
if (ret <= 0) {
wsm_release_tx_buffer(hw_priv, 1);
if (WARN_ON(ret < 0))
break;
} else {
wsm = (struct wsm_hdr *)data;
BUG_ON(tx_len < sizeof(*wsm));
BUG_ON(__le32_to_cpu(wsm->len) != tx_len);
#if 0 /* count is not implemented */
if (ret > 1)
atomic_add(1, &hw_priv->bh_tx);
#else
atomic_add(1, &hw_priv->bh_tx);
#endif
#if defined(CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES)
if (tx_len <= 8)
tx_len = 16;
tx_len = hw_priv->sbus_ops->align_size(
hw_priv->sbus_priv, tx_len);
#else /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */
/* HACK!!! Platform limitation.
* It is also supported by upper layer:
* there is always enough space at the
* end of the buffer. */
if (tx_len & (SDIO_BLOCK_SIZE - 1)) {
tx_len &= ~(SDIO_BLOCK_SIZE - 1);
tx_len += SDIO_BLOCK_SIZE;
}
#endif /* CONFIG_BES2600_NON_POWER_OF_TWO_BLOCKSIZES */
/* Check if not exceeding BES2600
capabilities */
if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
bes_devel("Write aligned len: %d\n", tx_len);
wsm->id &= __cpu_to_le32(
~WSM_TX_SEQ(WSM_TX_SEQ_MAX));
wsm->id |= cpu_to_le32(WSM_TX_SEQ(
hw_priv->wsm_tx_seq));
if (WARN_ON(bes2600_data_write(hw_priv,
data, tx_len))) {
wsm_release_tx_buffer(hw_priv, 1);
break;
}
if (vif_selected != -1) {
hw_priv->hw_bufs_used_vif[
vif_selected]++;
}
#if defined(CONFIG_BES2600_WSM_DUMPS)
if (unlikely(hw_priv->wsm_enable_wsm_dumps)) {
u16 msgid, ifid;
u16 *p = (u16 *)data;
msgid = (*(p + 1)) & 0x3F;
ifid = (*(p + 1)) >> 6;
ifid &= 0xF;
if (msgid == 0x0006)
bes_devel("[DUMP] >>> msgid 0x%.4X ifid %d len %d MIB 0x%.4X\n", msgid, ifid, *p, *(p + 2));
else
bes_devel("[DUMP] >>> msgid 0x%.4X ifid %d len %d\n", msgid, ifid, *p);
print_hex_dump(KERN_DEBUG, "--> ", DUMP_PREFIX_NONE, data, min(__le32_to_cpu(wsm->len), wsm_dump_max));
}
#endif /* CONFIG_BES2600_WSM_DUMPS */
wsm_txed(hw_priv, data);
hw_priv->wsm_tx_seq = (hw_priv->wsm_tx_seq + 1)
& WSM_TX_SEQ_MAX;
if (tx_burst > 1) {
bes2600_debug_tx_burst(hw_priv);
++rx_burst;
goto tx;
}
}
}
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
goto rx;
}
if (skb_rx) {
bes2600_put_skb(hw_priv, skb_rx);
skb_rx = NULL;
}
if (!term) {
bes_devel("[BH] Fatal error, exitting.\n");
#if defined(CONFIG_BES2600_DUMP_ON_ERROR)
BUG_ON(1);
#endif /* CONFIG_BES2600_DUMP_ON_ERROR */
hw_priv->bh_error = 1;
#if defined(CONFIG_BES2600_USE_STE_EXTENSIONS)
spin_lock(&hw_priv->vif_list_lock);
bes2600_for_each_vif(hw_priv, priv, i) {
if (!priv)
continue;
ieee80211_driver_hang_notify(priv->vif, GFP_KERNEL);
}
spin_unlock(&hw_priv->vif_list_lock);
bes2600_pm_stay_awake(&hw_priv->pm_state, 3*HZ);
#endif
/* TODO: schedule_work(recovery) */
#ifndef HAS_PUT_TASK_STRUCT
/* The only reason of having this stupid code here is
* that __put_task_struct is not exported by kernel. */
for (;;) {
int status = wait_event_interruptible(hw_priv->bh_wq, ({
term = atomic_xchg(&hw_priv->bh_term, 0);
(term);
}));
if (status || term)
break;
}
#endif
}
return 0;
}
#else
extern int bes2600_bh_read_ctrl_reg(struct bes2600_common *priv, u32 *ctrl_reg);
@@ -413,119 +958,6 @@ static void bes2600_bh_parse_wakeup_event(struct bes2600_common *hw_priv, struct
}
}
/*
* Direct-deliver an RX SKB into the WSM/mac80211 stack.
*
* Patch C v3 (no-relay architecture, matches cw1200): the bh thread
* calls bes2600_sdio_read_rx_batch which calls
* bes2600_sdio_extract_packets which calls THIS function per parsed
* SKB. No rx_queue, no sdio_rx_work, no inter-thread handoff.
*
* Single-writer-from-bh invariant on hw_priv->hw_bufs_used,
* hw_priv->hw_bufs_used_vif[] and hw_priv->wsm_tx_pending[] is
* preserved BY CONSTRUCTION there is now only one writer (the bh
* thread itself), same as cw1200's design. No atomic_t conversion
* needed.
*
* Contract:
* - process context, sleepable. wsm_handle_rx (wsm.c, EXPORT_SYMBOL)
* acquires wsm_cmd.lock and may sleep on wait_event_timeout.
* - caller holds no bes2600 spinlock. bes2600_sdio_unlock(self) is
* called inside read_rx_batch before extract_packets is invoked.
* - SKB ownership: function frees on every path (success + error).
* - No need to wake the bh thread on TX-confirm we ARE the bh
* thread; tx_burst is signalled by returning *tx_out = 1 to the
* caller (bh_rx_helper), which propagates it to bh's outer loop.
*/
int bes2600_bh_handle_rx_skb(struct bes2600_common *priv, struct sk_buff *skb)
{
struct wsm_hdr *wsm;
size_t wsm_len;
u16 wsm_id;
u8 wsm_seq;
int tx = 0;
u32 confirm_label = 0x0;
if (!skb)
return 0;
wsm = (struct wsm_hdr *)skb->data;
wsm_len = __le16_to_cpu(wsm->len);
if (WARN_ON(wsm_len > skb->len)) {
bes_err("wsm_len err %d %d\n", (int)wsm_len, (int)skb->len);
dev_kfree_skb(skb);
return -1;
}
if (priv->wsm_enable_wsm_dumps)
print_hex_dump(KERN_DEBUG, "<-- ", DUMP_PREFIX_NONE, 16, 1,
skb->data, wsm_len, false);
wsm_id = __le16_to_cpu(wsm->id) & 0xFFF;
wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
bes_devel("bes2600_bh_handle_rx_skb wsm_id:0x%04x seq:%d\n",
wsm_id, wsm_seq);
skb_trim(skb, wsm_len);
if (wsm_id == 0x0800) {
wsm_handle_exception(priv,
&skb->data[sizeof(*wsm)],
wsm_len - sizeof(*wsm));
bes_err("wsm exception\n");
dev_kfree_skb(skb);
return -1;
} else if ((wsm_seq != priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)])) {
bes_err("seq error! %u. %u. 0x%x.", wsm_seq,
priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)], wsm_id);
dev_kfree_skb(skb);
return -1;
}
bes2600_bh_parse_wakeup_event(priv, skb);
priv->wsm_rx_seq[WSM_TXRX_SEQ_IDX(wsm_id)] = (wsm_seq + 1) & 7;
if (IS_DRIVER_TO_MCU_CMD(wsm_id))
confirm_label = __le32_to_cpu(((struct wsm_mcu_hdr *)wsm)->handle_label);
if (WSM_CONFIRM_CONDITION(wsm_id, confirm_label)) {
int rc = wsm_release_tx_buffer(priv, 1);
bes2600_bh_dec_pending_count(priv, WSM_TXRX_SEQ_IDX(wsm->id));
if (rc < 0) {
bes_err("wsm_release_tx_buffer failed: %d\n", rc);
dev_kfree_skb(skb);
return rc;
} else if (rc > 0) {
tx = 1;
}
}
/* wsm_handle_rx takes care of SKB lifetime: zeroes *skb_p if consumed. */
if (wsm_handle_rx(priv, wsm_id, wsm, &skb)) {
bes_err("wsm_handle_rx failed (id=0x%04x)\n", wsm_id);
if (skb)
dev_kfree_skb(skb);
return -1;
}
if (skb)
dev_kfree_skb(skb);
/*
* Signal "tx side has new headroom" via atomic so the bh outer
* loop's wait_event predicate notices on its next wait. No
* cross-thread wake needed because we are the bh thread; the
* outer loop will pick this up after read_rx_batch returns.
*/
if (tx)
atomic_inc(&priv->bh_tx);
return 0;
}
EXPORT_SYMBOL(bes2600_bh_handle_rx_skb);
static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx)
{
struct sk_buff *skb = NULL;
@@ -537,18 +969,10 @@ static int bes2600_bh_rx_helper(struct bes2600_common *priv, int *tx)
u32 confirm_label = 0x0; /* wsm to mcu cmd cnfirm label */
#if defined(BES_SDIO_RX_MULTIPLE_ENABLE)
/*
* Patch C v3: the bh thread does the SDIO read inline via
* sbus_ops->bus_rx_batch. bes2600_sdio_read_rx_batch reads the
* multi-RX coalesced frames out of the chip and delivers each
* one inline via bes2600_bh_handle_rx_skb (no rx_queue, no
* pipe_read, no inter-thread handoff). Return value: 0 on
* success (bh outer loop will check whether to continue),
* negative on read error.
*/
if (priv->sbus_ops->bus_rx_batch)
return priv->sbus_ops->bus_rx_batch(priv->sbus_priv);
return 0;
skb = (struct sk_buff *)priv->sbus_ops->pipe_read(priv->sbus_priv);
if (!skb)
return 0;
rx = 1; // always consider rx pipe not empty
#else
u32 ctrl_reg = 0;
size_t read_len = 0;
@@ -710,7 +1134,7 @@ static int bes2600_bh_tx_helper(struct bes2600_common *hw_priv,
tx_len += 4;
#endif
atomic_inc(&hw_priv->bh_tx);
atomic_add(1, &hw_priv->bh_tx);
tx_len = hw_priv->sbus_ops->align_size(
hw_priv->sbus_priv, tx_len);
@@ -835,8 +1259,6 @@ int bes2600_bh_sw_process(struct bes2600_common *hw_priv,
delta_time = jiffies + ((unsigned long)0xffffffff - timestamp);
else
delta_time = jiffies - timestamp;
bes2600_add_tx_delta_time(delta_time);
bes2600_add_tx_ac_delta_time(queue_id, delta_time);
if (bes2600_need_retry_type(skb, tx_confirm->status) == 0)
return -1;
@@ -845,12 +1267,8 @@ int bes2600_bh_sw_process(struct bes2600_common *hw_priv,
return -1;
if (txpriv->retry_count < CW1200_MAX_SW_RETRY_CNT ) {
struct bes2600_vif *priv =
__cw12xx_hwpriv_to_vifpriv(hw_priv, txpriv->if_id);
txpriv->retry_count++;
bes2600_tx_status(priv,skb);
bes2600_pwr_set_busy_event_with_timeout_async(
hw_priv, BES_PWR_LOCK_ON_TX, BES_PWR_EVENT_TX_TIMEOUT);
@@ -886,14 +1304,14 @@ void bes2600_bh_dec_pending_count(struct bes2600_common *hw_priv, int idx)
}
if (--hw_priv->wsm_tx_pending[idx] == 0)
timer_delete_sync(timer);
del_timer_sync(timer);
else
mod_timer(timer, jiffies + 3 * HZ);
}
void bes2600_bh_mcu_active_monitor(struct timer_list* t)
{
struct bes2600_common *hw_priv = timer_container_of(hw_priv, t, mcu_mon_timer);
struct bes2600_common *hw_priv = from_timer(hw_priv, t, mcu_mon_timer);
bes_err("link break between mcu and host, hw_buf_used:%d pending:%d\n",
hw_priv->hw_bufs_used, hw_priv->wsm_tx_pending[1]);
@@ -902,7 +1320,7 @@ void bes2600_bh_mcu_active_monitor(struct timer_list* t)
void bes2600_bh_lmac_active_monitor(struct timer_list* t)
{
struct bes2600_common *hw_priv = timer_container_of(hw_priv, t, lmac_mon_timer);
struct bes2600_common *hw_priv = from_timer(hw_priv, t, lmac_mon_timer);
bes_err("link break between lmac and host, hw_buf_used:%d pending:%d\n",
hw_priv->hw_bufs_used, hw_priv->wsm_tx_pending[0]);
@@ -1017,7 +1435,7 @@ static int bes2600_bh(void *arg)
bes_devel("[BH] Device resume.\n");
atomic_set(&hw_priv->bh_suspend, BES2600_BH_RESUMED);
wake_up(&hw_priv->bh_evt_wq);
atomic_inc(&hw_priv->bh_rx);
atomic_add(1, &hw_priv->bh_rx);
goto done;
}
@@ -1053,15 +1471,7 @@ static int bes2600_bh(void *arg)
tx = 0;
/*
* Patch H: BUG_ON -> WARN_ON_ONCE in the steady-state
* hot path. The original BUG_ON ran every bh-loop
* iteration; tripping it on a bookkeeping bug locks
* the kernel up during normal operation, which is
* the wrong response. WARN_ON_ONCE surfaces the
* issue without taking the system down.
*/
WARN_ON_ONCE(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs);
BUG_ON(hw_priv->hw_bufs_used > hw_priv->wsm_caps.numInpChBufs);
tx_burst = hw_priv->wsm_caps.numInpChBufs - hw_priv->hw_bufs_used;
tx_allowed = tx_burst > 0;
@@ -1105,19 +1515,18 @@ static int bes2600_bh(void *arg)
goto tx;
done:
/*
* Patch H: dropped the dead `__bes2600_irq_enable(1)` /
* `asm volatile("nop")` placeholder that used to sit here.
* `__bes2600_irq_enable()` is a stub that returns 0 on
* bes2600 silicon the IRQ is managed by sdio_claim_irq
* and chip-side firmware, not by a driver-side enable bit.
* (cw1200 inherited the function from a different chip
* shape; bes2600 kept the stub but the call sites are
* meaningless.)
*/
;
/* Re-enable device interrupts */
//hw_priv->sbus_ops->lock(hw_priv->sbus_priv);
//__bes2600_irq_enable(1);
//hw_priv->sbus_ops->unlock(hw_priv->sbus_priv);
asm volatile ("nop");
}
/* Explicitly disable device interrupts */
hw_priv->sbus_ops->lock(hw_priv->sbus_priv);
__bes2600_irq_enable(0);
hw_priv->sbus_ops->unlock(hw_priv->sbus_priv);
if (!term) {
bes_err("[BH] Fatal error, exiting.\n");
sdio_work_debug(hw_priv->sbus_priv);
@@ -1126,3 +1535,4 @@ static int bes2600_bh(void *arg)
}
return 0;
}
#endif
+6 -15
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Bottom-half thread interface for BES2600 mac80211 driver
* Device handling thread interface for mac80211 BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_BH_H
@@ -39,15 +39,6 @@ int wsm_release_vif_tx_buffer(struct bes2600_common *hw_priv, int if_id,
int bes2600_bh_sw_process(struct bes2600_common *hw_priv,
struct wsm_tx_confirm *tx_confirm);
/*
* Direct-deliver an RX SKB into the WSM/mac80211 stack from the bh thread.
* Called by bes2600_sdio_extract_packets per RX frame, no queueing.
* Process context, sleepable, caller holds no bes2600 spinlock.
* Function frees skb on every path. See bh.c for full contract.
*/
int bes2600_bh_handle_rx_skb(struct bes2600_common *hw_priv,
struct sk_buff *skb);
void bes2600_bh_inc_pending_count(struct bes2600_common *hw_priv, int idx);
void bes2600_bh_dec_pending_count(struct bes2600_common *hw_priv, int idx);
+11 -18
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Debugging interface for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
@@ -110,20 +110,17 @@ static int bes2600_status_show_common(struct seq_file *seq, void *v)
int ba_cnt, ba_acc, ba_cnt_rx, ba_acc_rx, ba_avg = 0, ba_avg_rx = 0;
bool ba_ena;
/*
* Patch D: ba_lock removed. hw_priv->debug->ba_* are written only
* by the timer callback (single writer); reading without a lock is
* fine for stats. ba_ena is atomic_t.
*/
ba_cnt = hw_priv->debug->ba_cnt;
ba_acc = hw_priv->debug->ba_acc;
spin_lock_bh(&hw_priv->ba_lock);
ba_cnt = hw_priv->debug->ba_cnt;
ba_acc = hw_priv->debug->ba_acc;
ba_cnt_rx = hw_priv->debug->ba_cnt_rx;
ba_acc_rx = hw_priv->debug->ba_acc_rx;
ba_ena = !!atomic_read(&hw_priv->ba_ena);
ba_ena = hw_priv->ba_ena;
if (ba_cnt)
ba_avg = ba_acc / ba_cnt;
if (ba_cnt_rx)
ba_avg_rx = ba_acc_rx / ba_cnt_rx;
spin_unlock_bh(&hw_priv->ba_lock);
seq_puts(seq, "BES2600 Wireless LAN driver status\n");
seq_printf(seq, "Hardware: %d.%d\n",
@@ -545,10 +542,6 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v)
priv->listening ? " (listening)" : "");
seq_printf(seq, "Assoc: %s\n",
bes2600_debug_join_status[priv->join_status]);
seq_printf(seq, "DecryptStormRecoveries: %u\n",
priv->decrypt_storm_recoveries);
seq_printf(seq, "ConnectionLossStormRecoveries: %u\n",
priv->connection_loss_storm_recoveries);
if (priv->rx_filter.promiscuous)
seq_puts(seq, "Filter: promisc\n");
else if (priv->rx_filter.fcs)
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Debugging interface for BES2600 mac80211 driver
* DebugFS code for BES2600 mac80211 driver
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2011, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_DEBUG_H_INCLUDED
+6 -3
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* BT/WiFi coexistence (ePTA) for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/version.h>
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* BT/WiFi coexistence interface for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __EPTA_COEX_H__
#define __EPTA_COEX_H__
+6 -3
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* BT/WiFi coexistence request handling
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/kernel.h>
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* BT/WiFi coexistence request interface
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef EPTA_REQUEST_H
#define EPTA_REQUEST_H
+6 -6
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Firmware I/O for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Firmware I/O interface for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef FWIO_H_INCLUDED
#define FWIO_H_INCLUDED
-154
View File
@@ -1,154 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copy of drivers/bluetooth/h4_recv.h
*
* Generic Bluetooth HCI UART driver
*
* Copyright (C) 2015-2018 Intel Corporation
*/
#include <linux/unaligned.h>
struct h4_recv_pkt {
u8 type; /* Packet type */
u8 hlen; /* Header length */
u8 loff; /* Data length offset in header */
u8 lsize; /* Data length field size */
u16 maxlen; /* Max overall packet length */
int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
};
#define H4_RECV_ACL \
.type = HCI_ACLDATA_PKT, \
.hlen = HCI_ACL_HDR_SIZE, \
.loff = 2, \
.lsize = 2, \
.maxlen = HCI_MAX_FRAME_SIZE \
#define H4_RECV_SCO \
.type = HCI_SCODATA_PKT, \
.hlen = HCI_SCO_HDR_SIZE, \
.loff = 2, \
.lsize = 1, \
.maxlen = HCI_MAX_SCO_SIZE
#define H4_RECV_EVENT \
.type = HCI_EVENT_PKT, \
.hlen = HCI_EVENT_HDR_SIZE, \
.loff = 1, \
.lsize = 1, \
.maxlen = HCI_MAX_EVENT_SIZE
#define H4_RECV_ISO \
.type = HCI_ISODATA_PKT, \
.hlen = HCI_ISO_HDR_SIZE, \
.loff = 2, \
.lsize = 2, \
.maxlen = HCI_MAX_FRAME_SIZE
static inline struct sk_buff *h4_recv_buf(struct hci_dev *hdev,
struct sk_buff *skb,
const unsigned char *buffer,
int count,
const struct h4_recv_pkt *pkts,
int pkts_count)
{
/* Check for error from previous call */
if (IS_ERR(skb))
skb = NULL;
while (count) {
int i, len;
if (!skb) {
for (i = 0; i < pkts_count; i++) {
if (buffer[0] != (&pkts[i])->type)
continue;
skb = bt_skb_alloc((&pkts[i])->maxlen,
GFP_ATOMIC);
if (!skb)
return ERR_PTR(-ENOMEM);
hci_skb_pkt_type(skb) = (&pkts[i])->type;
hci_skb_expect(skb) = (&pkts[i])->hlen;
break;
}
/* Check for invalid packet type */
if (!skb)
return ERR_PTR(-EILSEQ);
count -= 1;
buffer += 1;
}
len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
skb_put_data(skb, buffer, len);
count -= len;
buffer += len;
/* Check for partial packet */
if (skb->len < hci_skb_expect(skb))
continue;
for (i = 0; i < pkts_count; i++) {
if (hci_skb_pkt_type(skb) == (&pkts[i])->type)
break;
}
if (i >= pkts_count) {
kfree_skb(skb);
return ERR_PTR(-EILSEQ);
}
if (skb->len == (&pkts[i])->hlen) {
u16 dlen;
switch ((&pkts[i])->lsize) {
case 0:
/* No variable data length */
dlen = 0;
break;
case 1:
/* Single octet variable length */
dlen = skb->data[(&pkts[i])->loff];
hci_skb_expect(skb) += dlen;
if (skb_tailroom(skb) < dlen) {
kfree_skb(skb);
return ERR_PTR(-EMSGSIZE);
}
break;
case 2:
/* Double octet variable length */
dlen = get_unaligned_le16(skb->data +
(&pkts[i])->loff);
hci_skb_expect(skb) += dlen;
if (skb_tailroom(skb) < dlen) {
kfree_skb(skb);
return ERR_PTR(-EMSGSIZE);
}
break;
default:
/* Unsupported variable length */
kfree_skb(skb);
return ERR_PTR(-EILSEQ);
}
if (!dlen) {
/* No more data, complete frame */
(&pkts[i])->recv(hdev, skb);
skb = NULL;
}
} else {
/* Complete frame */
(&pkts[i])->recv(hdev, skb);
skb = NULL;
}
}
return skb;
}
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* HT capability config for BES2600
* HT-related code for BES2600 driver
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_HT_H_INCLUDED
+10 -13
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Low-level device I/O for BES2600 mac80211 driver
* Low-level device IO routines for BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
@@ -324,10 +324,7 @@ out:
}
#endif
/*
* Patch H: __bes2600_irq_enable stub removed. It was a no-op
* (always returned 0) inherited from cw1200 where the analogous
* function manipulates the chip's IRQ-enable register. bes2600
* silicon manages SDIO IRQ via sdio_claim_irq and chip-side
* firmware there is no driver-side enable register to write.
*/
int __bes2600_irq_enable(int enable)
{
return 0;
}
+10 -5
View File
@@ -1,12 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Low-level device I/O interface for BES2600 mac80211 driver
* Low-level API for mac80211 BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
* Copyright (c) 2010, Bestechnic
* Author:
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Based on:
* UMAC BES2600 driver which is
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_HWIO_H_INCLUDED
+8 -4
View File
@@ -1,9 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ITP (in-band test mode) for BES2600
* mac80211 glue code for mac80211 BES2600 drivers
* ITP code
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
@@ -566,7 +570,7 @@ int bes2600_itp_get_tx(struct bes2600_common *priv, u8 **data,
*burst = 2;
atomic_set(&priv->bh_tx, 1);
ktime_get_ts(&itp->last_sent);
atomic_inc(&itp->awaiting_confirm);
atomic_add(1, &itp->awaiting_confirm);
spin_unlock_bh(&itp->tx_lock);
return 1;
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ITP interface for BES2600
* ITP code for BES2600 mac80211 driver
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2011, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_ITP_H_INCLUDED
+11 -61
View File
@@ -1,18 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Main entry/init for BES2600 mac80211 driver
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
*
* Based on the mac80211 Prism54 code, which is
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
*
* Based on the islsm (softmac prism54) driver, which is
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
@@ -38,7 +32,6 @@
#include "pm.h"
#include "bes2600_factory.h"
#include "bes_chardev.h"
#include "txrx_opt.h"
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>");
MODULE_DESCRIPTION("Softmac BES2600 common code");
@@ -205,11 +198,7 @@ static const struct ieee80211_iface_limit bes2600_if_limits[] = {
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO) },
#ifdef P2P_MULTIVIF
/*
* HACK: Disable P2P_DEVICE implementation for BES2600
* as the code is a little buggy.
*/
//{ .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) },
{ .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) },
#endif
};
@@ -495,20 +484,17 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len)
spin_lock_init(&hw_priv->rtsvalue_lock);
INIT_WORK(&hw_priv->dynamic_opt_txrx_work, bes2600_dynamic_opt_txrx_work);
INIT_WORK(&hw_priv->tx_policy_upload_work, tx_policy_upload_work);
INIT_WORK(&hw_priv->connection_loss_storm_recover_work,
bes2600_connection_loss_storm_recover);
spin_lock_init(&hw_priv->event_queue_lock);
INIT_LIST_HEAD(&hw_priv->event_queue);
INIT_WORK(&hw_priv->event_handler, bes2600_event_handler);
INIT_WORK(&hw_priv->ba_work, bes2600_ba_work);
/* Patch D: ba_lock removed; ba_acc/ba_cnt/etc are atomic_t. */
spin_lock_init(&hw_priv->ba_lock);
timer_setup(&hw_priv->ba_timer, bes2600_ba_timer, 0);
if (unlikely(bes2600_queue_stats_init(&hw_priv->tx_queue_stats,
WLAN_LINK_ID_MAX,
bes2600_skb_dtor,
hw_priv))) {
destroy_workqueue(hw_priv->workqueue);
ieee80211_free_hw(hw);
return NULL;
}
@@ -520,7 +506,6 @@ static struct ieee80211_hw *bes2600_init_common(size_t hw_priv_data_len)
for (; i > 0; i--)
bes2600_queue_deinit(&hw_priv->tx_queue[i - 1]);
bes2600_queue_stats_deinit(&hw_priv->tx_queue_stats);
destroy_workqueue(hw_priv->workqueue);
ieee80211_free_hw(hw);
return NULL;
}
@@ -608,7 +593,7 @@ static void bes2600_unregister_common(struct ieee80211_hw *dev)
ieee80211_unregister_hw(dev);
timer_delete_sync(&hw_priv->ba_timer);
del_timer_sync(&hw_priv->ba_timer);
hw_priv->sbus_ops->irq_unsubscribe(hw_priv->sbus_priv);
bes2600_unregister_bh(hw_priv);
@@ -805,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;
@@ -911,8 +861,8 @@ int bes2600_wifi_stop(struct bes2600_common *hw_priv)
hw_priv->wsm_tx_seq[1] = 0;
hw_priv->wsm_tx_pending[0] = 0;
hw_priv->wsm_tx_pending[1] = 0;
timer_delete_sync(&hw_priv->mcu_mon_timer);
timer_delete_sync(&hw_priv->lmac_mon_timer);
del_timer_sync(&hw_priv->mcu_mon_timer);
del_timer_sync(&hw_priv->lmac_mon_timer);
#ifdef CONFIG_BES2600_STATIC_SDD
hw_priv->sdd = NULL;
#else
+6 -6
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Power management for BES2600 mac80211 driver
* Mac80211 power management API for BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2011, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/platform_device.h>
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Power management interface for BES2600 mac80211 driver
* Mac80211 power management interface for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2011, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef PM_H_INCLUDED
+19 -17
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* O(1) TX queue for BES2600 mac80211 driver
* O(1) TX queue with built-in allocator for BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <net/mac80211.h>
@@ -119,9 +119,10 @@ static void bes2600_queue_register_post_gc(struct list_head *gc_list,
struct bes2600_queue_item *item)
{
struct bes2600_queue_item *gc_item;
gc_item = kmemdup(item, sizeof(struct bes2600_queue_item),
gc_item = kmalloc(sizeof(struct bes2600_queue_item),
GFP_ATOMIC);
BUG_ON(!gc_item);
memcpy(gc_item, item, sizeof(struct bes2600_queue_item));
list_add_tail(&gc_item->head, gc_list);
}
@@ -130,9 +131,9 @@ static void bes2600_queue_pending_record(struct list_head *pending_record_list,
{
struct bes2600_queue_item *record_item;
record_item = kmemdup(pending_item, sizeof(struct bes2600_queue_item),
GFP_ATOMIC);
record_item = kmalloc(sizeof(struct bes2600_queue_item),GFP_ATOMIC);
BUG_ON(!record_item);
memcpy(record_item, pending_item, sizeof(struct bes2600_queue_item));
record_item->skb = skb_clone(pending_item->skb, GFP_ATOMIC);
list_add_tail(&record_item->head, pending_record_list);
}
@@ -195,7 +196,7 @@ static void __bes2600_queue_gc(struct bes2600_queue *queue,
static void bes2600_queue_gc(struct timer_list *t)
{
LIST_HEAD(list);
struct bes2600_queue *queue = timer_container_of(queue, t, gc);
struct bes2600_queue *queue = from_timer(queue, t, gc);
spin_lock_bh(&queue->lock);
__bes2600_queue_gc(queue, &list, true);
@@ -217,7 +218,7 @@ int bes2600_queue_stats_init(struct bes2600_queue_stats *stats,
spin_lock_init(&stats->lock);
init_waitqueue_head(&stats->wait_link_id_empty);
for (i = 0; i < CW12XX_MAX_VIFS; i++) {
stats->link_map_cache[i] = kcalloc(map_capacity, sizeof(int),
stats->link_map_cache[i] = kzalloc(map_capacity * sizeof(int),
GFP_KERNEL);
if (!stats->link_map_cache[i]) {
for (; i >= 0; i--)
@@ -248,14 +249,14 @@ int bes2600_queue_init(struct bes2600_queue *queue,
queue->queue_all_lock = false;
spin_lock_init(&queue->lock);
timer_setup(&queue->gc, bes2600_queue_gc, 0);
queue->pool = kcalloc(capacity, sizeof(struct bes2600_queue_item),
queue->pool = kzalloc(sizeof(struct bes2600_queue_item) * capacity,
GFP_KERNEL);
if (!queue->pool)
return -ENOMEM;
for (i = 0; i < CW12XX_MAX_VIFS; i++) {
queue->link_map_cache[i] =
kcalloc(stats->map_capacity, sizeof(int),
kzalloc(stats->map_capacity * sizeof(int),
GFP_KERNEL);
if (!queue->link_map_cache[i]) {
for (; i >= 0; i--)
@@ -362,7 +363,7 @@ void bes2600_queue_deinit(struct bes2600_queue *queue)
int i;
bes2600_queue_clear(queue, CW12XX_ALL_IFS);
timer_delete_sync(&queue->gc);
del_timer_sync(&queue->gc);
INIT_LIST_HEAD(&queue->free_pool);
kfree(queue->pool);
for (i = 0; i < CW12XX_MAX_VIFS; i++) {
@@ -409,6 +410,7 @@ int bes2600_queue_put(struct bes2600_queue *queue,
struct timespec64 tmval;
#endif /*CONFIG_BES2600_TESTMODE*/
LIST_HEAD(gc_list);
struct bes2600_queue_stats *stats = queue->stats;
/* TODO:COMBO: Add interface ID info to queue item */
@@ -827,19 +829,19 @@ int bes2600_queue_get_skb(struct bes2600_queue *queue, u32 packetID,
bes2600_queue_parse_id(packetID, &queue_generation, &queue_id,
&item_generation, &item_id, &if_id, &link_id);
spin_lock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock);
spin_lock(&queue->stats->hw_priv->tx_loop.pending_record_lock);
if (!list_empty(&queue->stats->hw_priv->tx_loop.pending_record_list)) {
list_for_each_entry_safe(record_item, temp_record_item, &queue->stats->hw_priv->tx_loop.pending_record_list, head) {
if (record_item->packetID == packetID) {
list_del(&record_item->head);
dev_kfree_skb(record_item->skb);
kfree(record_item);
spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock);
spin_unlock(&queue->stats->hw_priv->tx_loop.pending_record_lock);
return -EINVAL;
}
}
}
spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock);
spin_unlock(&queue->stats->hw_priv->tx_loop.pending_record_lock);
item = &queue->pool[item_id];
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* O(1) TX queue interface for BES2600 mac80211 driver
* O(1) TX queue with built-in allocator for BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_QUEUE_H_INCLUDED
+8 -23
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Bus abstraction interface for BES2600
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Replaces hwbus.h from drivers/net/wireless/st/cw1200/
* Copyright (c) 2010, ST-Ericsson
* Common sbus abstraction layer interface for bes2600 wireless driver
*
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_SBUS_H
@@ -75,26 +75,11 @@ struct sbus_ops {
void (*halt_device)(struct sbus_priv *self);
bool (*wakeup_source)(struct sbus_priv *self);
int (*reboot)(struct sbus_priv *self);
/*
* Force the host bus to re-detect and re-probe the chip. Called
* from the firmware-wedge recovery path when power_switch() has no
* effective chip-reset signal of its own (e.g. PineTab2, where the
* wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 node).
* Returns 0 on success or a negative errno.
*/
int (*bus_reset)(struct sbus_priv *self);
/*
* Read a batch of RX frames inline from the bus and deliver each
* one via bes2600_bh_handle_rx_skb(). Called from the bh thread
* (process context, sleepable). Replaces the
* sdio_rx_work + rx_queue + pipe_read relay (Patch C v3, 2026).
* Returns 0 on success, negative on read error.
*/
int (*bus_rx_batch)(struct sbus_priv *self);
};
void bes2600_irq_handler(struct bes2600_common *priv);
/* Patch H: __bes2600_irq_enable removed (was a stub). */
/* This MUST be wrapped with hwbus_ops->lock/unlock! */
int __bes2600_irq_enable(int enable);
#endif /* BES2600_SBUS_H */
+27 -35
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Scan implementation for BES2600 mac80211 driver
* Scan implementation for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/sched.h>
@@ -257,21 +257,18 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
bes2600_pwr_set_busy_event(hw_priv, BES_PWR_LOCK_ON_SCAN);
/* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb) {
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
if (!frame.skb)
return -ENOMEM;
}
if (req->ie_len)
skb_put_data(frame.skb, req->ie, req->ie_len);
/* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
if (frame.skb) {
int ret;
//if (priv->if_id == 0)
@@ -289,9 +286,9 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
}
#endif
if (ret) {
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret;
}
}
@@ -321,10 +318,10 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
++hw_priv->scan.n_ssids;
}
up(&hw_priv->conf_lock);
if (frame.skb)
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING);
#endif
@@ -365,18 +362,14 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
if (req->n_ssids > hw->wiphy->max_scan_ssids)
return -EINVAL;
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb)
return -ENOMEM;
/* will be unlocked in bes2600_scan_work() */
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb) {
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
return -ENOMEM;
}
if (frame.skb) {
int ret;
if (priv->if_id == 0)
@@ -387,9 +380,9 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
ret = wsm_set_probe_responder(priv, true);
}
if (ret) {
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret;
}
}
@@ -421,10 +414,10 @@ int bes2600_hw_sched_scan_start(struct ieee80211_hw *hw,
}
}
up(&hw_priv->conf_lock);
if (frame.skb)
dev_kfree_skb(frame.skb);
up(&hw_priv->conf_lock);
queue_work(hw_priv->workqueue, &hw_priv->scan.swork);
wiphy_warn(hw->wiphy, "<--[SCAN] Scheduled scan request.\n");
return 0;
@@ -592,10 +585,10 @@ void bes2600_scan_work(struct work_struct *work)
"[SCAN] Scan failed (%d).\n",
hw_priv->scan.status);
else if (hw_priv->scan.req)
wiphy_dbg(priv->hw->wiphy,
wiphy_info(priv->hw->wiphy,
"[SCAN] Scan completed.\n");
else
wiphy_dbg(priv->hw->wiphy,
wiphy_info(priv->hw->wiphy,
"[SCAN] Scan canceled.\n");
#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
@@ -680,9 +673,8 @@ void bes2600_scan_work(struct work_struct *work)
scan.scanType = WSM_SCAN_TYPE_BACKGROUND;
scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
}
scan.ch = kcalloc((it - hw_priv->scan.curr),
sizeof(struct wsm_scan_ch),
GFP_KERNEL);
scan.ch = kzalloc((it - hw_priv->scan.curr) *
sizeof(struct wsm_scan_ch), GFP_KERNEL);
if (!scan.ch) {
hw_priv->scan.status = -ENOMEM;
goto fail;
@@ -984,7 +976,7 @@ void bes2600_scan_complete_cb(struct bes2600_common *hw_priv,
// recover EPTA timer after scan wsm msg complete, in case of epta state error
// bwifi_change_current_status(hw_priv, BWIFI_STATUS_SCANNING_COMP);
#endif
wiphy_dbg(hw_priv->hw->wiphy, "bes2600_scan_complete_cb status: %u", arg->status);
wiphy_info(hw_priv->hw->wiphy, "bes2600_scan_complete_cb status: %u", arg->status);
if(hw_priv->scan.status == -ETIMEDOUT)
wiphy_warn(hw_priv->hw->wiphy,
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Scan interface for BES2600 mac80211 driver
* Scan interface for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef SCAN_H_INCLUDED
+55 -210
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Mac80211 STA API for BES2600 mac80211 driver
* Mac80211 STA API for BES2600 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/vmalloc.h>
@@ -42,8 +42,6 @@
#include "bes2600_factory.h"
#endif
#include "txrx_opt.h"
#define WEP_ENCRYPT_HDR_SIZE 4
#define WEP_ENCRYPT_TAIL_SIZE 4
#define WPA_ENCRYPT_HDR_SIZE 8
@@ -221,7 +219,7 @@ void bes2600_stop(struct ieee80211_hw *dev, bool suspend)
cancel_delayed_work_sync(&hw_priv->advance_scan_timeout);
#endif
flush_workqueue(hw_priv->workqueue);
timer_delete_sync(&hw_priv->ba_timer);
del_timer_sync(&hw_priv->ba_timer);
down(&hw_priv->conf_lock);
@@ -261,14 +259,13 @@ void bes2600_stop(struct ieee80211_hw *dev, bool suspend)
cancel_delayed_work_sync(&priv->bss_loss_work);
cancel_delayed_work_sync(&priv->connection_loss_work);
cancel_delayed_work_sync(&priv->link_id_gc_work);
timer_delete_sync(&priv->mcast_timeout);
del_timer_sync(&priv->mcast_timeout);
}
#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
cancel_work_sync(&hw_priv->coex_work);
coex_stop(hw_priv);
#endif
cancel_work_sync(&hw_priv->connection_loss_storm_recover_work);
bes2600_wifi_stop(hw_priv);
@@ -378,23 +375,9 @@ void bes2600_remove_interface(struct ieee80211_hw *dev,
atomic_set(&priv->enabled, 0);
down(&hw_priv->scan.lock);
down(&hw_priv->conf_lock);
/*
* There's a chance remove_interface will run again on the same
* (already removed) interface.
*
* Currently this only happens when NetworkManager creates a P2P_DEVICE
* alongside a STA.
*
* But there can be other cases where this may run as well. So if that
* happens, let's throw a warning and decrease the vifs count by one.
*/
if (WARN_ON(!__cw12xx_hwpriv_to_vifpriv(hw_priv, priv->if_id))) {
if (!__cw12xx_hwpriv_to_vifpriv(hw_priv, priv->if_id)) {
bes_devel(" !!! %s: interface addr %pM already removed\n",
__func__, vif->addr);
atomic_dec(&hw_priv->num_vifs);
up(&hw_priv->conf_lock);
up(&hw_priv->scan.lock);
return;
@@ -465,9 +448,8 @@ void bes2600_remove_interface(struct ieee80211_hw *dev,
cancel_delayed_work_sync(&priv->join_timeout);
cancel_delayed_work_sync(&priv->set_cts_work);
cancel_delayed_work_sync(&priv->pending_offchanneltx_work);
cancel_work_sync(&priv->decrypt_storm_recover_work);
timer_delete_sync(&priv->mcast_timeout);
del_timer_sync(&priv->mcast_timeout);
/* TODO:COMBO: May be reset of these variables "delayed_link_loss and
* join_status to default can be removed as dev_priv will be freed by
* mac80211 */
@@ -520,7 +502,7 @@ int bes2600_change_interface(struct ieee80211_hw *dev,
return ret;
}
int bes2600_config(struct ieee80211_hw *dev, int radio_idx, u32 changed)
int bes2600_config(struct ieee80211_hw *dev, u32 changed)
{
int ret = 0;
struct bes2600_common *hw_priv = dev->priv;
@@ -1155,7 +1137,7 @@ void bes2600_wep_key_work(struct work_struct *work)
wsm_unlock_tx(hw_priv);
}
int bes2600_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value)
int bes2600_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
{
struct bes2600_common *hw_priv = hw->priv;
int ret;
@@ -1500,7 +1482,7 @@ void bes2600_event_handler(struct work_struct *work)
IEEE80211_STYPE_DEAUTH | IEEE80211_FCTL_TODS);
deauth->u.deauth.reason_code = WLAN_REASON_DEAUTH_LEAVING;
deauth->seq_ctrl = 0;
ieee80211_rx_ni(priv->hw, skb);
ieee80211_rx_irqsafe(priv->hw, skb);
bes_devel(" Inactivity Deauth Frame sent for MAC SA %pM \t and DA %pM\n", deauth->sa, deauth->da);
queue_work(priv->hw_priv->workqueue, &priv->set_tim_work);
break;
@@ -1676,70 +1658,6 @@ report:
spin_unlock(&priv->bss_loss_lock);
}
/*
* Connection-loss-storm fast-recover (Trigger A).
*
* bes2600_connection_loss_work below is the driver's own decision-point
* to give up on a BSS (after bss-loss detection accumulates beyond
* tolerance) and tell mac80211 via ieee80211_connection_loss(). On the
* deployed pinetab2 stack a single ieee80211_connection_loss() event
* sometimes triggers a userspace reauth blackhole (assoc-comeback
* timeouts followed by AP unprotected-deauth-reason-6) that ends only
* via cross-channel/cross-SSID fallback and can take 80+ s. Receipts at
* https://git.reauktion.de/marfrit/besser, notes/phase4-2026-05-07.md.
*
* When N connection-loss decisions land within WINDOW on the same vif,
* skip the ieee80211_connection_loss() path and trigger a chip-level
* bus_reset (the c5.2-introduced bes2600_chrdev_do_bus_reset). The chip
* is removed and re-probed; userspace re-associates from a fresh state,
* dodging the assoc-comeback loop.
*
* Threshold (3 / 60 s) is chosen well above the steady-state per-vif
* connection-loss rate observed in the patch-A Phase-7 rep
* (0.86/h under sustained load), so a true storm is required.
*
* The recover work_struct lives on bes2600_common (hw_priv) so that
* scheduling it does not race with vif teardown after bus_reset frees
* the per-vif state.
*/
#define BES2600_CONNECTION_LOSS_STORM_THRESHOLD 3
#define BES2600_CONNECTION_LOSS_STORM_WINDOW_MS 60000
void bes2600_connection_loss_storm_recover(struct work_struct *work)
{
bes_warn("[bes2600] connection-loss-storm fast-recover: bus_reset\n");
bes2600_chrdev_trigger_bus_reset();
/*
* After bes2600_chrdev_do_bus_reset() returns, the SDIO core has
* scheduled a remove + rescan; per-vif state may already be gone.
* Do not dereference any per-vif pointer here.
*/
}
void bes2600_connection_loss_storm_init(struct bes2600_vif *priv)
{
priv->connection_loss_storm_window_start = 0;
priv->connection_loss_storm_count = 0;
priv->connection_loss_storm_recoveries = 0;
}
bool bes2600_connection_loss_storm_account(struct bes2600_vif *priv)
{
unsigned long now = jiffies;
unsigned long window =
msecs_to_jiffies(BES2600_CONNECTION_LOSS_STORM_WINDOW_MS);
if (priv->connection_loss_storm_window_start == 0 ||
time_after(now, priv->connection_loss_storm_window_start + window)) {
priv->connection_loss_storm_window_start = now;
priv->connection_loss_storm_count = 1;
return false;
}
return ++priv->connection_loss_storm_count >=
BES2600_CONNECTION_LOSS_STORM_THRESHOLD;
}
void bes2600_connection_loss_work(struct work_struct *work)
{
struct bes2600_vif *priv =
@@ -1749,21 +1667,9 @@ void bes2600_connection_loss_work(struct work_struct *work)
bes_devel("[CQM] Reporting connection loss.\n");
bes2600_pwr_clear_busy_event(priv->hw_priv, BES_PWR_LOCK_ON_BSS_LOST);
if (bes2600_connection_loss_storm_account(priv)) {
bes_warn("[bes2600] connection-loss storm: %u in %u s, scheduling bus reset\n",
priv->connection_loss_storm_count,
BES2600_CONNECTION_LOSS_STORM_WINDOW_MS / 1000);
priv->connection_loss_storm_count = 0;
priv->connection_loss_storm_recoveries++;
schedule_work(&hw_priv->connection_loss_storm_recover_work);
/* bus_reset will tear the chip down; skip the mac80211 path. */
return;
}
if (bes2600_suspend_status_get(hw_priv))
if(bes2600_suspend_status_get(hw_priv)) {
bes2600_pending_unjoin_set(hw_priv, priv->if_id);
else
} else
ieee80211_connection_loss(priv->vif);
#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
// set disconnected in BSS_CHANGED_ASSOC
@@ -2225,10 +2131,9 @@ void bes2600_join_work(struct work_struct *work)
struct wsm_template_frame probe_tmp = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
struct wsm_reset join_fail_reset = {
.reset_statistics = false,
};
bool join_failed = false;
/*struct wsm_reset reset = {
.reset_statistics = true,
};*/
BUG_ON(queueId >= 4);
@@ -2264,8 +2169,6 @@ void bes2600_join_work(struct work_struct *work)
wsm_unlock_tx(hw_priv);
return;
}
rcu_read_lock();
ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
dtimie = ieee80211_bss_get_ie(bss, WLAN_EID_TIM);
if (dtimie)
@@ -2349,8 +2252,6 @@ void bes2600_join_work(struct work_struct *work)
bes2600_rate_mask_to_wsm(hw_priv, 0xFF0);
}
rcu_read_unlock();
bes2600_pwr_set_busy_event(hw_priv, BES_PWR_LOCK_ON_JOIN);
wsm_flush_tx(hw_priv);
@@ -2363,19 +2264,14 @@ void bes2600_join_work(struct work_struct *work)
//WARN_ON(wsm_reset(hw_priv, &reset, priv->if_id));
WARN_ON(wsm_set_block_ack_policy(hw_priv,
0, hw_priv->ba_tid_mask, priv->if_id));
/*
* Patch D: ba_lock removed. Disconnect-reset clears the
* counters and the arm flag; producers racing here cannot
* cause harm at worst they re-arm the timer and bump
* counters that will be cleared on the next timer tick.
*/
atomic_set(&hw_priv->ba_ena, 0);
atomic_set(&hw_priv->ba_cnt, 0);
atomic_set(&hw_priv->ba_acc, 0);
spin_lock_bh(&hw_priv->ba_lock);
hw_priv->ba_ena = false;
hw_priv->ba_cnt = 0;
hw_priv->ba_acc = 0;
hw_priv->ba_hist = 0;
atomic_set(&hw_priv->ba_cnt_rx, 0);
atomic_set(&hw_priv->ba_acc_rx, 0);
atomic_set(&hw_priv->ba_armed, 0);
hw_priv->ba_cnt_rx = 0;
hw_priv->ba_acc_rx = 0;
spin_unlock_bh(&hw_priv->ba_lock);
mgmt_policy.protectedMgmtEnable = 0;
mgmt_policy.unprotectedMgmtFramesAllowed = 1;
@@ -2411,33 +2307,6 @@ void bes2600_join_work(struct work_struct *work)
#endif /*CONFIG_BES2600_TESTMODE*/
cancel_delayed_work_sync(&priv->join_timeout);
bes2600_pwr_clear_busy_event(priv->hw_priv, BES_PWR_LOCK_ON_JOIN);
/*
* Firmware rejected WSM_JOIN (wsm_join_confirm ret 1).
* Issue wsm_reset so the firmware returns to a clean
* IDLE state before the next association attempt.
*
* Without this reset the firmware sits in an
* intermediate post-reject state. A rapid second
* JOIN (e.g. wpa_supplicant retrying after the
* PREV_AUTH_NOT_VALID deauth that follows) hits an
* inconsistent firmware context, causing
* bes2600_sdio_read_rx_batch to return SDIO error
* which cascades into wifi_force_close.
*
* cw1200 ancestor (drivers/net/wireless/st/cw1200/
* sta.c:1339) queues unjoin_work on join failure for
* the same reason; bes2600_unjoin_work gates its
* wsm_reset on join_status != PASSIVE, so after a
* failed JOIN (join_status stays PASSIVE) that path
* never fires call wsm_reset directly here instead.
*
* Contract: wsm_reset takes only wsm_cmd_lock; safe
* to call while conf_lock is held. wsm_oper_unlock
* was already called in wsm_join_confirm() before
* wsm_join() returned the error.
*/
WARN_ON(wsm_reset(hw_priv, &join_fail_reset, priv->if_id));
join_failed = true;
} else {
/* Upload keys */
#ifdef CONFIG_BES2600_TESTMODE
@@ -2462,18 +2331,7 @@ void bes2600_join_work(struct work_struct *work)
up(&hw_priv->conf_lock);
if (bss)
cfg80211_put_bss(hw_priv->hw->wiphy, bss);
/*
* On join failure: queue unjoin_work so the next association
* attempt is serialised after any lingering cleanup, matching
* cw1200 sta.c:1344 "Tx lock still held, unjoin will clear it."
* If unjoin_work is already queued, release TX immediately.
*/
if (join_failed) {
if (queue_work(hw_priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(hw_priv);
} else {
wsm_unlock_tx(hw_priv);
}
wsm_unlock_tx(hw_priv);
}
void bes2600_join_timeout(struct work_struct *work)
@@ -2500,7 +2358,7 @@ void bes2600_unjoin_work(struct work_struct *work)
int i;
struct bes2600_vif *tmp_priv;
timer_delete_sync(&hw_priv->ba_timer);
del_timer_sync(&hw_priv->ba_timer);
down(&hw_priv->conf_lock);
if (unlikely(atomic_read(&hw_priv->scan.in_progress)
|| atomic_read(&priv->connect_in_process))) {
@@ -2693,11 +2551,10 @@ void bes2600_ba_work(struct work_struct *work)
return;*/
bes_devel("BA work****\n");
/*
* Patch D: ba_lock removed. ba_tid_mask is u8 set once at init
* (main.c); reading it without a lock is fine.
*/
spin_lock_bh(&hw_priv->ba_lock);
// tx_ba_tid_mask = hw_priv->ba_ena ? hw_priv->ba_tid_mask : 0;
tx_ba_tid_mask = hw_priv->ba_tid_mask;
spin_unlock_bh(&hw_priv->ba_lock);
wsm_lock_tx(hw_priv);
@@ -2710,49 +2567,37 @@ void bes2600_ba_work(struct work_struct *work)
void bes2600_ba_timer(struct timer_list *t)
{
bool ba_ena;
int cnt, acc, cnt_rx, acc_rx;
struct bes2600_common *hw_priv = timer_container_of(hw_priv, t, ba_timer);
struct bes2600_common *hw_priv = from_timer(hw_priv, t, ba_timer);
/*
* Patch D: ba_lock removed. Snapshot atomic counters into locals
* for the predicate evaluation; producers may race incrementing
* after the snapshot but the resulting decision is approximate
* which the policy already tolerates (next timer tick re-evaluates).
*/
cnt = atomic_read(&hw_priv->ba_cnt);
acc = atomic_read(&hw_priv->ba_acc);
cnt_rx = atomic_read(&hw_priv->ba_cnt_rx);
acc_rx = atomic_read(&hw_priv->ba_acc_rx);
bes2600_debug_ba(hw_priv, cnt, acc, cnt_rx, acc_rx);
spin_lock_bh(&hw_priv->ba_lock);
bes2600_debug_ba(hw_priv, hw_priv->ba_cnt, hw_priv->ba_acc,
hw_priv->ba_cnt_rx, hw_priv->ba_acc_rx);
if (atomic_read(&hw_priv->scan.in_progress)) {
atomic_set(&hw_priv->ba_cnt, 0);
atomic_set(&hw_priv->ba_acc, 0);
atomic_set(&hw_priv->ba_cnt_rx, 0);
atomic_set(&hw_priv->ba_acc_rx, 0);
atomic_set(&hw_priv->ba_armed, 0);
return;
hw_priv->ba_cnt = 0;
hw_priv->ba_acc = 0;
hw_priv->ba_cnt_rx = 0;
hw_priv->ba_acc_rx = 0;
goto skip_statistic_update;
}
if (cnt >= BES2600_BLOCK_ACK_CNT &&
(acc / cnt >= BES2600_BLOCK_ACK_THLD ||
(cnt_rx >= BES2600_BLOCK_ACK_CNT &&
acc_rx / cnt_rx >=
if (hw_priv->ba_cnt >= BES2600_BLOCK_ACK_CNT &&
(hw_priv->ba_acc / hw_priv->ba_cnt >= BES2600_BLOCK_ACK_THLD ||
(hw_priv->ba_cnt_rx >= BES2600_BLOCK_ACK_CNT &&
hw_priv->ba_acc_rx / hw_priv->ba_cnt_rx >=
BES2600_BLOCK_ACK_THLD)))
ba_ena = true;
else
ba_ena = false;
atomic_set(&hw_priv->ba_cnt, 0);
atomic_set(&hw_priv->ba_acc, 0);
atomic_set(&hw_priv->ba_cnt_rx, 0);
atomic_set(&hw_priv->ba_acc_rx, 0);
atomic_set(&hw_priv->ba_armed, 0);
hw_priv->ba_cnt = 0;
hw_priv->ba_acc = 0;
hw_priv->ba_cnt_rx = 0;
hw_priv->ba_acc_rx = 0;
if (ba_ena != !!atomic_read(&hw_priv->ba_ena)) {
if (ba_ena != hw_priv->ba_ena) {
if (ba_ena || ++hw_priv->ba_hist >= BES2600_BLOCK_ACK_HIST) {
atomic_set(&hw_priv->ba_ena, ba_ena ? 1 : 0);
hw_priv->ba_ena = ba_ena;
hw_priv->ba_hist = 0;
#if 0
bes_devel("[STA] %s block ACK:\n",
@@ -2762,6 +2607,9 @@ void bes2600_ba_timer(struct timer_list *t)
}
} else if (hw_priv->ba_hist)
--hw_priv->ba_hist;
skip_statistic_update:
spin_unlock_bh(&hw_priv->ba_lock);
}
int bes2600_vif_setup(struct bes2600_vif *priv)
@@ -2771,8 +2619,6 @@ int bes2600_vif_setup(struct bes2600_vif *priv)
/* Setup per vif workitems and locks */
spin_lock_init(&priv->vif_lock);
bes2600_decrypt_storm_init(priv);
bes2600_connection_loss_storm_init(priv);
INIT_WORK(&priv->join_work, bes2600_join_work);
INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout);
INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work);
@@ -2943,7 +2789,6 @@ void bes2600_dynamic_opt_txrx_work(struct work_struct *work)
if (priv != NULL && priv->join_status > BES2600_JOIN_STATUS_MONITOR) {
multivif_connected = true;
}
bes2600_txrx_opt_multivif_connected_handler(hw_priv, multivif_connected);
}
@@ -3788,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;
@@ -3818,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;
@@ -3858,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;
+8 -8
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Mac80211 STA API interface for BES2600 mac80211 driver
* Mac80211 STA interface for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/version.h>
#ifndef STA_H_INCLUDED
@@ -26,7 +26,7 @@ int bes2600_change_interface(struct ieee80211_hw *dev,
enum nl80211_iftype new_type,
bool p2p);
int bes2600_config(struct ieee80211_hw *dev, int radio_idx, u32 changed);
int bes2600_config(struct ieee80211_hw *dev, u32 changed);
int bes2600_change_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type,
@@ -48,7 +48,7 @@ int bes2600_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key);
int bes2600_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value);
int bes2600_set_rts_threshold(struct ieee80211_hw *hw, u32 value);
void bes2600_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop);
+8 -5
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test-mode TX loopback for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "bes2600.h"
#include "wsm.h"
@@ -109,9 +112,9 @@ void bes2600_tx_loop_set_enable(struct bes2600_common *hw_priv, bool need_warn)
bes2600_queue_iterate_pending_packet(&hw_priv->tx_queue[i],
bes2600_tx_loop_item_pending_item);
}
spin_lock_bh(&hw_priv->tx_loop.pending_record_lock);
spin_lock(&hw_priv->tx_loop.pending_record_lock);
bes2600_queue_iterate_record_pending_packet(hw_priv, bes2600_tx_loop_item_pending_item);
spin_unlock_bh(&hw_priv->tx_loop.pending_record_lock);
spin_unlock(&hw_priv->tx_loop.pending_record_lock);
if (atomic_read(&hw_priv->bh_rx) > 0)
wake_up(&hw_priv->bh_wq);
+6 -3
View File
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Test-mode TX loopback interface for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __TX_LOOP_H__
#define __TX_LOOP_H__
+25 -143
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Datapath implementation for BES2600 mac80211 driver
* Datapath implementation for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <net/mac80211.h>
@@ -21,83 +21,10 @@
#include "debug.h"
#include "sta.h"
#include "sbus.h"
#include "txrx_opt.h"
#include "bes_log.h"
#define BES2600_INVALID_RATE_ID (0xFF)
/*
* Decrypt-storm fast-recover (Trigger B).
*
* When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a
* burst of received frames (typically because the host's PTK or GTK
* has fallen out of sync with the AP), the AP eventually concludes that
* the STA is not authenticated and emits an unprotected deauth-reason-6
* ("Class 2 frame received from non-authenticated station"). On the
* deployed pinetab2 + bes2600 stack this AP-initiated deauth has been
* observed to leave the link blackholed for up to 109 s before
* userspace finds a different SSID/channel to recover on. (Receipts at
* https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.)
*
* Recovery here pre-empts the AP: when we see THRESHOLD decrypt
* failures within WINDOW, we ask mac80211 for a clean reassoc via
* ieee80211_connection_loss(), which causes immediate disassociation
* and lets userspace auto-reconnect with fresh keys.
*
* mac80211 contract: ieee80211_connection_loss() may be called
* regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate
* disassociation without driver-side recovery attempts. See
* include/net/mac80211.h for the canonical doc-comment.
*
* The threshold is set well above the steady-state per-vif
* decrypt-fail rate observed in measurement (~1/min even under
* sustained 1 MB/s load), so a true storm is required to trip it.
*/
#define BES2600_DECRYPT_STORM_THRESHOLD 5
#define BES2600_DECRYPT_STORM_WINDOW_MS 5000
static void bes2600_decrypt_storm_recover_work(struct work_struct *work)
{
struct bes2600_vif *priv = container_of(work, struct bes2600_vif,
decrypt_storm_recover_work);
if (!priv->vif)
return;
bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n");
ieee80211_connection_loss(priv->vif);
priv->decrypt_storm_recoveries++;
}
void bes2600_decrypt_storm_init(struct bes2600_vif *priv)
{
INIT_WORK(&priv->decrypt_storm_recover_work,
bes2600_decrypt_storm_recover_work);
priv->decrypt_storm_window_start = 0;
priv->decrypt_storm_count = 0;
priv->decrypt_storm_recoveries = 0;
}
void bes2600_decrypt_storm_account(struct bes2600_vif *priv)
{
unsigned long now = jiffies;
unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS);
if (priv->decrypt_storm_window_start == 0 ||
time_after(now, priv->decrypt_storm_window_start + window)) {
priv->decrypt_storm_window_start = now;
priv->decrypt_storm_count = 1;
return;
}
if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) {
priv->decrypt_storm_count = 0;
/* Skew the window so we don't re-fire on the same storm. */
priv->decrypt_storm_window_start = now + window;
schedule_work(&priv->decrypt_storm_recover_work);
}
}
#ifdef CONFIG_BES2600_TESTMODE
#include "bes_nl80211_testmode_msg.h"
#endif /* CONFIG_BES2600_TESTMODE */
@@ -996,18 +923,14 @@ bes2600_tx_h_ba_stat(struct bes2600_vif *priv,
if (!ieee80211_is_data(t->hdr->frame_control))
return;
/*
* Patch D: lock-free hot-path BA accounting. atomic_inc + atomic_add
* each per-frame; the once-per-window timer-arm uses cmpxchg on
* ba_armed so concurrent TX/RX can't both try to set the timer and
* we don't need cross-counter coherency on the ba_cnt/ba_cnt_rx pair.
*/
atomic_add(t->skb->len - t->hdrlen, &hw_priv->ba_acc);
atomic_inc(&hw_priv->ba_cnt);
if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) {
spin_lock_bh(&hw_priv->ba_lock);
hw_priv->ba_acc += t->skb->len - t->hdrlen;
if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) {
mod_timer(&hw_priv->ba_timer,
jiffies + BES2600_BLOCK_ACK_INTERVAL);
}
hw_priv->ba_cnt++;
spin_unlock_bh(&hw_priv->ba_lock);
}
static int
@@ -1550,35 +1473,14 @@ void bes2600_skb_dtor(struct bes2600_common *hw_priv,
struct bes2600_vif *priv =
__cw12xx_hwpriv_to_vifpriv(hw_priv, txpriv->if_id);
if (!skb)
return;
/*
* There should be no reason for skb buffer being larger
* than the offset..
*/
if(WARN_ON(txpriv->offset > skb->len)) {
ieee80211_free_txskb(hw_priv->hw, skb);
return;
}
bes_devel("%s: txpriv->offset: %d - skb->len: %d\n",
__func__, txpriv->offset, skb->len);
skb_pull(skb, txpriv->offset);
if (priv && txpriv->rate_id != BES2600_INVALID_RATE_ID) {
bes2600_notify_buffered_tx(priv, skb,
txpriv->raw_link_id, txpriv->tid);
tx_policy_put(hw_priv, txpriv->rate_id);
}
if (likely(!bes2600_is_itp(hw_priv))) {
if (priv) {
/* The interface may be already removed */
bes2600_tx_status(priv, skb);
}
if (likely(!bes2600_is_itp(hw_priv)))
ieee80211_tx_status_skb(hw_priv->hw, skb);
}
}
#ifdef CONFIG_BES2600_TESTMODE
@@ -1655,13 +1557,14 @@ bes2600_rx_h_ba_stat(struct bes2600_vif *priv,
if (!priv->setbssparams_done)
return;
/* Patch D: lock-free hot-path BA accounting; see TX side comment. */
atomic_add(skb_len - hdrlen, &hw_priv->ba_acc_rx);
atomic_inc(&hw_priv->ba_cnt_rx);
if (atomic_cmpxchg(&hw_priv->ba_armed, 0, 1) == 0) {
spin_lock_bh(&hw_priv->ba_lock);
hw_priv->ba_acc_rx += skb_len - hdrlen;
if (!(hw_priv->ba_cnt_rx || hw_priv->ba_cnt)) {
mod_timer(&hw_priv->ba_timer,
jiffies + BES2600_BLOCK_ACK_INTERVAL);
}
hw_priv->ba_cnt_rx++;
spin_unlock_bh(&hw_priv->ba_lock);
}
void bes2600_rx_cb(struct bes2600_vif *priv,
@@ -1769,8 +1672,6 @@ 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;
}
}
@@ -1948,7 +1849,6 @@ void bes2600_rx_cb(struct bes2600_vif *priv,
if (ieee80211_is_data(frame->frame_control)) {
bes2600_rx_h_ba_stat(priv, hdrlen, skb->len);
bes2600_rx_status(priv, skb);
}
#ifdef CONFIG_BES2600_TESTMODE
@@ -1965,33 +1865,15 @@ void bes2600_rx_cb(struct bes2600_vif *priv,
if (unlikely(bes2600_itp_rxed(hw_priv, skb)))
consume_skb(skb);
else if (unlikely(early_data)) {
/*
* Patch E: when c7 has latched pm_unsupported (firmware
* doesn't honour PSM, see feedback_bes2600_firmware_no_psm),
* AP-side power-save state machine is dead and link entries
* never transition to BES2600_LINK_SOFT. The double-check
* branch under ps_state_lock is unreachable in that case,
* so skip the per-frame lock acquisition entirely and
* deliver to mac80211 directly.
*
* On firmware that does honour PSM (the latch self-clears
* if a real PM_INDICATION ever arrives see c7), this
* predicate flips back to false and the original locked
* path is taken.
*/
if (hw_priv->bes_power.pm_unsupported) {
ieee80211_rx_ni(priv->hw, skb);
} else {
spin_lock_bh(&priv->ps_state_lock);
/* Double-check status with lock held */
if (entry->status == BES2600_LINK_SOFT)
skb_queue_tail(&entry->rx_queue, skb);
else
ieee80211_rx_ni(priv->hw, skb);
spin_unlock_bh(&priv->ps_state_lock);
}
spin_lock_bh(&priv->ps_state_lock);
/* Double-check status with lock held */
if (entry->status == BES2600_LINK_SOFT)
skb_queue_tail(&entry->rx_queue, skb);
else
ieee80211_rx_irqsafe(priv->hw, skb);
spin_unlock_bh(&priv->ps_state_lock);
} else {
ieee80211_rx_ni(priv->hw, skb);
ieee80211_rx_irqsafe(priv->hw, skb);
}
*skb_p = NULL;
+6 -6
View File
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Datapath interface for BES2600 mac80211 driver
* Datapath interface for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2010, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_TXRX_H
-569
View File
@@ -1,569 +0,0 @@
/***************************************************************************
*
* Copyright 2015-2022 BES.
* All rights reserved. All unpublished rights reserved.
*
* No part of this work may be used or reproduced in any form or by any
* means, or stored in a database or retrieval system, without prior written
* permission of BES.
*
* Use of this work is governed by a license granted by BES.
* This work contains confidential and proprietary information of
* BES. which is protected by copyright, trade secret,
* trademark and other intellectual property rights.
*
****************************************************************************/
#include <net/mac80211.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/timer.h>
#include "bes2600.h"
#include "wsm.h"
#include "bh.h"
#include "ap.h"
#include "debug.h"
#include "sta.h"
#include "sbus.h"
#include "bes_pwr.h"
#include "txrx_opt.h"
#define TXRX_OPT_CLOSE_EDCA 0
#define TXRX_OPT_EDCA_MAX_LEVEL 4
#define TX_AVG_TIME_COUNT 10
#define TXRX_OPT_PEROID 500
#define TXRX_OPT_DEBUG 1
#define TXRX_HIGH_TP_THRESHOLD_2G4 30000 // unit is kbps
#define TXRX_HIGH_TP_THRESHOLD_5G 40000 // unit is kbps
#define TXRX_HIGH_TP_DELTA_TIME_2G4 8 // unit ms
#define TXRX_HIGH_TP_DELTA_TIME_5G 6 // unit ms
#define TXRX_RTS_PROT_TRIG_THRESH 80 // percent * 100
#define TXRX_RTS_PROT_DURATION 10 // unit second
#define TXRX_RTS_PROT_OPEN(x) (x = 512)
#define TXRX_RTS_PROT_CLOSE(x) (x = 2437)
#define TXRX_RTS_PROT_OPENED(x) (x < 1536)
static uint32_t tx_delta_time_arr[4][TX_AVG_TIME_COUNT];
static uint32_t tx_queue_arr[4] = {0};
static uint32_t tx_delta_time_total = 0;
static uint32_t tx_delta_time_total_cnt = 0;
static u8 cur_pwr_tbl = 1;
static u16 cur_rts_thres = 2437;
static unsigned long last_rts_set_time = -1;
void bes2600_add_tx_delta_time(uint32_t tx_delta_time)
{
tx_delta_time_total += tx_delta_time;
tx_delta_time_total_cnt++;
}
static uint32_t bes2600_get_tx_delta_time(void)
{
if (tx_delta_time_total_cnt != 0)
return tx_delta_time_total / tx_delta_time_total_cnt;
else
return 0;
}
static void bes2600_clear_tx_delta_time(void)
{
tx_delta_time_total_cnt = 0;
tx_delta_time_total = 0;
return ;
}
static uint32_t bes2600_get_tx_ac_delta_time(int ac)
{
uint32_t avg_time = 0;
int i = 0;
for (i = 0; i < TX_AVG_TIME_COUNT; i++) {
avg_time += tx_delta_time_arr[ac][i];
}
return avg_time / TX_AVG_TIME_COUNT;
}
static void bes2600_clear_tx_ac_delta_time(int ac)
{
int i = 0;
for (i = 0; i < TX_AVG_TIME_COUNT; i++) {
tx_delta_time_arr[ac][i] = 0;
}
return ;
}
void bes2600_add_tx_ac_delta_time(int ac, uint32_t del_time)
{
#if 0
if (tx_queue_arr[ac] >= (TX_AVG_TIME_COUNT - 1)) {
static int num = 0;
if ((num ++ % 10) == 0)
bes_devel( "%s %d %d %d %d %d del=%d\n\r", __func__, tx_delta_time_arr[ac][0],
tx_delta_time_arr[ac][2], tx_delta_time_arr[ac][4], tx_delta_time_arr[ac][6],
tx_delta_time_arr[ac][8], del_time);
}
#endif
tx_delta_time_arr[ac][tx_queue_arr[ac]] = del_time;
tx_queue_arr[ac] = (tx_queue_arr[ac] >= (TX_AVG_TIME_COUNT - 1)) ? 0 : (tx_queue_arr[ac] + 1);
}
static int bes2600_set_txrx_opt_param(struct bes2600_common *hw_priv,
struct bes2600_vif *priv,
MIB_TXRX_OPT_PARAM *para)
{
int ret = 0;
ret = WARN_ON(wsm_write_mib(hw_priv,
WSM_MIB_ID_EXT_TXRX_OPT_PARAM,
(u8 *)para,
sizeof(MIB_TXRX_OPT_PARAM),
priv->if_id));
return ret;
}
static int bes2600_enable_tx_shortgi(struct bes2600_common *hw_priv,
struct bes2600_vif *priv,
u8 onoff)
{
int ret = 0;
static u8 en = 0xff;
bes_devel( "%s onoff=%d\n\r", __func__, onoff);
if (en != onoff) {
en = onoff;
ret = WARN_ON(wsm_write_mib(hw_priv,
WSM_MIB_ID_EXT_TX_SHORT_GI_ENABLED,
(u8 *)&onoff,
sizeof(onoff),
priv->if_id));
}
return ret;
}
void bes2600_rx_status(struct bes2600_vif *priv, struct sk_buff *skb)
{
priv->dot11ReceivedFragmentCount++;
priv->dot11ReceivedDataBytes += skb->len;
}
void bes2600_tx_status(struct bes2600_vif *priv, struct sk_buff *skb)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
int i;
int retry_count = -1;
if(WARN_ON(!priv->hw))
return;
if (!ieee80211_is_data(hdr->frame_control))
return;
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
if (info->status.rates[i].idx < 0)
break;
retry_count += info->status.rates[i].count;
}
if (retry_count < 0)
retry_count = 0;
if (info->flags & IEEE80211_TX_STAT_ACK) {
priv->dot11TransmittedFrameCount++;
priv->dot11TransmittedDataBytes += (skb->len + 4);
if (retry_count > 0)
priv->dot11RetryCount += retry_count;
} else {
/* tx fail.*/
priv->dot11FailedCount++;
}
}
void bes2600_set_default_params(struct bes2600_common *hw_priv, struct bes2600_vif *priv);
static int bes2600_set_high_edca_params(struct bes2600_common *hw_priv, struct bes2600_vif *priv, int level)
{
struct wsm_edca_params arg;
int i = 0;
static int lev = 0;
bes_devel( "set edca level=%d\n\r", level);
if (lev == level)
return 0;
lev = level;
memcpy(&arg, &(priv->edca), sizeof(struct wsm_edca_params));
if (level == 0) {
bes2600_set_default_params(hw_priv, priv);
return 0;
} else if (level == 1) {
for ( i = 0; i < 4; i++) {
arg.params[i].aifns = 2;
arg.params[i].cwMax = 7;
arg.params[i].cwMin = 3;
/*
* tx op must set 0
* set other, some AP may not response BA when rx data.
*/
arg.params[i].txOpLimit = 0;
arg.params[i].maxReceiveLifetime = 0xc8;
}
} else if (level == 2) {
for ( i = 0; i < 4; i++) {
arg.params[i].aifns = 2;
arg.params[i].cwMax = 5;
arg.params[i].cwMin = 1;
arg.params[i].txOpLimit = 0;
arg.params[i].maxReceiveLifetime = 0xc8;
}
} else if (level == 3) {
for ( i = 0; i < 4; i++) {
arg.params[i].aifns = 2;
arg.params[i].cwMax = 3;
arg.params[i].cwMin = 1;
arg.params[i].txOpLimit = 0;
arg.params[i].maxReceiveLifetime = 0xc8;
}
} else if (level == 4) {
for ( i = 0; i < 4; i++) {
arg.params[i].aifns = 1;
arg.params[i].cwMax = 3;
arg.params[i].cwMin = 1;
arg.params[i].txOpLimit = 0;
arg.params[i].maxReceiveLifetime = 0xc8;
}
}
wsm_set_edca_params(hw_priv, &arg, priv->if_id);
return 0;
}
void bes2600_set_default_params(struct bes2600_common *hw_priv, struct bes2600_vif *priv)
{
bes_devel( "set edca default\n\r");
wsm_set_edca_params(hw_priv, &priv->edca, priv->if_id);
}
static void bes2600_set_cca_method(struct bes2600_common *hw_priv, struct bes2600_vif *priv, int value)
{
// todo set cca alg
}
static void bes2600_set_dynamic_agc(struct bes2600_common *hw_priv, struct bes2600_vif *priv, int value)
{
// todo set agc alg
}
static int bes2600_update_pwr_table(struct bes2600_common *hw_priv,
struct bes2600_vif *priv,
u8 pwr_tbl_idx)
{
int ret = 0;
static u8 cur_pwr_tbl_idx = 0xff;
if (cur_pwr_tbl_idx != pwr_tbl_idx) {
cur_pwr_tbl_idx = pwr_tbl_idx;
ret = WARN_ON(wsm_write_mib(hw_priv,
WSM_MIB_ID_EXT_PWR_TBL_UPDATE,
(u8 *)&cur_pwr_tbl_idx,
sizeof(cur_pwr_tbl_idx),
priv->if_id));
bes_devel( "%s pwr_tbl_idx=%d\n\r", __func__, pwr_tbl_idx);
}
return ret;
}
static int bes2600_get_tx_av_max_delta_time(void)
{
int max_avg = 0;
int i = 0;
for (i = 0; i < 4; i++) {
if (max_avg < bes2600_get_tx_ac_delta_time(i)) {
max_avg = bes2600_get_tx_ac_delta_time(i);
}
//bes2600_clear_tx_ac_delta_time(i);
}
return max_avg;
}
static bool bes2600_station_is_ap_ht40(struct bes2600_common *hw_priv)
{
if (hw_priv->hw) {
struct ieee80211_conf *conf = &hw_priv->hw->conf;
if (conf != NULL)
if (conf->chandef.width == NL80211_CHAN_WIDTH_40)
return true;
}
return false;
}
void bes2600_dynamic_opt_rxtx(struct bes2600_common *hw_priv, struct bes2600_vif *priv, int rssi)
{
u32 succPro = 0, tx_cnt, tx_retry, rx_cnt, tx_fail;
static u32 l_tx_cnt = 0, l_tx_fail = 0, l_tx_retry = 0, l_rx_cnt = 0;
static u32 tx_bps = 0, rx_bps = 0;
u32 total_kbps = 0;
static int level;
/* calculate real time throughput */
if (hw_priv == NULL || priv == NULL) {
return;
}
tx_bps = abs (priv->dot11TransmittedDataBytes - tx_bps);
rx_bps = abs (priv->dot11ReceivedDataBytes - rx_bps);
total_kbps = (tx_bps / 128 + rx_bps / 128);
total_kbps *= 1000;
total_kbps /= TXRX_OPT_PEROID;
/* if tx/rx < 100k/s, close*/
if (total_kbps < 100) {
level = 0;
last_rts_set_time = -1;
TXRX_RTS_PROT_CLOSE(cur_rts_thres);
goto txrx_opt_clear;
}
/* calculate tx_cnt, tx_retry, rx_cnt */
tx_cnt = (priv->dot11TransmittedFrameCount - l_tx_cnt);
tx_fail = (priv->dot11FailedCount - l_tx_fail);
tx_retry = (priv->dot11RetryCount - l_tx_retry);
rx_cnt = (priv->dot11ReceivedFragmentCount - l_rx_cnt);
( (tx_cnt + tx_retry) > 0 ) ? (succPro = tx_cnt * 100 / (tx_cnt + tx_retry)) : (succPro = 0);
bes_devel( "%s, tx_cnt:%d prob:%d\n", __func__, tx_cnt, succPro);
/* set rts/cts protection dynamically */
if (tx_cnt > 50 && succPro != 0) {
if (succPro > TXRX_RTS_PROT_TRIG_THRESH &&
TXRX_RTS_PROT_OPENED(cur_rts_thres) &&
time_after(jiffies, last_rts_set_time + TXRX_RTS_PROT_DURATION * HZ)) {
TXRX_RTS_PROT_CLOSE(cur_rts_thres);
} else if (succPro <= TXRX_RTS_PROT_TRIG_THRESH){
TXRX_RTS_PROT_OPEN(cur_rts_thres);
last_rts_set_time = jiffies;
}
}
/* dynamic set edca param */
if (succPro != 0) {
if (bes2600_station_is_ap_ht40(hw_priv)) {
if (bes2600_get_tx_delta_time() > 8 || bes2600_get_tx_av_max_delta_time() > 8) {
if (level < 4)
level++;
} else {
if (level > 0)
level--;
}
/* high throughput force level = 0 */
if (total_kbps > TXRX_HIGH_TP_THRESHOLD_5G && level > 0 && priv->hw_value > 19) {
level = 0;
}
} else {//shiled room 13, office 8
if (bes2600_get_tx_delta_time() > (TXRX_HIGH_TP_DELTA_TIME_5G + total_kbps / 8000)
|| bes2600_get_tx_av_max_delta_time() > (TXRX_HIGH_TP_DELTA_TIME_5G + total_kbps / 8000)) {
if (level < 4)
level++;
} else {
if (level > 0)
level--;
}
/* high throughput force level = 0 */
if (total_kbps > TXRX_HIGH_TP_THRESHOLD_2G4 && level > 0) {
level = 0;
}
}
}
/* dynamic set power table */
if (rssi <= BES2600_TX_RSSI_LOW)
cur_pwr_tbl = 2; // use high power table
else if(rssi >= BES2600_TX_RSSI_HIGH)
cur_pwr_tbl = 1; // use standard power table
#if TXRX_OPT_CLOSE_EDCA
level = 0;
#endif
if (level > TXRX_OPT_EDCA_MAX_LEVEL)
level = TXRX_OPT_EDCA_MAX_LEVEL;
bes_devel( "txrx_opt: tx(cnt=%d retry=%d psr=%d tx_fail=%d (wsm level=%d) tx=%dk/s)\n\r",
tx_cnt, tx_retry, succPro, tx_fail, level, tx_bps / 128);
bes_devel( "txrx_opt: rx(cnt=%d rx=%dk/s) total=%dk/s\n\r", rx_cnt, rx_bps / 128, total_kbps);
bes_devel( "txrx_opt: tx_delta_time=%d [%d %d %d %d] hw_value=%d ht=%d maxtxcnt=%d\n\r",
bes2600_get_tx_delta_time(), bes2600_get_tx_ac_delta_time(0), bes2600_get_tx_ac_delta_time(1),
bes2600_get_tx_ac_delta_time(2), bes2600_get_tx_ac_delta_time(3), priv->hw_value,
bes2600_station_is_ap_ht40(hw_priv), hw_priv->long_frame_max_tx_count);
/* dynamic set cca */
bes2600_set_cca_method(hw_priv, priv, 0);
/* dynamic set agc */
bes2600_set_dynamic_agc(hw_priv, priv, 0);
bes2600_update_pwr_table(hw_priv, priv, cur_pwr_tbl);
txrx_opt_clear:
bes2600_set_high_edca_params(hw_priv, priv, level);
bes2600_set_rts_threshold(hw_priv->hw, -1, cur_rts_thres);
bes2600_clear_tx_delta_time();
bes2600_clear_tx_ac_delta_time(0);
bes2600_clear_tx_ac_delta_time(1);
bes2600_clear_tx_ac_delta_time(2);
bes2600_clear_tx_ac_delta_time(3);
tx_bps = priv->dot11TransmittedDataBytes;
rx_bps = priv->dot11ReceivedDataBytes;
l_tx_cnt = priv->dot11TransmittedFrameCount;
l_tx_fail = priv->dot11FailedCount;
l_tx_retry = priv->dot11RetryCount;
l_rx_cnt = priv->dot11ReceivedFragmentCount;
return ;
}
static struct bes2600_common *txrx_hw_priv = NULL;
static bool bes2600_is_sta_connected(void)
{
if (txrx_hw_priv == NULL)
return false;
else
return true;
}
void bes2600_txrx_opt_timer_restore(void)
{
if (bes2600_is_sta_connected()) {
mod_timer(&txrx_hw_priv->txrx_opt_timer, jiffies + msecs_to_jiffies(TXRX_OPT_PEROID));
}
}
static void txrx_opt_timer_callback(struct timer_list* data)
{
bes_devel( "####Timer callback function Called time = %lu\n", jiffies);
queue_work(txrx_hw_priv->workqueue, &txrx_hw_priv->dynamic_opt_txrx_work);
}
static void txrx_opt_timer_start(struct bes2600_common *hw_priv)
{
mod_timer(&hw_priv->txrx_opt_timer, jiffies + msecs_to_jiffies(TXRX_OPT_PEROID));
}
static void txrx_opt_timer_stop(struct bes2600_common *hw_priv)
{
timer_delete_sync(&hw_priv->txrx_opt_timer);
}
static void bes2600_set_txrx_opt_default_param(struct bes2600_common * hw_priv)
{
MIB_TXRX_OPT_PARAM g_txrx_param = {2, (PROCTECT_MODE_RTS_CTS | PROCTECT_MODE_RTS_CTS_RETRY), 3000};
struct bes2600_vif *priv = __cw12xx_hwpriv_to_vifpriv(hw_priv, 0);
struct ieee80211_sta *sta = NULL;
int shortgi = 0;
if (priv == NULL)
return;
/* reset states */
cur_pwr_tbl = 1;
TXRX_RTS_PROT_CLOSE(cur_rts_thres);
last_rts_set_time = -1;
memcpy(&hw_priv->txrx_opt_param, &g_txrx_param, sizeof(MIB_TXRX_OPT_PARAM));
/* reset device states */
bes2600_set_txrx_opt_param(hw_priv, priv, &hw_priv->txrx_opt_param);
bes2600_set_rts_threshold(hw_priv->hw, -1, cur_rts_thres); // close rts/cts
bes2600_update_pwr_table(hw_priv, priv, cur_pwr_tbl); // use standard pwr table
if (priv->join_status == BES2600_JOIN_STATUS_STA) {
rcu_read_lock();
sta = ieee80211_find_sta(priv->vif, priv->vif->bss_conf.bssid);
if (sta) {
if (sta->deflink.ht_cap.ht_supported &&
((priv->vif->bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20 &&
sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ||
(priv->vif->bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_40 &&
sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_SGI_40))) {
shortgi = 1;
}
}
rcu_read_unlock();
bes_devel("short gi tx status: %d\n", shortgi);
bes2600_enable_tx_shortgi(hw_priv, priv, shortgi);
}
}
static int bes2600_set_txrx_opt_unjoin_param(struct bes2600_common * hw_priv)
{
MIB_TXRX_OPT_PARAM g_txrx_param = {1, 0, 2002};
struct bes2600_vif *priv = __cw12xx_hwpriv_to_vifpriv(hw_priv, 0);
if (priv == NULL)
return 0;
/* reset states */
cur_pwr_tbl = 1;
bes2600_update_pwr_table(hw_priv, priv, cur_pwr_tbl);
memcpy(&hw_priv->txrx_opt_param, &g_txrx_param, sizeof(MIB_TXRX_OPT_PARAM));
bes2600_set_txrx_opt_param(hw_priv, priv, &hw_priv->txrx_opt_param);
return 0;
}
void bes2600_txrx_opt_multivif_connected_handler(struct bes2600_common *hw_priv, bool multivif_connected)
{
struct bes2600_vif *priv = __cw12xx_hwpriv_to_vifpriv(hw_priv, 0);
if (multivif_connected) {
bes2600_set_txrx_opt_default_param(hw_priv);
} else {
bes_devel("%s, rssi:%d\n", __func__, priv->signal);
bes2600_dynamic_opt_rxtx(hw_priv, priv, priv->signal);
mod_timer(&hw_priv->txrx_opt_timer, jiffies + msecs_to_jiffies(TXRX_OPT_PEROID));
}
}
int txrx_opt_timer_init(struct bes2600_vif *priv)
{
struct bes2600_common *hw_priv = cw12xx_vifpriv_to_hwpriv(priv);
bes_devel( "txrx_opt_timer_init:%p", txrx_hw_priv);
if (priv->if_id != 0)
return 0;
if (!txrx_hw_priv) {
txrx_hw_priv = hw_priv;
bes_devel( "####Timer init hw_priv = %p\n", txrx_hw_priv);
timer_setup(&hw_priv->txrx_opt_timer, txrx_opt_timer_callback, 0);
bes2600_set_txrx_opt_default_param(hw_priv);
}
mod_timer(&hw_priv->txrx_opt_timer, jiffies + msecs_to_jiffies(TXRX_OPT_PEROID));
bes2600_pwr_register_en_lp_cb(hw_priv, txrx_opt_timer_stop);
bes2600_pwr_register_exit_lp_cb(hw_priv, txrx_opt_timer_start);
return 0;
}
void txrx_opt_timer_exit(struct bes2600_vif *priv)
{
struct bes2600_common *hw_priv = cw12xx_vifpriv_to_hwpriv(priv);
bes_devel( "txrx_opt_timer_exit");
if (priv->if_id == 0) {
timer_delete_sync(&hw_priv->txrx_opt_timer);
cancel_work_sync(&hw_priv->dynamic_opt_txrx_work);
bes2600_pwr_unregister_en_lp_cb(hw_priv, txrx_opt_timer_stop);
bes2600_pwr_unregister_exit_lp_cb(hw_priv, txrx_opt_timer_start);
txrx_hw_priv = NULL;
bes2600_set_txrx_opt_unjoin_param(hw_priv);
} else if (priv->if_id == 1) {
bes2600_txrx_opt_timer_restore();
}
}
-38
View File
@@ -1,38 +0,0 @@
/***************************************************************************
*
* Copyright 2015-2022 BES.
* All rights reserved. All unpublished rights reserved.
*
* No part of this work may be used or reproduced in any form or by any
* means, or stored in a database or retrieval system, without prior written
* permission of BES.
*
* Use of this work is governed by a license granted by BES.
* This work contains confidential and proprietary information of
* BES. which is protected by copyright, trade secret,
* trademark and other intellectual property rights.
*
****************************************************************************/
#ifndef bes2600_TXRX_OPT_H
#define bes2600_TXRX_OPT_H
#include <linux/list.h>
/* open it for enhance wifi throughput */
#define BES2600_TX_RX_OPT 1
/* Threshold for powrt table switch */
#define BES2600_TX_RSSI_LOW -65
#define BES2600_TX_RSSI_HIGH -60
void bes2600_add_tx_ac_delta_time(int ac, uint32_t del_time);
void bes2600_add_tx_delta_time(uint32_t tx_time);
void bes2600_rx_status(struct bes2600_vif *priv, struct sk_buff *skb);
void bes2600_tx_status(struct bes2600_vif *priv, struct sk_buff *skb);
void bes2600_dynamic_opt_rxtx(struct bes2600_common *hw_priv,struct bes2600_vif *priv, int rssi);
void bes2600_txrx_opt_multivif_connected_handler(struct bes2600_common *hw_priv, bool multivif_connected);
void bes2600_txrx_opt_timer_restore(void);
int txrx_opt_timer_init(struct bes2600_vif *priv);
void txrx_opt_timer_exit(struct bes2600_vif *priv);
#endif
+6 -3
View File
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* WiFi testmode commands for BES2600
* Mac80211 driver for BES2600 device
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifdef CONFIG_BES2600_TESTMODE
#include <net/netlink.h>
+8 -7
View File
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* WSM host interface for BES2600 mac80211 driver
* WSM host interface (HI) implementation for
* BES2600 mac80211 drivers.
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Copyright (c) 2022, Bestechnic
* Author:
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/skbuff.h>
@@ -2412,7 +2413,7 @@ int wsm_handle_rx(struct bes2600_common *hw_priv, int id,
if (!hw_priv->beacon_bkp)
hw_priv->beacon_bkp = \
skb_copy(hw_priv->beacon, GFP_ATOMIC);
ieee80211_rx_ni(hw_priv->hw, hw_priv->beacon);
ieee80211_rx_irqsafe(hw_priv->hw, hw_priv->beacon);
hw_priv->beacon = hw_priv->beacon_bkp;
hw_priv->beacon_bkp = NULL;
+9 -7
View File
@@ -1,12 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* WSM host interface for BES2600 mac80211 driver
* WSM host interface (HI) interface for BES2600 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
* Copyright (c) 2022, Bestechnic
* Author:
*
* Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.
* Based on BES2600 UMAC WSM API, which is
* Copyright (C) SA 2010
* Author: Stewart Mathers <stewart.mathers@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef BES2600_WSM_H_INCLUDED
@@ -2232,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 */
+1 -2
View File
@@ -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".