The Pine64 PineTab2 ships with a Bestechnic BES2600WM combo chip for Wi-Fi and Bluetooth. The Linux driver for it lives at drivers/staging/bes2600/ in the DanctNIX linux-pinetab2 fork, and as a DKMS package in Mobian’s bes2600-dkms. Both descend from the same out-of-tree drop Bestechnic published in 2022 and which, to date, has not been upstreamed.
The driver works. It mostly works. It works in the way that any 2010s-vintage out-of-tree SDIO Wi-Fi driver works: well enough for the chip to associate, exchange frames, and reach the internet, and badly enough that you eventually start reading the source. What follows is a tour of the patch series that came out of that reading, in the rough order the issues surfaced. Patches live in marfrit/kernel-agent/patches/driver/bes2600/. Each subdirectory ships in two flavors: a -danctnix suffix for the in-tree build and a non-suffixed one for the Mobian DKMS variant, because the two trees disagree about timer APIs and a few function signatures.
Stage 1: the random disconnect
The entry point was a Wi-Fi connection that would simply die every once in a while and not come back without a rmmod bes2600 && modprobe bes2600. Logs showed bes2600_bh_lmac_active_monitor declaring „link break between lmac and host“ — the firmware watchdog noticed that the LMAC had stopped acknowledging host pointers. So far, so reasonable. What it then did about that was less reasonable.
The recovery path called bes2600_chrdev_wifi_force_close(), which scheduled sdio_scan_work, which on the PineTab2 is a literal one-line stub:
bes_warn("...this function does nothing\n");
The companion bes2600_sdio_on() path then toggled pdata->powerup, which is NULL on this board because the Wi-Fi reset GPIO is owned by sdio_pwrseq and not by the bes2600 device-tree node. (The DT file’s own comment notes that the pin „is claimed by sdio_mmcseq, It is better to move it to U-Boot so the OS can use it.“ Future work, presumably.)
Net result: the chip is never actually reset, the SDIO core never finds out the card is gone, and a subsequent rmmod leaves the SDIO function objects dangling. The recovery handler was, in effect, a no-op with logging.
lmac-recover-via-mmc-hw-reset calls mmc_hw_reset() on the SDIO function instead. The MMC core re-probes, the driver re-binds, and the firmware re-loads. Disconnect events that previously required a manual modprobe now self-heal in about three seconds.
Stage 2: the scan-defer logic that didn’t defer
While instrumenting the disconnect, a second symptom appeared: bursts of failed scans during roaming. The firmware would WSM_REQUIRED_CONFIRM on a scan, the driver would log a warning, and mac80211 would retry roughly every 12 seconds. The kernel had a backoff guard in place — BES2600_SCAN_BACKOFF_JIFFIES = 10 * HZ — but the retry cadence was just outside that window, so every retry slipped through, the firmware rejected again, and the cycle continued until mac80211 gave up and sent DEAUTH_LEAVING reason=3.
scan-defer-on-reject softens the WARN and introduces a reject counter so that the third in-window reject trips a real defer. scan-defer-backoff-tune widens the window from 10 s to 30 s — enough to catch the 12 s mac80211 cadence — and decays the counter on quiet periods so a slow trickle of legitimate failures doesn’t permanently lock out scans.
These two together turned roam-burst disconnects from „user-visible WiFi outage“ into „two log lines and a slightly later association.“
Stage 3: the DMA out-of-bounds read
With KFENCE enabled in the kernel config, a different symptom showed up: a clean panic on heavy TCP transfer.
BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic
Out-of-bounds read at ... (704B right of kfence-#...):
swiotlb_tbl_map_single
...
bes_sdio_memcpy_to_io_helper [bes2600]
sdio_tx_work [bes2600]
The TX path rounds the DMA transfer length up to the host’s block size and passes the rounded length to sg_set_buf(). The tx_buffer->buf pointer, however, aliases an skb whose actual allocation matches the unrounded length. The DMA engine then reads up to one block past the end of the buffer. On boards without KFENCE this is invisible — the read is harmless padding — but the bug is real and would trip any future sanitizer. tx-sdio-dma-oob routes oversized transfers through a bounce buffer of the rounded length.
Stage 4: the power-management arc
Power management on the bes2600 is a small theatre of state machines that occasionally lose track of each other. Five patches address overlapping symptoms:
pm-state-resync— fixes lost wake events when the firmware ACK arrives out of order with the host state update.pm-timeout-silence— replaces a deeply nested 30-second timeout WARN with a single rate-limited info-level log line. The timeout was a known, recoverable condition; logging it asWARN_ONwas just generating noise.pm-wake-consume-state— ensures the wake-handler actually consumes the pending wake state instead of leaving it set, which previously caused phantom re-wakes.pm-gate-on-handshake— gates LP-mode entry on a successful per-device handshake rather than firing it unconditionally.pm-detect-firmware-unsupported— detects when the loaded firmware does not actually implement the PM protocol and disables PM gracefully rather than wedging.
None of these are individually exciting; together they cut the PM-related kernel log noise by about 90% and stopped two specific suspend/resume hangs.
Stage 5: the factory-calibration cleanup
The driver loads per-device calibration data from a 30-field text file. Upstream-wise this is also where things get embarrassing: the original code path was filp_open() plus kernel_read() against a hard-coded /lib/firmware/bes2600_factory.txt, which is a kernel-mainline anti-pattern dating back to before request_firmware() was ubiquitous. Worse, the path was being constructed against NULL device contexts, which generated the boot-time spam (NULL device *): read and check /lib/firmware/bes2600_factory.txt error on every probe.
The factory-series, factory-thread-dev, and factory-drop-kernel-write patches route the read through request_firmware(), rename the file to match firmware-class conventions (bes2600/bes2600_factory.txt), and thread a real struct device * through so the firmware loader has somewhere to log. A companion patch flips STANDARD_FACTORY_EFUSE_FLAG from default-on to default-off, because the file that the PineTab2 actually ships has 30 fields and the parser was expecting 31 (with the missing ##select_efuse_flag section generating a parse error on every load — which the driver, naturally, swallowed silently).
drop-dpd-file-paths and drop-orphan-file-io remove the remaining filp_open() call sites elsewhere in the tree.
Stage 6: the housekeeping
Two upstreaming-prep changes round out the series:
remove-chardev-user-interface— drops the/dev/bes2600character device. It exposed a small ad-hoc ioctl surface that no userspace ever stabilised against, and which would block the driver from leavingstaging/. Anyone who needs the equivalent can usenl80211debug attributes.enable-testmode— setsCONFIG_BES2600_TESTMODE=yby default for in-tree builds, because the test hooks were already compiled in for DKMS but not for danctnix.
Plus one trivial debian-copyright-fsf-address update so lintian stops complaining, which matters only if you build the Debian DKMS package.
Genealogy footnote
The bes2600 driver is, at the source level, a fork of the ST-Ericsson CW1200 (drivers/net/wireless/st/cw1200/): same author (Dmitry Tarnyagin), same WSM host/firmware protocol, same SDIO bus backend, and surviving Kconfig markers (CONFIG_BES2600_USE_STE_EXTENSIONS, CONFIG_BES2600_WSM_DEBUG) that read as ST-Ericsson archaeology. ST-Ericsson wound down in 2013; Bestechnic was founded in 2015. The IP lineage is presumably a license or an asset purchase, but no linux-wireless RFC has ever connected the two trees, and the bes2600 maintainers have not commented on it.
What was left out: firmware reverse-engineering
Everything above is host-side. The bes2600 firmware itself — the blob the host downloads onto the chip’s internal MCU at probe time — is opaque, undocumented, and almost certainly the source of more than one of the symptoms the host-side patches work around. (The „link break between lmac and host“ condition, for instance, would be far more cheaply diagnosed if the LMAC side were inspectable.)
Reverse-engineering it is a legitimate next step. The firmware images are roughly 600 KB, fit on a single SDIO download, and the chip’s MCU is documented to be a single Cortex-M class core. None of that work has been done here, on the grounds that the host-side cleanup is already a self-contained body of work and the firmware RE would dwarf it by an order of magnitude. Parked for a future hacking weekend that we will all enthusiastically agree should happen sooner rather than later, and then will not happen for another year.
In the meantime the host-side patches are upstreamable, individually testable, and bisectable. The full series applies cleanly against linux-pinetab2 6.19.10-danctnix1-1 and a representative subset has been validated on Mobian DKMS via the -danctnix/non-suffixed variants. Mirrored in marfrit/kernel-agent; the original umbrella issue thread is at marfrit/besser for anyone who enjoys reading commits in reverse.