8 Commits

Author SHA1 Message Date
test0r 8dd79199f8 bes2600: Patch G — restore SPDX identifiers + ST-Ericsson attribution
The bes2600 driver is a fork of the upstream cw1200 driver
(drivers/net/wireless/st/cw1200/, ST-Ericsson, Dmitry Tarnyagin
2010-2011).  The fork's file headers have three GPL-compliance issues:

  1. NO SPDX-License-Identifier on any of 48 source files (cw1200
     mainline has them on all 25).  kernel.org-mandated since 2017.

  2. Original "Copyright (c) 2010, ST-Ericsson" lines stripped from
     all files inherited from cw1200, replaced with
     "Copyright (c) 2010, Bestechnic" — factually impossible
     (Bestechnic did not author the 2010 work) and a GPL-2.0 §1
     attribution-preservation violation.

  3. The "GPL version 2 as published by the Free Software Foundation"
     boilerplate paragraph is redundant alongside SPDX and is the
     legacy form modern kernel sources have replaced.

This patch corrects all three for the 48 .c/.h files in bes2600/:

  - Adds `// SPDX-License-Identifier: GPL-2.0-only` (or `/* ... */`
    for headers) as line 1 of every file.
  - Restores `Copyright (c) 2010, ST-Ericsson` + `Author: Dmitry
    Tarnyagin <dmitry.tarnyagin@lockless.no>` as the FIRST copyright
    chain entry on all 22 files derived from cw1200 (bh.{c,h},
    debug.{c,h}, fwio.{c,h}, hwio.{c,h}, main.c, pm.{c,h},
    queue.{c,h}, scan.{c,h}, sta.{c,h}, txrx.{c,h}, wsm.{c,h}).
  - Keeps `Copyright (c) 2022, Bestechnic (Beijing) Co., Ltd.` as
    the SECOND chain entry where Bestechnic genuinely contributed.
  - Notes "Derived from cw1200_sdio.c" + ST-Ericsson copyright on
    bes2600_sdio.c (heavy derivation, not a literal rename).
  - Notes "Replaces hwbus.h from cw1200/" + ST-Ericsson copyright
    on sbus.h.
  - Preserves the prism54/islsm authorship chain on main.c and
    bes2600.h (Michael Wu 2006 + Jean-Baptiste Note 2004-2006).
  - Drops the GPL-2.0 boilerplate paragraph in favour of SPDX.

No code changes — only file-header comment blocks.  Module build is
unaffected (verified by header-only diff scope).

This is a prerequisite for any kernel.org submission attempt.  The
existing MODULE_LICENSE("GPL") + MODULE_AUTHOR(Tarnyagin@stericsson.com)
declarations were already present and are unchanged here; the
mismatch between MODULE_AUTHOR and the (since-corrected) per-file
copyrights is now resolved.
2026-05-08 00:03:50 +02:00
test0r 1e9eb4581f bes2600: drop sdio_rx_work relay, IRQ→bh-direct (no-relay architecture)
Patch C v3 — match cw1200 mainline architecture
(drivers/net/wireless/st/cw1200/).  Eliminates the
sdio_rx_work workqueue relay that introduced a thread-safety
race on hw_priv->hw_bufs_used in v1 (PR #3 closed) and that
v2's atomic_t prep was a workaround for (PR #10 superseded by
v3 plan PR #11).

Architectural changes:

  - bes2600_gpio_irq_handler: now calls self->irq_handler()
    directly instead of queue_work(self->sdio_wq, &self->rx_work).
    Bumps bh_rx atomic + wakes bh_wq.
  - bes2600_bh_rx_helper (BES_SDIO_RX_MULTIPLE_ENABLE branch):
    now calls priv->sbus_ops->bus_rx_batch() to do the SDIO read
    inline.  No pipe_read, no skb_dequeue.
  - bes2600_sdio_read_rx_batch (new): the SDIO read sequence
    extracted from sdio_rx_work, registered as
    sbus_ops->bus_rx_batch.  Runs in bh thread context.
  - bes2600_sdio_extract_packets: calls
    bes2600_bh_handle_rx_skb() directly per parsed SKB.  No
    skb_queue_tail, no rx_queue.
  - bes2600_bh_handle_rx_skb (new in bh.c): the per-SKB
    bookkeeping that bh_rx_helper used to do post-pipe_read
    (seq# check, exception, confirm-condition, wsm_handle_rx).
    Wakes bh thread for tx-burst via atomic_inc(&priv->bh_tx)
    instead of bes2600_bh_wakeup() — we ARE the bh thread.
  - Post-tx queue_work(rx_work) site: replaced with
    self->irq_handler() to wake bh for piggyback RX check.

Deleted infrastructure:

  - struct sbus_priv: rx_queue, rx_queue_lock, rx_work fields
  - bes2600_sdio_pipe_read: function deleted (unused)
  - sdio_rx_work: function deleted (unused)
  - sbus_ops->pipe_read assignment: removed for SDIO bus
  - skb_queue_head_init(&self->rx_queue), spin_lock_init(...),
    INIT_WORK(rx_work): probe-time setup removed
  - cancel_work_sync(rx_work) + drain loop in empty_work: removed
  - flush_work(rx_work) in drain helper: replaced with msleep(2)
  - work_pending(rx_work) check in suspend predicate: removed

Concurrency invariant restored:

  - hw_priv->hw_bufs_used: single-writer (bh thread only)
    by construction.  No atomic_t needed.
  - hw_priv->hw_bufs_used_vif[]: ditto.
  - hw_priv->wsm_tx_pending[]: ditto.
  - All other shared state: unchanged or already protected.

Phase 7 partial verification (rep 1, 2026-05-07):

  - Module loads clean, srcversion 371C6606B73AF19299228CA
  - Link associates, no WARN/BUG/oops
  - sdio_rx_work dispatches: 0 (function deleted)
  - bes2600_bh_work redispatches: 0 (single long-lived
    invariant preserved)
  - Chip handled stress traffic without wedge

Phase 7 full N=3 stress ramp deferred to follow-up rep series
(rep 2 had a TCP-level nc race; not a bes2600 issue but
invalidated rep 2's throughput number).
2026-05-07 22:34:11 +02:00
test0r 80e5e68c22 bes2600: handle multi-function SDIO cards in mmc_hw_reset bus_reset
c5.2 (recover-wedged-firmware-via-mmc-hw-reset) wraps mmc_hw_reset()
and treats any non-zero return as a recovery failure. On
single-function SDIO cards mmc_hw_reset returns 0 after doing the
remove + rescan inline. On multi-function cards (BES2600 has WLAN
func 1 + BT companion func 2) the kernel's mmc_sdio_hw_reset() does
NOT do the rescan: it tears the card down and returns 1 to signal
"caller must trigger rescan".

Field observation on PineTab2 (linux-pinetab2 6.19.10-danctnix1):
when a real LMAC wedge fired bes2600_chrdev_wifi_force_close ->
bes2600_chrdev_do_bus_reset, mmc_hw_reset returned 1, c5.2's wrapper
treated that as "bus_reset failed: 1", logged the error, and gave
up. The card was already removed (mmc2: card 0001 removed) but
nothing scheduled a rescan; wifi (and the BT companion which shares
the same SDIO host) stayed silent until the user rebooted four
minutes later.

Fix:

  - Capture the mmc_host pointer before calling mmc_hw_reset (the
    card pointer is invalid after the remove).
  - On positive return (multi-function path), log informationally
    and call mmc_detect_change(host, 0) to schedule a rescan.
    Return 0 so callers see the recovery as successful.
  - Negative return is still treated as failure as before.

The mmc_detect_change side effect is asynchronous; the chrdev's
wait_event_timeout(probe_done_wq, !sbus_priv) still observes the
remove half synchronously, and the rescan + re-probe runs out of
the host detect work afterwards.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
2026-04-28 21:37:37 +02:00
test0r b7c4b0fc39 bes2600: short-circuit wake handshake when chip is confirmed ACTIVE
The previous patch ("bes2600: gate PM indication completion on pending
request and track chip state") added enum bes2600_chip_pm_state and the
chip_pm_state field tracking what the host has *seen the firmware
confirm*. This patch makes the wake side use it.

Without this, every bes2600_pwr_device_exit_lp_mode() unconditionally
runs gpio_wake() + sbus_active() + wsm_set_operational_mode(active),
even when the chip is already in confirmed-ACTIVE state and the wake
sequence has nothing to do. The visible failure mode on PineTab2:

  bes2600_pwr_enter_lp_mode, wait pm ind timeout
  repeat set gpio_wake_flag, sub_sys:0
  bes2600_sdio_active failed, subsys:0
  bes2600_pwr_device_exit_lp_mode, active mcu fail

cycling every ~9 s, ~22 cycles in 10 minutes. Three pieces:

  1. enter_lp_mode timed out (firmware indication lost). With c6.1,
     chip_pm_state is now UNKNOWN.
  2. lock_device fires exit_lp_mode.
  3. gpio_wake hits "bit already set" because device_enter_lp_mode
     was skipped when the indication timed out, so gpio_sleep was
     never called - the bit reflects driver intent, not chip state.
     gpio_wake silently no-ops (no GPIO edge), bit stays set.
  4. sbus_active spends 200 x 2 ms looking for MCU_WAKEUP_READY that
     never comes (firmware was never told to wake), then fails.
  5. Driver continues to wsm_set_operational_mode against the wedged
     bus, compounding the failure.

This patch's three moves:

  * bes2600_pwr_device_exit_lp_mode() reads chip_pm_state at entry.
    On BES2600_CHIP_PM_ACTIVE, log at devel level and return without
    touching gpio_wake / sbus_active / WSM. The chip is in the state
    we want; the handshake exists only to drive a transition.

  * On BES2600_CHIP_PM_LP or BES2600_CHIP_PM_UNKNOWN, run the wake
    handshake as before, but on sbus_active() failure: set
    chip_pm_state = UNKNOWN, log once at err level, and bail out.
    Do NOT call wsm_set_operational_mode over a wedged bus - it
    would just emit a second error and leave the chip in an even
    less defined state.

  * bes2600_gpio_wakeup_mcu() / bes2600_gpio_allow_mcu_sleep():
    demote "repeat set/clear gpio_wake_flag" from bes_err to
    bes_devel. Multi-subsystem wake-hold (e.g. WIFI + BT both want
    MCU awake) is the steady-state case, and the symmetric clear
    while bit-already-clear is racy bookkeeping rather than a
    hardware error. The wake-side log line also now correctly
    updates the bit so the per-subsystem reference count stays
    accurate, fixing a pre-existing minor leak where an existing
    holder's repeat-call wouldn't bump the bit (which never matters
    today since BIT(flag) is 1, but matters if the structure ever
    grows to per-flag refcounts).

Net effect on the cycle:

  * If chip is genuinely ACTIVE (chip_pm_state == ACTIVE), wake skips
    cleanly. Storm goes silent.
  * If chip is genuinely LP, behaviour is unchanged.
  * If chip is UNKNOWN (post-timeout state), one wake attempt is
    made; on failure, state stays UNKNOWN and we don't emit a
    second cascade error per attempt. Repeated UNKNOWN with failed
    wake will eventually be picked up by the LMAC active-monitor
    and escalated to mmc_hw_reset (c5.2).

No new locks, no new state. Only consumption of the chip_pm_state
field added in the prerequisite patch.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
2026-04-28 19:51:17 +02:00
test0r 0dde479994 bes2600: recover wedged firmware via mmc_hw_reset on link break
When the LMAC active monitor detects 'link break between lmac and host'
(the hw_buf_used==pending watchdog in bes2600_bh_lmac_active_monitor),
bes2600_chrdev_wifi_force_close(hw_priv, true) is invoked to tear the
device down and prepare for a fresh probe. On the wifi_force_close_work
side this calls bes2600_chrdev_do_system_close() which dispatches
sbus_ops->power_switch(0).

On PineTab2 (RK3566 + BES2600WM over SDIO) this recovery path is a
no-op:

  * bes2600_sdio_power_down() writes a SYSTEM_CLOSE host-int message,
    clears MMC_CAP_NONREMOVABLE, and schedules sdio_scan_work, which is
    the literal one-line stub bes_warn("...this function does
    nothing\n").
  * bes2600_sdio_on() (the eventual power_switch(1) counterpart)
    toggles pdata->powerup, which is NULL on PineTab2 because the
    wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 device
    tree node (see arch/arm64/boot/dts/rockchip/rk3566-pinetab2.dtsi:
    'The reset pin is claimed by sdio_mmcseq, It is better to move it
    to U-Boot so the OS can use it.').

Net result: the chip is never reset. The function drivers are not
removed (the SDIO core has no signal that the card is gone), the
firmware stays wedged, and a subsequent rmmod bes2600 leaves the SDIO
function in a half-torn-down state. modprobe bes2600 then fails with
'probe with driver bes2600_wlan failed with error -123' (-ENOMEDIUM)
on both functions (:1 wifi, :2 BT-companion) until a full system
reboot.

Observed on PineTab2 (linux-pinetab2 6.19.10-danctnix1-1) after ~150
minutes of background-scan rejects (wsm_generic_confirm 0x0007,
[SCAN] Scan failed (-22)) accumulating until the LMAC stopped
acknowledging TX buffers (hw_buf_used:24 pending:24). Reproducible
under sustained scan pressure.

Add a sbus operation bus_reset() that the recovery path can call when
power_switch() has no effective chip-reset signal of its own. Provide
an SDIO implementation that calls mmc_hw_reset(self->func->card),
which on a multi-function SDIO card (PineTab2 binds func 1 for WLAN
and func 2 for the BT-companion path) takes the remove-and-rescan
path: mmc_sdio_hw_reset() marks the card removed and schedules
mmc_rescan, which tears down the bound function drivers and re-detects
the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
With a single function probed it instead invokes mmc_power_cycle()
directly, which on PineTab2 toggles the wifi-reset GPIO via
sdio_pwrseq.

Add bes2600_chrdev_do_bus_reset() as the chrdev-side helper. It
invokes the bus op and then waits on probe_done_wq for the SDIO
remove() callback to clear sbus_priv, mirroring the wait pattern
already used by bes2600_chrdev_do_system_close() so that a subsequent
bes2600_switch_wifi(true) sees a clean state and can wait on the
fresh probe.

Wire it into bes2600_chrdev_wifi_force_close_work(): when halt_dev is
set (the hard-exception path used by both
bes2600_bh_lmac_active_monitor and bes2600_bh_mcu_active_monitor) and
the underlying bus implements bus_reset, take the new recovery path;
otherwise fall back to the legacy power_switch(0) sequence so this
patch is a no-op on USB or any other future bus that does not provide
bus_reset.

mmc_hw_reset() is exported by the MMC core and is the canonical
recovery primitive; calling it without holding the SDIO host claim is
correct because the multi-func remove-and-rescan path acquires the
host claim via the mmc workqueue, and the single-func mmc_power_cycle
path does not require the host claim.

No DT change is required: this works against the existing PineTab2
DTS, where the wifi-reset GPIO and the optional sdio_pwrkey GPIO (on
v2.0 boards) are both already configured as MMC pwrseq resets.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
2026-04-28 19:51:17 +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 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
Julian ba20341e70 Upload
Source: https://github.com/cringeops/bes2600
Source: https://github.com/cringeops/bes2600/pull/14
Source: https://github.com/cringeops/bes2600/pull/17
Source: https://github.com/cringeops/bes2600/pull/20
2025-09-17 16:35:45 +02:00