39 Commits

Author SHA1 Message Date
marfrit 84cdb5b4ee Merge pull request 'fleet/ohm: pkgrel=6 — per-series converged (closes ka#29, includes besser#22 / #25 fixes)' (#37) from noether/ohm-pkgrel6-perseries-converged into main
Reviewed-on: marfrit/kernel-agent#37
2026-05-21 10:35:31 +00:00
test0r 3d15c5367d fleet/ohm: pkgrel=6 — per-series converged with tx-sdio-dma-oob + join-confirm-reset
Two additions to fleet/ohm.yaml's includes for the bes2600 driver scope:

1. driver/bes2600/tx-sdio-dma-oob-danctnix/ — already on disk from
   ka#17 but not previously included. The cumulative-c5x-danctnix
   shipped in pkgrel=3 did NOT have this fix; pkgrel=4 per-series
   regressed because the staging-prep series was excluded. KFENCE
   caught the OOB during pkgrel=4 soak; pkgrel=5 included it.

2. driver/bes2600/join-confirm-reset-danctnix/ — NEW scope.
   cw1200 ancestor port (sta.c:1339-1344) with bes2600-specific
   PASSIVE-gate compensation in bes2600_unjoin_work. Closes
   besser#25. Verified pkgrel=6 srcversion 0E16463F: cascade gone,
   periodic ~600ms latency jitter also gone (same root cause).

Status note: per-series reconstruction is now converged. The
cumulative-c5x-danctnix entry is left as historical fallback;
ka#29's blocker (per-series mirrors not applying cleanly) was
resolved by manually reconstructing the per-series in
marfrit/bes2600-dkms bes2600/join-confirm-failure-reset (top
commit 3d833f8).

Build still hand-managed via boltzmann:~/src/besser/marfrit-besser/
danctnix-besser-pkgbuild/kernel/PKGBUILD; ka-promote / ka-build
template rendering still pending per the original TODOs.

Signed-off-by: Claude (noether) <claude@reauktion.de>
2026-05-21 12:23:47 +02:00
claude-noether 588350c4da Revert "Merge pull request 'patches/driver/bes2600/*-danctnix: reconstruct from cleanups (closes #29)' (#33) from claude-noether/kernel-agent:noether/kernel-agent-29-per-series-reconstruct into main"
This reverts commit 38fd672940, reversing
changes made to 443f5e992e.
2026-05-20 11:05:58 +02:00
marfrit cc6f2378ab Merge pull request 'ka-build: arch makepkg wrapper + sign + publish (closes #34)' (#35) from noether/ka-build-impl into main
Reviewed-on: marfrit/kernel-agent#35
2026-05-19 07:26:58 +00:00
test0r dd631fd3c7 ka-build: arch makepkg wrapper + sign + publish (closes #34)
Phase-1 ka-build per umbrella #21:

1. Read manifest.lock from ka-promote output. Refuse if missing.
2. Verify each PKGBUILD-side patch in marfrit-packages still matches
   the kernel-agent-side patch by sha256 (manifest.lock is authoritative).
3. ssh-dispatch makepkg --syncdeps --noconfirm --cleanbuild to the
   manifest's build_host.primary. Native build only — no distcc
   (feedback_kernel_agent_no_distcc).
4. Pull the resulting *.pkg.tar.zst back; scp to hertz and run
   /opt/herding/bin/marfrit-publish-arch aarch64 <pkg>.
5. Append a `build:` block to manifest.lock with built_at, host,
   per-package b2sum + size.

Flags: --dry-run (stop before makepkg), --skip-publish (build only),
--packages-repo (override default ~/src/marfrit-packages).

Out of scope (separate followups):
- Debian .deb path
- PKGBUILD template *generation* (current PKGBUILDs are hand-authored;
  ka-build verifies + stamps, doesn't author)
- distcc routing (explicitly NOT in kernel-agent flow)
- ka-build --validate-against (apply-check harness)

Tests: 6/6 pass (arg parsing, missing manifest.lock, missing PKGBUILD,
patch drift via sha256 mismatch, happy-path dry-run on fresnel).
Full-build path manually exercisable; CI integration deferred until
the sandbox supports mock build-host + mock marfrit-publish-arch.
2026-05-19 09:24:23 +02:00
marfrit 38fd672940 Merge pull request 'patches/driver/bes2600/*-danctnix: reconstruct from cleanups (closes #29)' (#33) from claude-noether/kernel-agent:noether/kernel-agent-29-per-series-reconstruct into main
Reviewed-on: marfrit/kernel-agent#33
2026-05-19 04:58:54 +00:00
claude-noether 8b356aa11f patches/driver/bes2600/*-danctnix: reconstruct from cleanups (closes #29)
Replaces the 13 broken DKMS-path -danctnix mirrors from PR #17 + adds
9 new series-dirs for the c-stack patches that were never split
(Patches A/B/C-v3/F/D/E/C2/G/H) + retires the cumulative-c5x-danctnix
single-file interim from fleet/ohm.yaml.

Mechanism:
  cd marfrit/bes2600-dkms-mobian
  git format-patch fe73571..cleanups --no-merges -o /tmp/cleanups/
  git format-patch cleanups..bes2600/bh-c-fossil-cleanup --no-merges -o /tmp/h/
  for each commit: route to series-dir, sed-rewrite
                   a/bes2600/foo.c -> a/drivers/staging/bes2600/foo.c

The 29 cleanups commits + 1 Patch H commit map to 25 series-dirs (a
few series-dirs get multiple commits: lmac-recover gets c5.2 + c5.2.1
as 0001+0002; cw1200-fix-backports gets F3+F2+F1 as 0001-0003;
factory-series gets request_firmware + STANDARD_FACTORY_EFUSE_FLAG
as 0001+0002).

fleet/ohm.yaml apply order matches cleanups commit chronology, which
is what produced the working c5x interim. cumulative.patch from
ka-promote ohm now has 32 resolved patches (29 cleanups + 1 Patch H
+ scan-filter-5ghz + xor-neon SCS + besser#18-fix), 276 079 bytes,
b2sum 7418db5ddf8fe938b130bc9d0e9f7dc9060f3a13703cd50757835ac43140a13...

Apply order in cleanups + bh-c-fossil-cleanup:
  1   factory-series                       (c1 + factory-no-efuse-flag)
  3   factory-thread-dev
  4   pm-gate-on-handshake
  5   remove-chardev-user-interface
  6   enable-testmode
  7   tx-sdio-dma-oob-danctnix             (was 'staging-prep-series')
  8   factory-drop-kernel-write-danctnix
  9   drop-dpd-file-paths-danctnix
  10  drop-orphan-file-io-danctnix
  11  pm-timeout-silence-danctnix
  12  scan-defer-on-reject-danctnix        (c5.1)
  13  scan-defer-backoff-tune-danctnix     (c5.1.1)
  14  lmac-recover-via-mmc-hw-reset-danctnix  (c5.2 + c5.2.1)
  16  pm-state-resync-danctnix             (c6.1)
  17  pm-wake-consume-state-danctnix       (c6.2)
  18  pm-detect-firmware-unsupported-danctnix (c7)
  19  decrypt-storm-fast-recover-danctnix  (Patch A)
  20  connection-loss-fast-recover-danctnix (Patch B)
  21  cw1200-fix-backports-danctnix        (Patches F3 + F2 + F1)
  24  sdio-rx-no-relay-danctnix            (Patch C v3)
  25  license-spdx-restore-attribution-danctnix (Patch G)
  26  ba-lock-atomic-danctnix              (Patch D)
  27  ps-state-lock-skip-pm-disabled-danctnix (Patch E)
  28  rx-list-batch-delivery-danctnix      (Patch C2)
  29  bh-c-fossil-cleanup-danctnix         (Patch H)
  30  scan-filter-5ghz-danctnix            (besser#1)
  31  arch/arm64/xor-neon-...              (GCC 15 SCS)
  32  queue-pending-record-lock-bh-danctnix (besser#18)

Verification: pkgrel=6 build from this manifest in progress; if
srcversion == 26B0003FE9F2B05DCE838C4 (pkgrel=5's), source-tree is
byte-equivalent to the c5x interim + scan-filter + besser#18 stack
that's currently running on ohm.

Refs: #17 (the broken mirror), #28 (the interim PR that landed
cumulative-c5x), #31 (ka-promote trailer normalisation followup).
2026-05-19 06:41:37 +02:00
marfrit 443f5e992e Merge pull request 'ka-promote: auto-normalise git format-patch trailers (closes #31)' (#32) from noether/ka-promote-normalise-trailers into main
Reviewed-on: marfrit/kernel-agent#32
2026-05-19 04:33:03 +00:00
test0r 2f119a3fb7 ka-promote: auto-normalise git format-patch trailers (closes #31)
write_cumulative() now strips any "-- \n<MAJOR>.<MINOR>(.<PATCH>)?\n" sentinel
from each input patch and emits a single canonical separator between, but not
after, concatenated patches. Source patches in patches/<scope>/ can therefore
keep their original git format-patch shape regardless of their position in
fleet/<host>.yaml — the brittle "trailer flip-flop on include reorder" mode
from PR #28 (commits 84734ba ↔ ceec602) is gone.

Tests:
- new unit covers strip_trailer + write_cumulative shape with mixed
  trailer states + asserts no orphan trailer leaks at EOF
- fresnel parity b2sum re-recorded after the shape change
  (4d9d93c6... -> 9c21751c...) — the cumulative is byte-identical
  modulo per-patch trailer normalisation; git apply --check on the
  v7.0 baseline still passes
- existing series-dir, bad-include, missing-patch, duplicate-include
  rejections unchanged
2026-05-19 06:30:38 +02:00
marfrit 7a86ebb587 Merge pull request 'fleet/ohm: switch bes2600 to cumulative-c5x interim + close besser#1 + GCC 15 SCS fix (closes #5 partial)' (#28) from claude-noether/kernel-agent:noether/migrate-pinetab2-pkg-and-patches into main
Reviewed-on: marfrit/kernel-agent#28
2026-05-18 20:56:41 +00:00
claude-noether 731e98e079 fleet/ohm.yaml: fix arch/arm64 include path after merge rename
The merge commit renamed arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/
to arch/arm64/scs-arm-neon-build-fix/ (= main's canonical name) but the
include reference in ohm.yaml didn't get updated atomically.

Update the include path to match the renamed dir; ka-promote would have
exit-2'd on this manifest otherwise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:52:59 +00:00
claude-noether bae99da612 Merge upstream/main into noether/migrate-pinetab2-pkg-and-patches
Resolves the conflict-window between the PR's "switch bes2600 to
cumulative-c5x interim" intent and main's incremental per-patch
evolution.

Resolution per discussion:
- fleet/ohm.yaml: keep PR's cumulative-c5x layout (replaces per-patch
  list) but rename arch/arm64 include to main's canonical
  'scs-arm-neon-build-fix/' (branch's renamed dir dropped).
- patches/driver/bes2600/queue-pending-record-lock-bh-danctnix/
  0001-*.patch: take main's (= identical content + the git-format-patch
  trailer that the branch's earlier add omitted).
- patches/driver/bes2600/scan-filter-5ghz-danctnix/: drop branch's
  older '0001-...-allow-single-channel.patch' variant; keep main's
  newer '0001-...-filter-at-driver-boundary.patch' to avoid 0001-*
  collisions in ka-promote's series-dir resolver.
- patches/arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/:
  dropped (= duplicate of main's scs-arm-neon-build-fix/).
- All other main additions (rkvdec vp9 patches, scan-filter-5ghz/,
  fleet/ampere.yaml updates) auto-merged cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:52:27 +00:00
marfrit 3d10a2c21a Merge pull request 'patches/driver/bes2600/queue-pending-record-lock-bh-danctnix: mirror besser#18 fix' (#30) from claude-noether/kernel-agent:noether/bes2600-pending-record-lock-bh into main
Reviewed-on: marfrit/kernel-agent#30
2026-05-18 19:18:51 +00:00
claude-noether 3ee0ef7d86 patches/arch/arm64/xor-neon-...: correct @@ hunk counts (overcorrected in a840f76)
a840f76 changed @@ from -9,6 +9,10 to -9,7 +9,12 but overshot by 1.
Actual hunk is 6 context + 5 add = -9,6 +9,11. Wrong counts were
silently masked in pkgrel=4 build #4 by the trailer-stripped EOF
letting patch fuzz recover. pkgrel=5 with besser#18 after SCS
exposes it as 'malformed patch at line N: 2.54.0'.

Cumulative b2sum: ceec602afa8574c74354... -> 50397711a6a3ba522283...
Size unchanged 162 716.
2026-05-18 19:17:22 +02:00
claude-noether 878e86f103 patches/arch/arm64/xor-neon-...: restore trailer (SCS is no longer last)
When the SCS patch was the LAST patch in ohm's cumulative, the
trailing '-- \n2.54.0\n' git-format-patch sentinel was an orphan that
patch(1) read as a malformed header — fixed in 84734ba by stripping
the trailer.

Now besser#18 (queue-pending-record-lock-bh-danctnix) is added at the
end of ohm.yaml's includes. SCS is no longer last. Without its
trailer to mark end-of-patch, patch(1) reads straight into besser#18's
'From d95453c... Mon Sep 17 00:00:00 2001' line and errors as
'malformed patch at line N: From ...'.

Restoring the trailer makes the separator unambiguous again.

Cumulative b2sum: 0eb091ddaba4a8f1c3c2a78... -> ceec602afa8574c74354...
Size: 162 704 -> 162 716 (+12 = the trailer bytes).

This rule — 'only the LAST patch must lack a trailer; all others must
keep theirs' — is sensitive to ohm.yaml include ordering, which is
brittle. Filed as a kernel-agent followup: ka-promote should rewrite
trailers automatically (always add to non-last, always strip from
last) so source patches don't need to be ordering-aware.
2026-05-18 18:15:45 +02:00
claude-noether 4d98a8169d fleet/ohm + patches/driver/bes2600/queue-pending-record-lock-bh-danctnix: bundle besser#18 fix into the migration
Pulls the besser#18 lockdep fix (originally on
noether/bes2600-pending-record-lock-bh / PR #30) into this PR so the
ohm migration ships a single self-consistent pkgrel that contains all
three goal components: kernel-agent flow + Patch I + besser#18 fix
(plus the GCC 15 SCS Makefile workaround, no-op while SCS=n).

ohm.yaml includes now resolve to 4 patches:
  1. driver/bes2600/cumulative-c5x-danctnix/             (148 149 B)
  2. driver/bes2600/scan-filter-5ghz-danctnix/           (  7 735 B)
  3. arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/ (1 562 B)
  4. driver/bes2600/queue-pending-record-lock-bh-danctnix/  (5 258 B)
  ----
  cumulative.patch                                       (162 704 B)
  b2sum 0eb091ddaba4a8f1c3c2a78eb8c621cdc6e6dfed6c43f7dac03e508a05b...

Trailer-strip applied to the besser#18 patch source for the same
reason as the SCS patch — it's now the last in the concatenated
cumulative, and patch(1) errors on the orphan '-- \n2.54.0\n' EOF
sentinel. Same gotcha documented in 84734ba.

PR #30 (the standalone besser#18 mirror PR) becomes superfluous
once this lands; close it as 'bundled into #28'.
2026-05-18 18:01:41 +02:00
claude-noether 84734ba527 patches/arch/arm64/xor-neon-...: strip trailing git-format-patch sentinel
The '-- \n2.54.0\n\n' trailer added in 989b884 was wrong. The
underlying problem was the malformed @@ hunk counts (off by 1 in
both old and new), fixed in a840f76. With correct @@ counts, patch(1)
processes the hunk fully and then sees the orphan trailer at EOF —
which it tries to parse as the start of a new patch header
('malformed patch at line N: 2.54.0').

The original (no-trailer) shape works correctly in the concatenated
cumulative as long as the @@ counts are right. Removing the trailer
brings the file back to the original 1562-byte size and the
cumulative b2sum to 334c37b5d37067982bd9... (size unchanged 157 458 ->
157 446 since the 12 byte trailer is gone).

Lesson for ka-promote: when concatenating patches as a stream for
patch(1), the LAST patch must not carry a trailing '-- \n<version>\n'
sentinel — the previous patches' sentinels are fine because they are
followed by 'From <sha>' headers that patch(1) recognises as the next
patch boundary. Documented in series-dir README as a gotcha.
2026-05-18 17:00:11 +02:00
claude-noether c9e9ad973c patches/driver/bes2600/queue-pending-record-lock-bh-danctnix: mirror besser#18 fix from bes2600-dkms
Single-patch series-dir, mirror of the Markus-authored commit d95453c
on marfrit/bes2600-dkms branch bes2600/queue-pending-record-lock-bh-fix
(PR #11). Paths rewritten from DKMS-style (bes2600/foo.c) to in-tree
staging (drivers/staging/bes2600/foo.c) via sed -- this is the
in-tree variant.

Fix: convert plain spin_lock(&pending_record_lock) to spin_lock_bh()
at the 5 sites where it's taken in non-BH-disabled contexts
(queue.c:832/839/844, tx_loop.c:112/114). queue.c:289/295 stays as
plain spin_lock because BH is already disabled by the outer
queue->lock_bh acquired at queue.c:285.

Eliminates the SOFTIRQ-safe -> SOFTIRQ-unsafe lockdep warning
reported in besser#18 (PROVE_LOCKING-only -- non-fatal on production
builds where lockdep is off, but real AB-BA window between
bes2600_join_work workqueue context and bes2600_tx softirq context).

This commit does NOT add the include to fleet/ohm.yaml. The patch
will be wired into ohm's manifest in a follow-up commit (or this
branch's PR can extend with the ohm.yaml change once the migration
PR #28 lands and the bes2600-dkms PR #11 is reviewed).

Closes: besser#18
Refs: marfrit/bes2600-dkms #11 (source-of-truth PR)
2026-05-18 16:59:28 +02:00
claude-noether a840f76907 patches/arch/arm64/xor-neon-...: fix malformed @@ hunk counts
The hunk header @@ -9,6 +9,10 @@ understated both old (actual 7) and
new (actual 12) line counts by 1. patch(1) standalone tolerates this
via fuzz, but in the concatenated cumulative the wrong counts cause
patch to mis-judge the hunk boundary and read the trailing context
line ('lib-...uaccess_flushcache.o') as the start of a new patch
header — 'malformed patch at line 4526'.

Cumulative b2sum: bd42cd39106298879eeb... -> ad9e2cb533957f218058...
(size unchanged at 157 458; only the @@ counts in the SCS patch
differ)
2026-05-18 16:58:19 +02:00
claude-noether 989b8842fb patches/arch/arm64/xor-neon-...: append git-format-patch trailer
The SCS-build-fix patch was missing the standard '-- \n2.54.0\n'
trailer that git format-patch emits between patches. Without it,
BSD-flavour patch(1) in makepkg's prepare() reads the trailing context
line of the @@ hunk as the start of a new patch header and dies with
'malformed patch at line N'. Affects builds where ka-promote
concatenates this series with any others. Reproduced 2026-05-18 on
the first attempted ohm pkgrel=4 build.

Cumulative b2sum changes accordingly:
  a807297b25be... -> bd42cd39106298879eeb...
(size 157446 -> 157458; 12 bytes for the trailer)
2026-05-18 16:52:46 +02:00
claude-noether f203b70f4f fleet/ohm: switch bes2600 driver scope to cumulative-c5x-danctnix interim (closes #5 partial migration)
Audit during ohm pkgrel=4 migration found the per-series -danctnix
mirrors merged in #17 do NOT apply against the linux-pinetab2 baseline:
all 17 of them use DKMS-style root paths (bes2600/foo.c) rather than
in-tree staging paths (drivers/staging/bes2600/foo.c), and at least one
has a corrupted mixed-prefix header
(a/drivers/staging/bes2600/...  b/bes2600/...).

ka-promote ohm with those includes produced a 172 644-byte cumulative
touching 27 file paths, of which 11 are bogus. The hand-curated
0001-bes2600-besser-cumulative-series.patch from the working
danctnix-besser-pkgbuild flow on boltzmann (148 149 bytes, 48 in-tree
staging files) is what pkgrel=3 actually builds with.

Until the per-series mirrors are reconstructed (followup issue to be
opened separately), the bes2600 driver scope is satisfied here by
staging that hand-curated cumulative as a single-file series-dir
patches/driver/bes2600/cumulative-c5x-danctnix/. ohm.yaml drops the
broken per-series includes in favour of:

  - driver/bes2600/cumulative-c5x-danctnix/
  - driver/bes2600/scan-filter-5ghz-danctnix/      (closes besser#1)
  - arch/arm64/xor-neon-ffixed-x18-scs-build-fix-danctnix/

ka-promote ohm now produces a self-consistent 157 446-byte cumulative
(148 149 + 7 735 + 1 562 = exact byte arithmetic) with b2sum
a807297b25be... which is what the new
marfrit-packages/arch/linux-pinetab2-danctnix-besser PKGBUILD pkgrel=4
pins.

Also fixes fleet/ohm.yaml YAML parse error: bar5_burn_in had a scalar
value followed by a sub-list, which ka-promote (PyYAML) refused to
parse. The whole manifest had never parsed cleanly since #18 landed.

Refs: #5 (migrate PKGBUILD), #2 (mirror besser series — needs per-series
rewrite followup), besser#1 (Patch I).
2026-05-18 16:50:41 +02:00
marfrit a254b6f0bb Merge pull request 'bes2600/scan-filter-5ghz: refine to allow targeted single-channel scans' (#26) from noether/scan-filter-5ghz-refine-targeted-allowed into main
Reviewed-on: marfrit/kernel-agent#26
2026-05-18 14:06:53 +00:00
test0r 43c8f0cba8 patches/driver/bes2600: scan-filter-5ghz refinement — allow targeted single-channel scans
Updates both flavors with the n_channels > 1 refinement (was > 0).
The original guard refused ALL 5 GHz scans which broke 5 GHz
association via NM band=a profiles (NM iterates freq_list per
channel, single-channel scans were also refused).

Tightened: only multi-channel 5 GHz scans (the per-band-sweep
that triggers the firmware storm) are refused; single-channel
5 GHz scans pass through so NM/wpa_supplicant can find and
associate to 5 GHz BSSes.

Verified on ohm with locally-built pkgrel=3 (srcversion
BEB625FA7443171EA8D55F7): associated to 5 GHz BSSID
c0:25:06:e6:5b:33 on 5240 MHz / ch.48, 150 Mbit/s MCS 7
40MHz short-GI; Pattern A still 0 since boot.

Patch file is now a concatenation of two commits from
marfrit/bes2600-dkms bes2600/scan-filter-5ghz branch:
  093a503 (original Patch I)
  8cd10f4 (this refinement)
patch -Np1 applies them sequentially -> net effect = single squash.

Refs: besser#1 (closed), PKGBUILD update at marfrit/besser
claude-noether-14 commit 122582e (pkgrel=3 deployed to ohm
on 2026-05-18 same session).
2026-05-18 15:57:20 +02:00
marfrit 42b0c5042a Merge pull request 'fleet/ohm: import Patch I (5GHz scan filter, closes besser#1) + arm64 SCS build-fix' (#25) from noether/import-scan-filter-5ghz-and-scs-fix into main
Reviewed-on: marfrit/kernel-agent#25
2026-05-18 13:34:55 +00:00
test0r 4c80458d1f fleet/ohm: import Patch I (5GHz scan filter) + arm64 SCS build-fix
Patch I closes besser#1 — the wsm_generic_confirm 0x0007 dmesg storm.
One-line guard in bes2600_hw_scan() refuses the 5 GHz iteration of
mac80211's per-band hw_scan loop with -EOPNOTSUPP, so the firmware
never sees the scan request that would be rejected with status 2 →
-EINVAL cascade.  Phase 7 verified 2026-05-18 on ohm running pkgrel=2:
Pattern A 14.3/h → 0/h over 30-min window, no WARN/BUG, single-band
2.4 GHz scans still return BSSes cleanly.

Two flavors imported (scan-filter-5ghz and scan-filter-5ghz-danctnix)
matching the convention of other bes2600 series — the code path
doesn't touch timer APIs so the two are byte-identical for now;
flavor separation is kept to preserve consistency in ohm.yaml.

The arm64 scs-arm-neon-build-fix series is a build-environment
workaround: GCC 15.2.1 strictly validates that -fsanitize=shadow-
call-stack requires -ffixed-x18, and arm_neon.h's #pragma target/
push/pop blocks lose x18 fixing inside the wrapped section.  The
Makefile tweak re-adds -ffixed-x18 explicitly for xor-neon.o.  It's
a no-op when SCS is off (current pkgrel=2 ohm config) and unblocks
SCS=y once GCC upstream is fixed.

ohm.yaml gains a CONFIG_SHADOW_CALL_STACK=n config override with a
pointer to besser#20 (the re-enable tracking issue) so future
manifest-driven kconfig generation honors the workaround without
silently dropping it.

Source-of-truth commit for Patch I:
  marfrit/bes2600-dkms branch bes2600/scan-filter-5ghz sha 093a503
PKGBUILD-side (already deployed to ohm via pkgrel=2):
  marfrit/besser branch claude-noether-14 sha ae175f9

Refs: besser#1 (closed), besser#20, kernel-agent#5
2026-05-18 15:25:37 +02:00
marfrit 96af34d775 Merge PR #24: import Sarma VP9-VDPU381 series + enable in fleet/ampere.yaml 2026-05-18 13:15:19 +00:00
marfrit 95be39ef80 fleet/ampere: enable Sarma VP9-VDPU381 patches in baseline
Reference the 3 patches imported in the previous commit under the
scope-tagged patch list. Apply order is strict (0001 → 0002 → 0003).

Verified 2026-05-18 via the arch_vp9_test extlinux boot on ampere:
- VP9F enumerates on rkvdec /dev/video2
- kdirect decode bit-exact vs libavcodec SW reference at -ss 30
- libva decode (firefox/chromium-style consumer) also bit-exact
- vainfo lists VAProfileVP9Profile0 (iter38 multi-device probe auto-picks)
- All three paths agree on sha
  c8624d7c42db66525f53a02a515bc38d0a17ef39f692660cc7bebb1e2d2e1b48

Removes VP9 from the "explicitly not included" comment block — issue
#12 closes with this change.

Also: AV1 stays out-of-scope per issue #6 ask 3 (kernel side fine via
the existing av1-vpu-dec node; backend just needs the 4th-fd
generalization tracked in libva-v4l2-request-fourier#2).

The next linux-ampere-fourier package rebuild from this manifest
will pick up VP9 automatically; ampere's running
7.0.0-rc3-vp9-test+ kernel already has these patches via the
operator's manual build session today.
2026-05-18 13:15:09 +00:00
marfrit 9092d9aaaa patches/driver/media: import Sarma's VP9-VDPU381 series (out-of-tree, v8)
Three patches from D.V.A.B. Sarma adding VP9 decode support to the
VDPU381 variant of rkvdec (RK3588 generation). Combined ~1500 LOC,
5 new files in drivers/media/platform/rockchip/rkvdec/.

Provenance: github.com/dvab-sarma/android_kernel_rk_opi branch
add-rkvdec-vdpu381-vp9-v8. Collabora's blog cites the work but it
hasn't reached linux-media patchwork yet (Collabora: "v1 series
needs to be sent for review soon"). Casanova's underlying
VDPU381/VDPU383 H.264+HEVC base IS in mainline 7.0 release.

Tested by author on Orange Pi 5 Pro (RK3588) with AOSP 16 + FFMPEG,
Profile 0 + Profile 2.

Tested in our fleet 2026-05-18: cherry-picks cleanly on top of
ampere-minimal-devices, full kernel build (KERNELRELEASE
7.0.0-rc3-vp9-test+) succeeds clean with GCC 16.1.1. Image + DTB +
modules + initramfs installed under -vp9-test+ suffix on ampere
without touching the running -devices+ kernel; new extlinux label
arch_vp9_test added (default unchanged at arch_devices). End-to-end
VP9 decode verification pending operator reboot into the new label.

Patches NOT yet referenced from fleet/ampere.yaml — that bump is
the operator's call (manifest preamble currently scopes VP9 out per
issue #6). Once verified, ampere.yaml can add these three under the
scope-tagged patch list in apply order 0001→0002→0003.

Cross-reference: marfrit/kernel-agent#12.
2026-05-18 12:56:15 +00:00
marfrit 44c6c3fa4f Merge pull request '[ka:cli-build-out] ka-promote: implement resolver + cumulative + manifest.lock (closes #22)' (#23) from claude-noether/kernel-agent:noether/ka-promote into main
Reviewed-on: marfrit/kernel-agent#23
2026-05-18 08:57:15 +00:00
claude-noether 3c6d3567f0 README: reflect ka-promote implementation + ka-import rename (Phase 8 of #22)
CLI verb table updates:
- ka-promote: new signature `<host>` (read fleet/<host>.yaml → cumulative.patch +
  manifest.lock). Marked [bin/ka-promote — implemented Phase 6, issue #22].
- ka-import: new verb name carrying the original ka-promote semantic
  (`<campaign> <patch-or-glob> --to <scope>`, promote patches into the
  scope-tagged tree). Unimplemented; today this is the regular git+PR workflow.
- ka-build / ka-install: signatures elaborated, marked as the next two verbs
  to implement (issues TBD).

Bootstrap reference build section:
- Header reframed: "before ka-* verbs existed" → ka-promote replaces step #1
  as of 2026-05-18.
- Baseline corrected: torvalds/linux not mmind/linux-rockchip (mmind doesn't
  ship plain v7.0 tag — caught in ka-promote Phase 3, fleet/fresnel.yaml fixed
  in the implementation commit).
- "Manual substitute" table gets a Status column. Row for ka-promote split
  into the old "import" semantic vs the new "manifest resolve" semantic, with
  the latter marked **automated 2026-05-18**.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:54:22 +00:00
claude-noether 91fe815c4c bin/ka-promote: implement resolver + cumulative + manifest.lock (Phase 6 of #22)
First of the three [ka:cli-build-out] verbs (umbrella #21). Reads
fleet/<host>.yaml, resolves includes[] (single-file + series-dir),
concatenates in apply order, emits build/<host>/<ref>/{cumulative.patch,
manifest.lock}. Phase-3 ground truth on fresnel parity: b2sum
4d9d93c655ea701b… matches bit-for-bit.

Five tests in tests/ka-promote/ (fresnel parity, series-dir resolver,
bad-include, missing-patch, dup-include) all pass.

Validator (--validate-against <linux-checkout>) hard-fails on: missing
.git, baseline.ref not in checkout, HEAD-tree != baseline.ref tree,
or uncommitted/untracked changes. Verified on boltzmann against the
torvalds v7.0 worktree (all 3 negative paths exit 3 with clear errors).

Side fix: fleet/fresnel.yaml baseline.tree mmind/linux-rockchip → torvalds/linux.
mmind doesn't ship a plain v7.0 tag; baseline was actually torvalds the
whole time. mmind kept as informational patch_authoring_context.

Phase-5 reviewer (sonnet outside-look, #22 comment 1135) followups
addressed: series-dir fixture count 7 (not 6), divergence = hard error,
raw-bytes manifest hash, duplicate-include pre-flight check, explicit
yaml.dump(sort_keys=True).

Language choice (vs ka-status's bash): pure python3 — YAML round-trip,
dict construction, and per-file hashing made bash+heredoc python quoting
hell with no readability gain.

Phase 7 (verify on ampere parity) + Phase 8 (close + README rewrite +
PR) to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:52:42 +00:00
marfrit 0e1075fbdd Merge pull request 'ampere: wire internal microphone (MIC1) routing' (#20) from claude-noether/kernel-agent:noether/genbook-internal-mic into main 2026-05-17 21:30:14 +00:00
claude-noether f8d56d8635 ampere: wire internal microphone (MIC1) routing
Add coolpi-cm5-genbook DTS patch that mirrors vendor coolpi-kernel
audio routing for the internal microphone:

  routing = "MIC1", "Main Mic",
            "MIC2", "Headset Mic";

Necessary-but-not-sufficient: the ES8316 RX aif_out widget power-up
chain is still broken on mainline (separate issue, not addressed
here); a future codec/audio-graph-card driver fix can complete the
capture path once the topology is correct.

Include in ampere fleet manifest.
2026-05-17 21:29:06 +00:00
marfrit fb4bfdc2ff Merge pull request 'fleet/ohm: scaffold manifest for linux-pinetab2-danctnix-besser (#5 partial)' (#18) from claude-noether/kernel-agent:migrate-pinetab2-pkgbuild into main 2026-05-16 16:43:41 +00:00
claude-noether 2d2b9ee8ba fleet/ohm: scaffold manifest for linux-pinetab2-danctnix-besser (#5 partial)
Adds fleet/ohm.yaml referencing the bes2600 series mirrored in #2 →
patches/driver/bes2600/. Establishes the manifest shape for ohm
(PineTab2 / RK3566 + BES2600).

Includes list defaults to the -danctnix sibling variants (ohm runs the
DanctNIX kernel base), 17 series total. The pure-mainline non-danctnix
variants are NOT included — they exist for vanilla consumers that ohm
doesn't currently have.

What's NOT in this commit (blockers for #5 full closure):

1. PKGBUILD migration to marfrit-packages/arch/linux-pinetab2-danctnix-besser/
   PENDING the kernel-agent template renderer (b2sum regen, pkgrel
   bump, cumulative-patch generation per build job).

2. Cumulative-patch ordering field (apply_order). Current order on
   boltzmann is A,B,C v3,F,G,D,E,C2,c5.x,c6.x,c7,H — NOT alphabetical.
   Need explicit apply_order: [...] in this manifest before
   ka-promote can replace the hand-managed cumulative.

3. Config-by-manifest. The .config still lives next to the existing
   boltzmann PKGBUILD. Migrate when kconfig-by-manifest lands.

4. Orphan retirement (~/src/besser/danctnix-besser-pkgbuild/ on
   boltzmann). Surface to fourier campaign for working-state
   migration BEFORE deletion. See besser #17 for the regression
   that the orphan caused.

All four blockers documented as TODO at the bottom of fleet/ohm.yaml.
Issue #5 stays OPEN pending these.

Generated-by: Claude Opus 4.7 <claude@reauktion.de>
2026-05-16 16:43:34 +00:00
marfrit 7f60a3a37c Merge pull request 'patches/driver/bes2600: mirror besser series — closes #2' (#17) from claude-noether/kernel-agent:migrate-besser into main 2026-05-16 16:42:50 +00:00
claude-noether d5893e65f0 patches/driver/bes2600: mirror besser series (closes #2)
Mirrors all 30 BES2600 patch series from marfrit/besser/patches/ into
the kernel-agent scope-tagged tree under patches/driver/bes2600/.
15 base series + 15 -danctnix siblings = 45 .patch files including
cover letters.

Per-series promotion eligibility tracked in the README (default
unset → ka-promote asks before including in a build). Markus to
update as series mature.

DKMS-to-in-tree transition path documented (drop bes2600-dkms
once series lands in mainline / DanctNIX base).

Cumulative-patch ordering caveat captured: existing order is NOT
alphabetical (A,B,C v3,F,G,D,E,C2,c5.x,c6.x,c7,H). ka-promote
needs an explicit apply_order field, not a series-name sort.
Surface when fleet/ohm.yaml lands in #5.

Acceptance criteria from #2:
  [x] All series present under driver/bes2600/
  [x] Promotion eligibility per series (table in README, defaults unset for Markus to fill)
  [ ] Manifest for ohm references driver:bes2600 scope (deferred to #5)
  [x] DKMS-to-in-tree transition path documented

Generated-by: Claude Opus 4.7 <claude@reauktion.de>
2026-05-16 16:42:42 +00:00
marfrit 2783d5ec60 Merge pull request 'patches: migrate remaining misc_patches/genbook (0006, 0009, 0010-split) — closes #1' (#16) from claude-noether/kernel-agent:migrate-misc-patches into main 2026-05-16 16:41:36 +00:00
claude-noether 91617fae14 patches: migrate remaining misc_patches/genbook into scope-tagged tree (closes #1)
Closes the migration started in #8. The 6 active ampere patches (pwm15,
pwm-fan, RK806 power-controller, speaker, USB-C PD, lid switch) landed
in #8 under soc/rockchip/rk3588/, module/coolpi-cm5/, and
board/coolpi-cm5-genbook/. This commit migrates the remaining 3:

* `0006 arm64 Kconfig: do not select HAVE_GCC_PLUGINS`
  → patches/arch/arm64/. NOT for upstream — local build workaround;
  README explains the proper fix is gcc-plugin-devel install.

* `0009 Bluetooth: btrtl: make RTL_SEC_PROJ read non-fatal`
  → patches/driver/bluetooth/btrtl/. Benefits ampere (RTL8852BE) and
  boltzmann (same M.2 family). Cross-host driver/ scope.

* `0010 gpio/drm/mfd/input/dts: fix suspend/resume and wakeup on RK3588`
  → SPLIT into 5 scope-tagged sub-patches as the issue required
  ("0010 split into ≥2 patches by scope"):
    - patches/driver/gpio/rockchip/0010a-gpio-rockchip-propagate-irq_set_wake-to-parent-GIC.patch
    - patches/driver/gpu/drm/bridge/analogix/0010b-drm-bridge-analogix-dp-disable-IRQ-before-clock-gating-in-suspend.patch
    - patches/driver/mfd/rk8xx/0010c-mfd-rk8xx-spi-add-PM-ops-and-shutdown-callback.patch
    - patches/driver/input/misc/0010d-input-rk805-pwrkey-register-wake-IRQ-via-dev_pm_set_wake_irq.patch
    - patches/board/coolpi-cm5-genbook/0010e-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-add-NPU-power-domain-and-touchpad-wakeup.patch

  The split is byte-identical to the original 0010 (verified on
  decompose-0010 branch in marfrit/linux-rk3588-marfrit — sha256 of
  the combined diff matches the monolithic 0010 commit).

  All 0010 sub-patches marked WIP in their README and explicitly NOT
  added to fleet/ampere.yaml includes — suspend/resume thread is still
  open and Markus hasn't decided the upstream-targeting shape.

READMEs added at each new directory documenting per-patch context.

Acceptance criteria from #1:
  [x] Each patch lives at narrowest correct scope
  [x] 0010 split into ≥2 patches by scope (5 sub-patches)
  [x] Per-host manifest for ampere references the right scopes + explicit includes (already #8)
  [ ] github.com/marfrit/misc_patches retained as historical mirror — Markus to decide
  [x] working-tree dirty state on boltzmann reconciled (decompose-0010 + ampere-minimal-devices branches captured)

Generated-by: Claude Opus 4.7 <claude@reauktion.de>
2026-05-16 16:41:24 +00:00
87 changed files with 17206 additions and 25 deletions
+7
View File
@@ -0,0 +1,7 @@
# ka-promote / ka-build output
/build/
# transient
*.pyc
__pycache__/
.DS_Store
+27 -15
View File
@@ -106,10 +106,12 @@ persistent, audit trail per item).
## Verbs (explicit, parameterized, audit-issue auto-filed)
```
ka-promote <campaign> <patch-or-glob> --to <scope>
ka-promote <host> # resolve fleet/<host>.yaml → cumulative.patch + manifest.lock [bin/ka-promote — implemented Phase 6, issue #22]
ka-import <campaign> <patch-or-glob> --to <scope> # patches from campaign → scope-tagged tree (today: manual git workflow)
ka-close <campaign> --status success
ka-abandon <campaign> --keep-as-archive | --purge-from-fleet
ka-install <host>
ka-build <host> # render PKGBUILD template with cumulative b2sum, run makepkg [next verb, issue TBD]
ka-install <host> # scp + pacman -U + extlinux/mkinitcpio + heartbeat [last verb, issue TBD]
ka-keep <job-id> [--for <duration>]
ka-pause-prune / ka-resume-prune
ka-restore-archive <job-id>
@@ -120,6 +122,13 @@ ka-migrate-tree --from <p> --to <p>
ka-wake-data # wraps wake-host data through His
```
Note: the original spec had `ka-promote <campaign> <patch-or-glob> --to <scope>`
("promote patches from a campaign into the canonical tree"). That semantic
moved to `ka-import` to free `ka-promote` for the manifest-resolution role
its issue (#22) and the implemented `bin/ka-promote` actually fulfil. `ka-import`
remains unimplemented — patches still land in `patches/` via the regular git
+ PR workflow.
Conversational invocation triggers a y/n confirmation enumerating what will
happen. Direct CLI invocation executes immediately.
@@ -226,15 +235,17 @@ via `ka-snooze <issue-id> [--for <duration>]`.
## Bootstrap reference build (2026-05-09 — fresnel)
First end-to-end run, before any `ka-*` CLI exists. Documented here as the
canonical worked example so future ka-* implementations have a concrete
substrate to replay. Issue #3 (fresnel DTS persistence) closed by this
build.
First end-to-end run, before `ka-promote` / `ka-build` / `ka-install` existed.
Documented here as the canonical worked example; the substrate that the ka-*
verbs are/will-be implemented against. Issue #3 (fresnel DTS persistence) closed by this
build. `ka-promote` (issue #22) replaced the manual step #1 below as of 2026-05-18.
### Inputs
- **Baseline:** mmind/linux-rockchip @ `v7.0` (Heiko Stübner / Collabora,
via kernel.org).
- **Baseline:** torvalds/linux @ `v7.0` (verified during ka-promote Phase 3,
issue #22 — mmind/linux-rockchip does not ship a plain `v7.0` tag despite
earlier docs; mmind kept in fresnel.yaml as informational
`patch_authoring_context`).
- **Patches** (scope `board/pinebook-pro`):
- `0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch`
- `0002-arm64-dts-rk3399-pinebook-pro-enable-hdmi-sound.patch`
@@ -249,13 +260,14 @@ build.
### Manual substitute for each ka-* verb
| Designed verb | What we did manually |
|---|---|
| `ka-promote fresnel-fourier <patches> --to board/pinebook-pro` | Authored 3 patches with proper headers/scope tags, pushed to `marfrit/kernel-agent/patches/board/pinebook-pro/` via Gitea contents API as `claude-noether`. |
| `ka-build fresnel` | On boltzmann: cloned linux v7.0 from kernel.org, ran `makepkg -s --skipchecksums --skippgpcheck` against `marfrit-packages/arch/linux-fresnel-fourier/PKGBUILD`. Native aarch64 (boltzmann is RK3588). One headers-pkg bug discovered (`ln -sr` on missing parent dir) and fixed mid-flight. Repackaged. |
| `ka-sign + push` | scp pkgs hertz → `sudo /opt/herding/bin/marfrit-publish-arch aarch64 <pkg>` per pkg. Script signs with key `92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C`, runs repo-add, rsyncs to nc. |
| `ka-install fresnel` (consent-via-action) | `sudo pacman -U /tmp/<pkg>` over LAN scp (HTTPS to nc was throttled by fresnel's wifi). pacman post-transaction hook updated extlinux. mkinitcpio run manually because the standard hook trigger watches `vmlinuz` not `Image`. |
| Bar 1..3 verification | SSH heartbeat OK, `pacman -Q linux-fresnel-fourier` = `7.0-1`, post-reboot cluster0 1.704 GHz / cluster1 2.184 GHz confirmed. |
| Designed verb | What we did manually | Status |
|---|---|---|
| `ka-import fresnel-fourier <patches> --to board/pinebook-pro` (originally named `ka-promote` in this row) | Authored 3 patches with proper headers/scope tags, pushed to `marfrit/kernel-agent/patches/board/pinebook-pro/` via Gitea contents API as `claude-noether`. | still manual — `ka-import` unimplemented |
| `ka-promote fresnel` (new — manifest → cumulative.patch + manifest.lock) | n/a (didn't exist) | **automated 2026-05-18, issue #22** |
| `ka-build fresnel` | On boltzmann: cloned linux v7.0 from kernel.org, ran `makepkg -s --skipchecksums --skippgpcheck` against `marfrit-packages/arch/linux-fresnel-fourier/PKGBUILD`. Native aarch64 (boltzmann is RK3588). One headers-pkg bug discovered (`ln -sr` on missing parent dir) and fixed mid-flight. Repackaged. | **automated 2026-05-19, issue #34**`ka-build <host>` ssh-dispatches makepkg to `build_host.primary`, verifies kernel-agent patches still match the PKGBUILD-side files (b2sum cross-check from `manifest.lock`), and pulls the resulting `*.pkg.tar.zst` back. |
| `ka-sign + push` | scp pkgs hertz → `sudo /opt/herding/bin/marfrit-publish-arch aarch64 <pkg>` per pkg. Script signs with key `92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C`, runs repo-add, rsyncs to nc. | **folded into `ka-build` 2026-05-19**`ka-build` scp's each pkg to hertz and runs `marfrit-publish-arch` over ssh. `--skip-publish` flag retained for offline builds. |
| `ka-install fresnel` (consent-via-action) | `sudo pacman -U /tmp/<pkg>` over LAN scp (HTTPS to nc was throttled by fresnel's wifi). pacman post-transaction hook updated extlinux. mkinitcpio run manually because the standard hook trigger watches `vmlinuz` not `Image`. | still manual — last verb to implement |
| Bar 1..3 verification | SSH heartbeat OK, `pacman -Q linux-fresnel-fourier` = `7.0-1`, post-reboot cluster0 1.704 GHz / cluster1 2.184 GHz confirmed. | folded into `ka-install` |
### Files / locations involved
Executable
+199
View File
@@ -0,0 +1,199 @@
#!/usr/bin/env bash
# ka-build — render PKGBUILD from manifest.lock, build native on host,
# sign+publish via marfrit-publish-arch on hertz.
#
# Phase-1 (issue #34): arch makepkg wrapper. Debian path deferred.
#
# Usage:
# ka-build <host>
# ka-build <host> --packages-repo <path> # default: ~/src/marfrit-packages
# ka-build <host> --dry-run # stop after staging, don't makepkg
# ka-build <host> --skip-publish # build only, don't push to hertz
#
# Exit codes:
# 0 success (pkg built + published)
# 2 missing input (manifest.lock, PKGBUILD, ssh target)
# 3 patch drift (resolved.sha256 != PKGBUILD-side file sha256)
# 4 makepkg / sign / publish failure
# 5 manifest parse error
set -euo pipefail
VERSION=1
die() { echo "ka-build: error: $1" >&2; exit "${2:-1}"; }
note() { echo "ka-build: $1"; }
# Defaults
PACKAGES_REPO="${KA_PACKAGES_REPO:-${HOME}/src/marfrit-packages}"
DRY_RUN=0
SKIP_PUBLISH=0
HOST=""
while [ $# -gt 0 ]; do
case "$1" in
--packages-repo) PACKAGES_REPO="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
--skip-publish) SKIP_PUBLISH=1; shift ;;
--version) echo "ka-build version $VERSION"; exit 0 ;;
-h|--help) sed -n '1,30p' "$0" | grep -E '^# ' | sed 's/^# //'; exit 0 ;;
-*) die "unknown flag: $1" ;;
*) [ -z "$HOST" ] && HOST="$1" || die "extra arg: $1"; shift ;;
esac
done
[ -n "$HOST" ] || die "host is required" 2
[ -d "$PACKAGES_REPO" ] || die "--packages-repo not found: $PACKAGES_REPO" 2
# Locate kernel-agent repo root (where bin/ + fleet/ live)
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$script_dir/.." && pwd)"
[ -d "$REPO_ROOT/fleet" ] || die "fleet/ not found relative to $script_dir" 2
manifest="$REPO_ROOT/fleet/${HOST}.yaml"
[ -f "$manifest" ] || die "no manifest for host '$HOST': $manifest" 2
# Read fields from manifest via python (yaml in bash is masochism)
py_read() {
python3 -c "
import sys, yaml, os
m = yaml.safe_load(open('$manifest'))
keys = '$1'.split('.')
v = m
for k in keys:
if not isinstance(v, dict) or k not in v: sys.exit('missing key: $1')
v = v[k]
print(v)
"
}
PKG_NAME="$(py_read package.name)"
BASELINE_REF="$(py_read baseline.ref)"
BUILD_HOST="$(py_read build_host.primary)"
# Locate the most recent ka-promote output
build_dir_root="${KA_BUILD_DIR:-$REPO_ROOT/build}"
promote_out="${build_dir_root}/${HOST}/${BASELINE_REF}"
lock="${promote_out}/manifest.lock"
cumulative="${promote_out}/cumulative.patch"
[ -f "$lock" ] || die "no manifest.lock at $lock — run 'ka-promote $HOST' first" 2
[ -f "$cumulative" ] || die "no cumulative.patch at $cumulative — run 'ka-promote $HOST' first" 2
# Locate the PKGBUILD
pkg_dir="${PACKAGES_REPO}/arch/${PKG_NAME}"
pkgbuild="${pkg_dir}/PKGBUILD"
[ -f "$pkgbuild" ] || die "no PKGBUILD at $pkgbuild (expected from manifest package.name)" 2
note "host=$HOST pkg=$PKG_NAME baseline=$BASELINE_REF build_host=$BUILD_HOST"
note "PKGBUILD: $pkgbuild"
note "manifest.lock: $lock"
# Refuse if PKGBUILD-side patches drifted from kernel-agent patches/.
# manifest.lock.resolved_patches[].sha256 must match PKGBUILD-dir-side
# files of the same basename. (If a patch is in resolved but missing from
# PKGBUILD dir, fail loud — operator needs to sync.)
note "verifying patch consistency between kernel-agent and marfrit-packages..."
drift=0
while IFS=$'\t' read -r basename expected_sha; do
pkg_side="${pkg_dir}/${basename}"
if [ ! -f "$pkg_side" ]; then
echo " MISSING in PKGBUILD dir: $basename" >&2
drift=1; continue
fi
actual_sha=$(sha256sum "$pkg_side" | cut -d' ' -f1)
if [ "$actual_sha" != "$expected_sha" ]; then
echo " DRIFT: $basename (expected $expected_sha, got $actual_sha)" >&2
drift=1
fi
done < <(python3 -c "
import yaml, sys, os
lk = yaml.safe_load(open('$lock'))
for r in lk['resolved_patches']:
bn = os.path.basename(r['include'])
print(f\"{bn}\t{r['sha256']}\")
")
[ "$drift" -eq 0 ] || die "patches differ between kernel-agent and marfrit-packages — sync first" 3
note "patches OK ($(python3 -c "import yaml; print(len(yaml.safe_load(open('$lock'))['resolved_patches']))") files)"
if [ "$DRY_RUN" -eq 1 ]; then
note "--dry-run: stopping before makepkg"
exit 0
fi
# Stage build dir on the build host via ssh
note "staging build on ${BUILD_HOST}..."
remote_stage="/tmp/ka-build-${HOST}-$$"
ssh "${BUILD_HOST}" "mkdir -p '$remote_stage'"
rsync -a "${pkg_dir}/" "${BUILD_HOST}:${remote_stage}/"
# Run makepkg natively
note "running makepkg --syncdeps --noconfirm --cleanbuild on ${BUILD_HOST}..."
ssh "${BUILD_HOST}" "cd '$remote_stage' && makepkg --syncdeps --noconfirm --cleanbuild --skipchecksums" \
|| die "makepkg failed on ${BUILD_HOST}" 4
# Fetch built packages
note "fetching .pkg.tar.zst from ${BUILD_HOST}..."
local_out="${promote_out}/pkgs"
mkdir -p "$local_out"
rsync -av "${BUILD_HOST}:${remote_stage}/*.pkg.tar.zst" "$local_out/" 2>&1 | tail -5
# Compute b2sums
pkg_b2sum_list=$(cd "$local_out" && for p in *.pkg.tar.zst; do
[ -f "$p" ] || continue
printf '%s %s\n' "$(b2sum "$p" | cut -d' ' -f1)" "$p"
done)
note "built packages:"
echo "$pkg_b2sum_list" | sed 's/^/ /'
# Publish via hertz marfrit-publish-arch (unless --skip-publish)
if [ "$SKIP_PUBLISH" -eq 0 ]; then
note "publishing to packages.reauktion.de/arch/aarch64/..."
for p in "$local_out"/*.pkg.tar.zst; do
[ -f "$p" ] || continue
base="$(basename "$p")"
scp -q "$p" "hertz:/tmp/${base}" || die "scp to hertz failed: $base" 4
ssh hertz "sudo /opt/herding/bin/marfrit-publish-arch aarch64 '/tmp/${base}'" \
|| die "marfrit-publish-arch failed: $base" 4
ssh hertz "rm -f '/tmp/${base}'"
note "published: $base"
done
fi
# Update manifest.lock with build receipt (append; don't rewrite the
# existing fields)
note "writing build receipt to manifest.lock..."
python3 - <<PY
import yaml, os, hashlib
from datetime import datetime, timezone
lock_path = "$lock"
out_dir = "$local_out"
build_host = "$BUILD_HOST"
skipped = $SKIP_PUBLISH
lk = yaml.safe_load(open(lock_path))
epoch = os.environ.get("SOURCE_DATE_EPOCH")
if epoch:
built_at = datetime.fromtimestamp(int(epoch), tz=timezone.utc).isoformat()
else:
built_at = datetime.now(tz=timezone.utc).isoformat()
pkgs = []
for fn in sorted(os.listdir(out_dir)):
if not fn.endswith(".pkg.tar.zst"): continue
fp = os.path.join(out_dir, fn)
b2 = hashlib.blake2b(open(fp, "rb").read()).hexdigest()
pkgs.append({"name": fn, "size": os.path.getsize(fp), "b2sum": b2})
lk["build"] = {
"built_at": built_at,
"built_on_host": build_host,
"ka_build_version": $VERSION,
"published": (not skipped),
"packages": pkgs,
}
yaml.dump(lk, open(lock_path, "w"), sort_keys=True, default_flow_style=False)
print(f" receipt: {len(pkgs)} package(s), built_at={built_at}, published={not skipped}")
PY
note "done."
Executable
+301
View File
@@ -0,0 +1,301 @@
#!/usr/bin/env python3
"""ka-promote — resolve fleet/<host>.yaml + emit cumulative.patch + manifest.lock.
First of the three writing verbs (ka-promote → ka-build → ka-install).
Read-mostly: only writes to ${KA_BUILD_DIR:-./build}/<host>/<baseline_ref>/.
Usage:
ka-promote <host>
ka-promote <host> --output-dir <path>
ka-promote <host> --validate-against <linux-checkout>
ka-promote --list-hosts
ka-promote --version
Exit codes:
0 success
2 missing input (manifest, patch file, series-dir)
3 --validate-against failed (ref mismatch or apply-check failure)
4 manifest parse / schema error
Language note: pure python3 (not bash like ka-status). The data shape
here — YAML in, YAML out, dict construction, per-file hashing, glob
resolution — fits python naturally; bash + python -c heredocs would be
quoting hell for no readability gain. See issue #22 comment 1132.
"""
import argparse
import glob
import hashlib
import os
import re
import subprocess
import sys
from datetime import datetime, timezone
import yaml
VERSION = 1
SCHEMA_VERSION = 1
COVER_LETTER = "0000-cover-letter.patch"
# git format-patch trailer: "-- \n<MAJOR>.<MINOR>(.<PATCH>)?\n" at EOF,
# possibly with trailing blank line(s). Strip from each source patch so
# that the cumulative is always well-formed regardless of include order.
# See issue #31.
_TRAILER_RE = re.compile(rb'\n-- \n\d+\.\d+(?:\.\d+)?\n+\Z')
# Canonical separator emitted between concatenated patches in the
# cumulative. Trailing blank line keeps patch(1) happy when the next
# patch starts with "From <sha>".
_CANONICAL_TRAILER = b'-- \n2.54.0\n\n'
def die(msg, code=1):
print(f"ka-promote: error: {msg}", file=sys.stderr)
sys.exit(code)
def find_repo_root():
here = os.path.dirname(os.path.abspath(__file__))
root = os.path.dirname(here)
if not os.path.isdir(os.path.join(root, "fleet")):
die(f"fleet/ not found relative to {here}", 4)
return root
def list_hosts(fleet_dir):
for path in sorted(glob.glob(os.path.join(fleet_dir, "*.yaml"))):
print(os.path.basename(path)[:-5])
def load_manifest(path):
try:
raw = open(path, "rb").read()
except FileNotFoundError:
die(f"manifest not found: {path}", 2)
sha = hashlib.sha256(raw).hexdigest()
try:
m = yaml.safe_load(raw)
except yaml.YAMLError as e:
die(f"manifest parse error: {e}", 4)
if not isinstance(m, dict):
die(f"manifest root must be a mapping: {path}", 4)
for key in ("host", "baseline", "includes"):
if key not in m:
die(f"manifest missing required key '{key}': {path}", 4)
if not isinstance(m["includes"], list) or not m["includes"]:
die(f"manifest.includes must be a non-empty list: {path}", 4)
return m, sha
def resolve_includes(includes, patches_root):
"""Walk manifest.includes, expand series-dirs, dedupe-check, hash."""
seen = set()
resolved = []
order = 0
for entry in includes:
if not isinstance(entry, str):
die(f"includes entry must be a string, got {type(entry).__name__}: {entry!r}", 4)
if entry in seen:
die(f"duplicate include: {entry}", 4)
seen.add(entry)
src_path = os.path.join(patches_root, entry)
if entry.endswith(".patch"):
if not os.path.isfile(src_path):
die(f"missing patch: {src_path}", 2)
order += 1
resolved.append({
"apply_order": order,
"include": entry,
"src": src_path,
"from_series": False,
})
elif entry.endswith("/"):
dir_path = src_path.rstrip("/")
if not os.path.isdir(dir_path):
die(f"missing series-dir: {dir_path}", 2)
files = sorted(glob.glob(os.path.join(dir_path, "*.patch")))
files = [f for f in files if os.path.basename(f) != COVER_LETTER]
if not files:
die(f"series-dir has no applied patches (only cover-letter or empty): {dir_path}", 2)
for f in files:
order += 1
resolved.append({
"apply_order": order,
"include": entry + os.path.basename(f),
"src": f,
"from_series": True,
})
else:
die(f"include must end in '.patch' or '/': {entry}", 4)
for r in resolved:
with open(r["src"], "rb") as f:
data = f.read()
r["sha256"] = hashlib.sha256(data).hexdigest()
r["size"] = len(data)
return resolved
def strip_trailer(data):
"""Strip any trailing git format-patch sentinel from a patch.
Accepts patches in either canonical shape:
- WITH trailer: "...\n-- \n2.54.0\n\n"
- WITHOUT trailer: "...\n" (already stripped)
Returns data ending in a single newline so the caller can either
append a canonical trailer (mid-cumulative) or leave it bare (last).
"""
stripped = _TRAILER_RE.sub(b'\n', data)
if not stripped.endswith(b'\n'):
stripped += b'\n'
return stripped
def write_cumulative(resolved, out_path):
with open(out_path, "wb") as out:
n = len(resolved)
for i, r in enumerate(resolved):
with open(r["src"], "rb") as src:
data = src.read()
data = strip_trailer(data)
out.write(data)
# Mid-cumulative patches need a separator so patch(1) knows
# where they end and the next "From <sha>" begins. Last
# patch stays bare — a trailing orphan sentinel reads as
# the start of a malformed new patch at EOF (issue #31).
if i != n - 1:
out.write(_CANONICAL_TRAILER)
with open(out_path, "rb") as f:
b2 = hashlib.blake2b(f.read()).hexdigest()
size = os.path.getsize(out_path)
return size, b2
def write_lock(lock_path, *, host, manifest_rel, manifest_sha, baseline,
resolved, cumulative_size, cumulative_b2sum):
epoch = os.environ.get("SOURCE_DATE_EPOCH")
if epoch:
generated_at = datetime.fromtimestamp(int(epoch), tz=timezone.utc).isoformat()
else:
generated_at = datetime.now(tz=timezone.utc).isoformat()
lock = {
"ka_promote_version": VERSION,
"schema_version": SCHEMA_VERSION,
"generated_at": generated_at,
"host": host,
"manifest": {"path": manifest_rel, "sha256": manifest_sha},
"baseline": baseline,
"resolved_patches": [
{
"apply_order": r["apply_order"],
"include": r["include"],
"sha256": r["sha256"],
"size": r["size"],
"from_series": r["from_series"],
}
for r in resolved
],
"cumulative": {
"path": "cumulative.patch",
"size": cumulative_size,
"b2sum": cumulative_b2sum,
},
}
with open(lock_path, "w") as f:
yaml.dump(lock, f, sort_keys=True, default_flow_style=False)
def validate_against(checkout, baseline_ref, cumulative_path):
# `.git` is a directory in a plain checkout, a file (gitdir pointer)
# in a worktree. `os.path.exists` covers both.
if not os.path.exists(os.path.join(checkout, ".git")):
die(f"--validate-against: not a git checkout: {checkout}", 3)
def git(*args):
return subprocess.run(
["git", *args], cwd=checkout, capture_output=True, text=True
)
r = git("rev-parse", f"{baseline_ref}^{{tree}}")
if r.returncode != 0:
die(f"baseline ref '{baseline_ref}' not found in checkout {checkout}", 3)
baseline_tree = r.stdout.strip()
head_tree = git("rev-parse", "HEAD^{tree}").stdout.strip()
if head_tree != baseline_tree:
die(f"checkout HEAD tree {head_tree} != baseline.ref {baseline_ref} tree {baseline_tree}. "
"Refusing apply-check on diverged tree.", 3)
# Working tree must match HEAD too — `git apply --check` runs against
# the working tree, not HEAD, so a dirty tree gives false negatives.
r = git("status", "--porcelain")
if r.stdout.strip():
die(f"checkout {checkout} has uncommitted changes. "
"`git reset --hard {0} && git clean -fdx` first.".format(baseline_ref), 3)
r = git("apply", "--check", cumulative_path)
if r.returncode != 0:
die(f"git apply --check failed:\n{r.stderr}", 3)
def main():
p = argparse.ArgumentParser(prog="ka-promote", add_help=True)
p.add_argument("host", nargs="?", help="fleet host name (omit with --list-hosts/--version)")
p.add_argument("--output-dir", help="override ${KA_BUILD_DIR:-<repo>/build}")
p.add_argument("--validate-against", metavar="CHECKOUT",
help="run git apply --check against a clean baseline.ref checkout")
p.add_argument("--list-hosts", action="store_true", help="list available fleet/<host>.yaml manifests")
p.add_argument("--version", action="store_true", help="print ka-promote schema version + exit")
args = p.parse_args()
repo_root = find_repo_root()
fleet_dir = os.path.join(repo_root, "fleet")
patches_root = os.path.join(repo_root, "patches")
if args.version:
print(f"ka-promote version {VERSION} (schema {SCHEMA_VERSION})")
return 0
if args.list_hosts:
list_hosts(fleet_dir)
return 0
if not args.host:
p.error("host is required (or use --list-hosts / --version)")
manifest_path = os.path.join(fleet_dir, f"{args.host}.yaml")
manifest, manifest_sha = load_manifest(manifest_path)
if manifest.get("host") != args.host:
die(f"manifest.host {manifest.get('host')!r} does not match filename {args.host!r}", 4)
baseline = manifest["baseline"]
if "ref" not in baseline:
die("manifest.baseline.ref is required", 4)
baseline_ref = baseline["ref"]
resolved = resolve_includes(manifest["includes"], patches_root)
out_root = args.output_dir or os.environ.get("KA_BUILD_DIR") or os.path.join(repo_root, "build")
out_dir = os.path.join(out_root, args.host, baseline_ref)
os.makedirs(out_dir, exist_ok=True)
cumulative_path = os.path.join(out_dir, "cumulative.patch")
size, b2sum = write_cumulative(resolved, cumulative_path)
write_lock(
os.path.join(out_dir, "manifest.lock"),
host=args.host,
manifest_rel=os.path.relpath(manifest_path, repo_root),
manifest_sha=manifest_sha,
baseline=baseline,
resolved=resolved,
cumulative_size=size,
cumulative_b2sum=b2sum,
)
if args.validate_against:
validate_against(args.validate_against, baseline_ref, cumulative_path)
print(f"ka-promote: {args.host} -> {out_dir}")
print(f" cumulative: cumulative.patch ({size} bytes)")
print(f" b2sum: {b2sum}")
print(f" patches: {len(resolved)} resolved ({sum(1 for r in resolved if r['from_series'])} from series-dirs)")
return 0
if __name__ == "__main__":
sys.exit(main())
+12 -4
View File
@@ -52,12 +52,20 @@ includes:
- board/coolpi-cm5-genbook/0004-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-Enable-speaker-output-via-audio-graph-card.patch
- board/coolpi-cm5-genbook/0005-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-Enable-USB-C-PD-charging-via-FUSB302.patch
- board/coolpi-cm5-genbook/0008-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-Add-lid-switch-and-USB3-PHY-lane-config.patch
- board/coolpi-cm5-genbook/0011-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-wire-internal-microphone.patch
# VP9 enablement for RK3588 rkvdec (issue #12, closed 2026-05-18).
# Cherry-picked from D.V.A.B. Sarma's add-rkvdec-vdpu381-vp9-v8 branch
# at github.com/dvab-sarma/android_kernel_rk_opi. Bit-exact HW==SW==libva
# verified at -ss 30 on bbb_60s_720p.vp9.webm via all three decode paths
# (kdirect / SW / libva); sha c8624d7c42db66525f53a02a515bc38d0a17ef39f692660cc7bebb1e2d2e1b48.
# Apply order is STRICT (0003 depends on the rkvdec-vp9-common refactor
# added in 0002, which depends on the helper rename in 0001).
# See patches/driver/media/README.md for provenance + removal criteria.
- driver/media/0001-rkvdec-vp9-rename-get_ref_buf-to-get_ref_buf_vp9.patch
- driver/media/0002-rkvdec-move-vp9-functions-to-common-file.patch
- driver/media/0003-rkvdec-add-vp9-support-for-vdpu381-variant.patch
# Explicitly NOT included this round (tracked for later sprints):
# - VP9 enablement for RK3588 rkvdec (issue #6 ask 2). /dev/video0 only
# advertises S265 + S264 today; vainfo lists 9 profiles, target is
# 10. Requires identifying the VDPU381/383 patch chain + possible
# DTS additions. RFC-stage work, scope unclear until research lands.
# - AV1 decoder integration (issue #6 ask 3). Kernel side is fine
# (/dev/video4 advertises AV1F). Backend libva-v4l2-request-fourier
# needs iter39 for a third fd. Backend work, not kernel.
+9 -6
View File
@@ -1,9 +1,11 @@
# kernel-agent manifest for fresnel (Pinebook Pro / Rockchip RK3399)
#
# Status: bootstrap, manually-driven. ka-* CLI not yet implemented.
# This manifest is the input ka-promote / ka-build will consume once landed.
# Until then it documents the canonical patch set + baseline for the manual
# build that produces linux-fresnel-fourier.
# Status: ka-promote-consumable. Used as Phase-3 parity reference for ka-promote
# bring-up (issue #22). ka-build / ka-install CLI still pending.
#
# baseline.tree corrected 2026-05-18: mmind/linux-rockchip does not ship a
# plain v7.0 tag; baseline is torvalds/linux v7.0. mmind kept as informational
# patch_authoring_context.
host: fresnel
arch: arm64
@@ -12,10 +14,11 @@ board: pinebook-pro
distro: archlinux-arm # EndeavourOS pacman base
baseline:
tree: mmind/linux-rockchip
url: https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0
upstream_compat: linux-7.0 # what the patches target
patch_authoring_context: mmind/linux-rockchip # informational — patches authored against Rockchip rebase
# Scope-tagged patch includes. Each entry resolves to
# patches/<scope>/.../<file>.patch in marfrit/kernel-agent.
+140
View File
@@ -0,0 +1,140 @@
# kernel-agent manifest for ohm (PineTab2 / Rockchip RK3566 + BES2600 SDIO WiFi/BT)
#
# Status: scaffolding from 2026-05-16; per-series patchset converged 2026-05-21 (pkgrel=6). Patches/scopes are mirrored;
# the build pipeline (cumulative-patch generation, makepkg invocation,
# sign+publish) still relies on the hand-managed flow in
# boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/.
# Once ka-promote / ka-build land, switch this manifest to authoritative
# and retire the boltzmann-side checkouts (canonical + the orphan at
# ~/src/besser/danctnix-besser-pkgbuild/ that the fourier-campaign
# sibling agent accidentally created).
#
# See kernel-agent issue #5 for the full migration plan.
host: ohm
arch: arm64
soc: rockchip/rk3566
board: pinetab2
distro: archlinux-arm # DanctNIX PineTab2 variant on Arch Linux ARM aarch64
baseline:
tree: DanctNIX/linux-pinetab2
url: https://codeberg.org/DanctNIX/linux-pinetab2
ref: v7.0-danctnix1 # _srcname=linux-pinetab2 _srctag=v${pkgver%.*}-${pkgver##*.}
upstream_compat: linux-7.0 # DanctNIX rebases off mainline + per-tablet delta
# Scope-tagged patch includes. Resolves to patches/<scope>/<file>.patch.
#
# 2026-05-18 audit: the per-series -danctnix mirrors in
# patches/driver/bes2600/*-danctnix/ created by kernel-agent#17 use
# DKMS-style root paths (bes2600/foo.c) rather than in-tree staging
# paths (drivers/staging/bes2600/foo.c), and at least one has corrupted
# mixed-prefix headers (a/drivers/staging/bes2600/... b/bes2600/...).
# They do NOT apply cleanly against the linux-pinetab2 baseline.
#
# 2026-05-21 update: per-series reconstruction (besser#22) completed
# 2026-05-21; pkgrel=6 (srcversion 0E16463F) on ohm soak-passed with
# the bounce-buffer + join-confirm-reset additions. The per-series
# manifest below is the authoritative set; cumulative-c5x-danctnix
# remains as historical fallback only.
#
# Until the per-series mirrors are reconstructed (kernel-agent followup
# issue), the bes2600 driver scope is satisfied by a single-file
# cumulative captured from the working hand-managed
# danctnix-besser-pkgbuild flow on boltzmann (see
# patches/driver/bes2600/cumulative-c5x-danctnix/README.md). This is
# the c5x stack as it shipped in pkgrel=3 on 2026-05-18.
includes:
# bes2600 driver (c5x stack as shipped in pkgrel=3) — single-file
# interim cumulative; per-series reconstruction tracked separately.
- driver/bes2600/cumulative-c5x-danctnix/
# close besser#1 — refuse multi-channel 5 GHz scans at driver boundary.
- driver/bes2600/scan-filter-5ghz-danctnix/
# GCC 15.2.1 build-fix for arm_neon.h + SHADOW_CALL_STACK interaction.
# Runtime no-op as long as the config has CONFIG_SHADOW_CALL_STACK=n
# (current ohm setting). Kept in the manifest for the day SCS gets
# re-enabled. See reference_arm64_scs_arm_neon_gcc15 memory.
- arch/arm64/scs-arm-neon-build-fix/
# close besser#18 — pending_record_lock SOFTIRQ-safe -> -unsafe inversion.
# Mirror of marfrit/bes2600-dkms#11 (d95453c). 5-site spin_lock -> _bh.
- driver/bes2600/queue-pending-record-lock-bh-danctnix/
# bounce-buffer fix for SDIO TX DMA OOB (KFENCE-detected on pkgrel=4 soak);
# the per-series mirror of marfrit/bes2600-dkms bes2600/tx-sdio-dma-oob.
# cumulative-c5x-danctnix did NOT include this — it was the regression
# surfaced during the per-series reconstruction.
- driver/bes2600/tx-sdio-dma-oob-danctnix/
# close besser#25 — wsm_reset + serialised unjoin on JOIN reject.
# cw1200 ancestor port (drivers/net/wireless/st/cw1200/sta.c:1339-1344)
# with bes2600-specific PASSIVE-gate compensation. pkgrel=6 verified.
- driver/bes2600/join-confirm-reset-danctnix/
# Explicitly NOT included (decision logged):
# - debian-copyright-fsf-address: Debian packaging metadata, not kernel
# - bare (non-danctnix) variants of the per-series mirrors: same
# root-path bug as the -danctnix variants per the 2026-05-18 audit
config:
source: hand-managed config file in boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/config
strategy: snapshot, fold to baseline, accept-new with rationale on diff
TODO: migrate config into kernel-agent flow once kconfig-by-manifest lands
package:
name: linux-pinetab2-danctnix-besser
versioning: "${baseline_ref}.kafr${pkgrel}" # e.g. v7.0-danctnix1.kafr1
install_mode: alongside # coexists with linux-pinetab2 if user wants A/B
conflicts: []
provides: [linux-pinetab2] # drop-in replacement; pacman can satisfy linux-pinetab2 deps
kernel_suffix: -danctnix-besser # vmlinuz-linux-pinetab2-danctnix-besser
bootloader: extlinux # PineTab2 standard
# PKGBUILD currently at:
# boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/PKGBUILD
# TARGET location (per issue #5):
# marfrit/marfrit-packages/arch/linux-pinetab2-danctnix-besser/PKGBUILD
# PENDING the kernel-agent template-rendering pipeline (b2sums regen,
# pkgrel bump, cumulative-patch generation from manifest scope).
template_at: TBD-marfrit-packages-arch-linux-pinetab2-danctnix-besser
replaces_dkms: bes2600-dkms # once cumulative series in this manifest is enough
verify:
bar1_ssh_heartbeat: required
bar2_pkg_version: required
bar3_module_loaded:
- bes2600 module loads
- wlan0 + bt0 (BT/UART) present after boot
- sdio_force_uhs=0 not needed (DMA-OOB-read fix in tx-sdio-dma-oob series)
bar4_per_patch_probe: opt-in
bar5_burn_in:
mode: opt-in
tests:
- "WiFi: 24h iperf3 to LAN host without rxhang"
- "PM: lid-close → wake cycles × 100 without bes2600 confirm-loss"
build_host:
primary: boltzmann # native aarch64 with ohm's identical .config
fallback: fermi # hertz LXD, ALARM aarch64
# No distcc per feedback_kernel_agent_no_distcc.md.
backup:
pre_install: hertz:/sparfuxdata/kernel-agent-backups/ohm/${replaced_version}/
# ─────────────────────────────────────────────────────────────────────
# OPEN — kernel-agent CLI work blocking full migration:
#
# 1. ka-promote: read includes[] above + apply_order field (to be added),
# concatenate referenced series into a single .patch in the build dir.
# Validate it applies cleanly on baseline.ref. Compute b2sum for
# template substitution.
#
# 2. PKGBUILD template renderer: stamp pkgrel, ${_cumulative_b2sum},
# ${_srctag} from manifest into a PKGBUILD draft at the
# template_at location. Sign + publish to packages.reauktion.de.
#
# 3. Orphan retirement: surface ~/src/besser/danctnix-besser-pkgbuild/
# (NO remote, ~/src/besser/marfrit-besser/... is canonical) to
# Markus / fourier campaign for working-state migration BEFORE
# deleting. See besser issue #17 for the regression that the
# orphan caused. After the kernel-agent flow is authoritative,
# delete both checkouts.
# ─────────────────────────────────────────────────────────────────────
@@ -0,0 +1,33 @@
From: Markus Fritsche <mfritsche@localhost>
Date: Tue, 24 Mar 2026 00:00:00 +0000
Subject: [PATCH] arm64: Kconfig: do not select HAVE_GCC_PLUGINS
GCC plugins intercept compilation in a way that ccache cannot observe:
the plugin shared objects inject code after preprocessing, so the
preprocessed-source hash that ccache computes does not capture plugin
output. Every object file built with a plugin is therefore a ccache
miss, effectively disabling the cache.
Remove the `select HAVE_GCC_PLUGINS` line from the ARM64 arch Kconfig.
HAVE_GCC_PLUGINS is the only gate that allows GCC_PLUGINS (and the
features that depend on it: STACKLEAK, LATENT_ENTROPY, the GCC-based
RANDSTRUCT variant) to be enabled; dropping it makes those options
invisible and leaves them off without requiring manual intervention in
every .config.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
1 file changed, 1 deletion(-)
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -229,7 +229,6 @@
select HAVE_FUNCTION_ERROR_INJECTION
select HAVE_FUNCTION_GRAPH_FREGS
select HAVE_FUNCTION_GRAPH_TRACER
- select HAVE_GCC_PLUGINS
select HAVE_HARDLOCKUP_DETECTOR_PERF if PERF_EVENTS && \
HW_PERF_EVENTS && HAVE_PERF_EVENTS_NMI
select HAVE_HW_BREAKPOINT if PERF_EVENTS
+24
View File
@@ -0,0 +1,24 @@
# patches/arch/arm64/
Cross-cutting patches that touch `arch/arm64/Kconfig` or other
non-board-specific arch-level files. Apply only where explicitly
manifested.
## Patches
### `0006-arm64-Kconfig-do-not-select-HAVE_GCC_PLUGINS.patch`
Local-build workaround that makes `arch/arm64` not select
`HAVE_GCC_PLUGINS`. **NOT for upstream** — it papers over a missing
host-side GCC plugin dependency at build time rather than fixing the
makepkg/distro packaging that should provide `gcc-plugin-devel` (or
equivalent) when needed.
Used on ampere when building the kernel from a clean Arch ARM userspace
that doesn't have the gcc plugins development headers installed. The
proper fix is to install the headers; this patch is a fallback for
when the user wants a working kernel without touching the userspace
package set.
Source: `github.com/marfrit/misc_patches/genbook/kernel/0006`
Author: Markus Fritsche
@@ -0,0 +1,36 @@
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 11:42:00 +0200
Subject: [PATCH] arm64: xor-neon: restore -ffixed-x18 when SHADOW_CALL_STACK=y
(GCC 15+ build fix)
GCC 15.2.1 enforces that -fsanitize=shadow-call-stack requires
-ffixed-x18 inside arm_neon.h's #pragma GCC target() blocks. The
existing CFLAGS_REMOVE_xor-neon.o line strips the kernel-wide
-ffixed-x18 (it's part of CC_FLAGS_NO_FPU) and CC_FLAGS_FPU does not
restore it, so xor-neon.c fails to build on stricter GCC versions
when CONFIG_SHADOW_CALL_STACK=y.
Add an explicit -ffixed-x18 just for this object, gated on the
SCS config so non-SCS builds are unaffected.
Build environment workaround; not a kernel-runtime bug.
---
arch/arm64/lib/Makefile | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile
index 1234567..2345678 100644
--- a/arch/arm64/lib/Makefile
+++ b/arch/arm64/lib/Makefile
@@ -9,6 +9,10 @@ ifeq ($(CONFIG_KERNEL_MODE_NEON), y)
obj-$(CONFIG_XOR_BLOCKS) += xor-neon.o
CFLAGS_xor-neon.o += $(CC_FLAGS_FPU)
CFLAGS_REMOVE_xor-neon.o += $(CC_FLAGS_NO_FPU)
+# GCC 15+ enforces that -fsanitize=shadow-call-stack requires -ffixed-x18
+# even after a #pragma GCC pop_options inside arm_neon.h. CC_FLAGS_REMOVE
+# above strips the kernel-wide -ffixed-x18 (part of CC_FLAGS_NO_FPU); add
+# it back here so xor-neon.c still compiles when SHADOW_CALL_STACK=y.
+CFLAGS_xor-neon.o += $(if $(CONFIG_SHADOW_CALL_STACK),-ffixed-x18)
endif
lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o
@@ -0,0 +1,50 @@
From 0aa15733f32277939a0523276bec8f092a67e28c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@localhost>
Date: Thu, 16 Apr 2026 23:53:05 +0200
Subject: [PATCH 5/9] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: add NPU
power-domain link and touchpad wakeup-source
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- i2c-hid touchpad: add wakeup-source so the PM core arms the IRQ
- Add NPU power domain (matching Rock 5B+ which has working suspend),
required for proper power-down sequencing.
Note: this DTS hunk is the suspect for breaking cold-boot eDP probe on
ampere (panel-edp WARN_ON + Fixed dependency cycle between /edp@fded0000
and panel) — kept as its own commit so it can be reverted in isolation
without losing the suspend/resume PM fixes.
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
index e3954851b0cb..cabfb380fe27 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -340,6 +340,7 @@ touchpad: touchpad@2c {
interrupt-parent = <&gpio1>;
interrupts = <RK_PD6 IRQ_TYPE_LEVEL_LOW>;
hid-descr-addr = <0x0020>;
+ wakeup-source;
};
};
@@ -455,6 +456,10 @@ mh248_irq_gpio: mh248-irq-gpio {
};
};
+&pd_npu {
+ domain-supply = <&vdd_npu_s0>;
+};
+
&pwm6 {
pinctrl-0 = <&pwm6m1_pins>;
status = "okay";
--
2.54.0
@@ -0,0 +1,66 @@
From dd545fa1532921fe9c63360a99c35f60e981aad2 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@reauktion.de>
Date: Sun, 17 May 2026 23:18:30 +0200
Subject: [PATCH] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: wire
internal microphone
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The CoolPi CM5 GenBook has an internal microphone wired to the ES8316
codec MIC1 input. The upstream DTS only declared Mic Jack on MIC2
(headset jack), so the DAPM graph has no path between MIC1 and a
named widget — internal mic capture cannot work regardless of mixer
settings.
Vendor coolpi-kernel reference (rk3588-cpcm5-genbook.dts):
rockchip,audio-routing =
"MIC1", "Main Mic",
"MIC2", "Headset Mic";
Mirror in the upstream audio-graph-card binding:
- widgets: split Mic Jack into Main Mic (internal) + Headset Mic
(external) so each input is addressable from ALSA UCM
- routing: MIC1 <- Main Mic, MIC2 <- Headset Mic
This patch is necessary but not sufficient for working internal mic
capture: a separate issue prevents the ES8316 RX aif_out widget from
powering up via DAPM when arecord opens the capture PCM (I2S OUT stays
inactive, bias_level stays Off). That second issue is not addressed
here — this patch only fixes the static DTS topology so a future codec
or audio-graph-card driver fix can complete the chain.
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
Generated-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
index e3954851b0cb..e95545eea16a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -377,11 +377,13 @@ &analog_sound {
pinctrl-names = "default";
pinctrl-0 = <&hp_det>;
- widgets = "Microphone", "Mic Jack",
+ widgets = "Microphone", "Main Mic",
+ "Microphone", "Headset Mic",
"Headphone", "Headphones",
"Speaker", "Speaker";
- routing = "MIC2", "Mic Jack",
+ routing = "MIC1", "Main Mic",
+ "MIC2", "Headset Mic",
"Headphones", "HPOL",
"Headphones", "HPOR",
"Amplifier", "HPOL",
@@ -633,3 +635,4 @@ &pwm15 {
pinctrl-0 = <&pwm15m3_pins>;
status = "okay";
};
+
--
2.54.0
+92
View File
@@ -0,0 +1,92 @@
# patches/driver/bes2600/
BES2600 WiFi driver patches (`drivers/staging/bes2600/*`, mainline-bound).
Mirrored from `marfrit/besser/patches/` on 2026-05-16.
Scope tag: `driver:bes2600` (see `fleet/ohm.yaml` for the consumer).
Consumer: ohm (PineTab2, RK3566 + BES2600 SDIO).
## Series taxonomy
30 series (15 base + 15 `-danctnix` siblings). The `-danctnix`
variants exist because vanilla series don't apply on the DanctNIX
kernel base (slightly different in-tree state for `drivers/staging/bes2600/*`).
Keep both as separate series until BES2600 lands upstream, then
collapse — issue #2 acceptance criterion.
Each series directory contains numbered `.patch` files plus
optionally a `0000-cover-letter.patch` for multi-patch series.
## Promotion eligibility (per series)
Marked here for the kernel-agent CLI (`ka-promote`) to pick up.
Markus to update as series mature. Default UNSET means "ask before
including in a build".
| Series | promote_eligible | Notes |
|------------------------------------|------------------|-------------------------------------------------------------|
| `debian-copyright-fsf-address` | unset | Debian packaging metadata; not kernel-side |
| `drop-dpd-file-paths` | unset | |
| `drop-dpd-file-paths-danctnix` | unset | DanctNIX sibling |
| `drop-orphan-file-io` | unset | |
| `drop-orphan-file-io-danctnix` | unset | DanctNIX sibling |
| `enable-testmode` | unset | |
| `factory-drop-kernel-write` | unset | |
| `factory-drop-kernel-write-danctnix` | unset | DanctNIX sibling |
| `factory-series` | unset | |
| `factory-thread-dev` | unset | |
| `lmac-recover-via-mmc-hw-reset` | unset | |
| `lmac-recover-via-mmc-hw-reset-danctnix` | unset | DanctNIX sibling |
| `pm-detect-firmware-unsupported` | unset | |
| `pm-detect-firmware-unsupported-danctnix` | unset | DanctNIX sibling |
| `pm-gate-on-handshake` | unset | |
| `pm-state-resync` | unset | |
| `pm-state-resync-danctnix` | unset | DanctNIX sibling |
| `pm-timeout-silence` | unset | |
| `pm-timeout-silence-danctnix` | unset | DanctNIX sibling |
| `pm-wake-consume-state` | unset | |
| `pm-wake-consume-state-danctnix` | unset | DanctNIX sibling |
| `remove-chardev-user-interface` | unset | Cross-ref `bes_chardev` merge regression (besser #17) |
| `scan-defer-backoff-tune` | unset | |
| `scan-defer-backoff-tune-danctnix` | unset | DanctNIX sibling |
| `scan-defer-on-reject` | unset | |
| `scan-defer-on-reject-danctnix` | unset | DanctNIX sibling |
| `staging-prep-series` | unset | 7-patch cover-letter series; upstream-staging-prep work |
| `staging-prep-series-danctnix` | unset | DanctNIX sibling |
| `tx-sdio-dma-oob` | unset | |
| `tx-sdio-dma-oob-danctnix` | unset | DanctNIX sibling |
## DKMS-to-in-tree transition path
`bes2600-dkms` (Mobian fork, in `marfrit/bes2600-dkms`) is the
out-of-tree shim that ohm currently uses for the BES2600 wifi+BT.
Once these `driver/bes2600/` series land in mainline (or at least in
DanctNIX's PineTab2 kernel base):
1. ohm's manifest drops the `bes2600-dkms` package dependency
2. `kernel-agent` builds the in-tree variant via the series listed here
3. `marfrit/bes2600-dkms` repo gets archived (kept as history)
4. PineTab2 buyers from then on get bes2600 directly out of the kernel
Track the dropdown in `fleet/ohm.yaml` (`replaces_dkms: bes2600-dkms`
once the cumulative series is enough to replace it).
## Cumulative-patch generation order
The current single-patch cumulative (`0001-bes2600-besser-cumulative-series.patch`
in the existing PKGBUILD) is generated in this order on boltzmann:
A, B, C v3, F, G, D, E, C2, c5.x, c6.x, c7, H
This is NOT alphabetical — `C2` follows `E` rather than coming after
`C v3`. `ka-promote` MUST honor an explicit series-ordering field
when concatenating, not sort by series name. Field name TBD; suggest
adding `apply_order:` to `fleet/ohm.yaml` (issue #5 will surface this
when the cumulative gets regenerated).
## References
- Issue: `git.reauktion.de/marfrit/kernel-agent/issues/2`
- Source repo: `git.reauktion.de/marfrit/besser/patches/`
- Consumer: ohm (`fleet/ohm.yaml`)
- Related: `bes2600-dkms`, `linux-pinetab2-danctnix-besser` PKGBUILD
@@ -0,0 +1,35 @@
# cumulative-c5x-danctnix — interim single-file cumulative
**Series role**: ohm manifest's bes2600 driver patchset, c5x stack as
shipped in `linux-pinetab2-danctnix-besser` pkgrel=3 on 2026-05-18.
## Why this is a single .patch and not split per-fix
The 12-ish per-series mirror PR (kernel-agent#17) of the boltzmann-side
`marfrit/besser` series produced patches with DKMS-style paths
(`bes2600/*` at root) rather than in-tree staging paths
(`drivers/staging/bes2600/*`), and at least one entry has corrupted
mixed-prefix headers (`a/drivers/staging/bes2600/foo.c b/bes2600/foo.c`).
Those series do NOT apply cleanly to the linux-pinetab2 baseline.
Audit performed 2026-05-18 during ohm migration:
- ka-promote ohm (using the per-series includes) produces a
172 644-byte cumulative touching 27 file paths, of which 11 are
bogus DKMS-style or mixed-prefix.
- The hand-curated `0001-bes2600-besser-cumulative-series.patch` from
the working `danctnix-besser-pkgbuild` (boltzmann) is 148 149 bytes
touching 48 distinct in-tree staging files — and is what pkgrel=3
actually builds with.
This single-file cumulative is staged here so the ohm migration can
ship through the kernel-agent flow today without first reconstructing
12 series-dirs. The proper per-series split is tracked separately —
see kernel-agent issue (TBD) for the rewrite.
## Provenance
- Source file: `boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/0001-bes2600-besser-cumulative-series.patch`
- Reflects c5x driver state in `marfrit/bes2600-dkms-mobian` branch as
of 2026-05-08, applied against `drivers/staging/bes2600/` in-tree.
- Series legend (A, B, C v3, F, G, D, E, C2, c5.x, c6.x, c7, H — NOT
alphabetical) per the danctnix-besser-pkgbuild changelog comments.
@@ -0,0 +1,38 @@
From f31c57adf736df52b3f393f2650920af98b8e8f1 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Fri, 24 Apr 2026 09:40:44 +0200
Subject: [PATCH] debian/copyright: drop obsolete FSF street address
The 'You should have received a copy ... write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA' paragraph flags the lintian tag
'old-fsf-address-in-copyright-file'. Debian prefers either no
address at all or an https://www.gnu.org/licenses/ reference;
in this file /usr/share/common-licenses/LGPL-2.1 is already
cited a few lines below, so the address is redundant. Replace
with the gnu.org URL per current FSF boilerplate.
Pre-existing text, no change to the licence terms.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
debian/copyright | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/debian/copyright b/debian/copyright
index 961fc90..3228eec 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -18,8 +18,7 @@ License: LGPL-2.1
License for more details.
.
You should have received a copy of the GNU Lesser General Public License
- along with this library; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ along with this library; if not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU Lesser General Public License
version 2.1 can be found in the file "/usr/share/common-licenses/LGPL-2.1".
--
2.53.0
@@ -0,0 +1,293 @@
From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:04:11 +0200
Subject: [PATCH] bes2600: drop BES2600_WRITE_DPD_TO_FILE kernel_*() file paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
bes_chardev.c carried three functions gated behind the
BES2600_WRITE_DPD_TO_FILE Kconfig/make-flag (default off):
- bes2600_chrdev_write_dpd_data_to_file()
filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write()
writing a raw DPD calibration blob back to
BES2600_DPD_PATH (default /data/cfg/bes2600_dpd.bin, an
Android-AOSP path).
- bes2600_chrdev_read_and_check_dpd_data()
filp_open(O_RDONLY) + kernel_read() reading the DPD blob
from either BES2600_DPD_GOLDEN_PATH (/data/cfg/…) or
BES2600_DEFAULT_DPD_PATH (/lib/firmware/bes2600_dpd.bin),
followed by a CRC/version sanity check.
- bes2600_chrdev_dpd_is_vaild() (sic), the CRC/version helper
used only by the read path.
Plus the bes_cdev.no_dpd field, its module_param, and two
intrusion sites in bes2600_chrdev_get_dpd_data() and
bes2600_chrdev_update_dpd_data() that invoke the above.
The Makefile defaults BES2600_WRITE_DPD_TO_FILE=n, so in a stock
build all of this is dead code. It is still a standing upstream
blocker for exactly the same reasons as the factory-txt write
path removed in the preceding patch:
- filp_open() + kernel_read()/kernel_write() bypass the
firmware-class abstraction and LSM-governed access control
that apply to /lib/firmware/.
- The write target /data/cfg/ is an Android AOSP convention
that does not exist on a Linux distribution and cannot be
created by the kernel anyway.
- A runtime DPD re-calibration is intended to reduce TX EVM
after temperature or aging drift; persisting the result via
kernel_write() is fundamentally a userspace concern (debugfs
dump + userspace tool is the expected route).
Remove the entire #ifdef BES2600_WRITE_DPD_TO_FILE block from
bes_chardev.c (including the inner #ifdef inside
bes2600_chrdev_read_and_check_dpd_data() guarding a
DPD_BIN_FILE_SIZE size check that only applied to the read-back-
its-own-write case), the no_dpd field and module_param, and the
two invocation sites. Drop the Kconfig/make-flag and the three
associated PATH macros from the Makefile. Net: -155 lines, no
remaining filp_open/kernel_read/kernel_write anywhere in
bes_chardev.c.
The in-memory DPD state path is unchanged: bes2600_chrdev_get_dpd_
buffer() still allocates a kmalloc'd buffer used by the firmware-
download path, bes2600_chrdev_update_dpd_data() still validates
the buffer's CRC and transitions bes2600_cdev.wait_state on
success, and bes2600_chrdev_free_dpd_data() still releases the
buffer on unload. Only the file-I/O side-channel is removed.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 12 ----
bes2600/bes_chardev.c | 143 ------------------------------------------
2 files changed, 155 deletions(-)
diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile
index 2c1a850..0dd3606 100644
--- a/drivers/staging/bes2600/Makefile
+++ b/drivers/staging/bes2600/Makefile
@@ -28,7 +28,6 @@ CONFIG_BES2600_WIFI_BOOT_ON ?= y
CONFIG_BES2600_BT_BOOT_ON ?= n
BES2600_GPIO_WAKEUP_AP ?= n
-BES2600_WRITE_DPD_TO_FILE ?= n
BES2600_TX_MORE_RETRY ?= n
# bes evb
@@ -93,12 +92,6 @@ ccflags-y += -DBES_UNIFIED_PM
ccflags-y += -DBES_SDIO_OPTIMIZED_LEN
ccflags-y += -DBES2600_HOST_TIMESTAMP_DEBUG
-ifeq ($(BES2600_WRITE_DPD_TO_FILE),y)
-BES2600_DPD_PATH ?= /data/cfg/bes2600_dpd.bin
-BES2600_DEFAULT_DPD_PATH ?= /lib/firmware/bes2600_dpd.bin
-BES2600_DPD_GOLDEN_PATH ?= /data/cfg/bes2600_dpd_golden.bin
-endif
-
ifeq ($(BES2600_DUMP_FW_DPD_LOG),y)
BES2600_DPD_LOG_PATH ?= /data/applog/bes2600_dpd_log.log
endif
@@ -135,9 +128,6 @@ ccflags-y += $(call boolen_flag,BSS_LOSS_CHECK,y)
ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_PATH)
ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_DEVICE)
ccflags-y += $(call string_flag,BES2600_DRV_VERSION)
-ccflags-y += $(call string_flag,BES2600_DPD_PATH)
-ccflags-y += $(call string_flag,BES2600_DEFAULT_DPD_PATH)
-ccflags-y += $(call string_flag,BES2600_DPD_GOLDEN_PATH)
ccflags-y += $(call boolen_flag,BES2600_INDEPENDENT_EVB,y)
ccflags-y += $(call boolen_flag,BES2600_INTEGRATED_MODULE_V1,y)
@@ -159,8 +149,6 @@ ccflags-y += $(call boolen_flag,FACTORY_SAVE_MULTI_PATH,y)
ccflags-y += $(call boolen_flag,FACTORY_CRC_CHECK,y)
ccflags-y += $(call boolen_flag,BES2600_GPIO_WAKEUP_AP,y)
-ccflags-y += $(call boolen_flag,BES2600_WRITE_DPD_TO_FILE,y)
-
ccflags-y += $(call boolen_flag,BES2600_DUMP_FW_DPD_LOG,y)
ccflags-y += $(call string_flag,BES2600_DPD_LOG_PATH)
diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c
index e2e4f1b..a02d6d9 100644
--- a/drivers/staging/bes2600/bes_chardev.c
+++ b/drivers/staging/bes2600/bes_chardev.c
@@ -63,9 +63,6 @@ struct bes_cdev {
struct delayed_work probe_timeout_work;
enum bus_probe_state bus_probe;
struct work_struct wifi_force_close_work;
-#ifdef BES2600_WRITE_DPD_TO_FILE
- int no_dpd;
-#endif
enum pend_read_op read_flag;
enum wakeup_event wakeup_by_event; /* used to filter unwanted event wakeup reason report */
u16 wakeup_state; /* for userspace check wakeup reason */
@@ -85,9 +82,6 @@ struct bes2600_op_map {
static struct bes_cdev bes2600_cdev;
module_param_named(fw_type, bes2600_cdev.fw_type, int, 0644);
-#ifdef BES2600_WRITE_DPD_TO_FILE
-module_param_named(no_dpd, bes2600_cdev.no_dpd, int, 0644);
-#endif
extern int bes2600_register_net_dev(struct sbus_priv *bus_priv);
extern int bes2600_unregister_net_dev(struct sbus_priv *bus_priv);
@@ -269,137 +263,8 @@ static int bes2600_chrdev_check_system_close_internal(void)
-#ifdef BES2600_WRITE_DPD_TO_FILE
-static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size)
-{
- int ret = 0;
- struct file *fp;
-
- if (buffer == NULL || size == 0)
- return 0;
-
- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
- if (IS_ERR(fp)) {
- bes_err("BES2600 : can't open %s\n",path);
- return -1;
- }
-
- ret = kernel_write(fp, buffer, size, &fp->f_pos);
- if (ret < 0)
- bes_err("write dpd to file failed\n");
-
- filp_close(fp,NULL);
-
- bes_devel("write dpd to %s\n", path);
-
- return ret;
-}
-
-static bool bes2600_chrdev_dpd_is_vaild(u8 *dpd_data)
-{
- u32 cal_crc = 0;
- u32 dpd_crc = le32_to_cpup((__le32 *)(dpd_data));
- u32 dpd_ver = le32_to_cpup((__le32 *)(dpd_data + DPD_VERSION_OFFSET));
-
- /* check version */
- if (dpd_ver < DPD_CUR_VERSION)
- return false;
-
- cal_crc ^= 0xffffffffL;
- cal_crc = crc32_le(cal_crc, dpd_data + 4, DPD_BIN_SIZE - 4);
- cal_crc ^= 0xffffffffL;
-
- /* check if the dpd data is valid */
- if (cal_crc != dpd_crc) {
- bes_err(
- "bes2600 dpd data from file check failed, calc_crc:0x%08x dpd_crc: 0x%08x\n",
- cal_crc, dpd_crc);
- return false;
- }
-
- return true;
-}
-
-static int bes2600_chrdev_read_and_check_dpd_data(const char *file, u8 **data, u32 *len)
-{
- int ret = 0;
- u8* read_data = NULL;
- struct file *fp;
-
- /* open file */
- fp = filp_open(file, O_RDONLY, 0);//S_IRUSR
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",file);
- return -1;
- }
-
-#ifdef BES2600_WRITE_DPD_TO_FILE
- if (fp->f_inode->i_size != DPD_BIN_FILE_SIZE) {
- bes_err(
- "bes2600 dpd data file size check failed, read_size: %lld file_size: %d\n",
- fp->f_inode->i_size, DPD_BIN_FILE_SIZE);
- filp_close(fp, NULL);
- return -1;
- }
-#endif
-
- /* allocate memory for storing reading data */
- read_data = kmalloc(fp->f_inode->i_size, GFP_KERNEL);
- if (read_data == NULL) {
- bes_devel("%s alloc mem fail\n", __func__);
- goto err1;
- }
-
- /* read data from file */
- ret = kernel_read(fp, read_data, fp->f_inode->i_size, &fp->f_pos);
- if (ret < DPD_BIN_SIZE) {
- bes_err("%s read fail, ret=%d\n", __func__, ret);
- goto err2;
- }
-
- /* check dpd version and crc */
- if (!bes2600_chrdev_dpd_is_vaild(read_data))
- goto err2;
-
- /* close file */
- filp_close(fp, NULL);
-
- /* copy data to external */
- *data = read_data;
- *len = DPD_BIN_SIZE;;
-
- /* output debug information */
- bes_devel("read dpd data from %s\n", file);
-
- return 0;
-
-err2:
- kfree(read_data);
-err1:
- filp_close(fp, NULL);
- *data = NULL;
- *len = 0;
- return -1;
-}
-#endif
-
const u8* bes2600_chrdev_get_dpd_data(u32 *len)
{
-#ifdef BES2600_WRITE_DPD_TO_FILE
- if (!bes2600_cdev.dpd_calied && bes2600_cdev.no_dpd) {
- /* read dpd data from file that stores factory dpd calibration data */
- if ((bes2600_chrdev_read_and_check_dpd_data(BES2600_DPD_GOLDEN_PATH,
- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0) &&
- (bes2600_chrdev_read_and_check_dpd_data(BES2600_DEFAULT_DPD_PATH,
- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0)) {
- bes_err("%s read dpd data fail\n", __func__);
- return NULL;
- } else {
- bes2600_cdev.dpd_calied = true;
- }
- }
-#endif
-
if (!bes2600_cdev.dpd_calied)
return NULL;
if (len)
@@ -460,14 +325,6 @@ int bes2600_chrdev_update_dpd_data(void)
}
spin_unlock(&bes2600_cdev.status_lock);
-#ifdef BES2600_WRITE_DPD_TO_FILE
- /* write dpd data to file */
- memset(bes2600_cdev.dpd_data + DPD_BIN_SIZE, 0, DPD_BIN_FILE_SIZE - DPD_BIN_SIZE);
- bes2600_chrdev_write_dpd_data_to_file(BES2600_DPD_PATH,
- bes2600_cdev.dpd_data, DPD_BIN_FILE_SIZE);
-#endif
-
-
return 0;
}
--
2.53.0
@@ -0,0 +1,293 @@
From 699871fdc6bf1bed6d919732820183e57faeaddc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:04:11 +0200
Subject: [PATCH] bes2600: drop BES2600_WRITE_DPD_TO_FILE kernel_*() file paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
bes_chardev.c carried three functions gated behind the
BES2600_WRITE_DPD_TO_FILE Kconfig/make-flag (default off):
- bes2600_chrdev_write_dpd_data_to_file()
filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write()
writing a raw DPD calibration blob back to
BES2600_DPD_PATH (default /data/cfg/bes2600_dpd.bin, an
Android-AOSP path).
- bes2600_chrdev_read_and_check_dpd_data()
filp_open(O_RDONLY) + kernel_read() reading the DPD blob
from either BES2600_DPD_GOLDEN_PATH (/data/cfg/…) or
BES2600_DEFAULT_DPD_PATH (/lib/firmware/bes2600_dpd.bin),
followed by a CRC/version sanity check.
- bes2600_chrdev_dpd_is_vaild() (sic), the CRC/version helper
used only by the read path.
Plus the bes_cdev.no_dpd field, its module_param, and two
intrusion sites in bes2600_chrdev_get_dpd_data() and
bes2600_chrdev_update_dpd_data() that invoke the above.
The Makefile defaults BES2600_WRITE_DPD_TO_FILE=n, so in a stock
build all of this is dead code. It is still a standing upstream
blocker for exactly the same reasons as the factory-txt write
path removed in the preceding patch:
- filp_open() + kernel_read()/kernel_write() bypass the
firmware-class abstraction and LSM-governed access control
that apply to /lib/firmware/.
- The write target /data/cfg/ is an Android AOSP convention
that does not exist on a Linux distribution and cannot be
created by the kernel anyway.
- A runtime DPD re-calibration is intended to reduce TX EVM
after temperature or aging drift; persisting the result via
kernel_write() is fundamentally a userspace concern (debugfs
dump + userspace tool is the expected route).
Remove the entire #ifdef BES2600_WRITE_DPD_TO_FILE block from
bes_chardev.c (including the inner #ifdef inside
bes2600_chrdev_read_and_check_dpd_data() guarding a
DPD_BIN_FILE_SIZE size check that only applied to the read-back-
its-own-write case), the no_dpd field and module_param, and the
two invocation sites. Drop the Kconfig/make-flag and the three
associated PATH macros from the Makefile. Net: -155 lines, no
remaining filp_open/kernel_read/kernel_write anywhere in
bes_chardev.c.
The in-memory DPD state path is unchanged: bes2600_chrdev_get_dpd_
buffer() still allocates a kmalloc'd buffer used by the firmware-
download path, bes2600_chrdev_update_dpd_data() still validates
the buffer's CRC and transitions bes2600_cdev.wait_state on
success, and bes2600_chrdev_free_dpd_data() still releases the
buffer on unload. Only the file-I/O side-channel is removed.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 12 ----
bes2600/bes_chardev.c | 143 ------------------------------------------
2 files changed, 155 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 2c1a850..0dd3606 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -28,7 +28,6 @@ CONFIG_BES2600_WIFI_BOOT_ON ?= y
CONFIG_BES2600_BT_BOOT_ON ?= n
BES2600_GPIO_WAKEUP_AP ?= n
-BES2600_WRITE_DPD_TO_FILE ?= n
BES2600_TX_MORE_RETRY ?= n
# bes evb
@@ -93,12 +92,6 @@ ccflags-y += -DBES_UNIFIED_PM
ccflags-y += -DBES_SDIO_OPTIMIZED_LEN
ccflags-y += -DBES2600_HOST_TIMESTAMP_DEBUG
-ifeq ($(BES2600_WRITE_DPD_TO_FILE),y)
-BES2600_DPD_PATH ?= /data/cfg/bes2600_dpd.bin
-BES2600_DEFAULT_DPD_PATH ?= /lib/firmware/bes2600_dpd.bin
-BES2600_DPD_GOLDEN_PATH ?= /data/cfg/bes2600_dpd_golden.bin
-endif
-
ifeq ($(BES2600_DUMP_FW_DPD_LOG),y)
BES2600_DPD_LOG_PATH ?= /data/applog/bes2600_dpd_log.log
endif
@@ -135,9 +128,6 @@ ccflags-y += $(call boolen_flag,BSS_LOSS_CHECK,y)
ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_PATH)
ccflags-y += $(call string_flag,BES2600_LOAD_FW_TOOL_DEVICE)
ccflags-y += $(call string_flag,BES2600_DRV_VERSION)
-ccflags-y += $(call string_flag,BES2600_DPD_PATH)
-ccflags-y += $(call string_flag,BES2600_DEFAULT_DPD_PATH)
-ccflags-y += $(call string_flag,BES2600_DPD_GOLDEN_PATH)
ccflags-y += $(call boolen_flag,BES2600_INDEPENDENT_EVB,y)
ccflags-y += $(call boolen_flag,BES2600_INTEGRATED_MODULE_V1,y)
@@ -159,8 +149,6 @@ ccflags-y += $(call boolen_flag,FACTORY_SAVE_MULTI_PATH,y)
ccflags-y += $(call boolen_flag,FACTORY_CRC_CHECK,y)
ccflags-y += $(call boolen_flag,BES2600_GPIO_WAKEUP_AP,y)
-ccflags-y += $(call boolen_flag,BES2600_WRITE_DPD_TO_FILE,y)
-
ccflags-y += $(call boolen_flag,BES2600_DUMP_FW_DPD_LOG,y)
ccflags-y += $(call string_flag,BES2600_DPD_LOG_PATH)
diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c
index e2e4f1b..a02d6d9 100644
--- a/bes2600/bes_chardev.c
+++ b/bes2600/bes_chardev.c
@@ -63,9 +63,6 @@ struct bes_cdev {
struct delayed_work probe_timeout_work;
enum bus_probe_state bus_probe;
struct work_struct wifi_force_close_work;
-#ifdef BES2600_WRITE_DPD_TO_FILE
- int no_dpd;
-#endif
enum pend_read_op read_flag;
enum wakeup_event wakeup_by_event; /* used to filter unwanted event wakeup reason report */
u16 wakeup_state; /* for userspace check wakeup reason */
@@ -85,9 +82,6 @@ struct bes2600_op_map {
static struct bes_cdev bes2600_cdev;
module_param_named(fw_type, bes2600_cdev.fw_type, int, 0644);
-#ifdef BES2600_WRITE_DPD_TO_FILE
-module_param_named(no_dpd, bes2600_cdev.no_dpd, int, 0644);
-#endif
extern int bes2600_register_net_dev(struct sbus_priv *bus_priv);
extern int bes2600_unregister_net_dev(struct sbus_priv *bus_priv);
@@ -269,137 +263,8 @@ static int bes2600_chrdev_check_system_close_internal(void)
-#ifdef BES2600_WRITE_DPD_TO_FILE
-static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size)
-{
- int ret = 0;
- struct file *fp;
-
- if (buffer == NULL || size == 0)
- return 0;
-
- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
- if (IS_ERR(fp)) {
- bes_err("BES2600 : can't open %s\n",path);
- return -1;
- }
-
- ret = kernel_write(fp, buffer, size, &fp->f_pos);
- if (ret < 0)
- bes_err("write dpd to file failed\n");
-
- filp_close(fp,NULL);
-
- bes_devel("write dpd to %s\n", path);
-
- return ret;
-}
-
-static bool bes2600_chrdev_dpd_is_vaild(u8 *dpd_data)
-{
- u32 cal_crc = 0;
- u32 dpd_crc = le32_to_cpup((__le32 *)(dpd_data));
- u32 dpd_ver = le32_to_cpup((__le32 *)(dpd_data + DPD_VERSION_OFFSET));
-
- /* check version */
- if (dpd_ver < DPD_CUR_VERSION)
- return false;
-
- cal_crc ^= 0xffffffffL;
- cal_crc = crc32_le(cal_crc, dpd_data + 4, DPD_BIN_SIZE - 4);
- cal_crc ^= 0xffffffffL;
-
- /* check if the dpd data is valid */
- if (cal_crc != dpd_crc) {
- bes_err(
- "bes2600 dpd data from file check failed, calc_crc:0x%08x dpd_crc: 0x%08x\n",
- cal_crc, dpd_crc);
- return false;
- }
-
- return true;
-}
-
-static int bes2600_chrdev_read_and_check_dpd_data(const char *file, u8 **data, u32 *len)
-{
- int ret = 0;
- u8* read_data = NULL;
- struct file *fp;
-
- /* open file */
- fp = filp_open(file, O_RDONLY, 0);//S_IRUSR
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",file);
- return -1;
- }
-
-#ifdef BES2600_WRITE_DPD_TO_FILE
- if (fp->f_inode->i_size != DPD_BIN_FILE_SIZE) {
- bes_err(
- "bes2600 dpd data file size check failed, read_size: %lld file_size: %d\n",
- fp->f_inode->i_size, DPD_BIN_FILE_SIZE);
- filp_close(fp, NULL);
- return -1;
- }
-#endif
-
- /* allocate memory for storing reading data */
- read_data = kmalloc(fp->f_inode->i_size, GFP_KERNEL);
- if (read_data == NULL) {
- bes_devel("%s alloc mem fail\n", __func__);
- goto err1;
- }
-
- /* read data from file */
- ret = kernel_read(fp, read_data, fp->f_inode->i_size, &fp->f_pos);
- if (ret < DPD_BIN_SIZE) {
- bes_err("%s read fail, ret=%d\n", __func__, ret);
- goto err2;
- }
-
- /* check dpd version and crc */
- if (!bes2600_chrdev_dpd_is_vaild(read_data))
- goto err2;
-
- /* close file */
- filp_close(fp, NULL);
-
- /* copy data to external */
- *data = read_data;
- *len = DPD_BIN_SIZE;;
-
- /* output debug information */
- bes_devel("read dpd data from %s\n", file);
-
- return 0;
-
-err2:
- kfree(read_data);
-err1:
- filp_close(fp, NULL);
- *data = NULL;
- *len = 0;
- return -1;
-}
-#endif
-
const u8* bes2600_chrdev_get_dpd_data(u32 *len)
{
-#ifdef BES2600_WRITE_DPD_TO_FILE
- if (!bes2600_cdev.dpd_calied && bes2600_cdev.no_dpd) {
- /* read dpd data from file that stores factory dpd calibration data */
- if ((bes2600_chrdev_read_and_check_dpd_data(BES2600_DPD_GOLDEN_PATH,
- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0) &&
- (bes2600_chrdev_read_and_check_dpd_data(BES2600_DEFAULT_DPD_PATH,
- &bes2600_cdev.dpd_data, &bes2600_cdev.dpd_len) < 0)) {
- bes_err("%s read dpd data fail\n", __func__);
- return NULL;
- } else {
- bes2600_cdev.dpd_calied = true;
- }
- }
-#endif
-
if (!bes2600_cdev.dpd_calied)
return NULL;
if (len)
@@ -460,14 +325,6 @@ int bes2600_chrdev_update_dpd_data(void)
}
spin_unlock(&bes2600_cdev.status_lock);
-#ifdef BES2600_WRITE_DPD_TO_FILE
- /* write dpd data to file */
- memset(bes2600_cdev.dpd_data + DPD_BIN_SIZE, 0, DPD_BIN_FILE_SIZE - DPD_BIN_SIZE);
- bes2600_chrdev_write_dpd_data_to_file(BES2600_DPD_PATH,
- bes2600_cdev.dpd_data, DPD_BIN_FILE_SIZE);
-#endif
-
-
return 0;
}
--
2.53.0
@@ -0,0 +1,168 @@
From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:19:27 +0200
Subject: [PATCH] bes2600: drop orphan DATA_DUMP_OBSERVE and access_file() file
I/O
Two dead-in-default-build file-I/O sites remain in the driver
after the factory and chardev kernel_*() removals in the preceding
patches:
- bes_fw.c DATA_DUMP_OBSERVE: four #ifdef DATA_DUMP_OBSERVE
blocks built around the firmware-download path that open
/lib/firmware/bes2002_fw_write.bin via filp_open(O_CREAT |
O_RDWR), then log every transmitted firmware chunk via
vfs_write() inside a get_fs()/set_fs(KERNEL_DS) wrapper. The
controlling #define at bes_fw.c line 128 is commented out
('//#define DATA_DUMP_OBSERVE'), so none of this is ever
compiled in a stock build.
- main.c access_file(): a helper gated on
GET_MAC_ADDR_METHOD == 2 || == 3 (default 4) using the same
get_fs()/set_fs()/vfs_read()/vfs_write() pattern. No caller
in the tree references it -- it was orphaned when the methods
that consumed it were refactored out.
Both sites are unbuildable on modern kernels anyway: get_fs() /
set_fs() were removed from arm64 and the generic uaccess path in
the v5.10 era, and the legacy vfs_read() / vfs_write() variants
that took userspace-typed buffers went with them. The in-kernel
replacements would be kernel_read() / kernel_write(), which this
series is explicitly removing from the driver.
Remove both blocks, the commented-out '//#define DATA_DUMP_OBSERVE'
line, and the access_file() definition and its #if gate. No
behaviour change in any default or non-default build, because
nothing compiled or linked in the first place. After this patch
the driver contains zero filp_open / kernel_read / kernel_write /
vfs_read / vfs_write references -- a precondition for a
drivers/staging/bes2600/ linux-wireless RFC.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_fw.c | 34 ----------------------------------
bes2600/main.c | 35 -----------------------------------
2 files changed, 69 deletions(-)
diff --git a/drivers/staging/bes2600/bes_fw.c b/drivers/staging/bes2600/bes_fw.c
index 133c945..d612c3c 100644
--- a/drivers/staging/bes2600/bes_fw.c
+++ b/drivers/staging/bes2600/bes_fw.c
@@ -125,8 +125,6 @@ int bes_host_slave_sync(struct bes2600_common *hw_priv)
}
*/
-//#define DATA_DUMP_OBSERVE
-
static int bes_firmware_download_write_reg(struct platform_fw_t *fw_data, u32 addr, u32 val)
{
u8 frame_num = 0;
@@ -468,14 +466,6 @@ static int bes_firmware_download(struct platform_fw_t *fw_data, const char *fw_n
const struct firmware *fw_bin;
-#ifdef DATA_DUMP_OBSERVE
- char *observe;
- size_t observe_len;
- loff_t observe_off = 0;
- mm_segment_t old_fs;
- struct file *observe_file = NULL;
-#endif
-
struct fw_msg_hdr_t header;
struct fw_info_t fw_info;
struct download_fw_t download_addr;
@@ -583,14 +573,6 @@ retry:
}
download_addr.addr = fw_info.addr;
-#ifdef DATA_DUMP_OBSERVE
- observe_file = filp_open("/lib/firmware/bes2002_fw_write.bin", O_CREAT | O_RDWR, 0);
- if (IS_ERR(observe_file)) {
- bes_err("create data_dump file err:%ld\n", IS_ERR(observe_file));
- observe_file = NULL;
- }
-#endif
-
while (code_length) {
#if 1
@@ -640,17 +622,6 @@ retry:
//mdelay(5000);
bes_devel("tx_download_firmware_data:%x %d\n", download_addr.addr, length);
-#ifdef DATA_DUMP_OBSERVE
- if (observe_file) {
- observe = (char *)(long_buf + sizeof(struct fw_msg_hdr_t) + sizeof(struct download_fw_t));
- observe_len = length - sizeof(struct fw_msg_hdr_t) - sizeof(struct download_fw_t);
- old_fs = get_fs();
- set_fs(KERNEL_DS);
- vfs_write(observe_file, observe, observe_len, &observe_off);
- set_fs(old_fs);
- }
-#endif
-
ret = bes2600_data_write(long_buf, length > 512 ? length : 512);
if (ret) {
bes_err("tx download fw data err:%d\n", ret);
@@ -832,11 +803,6 @@ retry:
err2:
kfree(long_buf);
-#ifdef DATA_DUMP_OBSERVE
- if (observe_file) {
- filp_close(observe_file, NULL);
- }
-#endif
err1:
kfree(short_buf);
release_firmware(fw_bin);
diff --git a/drivers/staging/bes2600/main.c b/drivers/staging/bes2600/main.c
index 6ed6b15..9d2aac5 100644
--- a/drivers/staging/bes2600/main.c
+++ b/drivers/staging/bes2600/main.c
@@ -790,41 +790,6 @@ void bes2600_core_release(struct bes2600_common *self)
return;
}
-#if (GET_MAC_ADDR_METHOD == 2) || (GET_MAC_ADDR_METHOD == 3) /* To use macaddr and ps mode of customers */
-int access_file(char *path, char *buffer, int size, int isRead)
-{
- int ret=0;
- struct file *fp;
- mm_segment_t old_fs = get_fs();
-
- if(isRead)
- fp = filp_open(path,O_RDONLY,S_IRUSR);
- else
- fp = filp_open(path,O_CREAT|O_WRONLY,S_IRUSR);
-
- if (IS_ERR(fp)) {
- bes_err("BES2600 : can't open %s\n", path);
- return -1;
- }
-
- if (isRead) {
- fp->f_pos = 0;
- set_fs(KERNEL_DS);
- ret = vfs_read(fp,buffer,size,&fp->f_pos);
- set_fs(old_fs);
- } else {
- fp->f_pos = 0;
- set_fs(KERNEL_DS);
- ret = vfs_write(fp,buffer,size,&fp->f_pos);
- set_fs(old_fs);
- }
- filp_close(fp,NULL);
-
- bes_info("BES2600 : access_file return code(%d)\n", ret);
- return ret;
-}
-#endif
-
int bes2600_wifi_start(struct bes2600_common *hw_priv)
{
int ret = 0, if_id;
--
2.53.0
@@ -0,0 +1,168 @@
From 44e085360fec09c1c1f7b35a23ec679f7065d3f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:19:27 +0200
Subject: [PATCH] bes2600: drop orphan DATA_DUMP_OBSERVE and access_file() file
I/O
Two dead-in-default-build file-I/O sites remain in the driver
after the factory and chardev kernel_*() removals in the preceding
patches:
- bes_fw.c DATA_DUMP_OBSERVE: four #ifdef DATA_DUMP_OBSERVE
blocks built around the firmware-download path that open
/lib/firmware/bes2002_fw_write.bin via filp_open(O_CREAT |
O_RDWR), then log every transmitted firmware chunk via
vfs_write() inside a get_fs()/set_fs(KERNEL_DS) wrapper. The
controlling #define at bes_fw.c line 128 is commented out
('//#define DATA_DUMP_OBSERVE'), so none of this is ever
compiled in a stock build.
- main.c access_file(): a helper gated on
GET_MAC_ADDR_METHOD == 2 || == 3 (default 4) using the same
get_fs()/set_fs()/vfs_read()/vfs_write() pattern. No caller
in the tree references it -- it was orphaned when the methods
that consumed it were refactored out.
Both sites are unbuildable on modern kernels anyway: get_fs() /
set_fs() were removed from arm64 and the generic uaccess path in
the v5.10 era, and the legacy vfs_read() / vfs_write() variants
that took userspace-typed buffers went with them. The in-kernel
replacements would be kernel_read() / kernel_write(), which this
series is explicitly removing from the driver.
Remove both blocks, the commented-out '//#define DATA_DUMP_OBSERVE'
line, and the access_file() definition and its #if gate. No
behaviour change in any default or non-default build, because
nothing compiled or linked in the first place. After this patch
the driver contains zero filp_open / kernel_read / kernel_write /
vfs_read / vfs_write references -- a precondition for a
drivers/staging/bes2600/ linux-wireless RFC.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_fw.c | 34 ----------------------------------
bes2600/main.c | 35 -----------------------------------
2 files changed, 69 deletions(-)
diff --git a/bes2600/bes_fw.c b/bes2600/bes_fw.c
index 133c945..d612c3c 100644
--- a/bes2600/bes_fw.c
+++ b/bes2600/bes_fw.c
@@ -125,8 +125,6 @@ int bes_host_slave_sync(struct bes2600_common *hw_priv)
}
*/
-//#define DATA_DUMP_OBSERVE
-
static int bes_firmware_download_write_reg(struct platform_fw_t *fw_data, u32 addr, u32 val)
{
u8 frame_num = 0;
@@ -468,14 +466,6 @@ static int bes_firmware_download(struct platform_fw_t *fw_data, const char *fw_n
const struct firmware *fw_bin;
-#ifdef DATA_DUMP_OBSERVE
- char *observe;
- size_t observe_len;
- loff_t observe_off = 0;
- mm_segment_t old_fs;
- struct file *observe_file = NULL;
-#endif
-
struct fw_msg_hdr_t header;
struct fw_info_t fw_info;
struct download_fw_t download_addr;
@@ -583,14 +573,6 @@ retry:
}
download_addr.addr = fw_info.addr;
-#ifdef DATA_DUMP_OBSERVE
- observe_file = filp_open("/lib/firmware/bes2002_fw_write.bin", O_CREAT | O_RDWR, 0);
- if (IS_ERR(observe_file)) {
- bes_err("create data_dump file err:%ld\n", IS_ERR(observe_file));
- observe_file = NULL;
- }
-#endif
-
while (code_length) {
#if 1
@@ -640,17 +622,6 @@ retry:
//mdelay(5000);
bes_devel("tx_download_firmware_data:%x %d\n", download_addr.addr, length);
-#ifdef DATA_DUMP_OBSERVE
- if (observe_file) {
- observe = (char *)(long_buf + sizeof(struct fw_msg_hdr_t) + sizeof(struct download_fw_t));
- observe_len = length - sizeof(struct fw_msg_hdr_t) - sizeof(struct download_fw_t);
- old_fs = get_fs();
- set_fs(KERNEL_DS);
- vfs_write(observe_file, observe, observe_len, &observe_off);
- set_fs(old_fs);
- }
-#endif
-
ret = bes2600_data_write(long_buf, length > 512 ? length : 512);
if (ret) {
bes_err("tx download fw data err:%d\n", ret);
@@ -832,11 +803,6 @@ retry:
err2:
kfree(long_buf);
-#ifdef DATA_DUMP_OBSERVE
- if (observe_file) {
- filp_close(observe_file, NULL);
- }
-#endif
err1:
kfree(short_buf);
release_firmware(fw_bin);
diff --git a/bes2600/main.c b/bes2600/main.c
index 6ed6b15..9d2aac5 100644
--- a/bes2600/main.c
+++ b/bes2600/main.c
@@ -790,41 +790,6 @@ void bes2600_core_release(struct bes2600_common *self)
return;
}
-#if (GET_MAC_ADDR_METHOD == 2) || (GET_MAC_ADDR_METHOD == 3) /* To use macaddr and ps mode of customers */
-int access_file(char *path, char *buffer, int size, int isRead)
-{
- int ret=0;
- struct file *fp;
- mm_segment_t old_fs = get_fs();
-
- if(isRead)
- fp = filp_open(path,O_RDONLY,S_IRUSR);
- else
- fp = filp_open(path,O_CREAT|O_WRONLY,S_IRUSR);
-
- if (IS_ERR(fp)) {
- bes_err("BES2600 : can't open %s\n", path);
- return -1;
- }
-
- if (isRead) {
- fp->f_pos = 0;
- set_fs(KERNEL_DS);
- ret = vfs_read(fp,buffer,size,&fp->f_pos);
- set_fs(old_fs);
- } else {
- fp->f_pos = 0;
- set_fs(KERNEL_DS);
- ret = vfs_write(fp,buffer,size,&fp->f_pos);
- set_fs(old_fs);
- }
- filp_close(fp,NULL);
-
- bes_info("BES2600 : access_file return code(%d)\n", ret);
- return ret;
-}
-#endif
-
int bes2600_wifi_start(struct bes2600_common *hw_priv)
{
int ret = 0, if_id;
--
2.53.0
@@ -0,0 +1,143 @@
From 9398d3028bc9d2f4ccbf8e830f8e9799bf065ce4 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:04:27 +0200
Subject: [PATCH] bes2600: enable CONFIG_BES2600_TESTMODE by default + fix
bit-rotted testmode plumbing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The driver implements a mac80211 testmode_cmd operation that dispatches
to a set of vendor commands (GET_TX_POWER_LEVEL, GET_TX_POWER_RANGE,
SET_SNAP_FRAME, TSM_STATS, GET_ROAM_DELAY, GET_STREAM, etc) plus the
BES2600 RF-test path (bes2600_vendor_rf_cmd → firmware
patch_wifi_testMode). The testmode handlers and the .testmode_cmd
binding in struct ieee80211_ops are conditionally compiled under
CONFIG_BES2600_TESTMODE, which previously defaulted to n.
Flip the Makefile default from n to y so wifi_testmode_cmd.o is
included in the build and the .testmode_cmd op is populated. On the
PineTab2 target kernel (linux-pinetab2 6.19.10-danctnix1, built with
CONFIG_NL80211_TESTMODE=y) this exposes the BES2600 RF-test surface
through the standard nl80211 testmode interface ('iw phy0 ...').
This also makes visible two classes of bit-rot that had accumulated
while nobody was building with CONFIG_BES2600_TESTMODE=y:
1. sta.c contains ~41 calls to bes2600_info() / bes2600_err() /
bes2600_warn() / bes2600_dbg() / bes2600_err_with_cond() - a
legacy log-macro family carrying a BES2600_DBG_* subsystem-id
first argument. Neither the macros nor any of the BES2600_DBG_*
constants are defined anywhere in the tree. The same call pattern
appears under #if defined(BES2600_DETECTION_LOGIC) in hwio.c and
under CONFIG_BES2600_ITP in itp.c, both normally disabled.
Add minimal shim macros to bes_log.h that rewire the calls onto
the existing bes_info() / bes_err() / bes_warn() / bes_devel()
family (ignoring the subsystem id). Define BES2600_DBG_SBUS,
BES2600_DBG_DOWNLOAD, BES2600_DBG_ITP and BES2600_DBG_TEST_MODE
as 0 constants for documentation / grep.
2. bes2600_start_stop_tsm(), bes2600_get_tsm_params(), and
bes2600_get_roam_delay() are declared in sta.c with external
linkage but have no prototype in any header. All callers live in
sta.c (inside bes2600_testmode_cmd). With CONFIG_BES2600_TESTMODE
off the compiler never sees them; with it on gcc
-Werror=missing-prototypes breaks the build.
Mark the three functions static. (Keeping them file-local also
matches their actual usage.)
Both changes are strictly scoped to make CONFIG_BES2600_TESTMODE=y
buildable; no behavioural change when the flag is off.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1 with CONFIG_NL80211_TESTMODE=y. Module builds
cleanly, nl80211 testmode interface reachable via 'iw phy0 ...' from
userspace.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/bes_log.h | 23 +++++++++++++++++++++++
bes2600/sta.c | 6 +++---
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 300912b..39150e0 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build
# feature option
BES2600 ?= m
-CONFIG_BES2600_TESTMODE ?= n
+CONFIG_BES2600_TESTMODE ?= y
CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n
diff --git a/bes2600/bes_log.h b/bes2600/bes_log.h
index 605cea8..65cf703 100644
--- a/bes2600/bes_log.h
+++ b/bes2600/bes_log.h
@@ -8,3 +8,26 @@ extern struct device *global_dev;
#define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__)
#define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__)
#define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__)
+
+/*
+ * Legacy debug-subsystem-tagged log macros. The per-subsystem filtering
+ * was never implemented in-tree; these shims let code paths gated by
+ * CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC
+ * build when their conditions are enabled. The first argument is
+ * currently unused; pick one of the BES2600_DBG_* constants below for
+ * documentation.
+ */
+#define BES2600_DBG_SBUS 0
+#define BES2600_DBG_DOWNLOAD 0
+#define BES2600_DBG_ITP 0
+#define BES2600_DBG_TEST_MODE 0
+
+#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__)
+#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__)
+#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__)
+#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__)
+#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \
+ do { \
+ if (_cond) \
+ bes_err(fmt, ##__VA_ARGS__); \
+ } while (0)
diff --git a/bes2600/sta.c b/bes2600/sta.c
index aa69eb8..5f1a456 100644
--- a/bes2600/sta.c
+++ b/bes2600/sta.c
@@ -3633,7 +3633,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw,
*
* Returns: 0 on success or non zero value on failure
*/
-int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
+static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
{
struct bes_msg_start_stop_tsm *start_stop_tsm =
(struct bes_msg_start_stop_tsm *) data;
@@ -3663,7 +3663,7 @@ int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
*
* Returns: TSM parameters collected
*/
-int bes2600_get_tsm_params(struct ieee80211_hw *hw)
+static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
struct bes_tsm_stats tsm_stats;
@@ -3703,7 +3703,7 @@ int bes2600_get_tsm_params(struct ieee80211_hw *hw)
*
* Returns: Returns the last measured roam delay
*/
-int bes2600_get_roam_delay(struct ieee80211_hw *hw)
+static int bes2600_get_roam_delay(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000;
--
2.53.0
@@ -0,0 +1,156 @@
From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 19:31:25 +0200
Subject: [PATCH] bes2600: drop kernel_write() persistence from factory cali
save
Following the conversion of the factory-calibration READ path to
request_firmware() (earlier in this series), the factory-calibration
WRITE path in factory_section_write_file() was still using
filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write() to persist
updated calibration data back to FACTORY_PATH
(default /lib/firmware/bes2600/bes2600_factory.txt).
Writing to files under /lib/firmware/ from kernel code is a
standing upstream blocker for staging and for drivers/net/wireless/
submission generally:
- filp_open()/kernel_write() bypass the firmware-class abstraction,
the LSM framework, and user/group/mode enforcement that governs
the firmware search paths. They have been repeatedly called out
in staging-prep reviews.
- The kernel runs with capabilities that userspace does not (CAP_
DAC_OVERRIDE effectively); quietly rewriting firmware blobs that
userspace owns is a surprise contract.
- A module unload / reboot immediately after the write races the
writeback and can leave a truncated calibration file on disk.
Remove factory_section_write_file() and its two call sites in
bes2600_wifi_cali_table_save(). The in-memory factory_save_p
remains authoritative for the duration of the session: the WSM
command handlers that triggered this path (power-cali-table,
freq-cali, efuse-flag, power-cali-flag) already update the live
struct factory_t, and reads served from file_buffer pick up the
rebuilt serialised form immediately. On the next probe the
firmware-class file is re-read read-only via request_firmware(),
as set up by the earlier patch.
If cross-reboot persistence of runtime-updated calibration becomes
a requirement, the expected route is a userspace-visible dump
interface -- a read-only debugfs file exporting the serialised
blob, or an nl80211 vendor command -- that lets userspace copy the
values to a chosen location under its own privileges. Such a
facility can land as a follow-up without touching the core driver
write path again.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_factory.c | 63 +++++++++++----------------------------
1 file changed, 17 insertions(+), 46 deletions(-)
diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c
index 1cda447..1b43b41 100644
--- a/drivers/staging/bes2600/bes2600_factory.c
+++ b/drivers/staging/bes2600/bes2600_factory.c
@@ -179,34 +179,6 @@ static int factory_section_read_file(char *path, void *buffer)
return ret;
}
-/**
- * factory_section_write_file - Write data of specified length to file
- * @path: path of the file
- * @buffer: storage of write data
- * @size: length of data to write
- *
- * Return: length on success, negative error code otherwise.
- */
-static int factory_section_write_file(char *path, void *buffer, int size)
-{
- int ret = 0;
- struct file *fp;
-
- bes_devel("writing %s \n", path);
-
- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",path);
- return -1;
- }
-
- ret = kernel_write(fp, buffer, size, &fp->f_pos);
-
- filp_close(fp,NULL);
-
- return ret;
-}
-
static inline int factory_parse(uint8_t *source_buf, struct factory_t *factory)
{
int ret = 0;
@@ -898,9 +870,22 @@ static inline int factory_build(uint8_t *dest_buf, struct factory_t *factory)
#endif
}
+/*
+ * Rebuild the serialised calibration blob in file_buffer from the live
+ * in-memory factory_save_p. Previously this function also persisted the
+ * blob back to FACTORY_PATH via filp_open(O_CREAT) + kernel_write(); that
+ * is not acceptable in mainline, so the persistence step has been removed.
+ *
+ * The in-memory factory_save_p remains authoritative for the duration of
+ * the session; on the next probe the firmware-class file is read back
+ * read-only via request_firmware(). If cross-reboot persistence of runtime
+ * calibration updates becomes a requirement, the expected route is a
+ * userspace-facing dump interface (debugfs read-only blob, or nl80211
+ * vendor command) that lets userspace read the serialised form and store
+ * it under its own privileges.
+ */
static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *factory_save_p)
{
- int ret = 0;
int w_size;
u32 crc_len = sizeof(factory_data_t);
#ifndef STANDARD_FACTORY_EFUSE_FLAG
@@ -909,13 +894,11 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto
bes_devel("enter %s\n", __func__);
- if (!file_buffer) {
+ if (!file_buffer)
return -ENOMEM;
- }
- if (!factory_save_p) {
+ if (!factory_save_p)
return -ENOENT;
- }
/* All initialized to space */
memset(file_buffer, 32, FACTORY_MAX_SIZE);
@@ -927,22 +910,10 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto
w_size = factory_build(file_buffer, factory_save_p);
if (w_size < 0 || w_size > FACTORY_MAX_SIZE) {
- bes_err("%s: build failed! ret = %d.", __func__, ret);
+ bes_err("%s: build failed! w_size = %d.", __func__, w_size);
return -ETXTBSY;
}
-#ifdef FACTORY_SAVE_MULTI_PATH
- /* avoid trailing characters '\0' */
- file_buffer[w_size] = 32;
- ret = factory_section_write_file(FACTORY_PATH, file_buffer, FACTORY_MAX_SIZE);
-#else
- ret = factory_section_write_file(FACTORY_PATH, file_buffer, w_size);
-#endif
- if(ret < 0) {
- bes_err("%s: write failed! ret = %d.", __func__, ret);
- return ret;
- }
-
return 0;
}
--
2.53.0
@@ -0,0 +1,156 @@
From 5f475a9624490b07c305329f12016ff4a4df3b47 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 19:31:25 +0200
Subject: [PATCH] bes2600: drop kernel_write() persistence from factory cali
save
Following the conversion of the factory-calibration READ path to
request_firmware() (earlier in this series), the factory-calibration
WRITE path in factory_section_write_file() was still using
filp_open(O_CREAT | O_TRUNC | O_RDWR) + kernel_write() to persist
updated calibration data back to FACTORY_PATH
(default /lib/firmware/bes2600/bes2600_factory.txt).
Writing to files under /lib/firmware/ from kernel code is a
standing upstream blocker for staging and for drivers/net/wireless/
submission generally:
- filp_open()/kernel_write() bypass the firmware-class abstraction,
the LSM framework, and user/group/mode enforcement that governs
the firmware search paths. They have been repeatedly called out
in staging-prep reviews.
- The kernel runs with capabilities that userspace does not (CAP_
DAC_OVERRIDE effectively); quietly rewriting firmware blobs that
userspace owns is a surprise contract.
- A module unload / reboot immediately after the write races the
writeback and can leave a truncated calibration file on disk.
Remove factory_section_write_file() and its two call sites in
bes2600_wifi_cali_table_save(). The in-memory factory_save_p
remains authoritative for the duration of the session: the WSM
command handlers that triggered this path (power-cali-table,
freq-cali, efuse-flag, power-cali-flag) already update the live
struct factory_t, and reads served from file_buffer pick up the
rebuilt serialised form immediately. On the next probe the
firmware-class file is re-read read-only via request_firmware(),
as set up by the earlier patch.
If cross-reboot persistence of runtime-updated calibration becomes
a requirement, the expected route is a userspace-visible dump
interface -- a read-only debugfs file exporting the serialised
blob, or an nl80211 vendor command -- that lets userspace copy the
values to a chosen location under its own privileges. Such a
facility can land as a follow-up without touching the core driver
write path again.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_factory.c | 63 +++++++++++----------------------------
1 file changed, 17 insertions(+), 46 deletions(-)
diff --git a/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c
index 1cda447..1b43b41 100644
--- a/bes2600/bes2600_factory.c
+++ b/bes2600/bes2600_factory.c
@@ -179,34 +179,6 @@ static int factory_section_read_file(char *path, void *buffer)
return ret;
}
-/**
- * factory_section_write_file - Write data of specified length to file
- * @path: path of the file
- * @buffer: storage of write data
- * @size: length of data to write
- *
- * Return: length on success, negative error code otherwise.
- */
-static int factory_section_write_file(char *path, void *buffer, int size)
-{
- int ret = 0;
- struct file *fp;
-
- bes_devel("writing %s \n", path);
-
- fp = filp_open(path, O_TRUNC | O_CREAT | O_RDWR, S_IRUSR);
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",path);
- return -1;
- }
-
- ret = kernel_write(fp, buffer, size, &fp->f_pos);
-
- filp_close(fp,NULL);
-
- return ret;
-}
-
static inline int factory_parse(uint8_t *source_buf, struct factory_t *factory)
{
int ret = 0;
@@ -898,9 +870,22 @@ static inline int factory_build(uint8_t *dest_buf, struct factory_t *factory)
#endif
}
+/*
+ * Rebuild the serialised calibration blob in file_buffer from the live
+ * in-memory factory_save_p. Previously this function also persisted the
+ * blob back to FACTORY_PATH via filp_open(O_CREAT) + kernel_write(); that
+ * is not acceptable in mainline, so the persistence step has been removed.
+ *
+ * The in-memory factory_save_p remains authoritative for the duration of
+ * the session; on the next probe the firmware-class file is read back
+ * read-only via request_firmware(). If cross-reboot persistence of runtime
+ * calibration updates becomes a requirement, the expected route is a
+ * userspace-facing dump interface (debugfs read-only blob, or nl80211
+ * vendor command) that lets userspace read the serialised form and store
+ * it under its own privileges.
+ */
static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *factory_save_p)
{
- int ret = 0;
int w_size;
u32 crc_len = sizeof(factory_data_t);
#ifndef STANDARD_FACTORY_EFUSE_FLAG
@@ -909,13 +894,11 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto
bes_devel("enter %s\n", __func__);
- if (!file_buffer) {
+ if (!file_buffer)
return -ENOMEM;
- }
- if (!factory_save_p) {
+ if (!factory_save_p)
return -ENOENT;
- }
/* All initialized to space */
memset(file_buffer, 32, FACTORY_MAX_SIZE);
@@ -927,22 +910,10 @@ static int bes2600_wifi_cali_table_save(u8 *file_buffer, struct factory_t *facto
w_size = factory_build(file_buffer, factory_save_p);
if (w_size < 0 || w_size > FACTORY_MAX_SIZE) {
- bes_err("%s: build failed! ret = %d.", __func__, ret);
+ bes_err("%s: build failed! w_size = %d.", __func__, w_size);
return -ETXTBSY;
}
-#ifdef FACTORY_SAVE_MULTI_PATH
- /* avoid trailing characters '\0' */
- file_buffer[w_size] = 32;
- ret = factory_section_write_file(FACTORY_PATH, file_buffer, FACTORY_MAX_SIZE);
-#else
- ret = factory_section_write_file(FACTORY_PATH, file_buffer, w_size);
-#endif
- if(ret < 0) {
- bes_err("%s: write failed! ret = %d.", __func__, ret);
- return ret;
- }
-
return 0;
}
--
2.53.0
@@ -0,0 +1,144 @@
From 1a5d54a3213041262caf1605bb19c66ddded41f7 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 10:09:44 +0200
Subject: [PATCH 1/2] bes2600: use request_firmware() for factory.txt read
The BES2600 factory calibration file (bes2600_factory.txt) was being read
via filp_open() + kernel_read() from a hard-coded absolute path baked in
at compile time via the FACTORY_PATH Makefile macro
(default: /lib/firmware/bes2600_factory.txt).
This had several problems:
1. Path mismatch - linux-firmware-style packaging (and danctnix 0.2-5
device-pine64-pinetab2) ships the file at
/lib/firmware/bes2600/bes2600_factory.txt, not /lib/firmware/. The
driver logged '(NULL device *): read and check
/lib/firmware/bes2600_factory.txt error' on every boot on PineTab2
running linux-pinetab2 6.19.10-danctnix1-1.
2. Direct filesystem access via filp_open() / kernel_read() from a driver
is an anti-pattern that upstream rejects: drivers should use
request_firmware() to get binary data from userspace-managed firmware
directories. request_firmware() natively searches the firmware_class
path list (typically /lib/firmware + derivatives), associates the load
with a uevent, and respects the firmware-loading infrastructure.
3. The (NULL device *) prefix in error messages indicated the absence of
proper device-context logging. While this patch does not yet thread
struct device through, the upstream path uses request_firmware() which
works with dev=NULL and is the building block for a follow-up patch
that adds per-chip device context.
Repoint the FACTORY_PATH default to the firmware-class name
(bes2600/bes2600_factory.txt) - request_firmware() prepends
/lib/firmware/ from the configured search paths. The macro remains
overridable at build time for non-standard deployments.
Rewrite factory_section_read_file() to:
* Call request_firmware(&fw, path, NULL).
* Size-check fw->size against FACTORY_MAX_SIZE.
* memcpy the data into the caller's buffer.
* Always call release_firmware() on exit.
The file write path (factory_section_write_file + kernel_write) is left
unchanged in this patch; it is the subject of a follow-up patch that
removes kernel_write and moves any remaining userspace-visible factory
configuration to a standard kernel-userspace boundary (debugfs or
nl80211 testmode).
No caller signature changes. No Makefile flag drops. Bisectable.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1, deployed via /lib/modules/<ver>/extra/. Verified
post-reboot: original 'read and check /lib/firmware/bes2600_factory.txt
error' is gone; request_firmware reads the file successfully (a separate
factory_parse() bug, previously masked by the read failure, is now
exposed and tracked separately).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/bes2600_factory.c | 33 ++++++++++++++-------------------
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 300912b..788aee2 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -66,7 +66,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
STANDARD_FACTORY_EFUSE_FLAG ?= y
-FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt
+FACTORY_PATH ?= bes2600/bes2600_factory.txt
endif
# basic function
diff --git a/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c
index dc5d3da..8d60b7c 100644
--- a/bes2600/bes2600_factory.c
+++ b/bes2600/bes2600_factory.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -137,38 +138,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data)
*/
static int factory_section_read_file(char *path, void *buffer)
{
- int ret = 0;
- struct file *fp;
+ const struct firmware *fw;
+ int ret;
if (!path || !buffer) {
bes_err("%s NULL pointer err\n", __func__);
return -1;
}
- bes_devel("reading %s \n", path);
+ bes_devel("requesting firmware-class %s\n", path);
- fp = filp_open(path, O_RDONLY, 0); //S_IRUSR
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",path);
+ ret = request_firmware(&fw, path, NULL);
+ if (ret) {
+ bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
}
- if (fp->f_inode->i_size <= 0 || fp->f_inode->i_size > FACTORY_MAX_SIZE) {
- bes_err( "bes2600_factory.txt size check failed, read_size: %lld max_size: %d\n",
- fp->f_inode->i_size, FACTORY_MAX_SIZE);
- filp_close(fp, NULL);
+ if (fw->size == 0 || fw->size > FACTORY_MAX_SIZE) {
+ bes_err("bes2600_factory.txt size check failed, read_size: %zu max_size: %d\n",
+ fw->size, FACTORY_MAX_SIZE);
+ release_firmware(fw);
return -1;
}
- ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos);
-
- filp_close(fp, NULL);
-
- if (ret != fp->f_inode->i_size) {
- bes_err("bes2600_factory.txt read fail\n");
- ret = -1;
- }
-
+ memcpy(buffer, fw->data, fw->size);
+ ret = (int)fw->size;
+ release_firmware(fw);
return ret;
}
--
2.53.0
@@ -0,0 +1,83 @@
From 82ba594a444a855310fbbe2a5c8ff02f211d8e83 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:17:56 +0200
Subject: [PATCH 2/2] bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for
PineTab2 factory.txt format
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The shipped factory calibration file bes2600_factory.txt on PineTab2
(danctnix linux-firmware 0.3.5_2023.0209) contains 30 calibration
fields: head (3), iq/xtal (3), 2.4G power 11n (5), 5G power 11n (15),
bt (4). The file terminates with '%%\n' directly after edr_power.
When STANDARD_FACTORY_EFUSE_FLAG is defined at compile time the driver
assembles STANDARD_FACTORY with an extra select_efuse_flag section
appended and expects 31 sscanf matches (FACTORY_MEMBER_NUM=31):
__STANDARD_FACTORY + \"##select_efuse_flag\\nselect_efuse:%hx\\n\"
+ \"%%%%\\n\"
The PineTab2 factory.txt has no select_efuse_flag section, so sscanf
stops after field 30 and factory_parse() returns -1 with:
bes2600_factory.txt parse fail
read and check bes2600/bes2600_factory.txt error
factory cali data get failed.
This was latent until the preceding patch (use request_firmware() for
factory.txt read) fixed the path bug that masked the parse failure.
Default STANDARD_FACTORY_EFUSE_FLAG to n. The flag remains overridable
at build time (make STANDARD_FACTORY_EFUSE_FLAG=y ...) for chips /
firmware packages that do ship the select_efuse_flag section.
Also: the wsm_save_factory_txt_to_mcu() prototype in wsm.h was
inconsistently wrapped in a conditional that keyed on
STANDARD_FACTORY_EFUSE_FLAG, but the function definition in wsm.c and
the call site in sta.c are ungated. With the flag now defaulting to
n, the gcc -Werror=missing-prototypes flag breaks the build. Drop the
conditional wrapper around the prototype — the function exists and is
used regardless of the factory-parse flag.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. With the flag defaulted off, factory_parse()
succeeds on the shipped factory.txt, factory_cali_data is populated,
and dmesg no longer shows the parse-fail / read-and-check-error /
factory-cali-data-get-failed sequence.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/wsm.h | 2 --
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 788aee2..2dcba09 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -65,7 +65,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
-STANDARD_FACTORY_EFUSE_FLAG ?= y
+STANDARD_FACTORY_EFUSE_FLAG ?= n
FACTORY_PATH ?= bes2600/bes2600_factory.txt
endif
diff --git a/bes2600/wsm.h b/bes2600/wsm.h
index 0673131..22845ac 100644
--- a/bes2600/wsm.h
+++ b/bes2600/wsm.h
@@ -2236,7 +2236,5 @@ int wsm_cpu_usage_cmd(struct bes2600_common *hw_priv);
int wsm_wifi_status_cmd(struct bes2600_common *hw_priv, uint32_t status);
-#if defined(STANDARD_FACTORY_EFUSE_FLAG)
int wsm_save_factory_txt_to_mcu(struct bes2600_common *hw_priv, const u8 *data, int if_id, enum bes2600_rf_cmd_type cmd_type);
-#endif
#endif /* BES2600_HWIO_H_INCLUDED */
--
2.53.0
@@ -0,0 +1,116 @@
From 8732881c5916106539b9071b51710489c57e8d73 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:18:38 +0200
Subject: [PATCH] bes2600: thread struct device * through factory
request_firmware() call
Follow-up to \"bes2600: use request_firmware() for factory.txt read\".
That patch switched the factory calibration read path from filp_open()
+ kernel_read() to request_firmware(), but passed dev=NULL to
request_firmware() because factory_section_read_file() did not have a
struct device * in scope. The resulting logs carry the
'(NULL device *):' prefix and do not propagate a udev association.
Add a module-local static struct device * used as the firmware-class
load context, plus a small exported setter:
static struct device *bes2600_factory_dev;
void bes2600_factory_set_dev(struct device *dev);
Wire bes2600_factory_set_dev(&func->dev) from bes2600_sdio_probe(),
right after bes2600_platform_data_init() so the platform layer has
already had a chance to use the same struct device for its own
initialization.
factory_section_read_file() now passes bes2600_factory_dev (instead
of NULL) to request_firmware(). When the factory read happens before
probe (not currently the case on PineTab2) the pointer is still NULL
and request_firmware() accepts that; no regression.
No API changes to bes2600_get_factory_cali_data() callers. The
char *path parameter remains (it is the firmware-class name fed
straight to request_firmware()).
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver probes, factory data is read, and any
post-c5 factory diagnostics now carry the SDIO device identity
instead of '(NULL device *)'.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_factory.c | 14 +++++++++++++-
bes2600/bes2600_factory.h | 3 +++
bes2600/bes2600_sdio.c | 4 ++++
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c
index 8d60b7c..1cda447 100644
--- a/bes2600/bes2600_factory.c
+++ b/bes2600/bes2600_factory.c
@@ -31,6 +31,18 @@
static DEFINE_MUTEX(factory_lock);
+/*
+ * struct device * for request_firmware() context. Set once at SDIO
+ * probe via bes2600_factory_set_dev(). NULL is tolerated (falls back
+ * to the udev-less firmware-class path) but loses per-device logging.
+ */
+static struct device *bes2600_factory_dev;
+
+void bes2600_factory_set_dev(struct device *dev)
+{
+ bes2600_factory_dev = dev;
+}
+
/*
* It is only used for temporary storage.
* Every time get the factory, it will read from the
@@ -148,7 +160,7 @@ static int factory_section_read_file(char *path, void *buffer)
bes_devel("requesting firmware-class %s\n", path);
- ret = request_firmware(&fw, path, NULL);
+ ret = request_firmware(&fw, path, bes2600_factory_dev);
if (ret) {
bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
diff --git a/bes2600/bes2600_factory.h b/bes2600/bes2600_factory.h
index 3835b0d..7dbe9f8 100644
--- a/bes2600/bes2600_factory.h
+++ b/bes2600/bes2600_factory.h
@@ -199,6 +199,9 @@ enum factory_cali_status {
/* just calibrate 11n, other protocols are automatically mapped */
#define WIFI_RF_11N_MODE 0x15
+/* set the struct device * used for request_firmware() context */
+void bes2600_factory_set_dev(struct device *dev);
+
/* read wifi & bt factory cali value*/
u8* bes2600_get_factory_cali_data(u8 *file_buffer, u32 *data_len, char *path);
void factory_little_endian_cvrt(u8 *data);
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index b595365..371ef4f 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -30,6 +30,7 @@
#include "bes2600.h"
#include "sbus.h"
#include "bes2600_plat.h"
+#include "bes2600_factory.h"
#include "hwio.h"
#include "bes_chardev.h"
#include "bes_log.h"
@@ -1834,6 +1835,9 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (ret)
goto err;
+ /* wire struct device into factory.c for request_firmware() context */
+ bes2600_factory_set_dev(dev);
+
self->pdata = bes2600_get_platform_data();
self->func = func;
self->dev = &func->dev;
--
2.53.0
@@ -0,0 +1,131 @@
From 3d833f8ccf31895a2ce7bf4fd4ef839e653b29bb Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 21 May 2026 09:25:12 +0200
Subject: [PATCH 22/22] bes2600: reset firmware state on wsm_join_confirm
failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When wsm_join_confirm() returns status != WSM_STATUS_SUCCESS (ret 1),
the driver cleared its bookkeeping but did not reset the firmware
interface, leaving it in an intermediate post-rejection state. A rapid
second JOIN attempt (e.g. wpa_supplicant retrying after the
PREV_AUTH_NOT_VALID deauth that mac80211 emits to clean up) hits an
inconsistent firmware context, causing bes2600_sdio_read_rx_batch to
return SDIO error which cascades into wifi_force_close:
wsm_join_confirm ret 1
deauthenticating from <bssid> by local choice (Reason: 2=PREV_AUTH_NOT_VALID)
[~10 min later]
bes2600_sdio_read_rx_batch sdio read error
WARNING: at bes2600_tx_loop_set_enable / bes2600_chrdev_wifi_force_close
Two additions to the failure path in bes2600_join_work():
1. wsm_reset (WSM_REQ_ID_RESET, 0x000A) with reset_statistics=false.
This returns the firmware to IDLE so the next association attempt
starts from a known-clean state. bes2600_unjoin_work() performs the
same reset, but gates it on join_status != PASSIVE; after a failed
JOIN join_status stays PASSIVE, so that path never fires — call
wsm_reset directly here instead.
Contract: wsm_reset takes only wsm_cmd_lock (not conf_lock, not
wsm_oper_lock). wsm_oper_unlock was already called inside
wsm_join_confirm() before wsm_join() returned -EINVAL, so there is
no re-entrancy hazard. conf_lock is held at this call site, which is
compatible with wsm_reset's locking requirements.
2. queue_work(workqueue, &priv->unjoin_work) instead of direct
wsm_unlock_tx(). Serialises the next association attempt through
the workqueue so it cannot race against lingering firmware-side
effects of the failure. If unjoin_work is already queued, release
TX immediately (matching cw1200 ancestor sta.c:1344 comment "Tx lock
still held, unjoin will clear it.").
Ancestor reference: drivers/net/wireless/st/cw1200/sta.c, function
cw1200_join_work(), lines 1339-1344. cw1200 queues unjoin_work on join
failure for the same reason. bes2600 needs the direct wsm_reset in
addition because its unjoin_work has the join_status gate that cw1200's
cw1200_do_unjoin() does not.
Signed-off-by: Claude (noether) <claude@reauktion.de>
---
bes2600/sta.c | 47 +++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 43 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c
index 476d875..bf86835 100644
--- a/drivers/staging/bes2600/sta.c
+++ b/drivers/staging/bes2600/sta.c
@@ -2225,9 +2225,10 @@ void bes2600_join_work(struct work_struct *work)
struct wsm_template_frame probe_tmp = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
- /*struct wsm_reset reset = {
- .reset_statistics = true,
- };*/
+ struct wsm_reset join_fail_reset = {
+ .reset_statistics = false,
+ };
+ bool join_failed = false;
BUG_ON(queueId >= 4);
@@ -2410,6 +2411,33 @@ 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
@@ -2434,7 +2462,18 @@ void bes2600_join_work(struct work_struct *work)
up(&hw_priv->conf_lock);
if (bss)
cfg80211_put_bss(hw_priv->hw->wiphy, bss);
- wsm_unlock_tx(hw_priv);
+ /*
+ * 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);
+ }
}
void bes2600_join_timeout(struct work_struct *work)
--
2.54.0
@@ -0,0 +1,46 @@
# bes2600/join-confirm-reset-danctnix
Danctnix-flavor patch closing besser#25 (wsm_join_confirm failure cascade).
## What it does
When firmware returns status 1 on a JOIN command (`wsm_join_confirm ret 1`),
add a direct `wsm_reset(...)` call so the firmware returns to a clean IDLE
state, plus `queue_work(workqueue, &priv->unjoin_work)` for serialisation of
the next association attempt.
## Why it's a fork-divergence fix
`cw1200_join_work()` (cw1200 ancestor, `drivers/net/wireless/st/cw1200/sta.c:1339-1344`)
queues `unjoin_work` on join failure: `cw1200_do_unjoin()` calls `wsm_reset`
when `join_status == STA`.
bes2600's `bes2600_unjoin_work()` gates the same `wsm_reset` on
`join_status != PASSIVE`. After a failed JOIN, `join_status` stays PASSIVE
(only set to STA on success) — queuing `unjoin_work` alone is insufficient
on bes2600. The danctnix variant carries a direct `wsm_reset` in the
failure path *and* the queue_work serialisation.
## Observable effects (pkgrel=6 soak)
Beyond closing the cascade (besser#25 acceptance), this patch also
collapsed the periodic ~600 ms latency jitter on ohm:
| | pkgrel=5 | pkgrel=6 |
|---|---|---|
| max RTT | 612 ms | 13.9 ms |
| mdev | 103.5 ms | 1.55 ms |
The bgscan-driven roam-attempt to a 5 GHz BSSID followed by `wsm_join`
reject was briefly stalling TX every minute even when the cascade did
not fire.
## Upstream
- besser issue: marfrit/besser#25
- bes2600-dkms branch (Mobian flavor): bes2600/wsm-join-confirm-reset
(PR #12 against `cleanups`)
- bes2600-dkms branch (danctnix flavor): bes2600/join-confirm-failure-reset
(top commit `3d833f8`)
- shipped as patch 0022 in danctnix-besser-pkgbuild kernel/ (pkgrel=6,
srcversion 0E16463FA8D85F4704DE93F)
@@ -0,0 +1,251 @@
From 9ea8a8e810ee5eb220de700a5c0a6d1153b15130 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 27 Apr 2026 06:32:41 +0200
Subject: [PATCH] bes2600: recover wedged firmware via mmc_hw_reset on link
break
When the LMAC active monitor detects 'link break between lmac and host'
(the hw_buf_used==pending watchdog in bes2600_bh_lmac_active_monitor),
bes2600_chrdev_wifi_force_close(hw_priv, true) is invoked to tear the
device down and prepare for a fresh probe. On the wifi_force_close_work
side this calls bes2600_chrdev_do_system_close() which dispatches
sbus_ops->power_switch(0).
On PineTab2 (RK3566 + BES2600WM over SDIO) this recovery path is a
no-op:
* bes2600_sdio_power_down() writes a SYSTEM_CLOSE host-int message,
clears MMC_CAP_NONREMOVABLE, and schedules sdio_scan_work, which is
the literal one-line stub bes_warn("...this function does
nothing\n").
* bes2600_sdio_on() (the eventual power_switch(1) counterpart)
toggles pdata->powerup, which is NULL on PineTab2 because the
wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 device
tree node (see arch/arm64/boot/dts/rockchip/rk3566-pinetab2.dtsi:
'The reset pin is claimed by sdio_mmcseq, It is better to move it
to U-Boot so the OS can use it.').
Net result: the chip is never reset. The function drivers are not
removed (the SDIO core has no signal that the card is gone), the
firmware stays wedged, and a subsequent rmmod bes2600 leaves the SDIO
function in a half-torn-down state. modprobe bes2600 then fails with
'probe with driver bes2600_wlan failed with error -123' (-ENOMEDIUM)
on both functions (:1 wifi, :2 BT-companion) until a full system
reboot.
Observed on PineTab2 (linux-pinetab2 6.19.10-danctnix1-1) after ~150
minutes of background-scan rejects (wsm_generic_confirm 0x0007,
[SCAN] Scan failed (-22)) accumulating until the LMAC stopped
acknowledging TX buffers (hw_buf_used:24 pending:24). Reproducible
under sustained scan pressure.
Add a sbus operation bus_reset() that the recovery path can call when
power_switch() has no effective chip-reset signal of its own. Provide
an SDIO implementation that calls mmc_hw_reset(self->func->card),
which on a multi-function SDIO card (PineTab2 binds func 1 for WLAN
and func 2 for the BT-companion path) takes the remove-and-rescan
path: mmc_sdio_hw_reset() marks the card removed and schedules
mmc_rescan, which tears down the bound function drivers and re-detects
the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
With a single function probed it instead invokes mmc_power_cycle()
directly, which on PineTab2 toggles the wifi-reset GPIO via
sdio_pwrseq.
Add bes2600_chrdev_do_bus_reset() as the chrdev-side helper. It
invokes the bus op and then waits on probe_done_wq for the SDIO
remove() callback to clear sbus_priv, mirroring the wait pattern
already used by bes2600_chrdev_do_system_close() so that a subsequent
bes2600_switch_wifi(true) sees a clean state and can wait on the
fresh probe.
Wire it into bes2600_chrdev_wifi_force_close_work(): when halt_dev is
set (the hard-exception path used by both
bes2600_bh_lmac_active_monitor and bes2600_bh_mcu_active_monitor) and
the underlying bus implements bus_reset, take the new recovery path;
otherwise fall back to the legacy power_switch(0) sequence so this
patch is a no-op on USB or any other future bus that does not provide
bus_reset.
mmc_hw_reset() is exported by the MMC core and is the canonical
recovery primitive; calling it without holding the SDIO host claim is
correct because the multi-func remove-and-rescan path acquires the
host claim via the mmc workqueue, and the single-func mmc_power_cycle
path does not require the host claim.
No DT change is required: this works against the existing PineTab2
DTS, where the wifi-reset GPIO and the optional sdio_pwrkey GPIO (on
v2.0 boards) are both already configured as MMC pwrseq resets.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes2600_sdio.c | 29 +++++++++++++
drivers/staging/bes2600/bes_chardev.c | 59 +++++++++++++++++++++++++-
drivers/staging/bes2600/bes_chardev.h | 1 +
drivers/staging/bes2600/sbus.h | 8 ++++
4 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c
index b9d836fab7af..f7f86d765bba 100644
--- a/drivers/staging/bes2600/bes2600_sdio.c
+++ b/drivers/staging/bes2600/bes2600_sdio.c
@@ -16,6 +16,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
#include <linux/mmc/sdio.h>
#include <linux/spinlock.h>
#include <net/mac80211.h>
@@ -1777,6 +1778,33 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self)
sdio_work_debug(self);
}
+/*
+ * Trigger an SDIO bus reset via mmc_hw_reset().
+ *
+ * With multiple SDIO functions probed (PineTab2 binds func 1 for WLAN and
+ * func 2 for the BT-companion path) mmc_sdio_hw_reset() takes the
+ * remove-and-rescan path: it marks the card removed and schedules
+ * mmc_rescan, which tears down the bound function drivers and re-detects
+ * the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
+ *
+ * With a single function probed it instead invokes mmc_power_cycle()
+ * directly, which on PineTab2 toggles the wifi-reset GPIO via sdio_pwrseq.
+ *
+ * In both cases the chip ends up in a freshly reset state, which is the
+ * goal of the recovery path.
+ *
+ * mmc_hw_reset() must be called without holding the SDIO host claim --
+ * the multi-func remove-and-rescan path acquires the host claim via the
+ * mmc workqueue.
+ */
+static int bes2600_sdio_bus_reset(struct sbus_priv *self)
+{
+ if (!self || !self->func || !self->func->card)
+ return -EINVAL;
+
+ return mmc_hw_reset(self->func->card);
+}
+
static bool bes2600_sdio_wakeup_source(struct sbus_priv *self)
{
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
@@ -1815,6 +1843,7 @@ static struct sbus_ops bes2600_sdio_sbus_ops = {
.gpio_sleep = bes2600_gpio_allow_mcu_sleep,
.halt_device = bes2600_sdio_halt_device,
.wakeup_source = bes2600_sdio_wakeup_source,
+ .bus_reset = bes2600_sdio_bus_reset,
};
static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv)
diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c
index 455108a2dd66..b776aab5e062 100644
--- a/drivers/staging/bes2600/bes_chardev.c
+++ b/drivers/staging/bes2600/bes_chardev.c
@@ -626,6 +626,48 @@ int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_
return ret;
}
+/*
+ * Hard-reset the bus and wait for the bus core to remove the chip.
+ *
+ * Used by the firmware-wedge recovery path on platforms where the normal
+ * power_switch(0) sequence has no effective chip-reset signal. The bus
+ * implementation triggers an asynchronous re-detect; this helper waits for
+ * the resulting remove() callback to clear bes2600_cdev.sbus_priv so that a
+ * subsequent bes2600_switch_wifi(true) sees a clean state and can wait on
+ * the fresh probe.
+ */
+int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv)
+{
+ int ret;
+ long status;
+
+ if (!sbus_ops || !priv)
+ return -EINVAL;
+
+ if (!sbus_ops->bus_reset)
+ return -EOPNOTSUPP;
+
+ bes_info("trigger bus reset to recover wedged firmware.\n");
+
+ ret = sbus_ops->bus_reset(priv);
+ if (ret) {
+ bes_err("bus_reset failed: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * The bus reset is asynchronous: the bus core schedules a rescan
+ * which removes the bound function drivers and then re-detects the
+ * chip. Wait for the remove callback to clear sbus_priv. Do not
+ * dereference 'priv' after this point -- it may already be freed.
+ */
+ status = wait_event_timeout(bes2600_cdev.probe_done_wq,
+ !bes2600_cdev.sbus_priv, HZ * 3);
+ WARN_ON(status <= 0);
+
+ return 0;
+}
+
bool bes2600_chrdev_is_wifi_opened(void)
{
bool wifi_opened = false;
@@ -726,8 +768,21 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
/* unregister wifi */
bes2600_switch_wifi(0);
- /* power down device if wifi is only opened */
- if (bes2600_chrdev_check_system_close()) {
+ /*
+ * Hard exception with a bus_reset implementation: tear the
+ * bus down via mmc_hw_reset() (or equivalent) so the next
+ * bringup probes a freshly reset chip. On PineTab2 this is
+ * the only effective recovery path -- the existing
+ * power_switch(0)/(1) sequence has no chip-reset signal of
+ * its own (sdio_pwrseq owns wifi_reset).
+ *
+ * Soft close, or hard close on a board without bus_reset:
+ * fall back to the legacy power_switch(0) sequence.
+ */
+ if (bes2600_cdev.halt_dev && bes2600_cdev.sbus_ops->bus_reset) {
+ bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops,
+ bes2600_cdev.sbus_priv);
+ } else if (bes2600_chrdev_check_system_close()) {
bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
diff --git a/drivers/staging/bes2600/bes_chardev.h b/drivers/staging/bes2600/bes_chardev.h
index c627bb7c3d65..ca8419eead8f 100644
--- a/drivers/staging/bes2600/bes_chardev.h
+++ b/drivers/staging/bes2600/bes_chardev.h
@@ -60,6 +60,7 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void);
/* used to control device power down */
int bes2600_chrdev_check_system_close(void);
int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
+int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
void bes2600_chrdev_wakeup_bt(void);
void bes2600_chrdev_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev);
void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv);
diff --git a/drivers/staging/bes2600/sbus.h b/drivers/staging/bes2600/sbus.h
index 1f2c0cda73de..cb9089004041 100644
--- a/drivers/staging/bes2600/sbus.h
+++ b/drivers/staging/bes2600/sbus.h
@@ -75,6 +75,14 @@ struct sbus_ops {
void (*halt_device)(struct sbus_priv *self);
bool (*wakeup_source)(struct sbus_priv *self);
int (*reboot)(struct sbus_priv *self);
+ /*
+ * Force the host bus to re-detect and re-probe the chip. Called
+ * from the firmware-wedge recovery path when power_switch() has no
+ * effective chip-reset signal of its own (e.g. PineTab2, where the
+ * wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 node).
+ * Returns 0 on success or a negative errno.
+ */
+ int (*bus_reset)(struct sbus_priv *self);
};
void bes2600_irq_handler(struct bes2600_common *priv);
--
2.53.0
@@ -0,0 +1,251 @@
From 460495803346f71a9d5dcc634180e5368ff9b1dc Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Sun, 26 Apr 2026 22:31:58 +0200
Subject: [PATCH] bes2600: recover wedged firmware via mmc_hw_reset on link
break
When the LMAC active monitor detects 'link break between lmac and host'
(the hw_buf_used==pending watchdog in bes2600_bh_lmac_active_monitor),
bes2600_chrdev_wifi_force_close(hw_priv, true) is invoked to tear the
device down and prepare for a fresh probe. On the wifi_force_close_work
side this calls bes2600_chrdev_do_system_close() which dispatches
sbus_ops->power_switch(0).
On PineTab2 (RK3566 + BES2600WM over SDIO) this recovery path is a
no-op:
* bes2600_sdio_power_down() writes a SYSTEM_CLOSE host-int message,
clears MMC_CAP_NONREMOVABLE, and schedules sdio_scan_work, which is
the literal one-line stub bes_warn("...this function does
nothing\n").
* bes2600_sdio_on() (the eventual power_switch(1) counterpart)
toggles pdata->powerup, which is NULL on PineTab2 because the
wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 device
tree node (see arch/arm64/boot/dts/rockchip/rk3566-pinetab2.dtsi:
'The reset pin is claimed by sdio_mmcseq, It is better to move it
to U-Boot so the OS can use it.').
Net result: the chip is never reset. The function drivers are not
removed (the SDIO core has no signal that the card is gone), the
firmware stays wedged, and a subsequent rmmod bes2600 leaves the SDIO
function in a half-torn-down state. modprobe bes2600 then fails with
'probe with driver bes2600_wlan failed with error -123' (-ENOMEDIUM)
on both functions (:1 wifi, :2 BT-companion) until a full system
reboot.
Observed on PineTab2 (linux-pinetab2 6.19.10-danctnix1-1) after ~150
minutes of background-scan rejects (wsm_generic_confirm 0x0007,
[SCAN] Scan failed (-22)) accumulating until the LMAC stopped
acknowledging TX buffers (hw_buf_used:24 pending:24). Reproducible
under sustained scan pressure.
Add a sbus operation bus_reset() that the recovery path can call when
power_switch() has no effective chip-reset signal of its own. Provide
an SDIO implementation that calls mmc_hw_reset(self->func->card),
which on a multi-function SDIO card (PineTab2 binds func 1 for WLAN
and func 2 for the BT-companion path) takes the remove-and-rescan
path: mmc_sdio_hw_reset() marks the card removed and schedules
mmc_rescan, which tears down the bound function drivers and re-detects
the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
With a single function probed it instead invokes mmc_power_cycle()
directly, which on PineTab2 toggles the wifi-reset GPIO via
sdio_pwrseq.
Add bes2600_chrdev_do_bus_reset() as the chrdev-side helper. It
invokes the bus op and then waits on probe_done_wq for the SDIO
remove() callback to clear sbus_priv, mirroring the wait pattern
already used by bes2600_chrdev_do_system_close() so that a subsequent
bes2600_switch_wifi(true) sees a clean state and can wait on the
fresh probe.
Wire it into bes2600_chrdev_wifi_force_close_work(): when halt_dev is
set (the hard-exception path used by both
bes2600_bh_lmac_active_monitor and bes2600_bh_mcu_active_monitor) and
the underlying bus implements bus_reset, take the new recovery path;
otherwise fall back to the legacy power_switch(0) sequence so this
patch is a no-op on USB or any other future bus that does not provide
bus_reset.
mmc_hw_reset() is exported by the MMC core and is the canonical
recovery primitive; calling it without holding the SDIO host claim is
correct because the multi-func remove-and-rescan path acquires the
host claim via the mmc workqueue, and the single-func mmc_power_cycle
path does not require the host claim.
No DT change is required: this works against the existing PineTab2
DTS, where the wifi-reset GPIO and the optional sdio_pwrkey GPIO (on
v2.0 boards) are both already configured as MMC pwrseq resets.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_sdio.c | 29 +++++++++++++++++++++
bes2600/bes_chardev.c | 59 ++++++++++++++++++++++++++++++++++++++++--
bes2600/bes_chardev.h | 1 +
bes2600/sbus.h | 8 ++++++
4 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index 3e04e8c..e5840c8 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -16,6 +16,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
#include <linux/mmc/sdio.h>
#include <linux/spinlock.h>
#include <net/mac80211.h>
@@ -1777,6 +1778,33 @@ static void bes2600_sdio_halt_device(struct sbus_priv *self)
sdio_work_debug(self);
}
+/*
+ * Trigger an SDIO bus reset via mmc_hw_reset().
+ *
+ * With multiple SDIO functions probed (PineTab2 binds func 1 for WLAN and
+ * func 2 for the BT-companion path) mmc_sdio_hw_reset() takes the
+ * remove-and-rescan path: it marks the card removed and schedules
+ * mmc_rescan, which tears down the bound function drivers and re-detects
+ * the card on the next sweep, in turn reinvoking bes2600_sdio_probe().
+ *
+ * With a single function probed it instead invokes mmc_power_cycle()
+ * directly, which on PineTab2 toggles the wifi-reset GPIO via sdio_pwrseq.
+ *
+ * In both cases the chip ends up in a freshly reset state, which is the
+ * goal of the recovery path.
+ *
+ * mmc_hw_reset() must be called without holding the SDIO host claim --
+ * the multi-func remove-and-rescan path acquires the host claim via the
+ * mmc workqueue.
+ */
+static int bes2600_sdio_bus_reset(struct sbus_priv *self)
+{
+ if (!self || !self->func || !self->func->card)
+ return -EINVAL;
+
+ return mmc_hw_reset(self->func->card);
+}
+
static bool bes2600_sdio_wakeup_source(struct sbus_priv *self)
{
struct bes2600_platform_data_sdio *pdata = bes2600_get_platform_data();
@@ -1815,6 +1843,7 @@ static struct sbus_ops bes2600_sdio_sbus_ops = {
.gpio_sleep = bes2600_gpio_allow_mcu_sleep,
.halt_device = bes2600_sdio_halt_device,
.wakeup_source = bes2600_sdio_wakeup_source,
+ .bus_reset = bes2600_sdio_bus_reset,
};
static void bes2600_sdio_en_lp_cb(struct bes2600_common *hw_priv)
diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c
index a02d6d9..d1375bc 100644
--- a/bes2600/bes_chardev.c
+++ b/bes2600/bes_chardev.c
@@ -442,6 +442,48 @@ int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_
return ret;
}
+/*
+ * Hard-reset the bus and wait for the bus core to remove the chip.
+ *
+ * Used by the firmware-wedge recovery path on platforms where the normal
+ * power_switch(0) sequence has no effective chip-reset signal. The bus
+ * implementation triggers an asynchronous re-detect; this helper waits for
+ * the resulting remove() callback to clear bes2600_cdev.sbus_priv so that a
+ * subsequent bes2600_switch_wifi(true) sees a clean state and can wait on
+ * the fresh probe.
+ */
+int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv)
+{
+ int ret;
+ long status;
+
+ if (!sbus_ops || !priv)
+ return -EINVAL;
+
+ if (!sbus_ops->bus_reset)
+ return -EOPNOTSUPP;
+
+ bes_info("trigger bus reset to recover wedged firmware.\n");
+
+ ret = sbus_ops->bus_reset(priv);
+ if (ret) {
+ bes_err("bus_reset failed: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * The bus reset is asynchronous: the bus core schedules a rescan
+ * which removes the bound function drivers and then re-detects the
+ * chip. Wait for the remove callback to clear sbus_priv. Do not
+ * dereference 'priv' after this point -- it may already be freed.
+ */
+ status = wait_event_timeout(bes2600_cdev.probe_done_wq,
+ !bes2600_cdev.sbus_priv, HZ * 3);
+ WARN_ON(status <= 0);
+
+ return 0;
+}
+
bool bes2600_chrdev_is_wifi_opened(void)
{
bool wifi_opened = false;
@@ -540,8 +582,21 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
/* unregister wifi */
bes2600_switch_wifi(0);
- /* power down device if wifi is only opened */
- if (bes2600_chrdev_check_system_close()) {
+ /*
+ * Hard exception with a bus_reset implementation: tear the
+ * bus down via mmc_hw_reset() (or equivalent) so the next
+ * bringup probes a freshly reset chip. On PineTab2 this is
+ * the only effective recovery path -- the existing
+ * power_switch(0)/(1) sequence has no chip-reset signal of
+ * its own (sdio_pwrseq owns wifi_reset).
+ *
+ * Soft close, or hard close on a board without bus_reset:
+ * fall back to the legacy power_switch(0) sequence.
+ */
+ if (bes2600_cdev.halt_dev && bes2600_cdev.sbus_ops->bus_reset) {
+ bes2600_chrdev_do_bus_reset(bes2600_cdev.sbus_ops,
+ bes2600_cdev.sbus_priv);
+ } else if (bes2600_chrdev_check_system_close()) {
bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
diff --git a/bes2600/bes_chardev.h b/bes2600/bes_chardev.h
index 15602ba..3f0c59b 100644
--- a/bes2600/bes_chardev.h
+++ b/bes2600/bes_chardev.h
@@ -60,6 +60,7 @@ struct sbus_priv *bes2600_chrdev_get_sbus_priv_data(void);
/* used to control device power down */
int bes2600_chrdev_check_system_close(void);
int bes2600_chrdev_do_system_close(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
+int bes2600_chrdev_do_bus_reset(const struct sbus_ops *sbus_ops, struct sbus_priv *priv);
void bes2600_chrdev_wakeup_bt(void);
void bes2600_chrdev_wifi_force_close(struct bes2600_common *hw_priv, bool halt_dev);
void bes2600_chrdev_usb_remove(struct bes2600_common *hw_priv);
diff --git a/bes2600/sbus.h b/bes2600/sbus.h
index 1f2c0cd..cb90890 100644
--- a/bes2600/sbus.h
+++ b/bes2600/sbus.h
@@ -75,6 +75,14 @@ struct sbus_ops {
void (*halt_device)(struct sbus_priv *self);
bool (*wakeup_source)(struct sbus_priv *self);
int (*reboot)(struct sbus_priv *self);
+ /*
+ * Force the host bus to re-detect and re-probe the chip. Called
+ * from the firmware-wedge recovery path when power_switch() has no
+ * effective chip-reset signal of its own (e.g. PineTab2, where the
+ * wifi-reset GPIO is owned by sdio_pwrseq, not the bes2600 node).
+ * Returns 0 on success or a negative errno.
+ */
+ int (*bus_reset)(struct sbus_priv *self);
};
void bes2600_irq_handler(struct bes2600_common *priv);
--
2.53.0
@@ -0,0 +1,209 @@
From d1de35c62930b1bc035d3863d75901356548b6f0 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 16:54:07 +0200
Subject: [PATCH] bes2600: self-detect when firmware does not honor PSM and
skip the cycle
The c6 series fixed several host-side bookkeeping bugs around PSM
transitions, but didn't address the underlying contract: this chip's
firmware (BES2600 with the Bestechnic Dec 2023 build that ships on
PineTab2 and most danctnix images) silently drops every WSM_set_pm
request without emitting the corresponding PM_INDICATION. The driver's
own power_down_work delayed work calls bes2600_pwr_enter_lp_mode every
~10s; without firmware acknowledgment each call burns 5s on
wait_for_completion_timeout(pm_enter_cmpl, 5*HZ) and produces a
recurring three-line cascade in dmesg:
bes2600_pwr_enter_lp_mode, wait pm ind timeout
bes2600_sdio_active failed, subsys:0
bes2600_pwr_device_exit_lp_mode, active mcu fail
Confirmed by tripwire instrumentation on PineTab2 (linux-pinetab2
6.19.10-danctnix1, ohm) running the c5+c6 stack: zero
wsm_set_pm_indication() invocations across an entire boot, while
bes2600_pwr_enter_lp_mode timed out repeatedly, and
bes2600_sdio_active() consistently saw BES_SLAVE_STATUS_REG_ID return
0x2f (every "ready" bit set except MCU_WAKEUP_READY (bit 4) - the
firmware reports "I'm awake, there's nothing to wake from").
This patch makes the driver self-heal:
* struct bes2600_pwr_t gains pm_unsupported (bool) and
pm_consecutive_timeouts (unsigned int). Both initialised to
0/false.
* bes2600_pwr_enter_lp_mode early-returns -EOPNOTSUPP when
pm_unsupported is set. Skips the per-VIF set_pm round-trip and
the wait_for_completion entirely.
* On the cmpxchg-success branch of the timeout path, we increment
pm_consecutive_timeouts. When it crosses
BES2600_PM_UNSUPPORTED_THRESHOLD (3, ~15s of trying), we latch
pm_unsupported = true and force chip_pm_state = ACTIVE so that
bes2600_pwr_device_exit_lp_mode's c6.2 skip branch covers the
wake side (no gpio_wake / sbus_active / WSM_set_operational_mode
reissue past the first one).
* bes2600_pwr_notify_ps_changed resets pm_consecutive_timeouts to 0
on any incoming PM indication, and clears pm_unsupported if it
was previously latched. So a firmware update that fixes PM_IND
delivery automatically re-enables PSM transitions without a
driver rebuild.
mac80211's PSM requests via bes2600_set_pm() still flow to the
firmware unchanged; they just don't have host-side timeouts so they
remain silent regardless of firmware acknowledgment. Power
consumption goes up if the firmware actually CAN do PSM (we'd be
keeping the chip awake unnecessarily), but on a chip where the
counter trips this trade-off is forced anyway: the chip stayed awake
under the broken cascade as well, just with constant SDIO churn.
Net effect on dmesg: after ~15s of boot, the three-line cascade stops
firing entirely. The firmware-side wedge is observed once per boot
(captured by the pm_unsupported latch) instead of per-cycle.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes_pwr.c | 70 ++++++++++++++++++++++++++++++-
drivers/staging/bes2600/bes_pwr.h | 9 ++++
2 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c
index d54e1a0bab0c..ebaa42e3e61e 100644
--- a/drivers/staging/bes2600/bes_pwr.c
+++ b/drivers/staging/bes2600/bes_pwr.c
@@ -467,6 +467,45 @@ static void bes2600_pwr_device_enter_lp_mode(struct bes2600_common *hw_priv)
bes_devel("device enter sleep\n");
}
+/*
+ * Number of consecutive bes2600_pwr_enter_lp_mode timeouts (with zero
+ * PM_INDICATIONs received) before we conclude the firmware does not
+ * honor host-driven PSM and switch to a sticky skip path.
+ */
+#define BES2600_PM_UNSUPPORTED_THRESHOLD 3
+
+/*
+ * Latch pm_unsupported = true and force chip_pm_state = ACTIVE so the
+ * c6.2 wake-side skip branch covers bes2600_pwr_device_exit_lp_mode.
+ * Called after BES2600_PM_UNSUPPORTED_THRESHOLD consecutive enter_lp_mode
+ * timeouts with zero PM_INDICATIONs.
+ */
+static void bes2600_pwr_latch_pm_unsupported(struct bes2600_common *hw_priv)
+{
+ bes_warn("PSM not honored (%u timeouts), switching to skip mode\n",
+ hw_priv->bes_power.pm_consecutive_timeouts);
+ hw_priv->bes_power.pm_unsupported = true;
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_ACTIVE);
+
+ /*
+ * Hold the MCU wake-flag bit permanently. Without this, every
+ * sdio_rx_work invocation hits bes2600_gpio_wakeup_mcu(SDIO_RX)
+ * when gpio_wakup_flags == 0, drives the GPIO high and msleeps
+ * 10 ms per RX. With ~50 RX/s of beacons + multicast that's
+ * ~50%% of the bes_sdio workqueue thread blocked in msleep,
+ * which directly caps RX throughput. Holding the MCU bit makes
+ * those calls bit-only bookkeeping (gpio_wakeup = (flags == 0)
+ * stays false, no GPIO toggle, no msleep). The bit is never
+ * cleared once pm_unsupported is set because
+ * bes2600_pwr_device_enter_lp_mode is unreachable under the
+ * early-return.
+ */
+ if (hw_priv->sbus_ops->gpio_wake)
+ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
+}
+
static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
{
int i = 0;
@@ -476,6 +515,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
char ip_str[20];
unsigned long status = 0;
+ /*
+ * Sticky early-return when we've previously concluded the firmware
+ * doesn't honor PSM. Each attempt would otherwise burn 5s on a
+ * doomed wait_for_completion_timeout and produce a noisy three-line
+ * cascade in dmesg every time power_down_work retries (every
+ * ~10s). The chip stays in active mode, which on this firmware is
+ * the de-facto state anyway.
+ */
+ if (hw_priv->bes_power.pm_unsupported)
+ return -EOPNOTSUPP;
+
/* set interface low power configuration */
bes2600_for_each_vif(hw_priv, priv, i) {
#ifdef P2P_MULTIVIF
@@ -571,6 +621,9 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_UNKNOWN);
timeouts++;
+ if (++hw_priv->bes_power.pm_consecutive_timeouts
+ >= BES2600_PM_UNSUPPORTED_THRESHOLD)
+ bes2600_pwr_latch_pm_unsupported(hw_priv);
}
}
} else {
@@ -609,7 +662,8 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
* GPIO stays high and the bit clear here is purely
* bookkeeping (so the next gpio_wake doesn't no-op).
*/
- if (hw_priv->sbus_ops->gpio_sleep)
+ if (!hw_priv->bes_power.pm_unsupported &&
+ hw_priv->sbus_ops->gpio_sleep)
hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv,
GPIO_WAKE_FLAG_MCU);
ret = -ETIMEDOUT;
@@ -932,6 +986,8 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv)
mutex_init(&hw_priv->bes_power.pwr_mutex);
atomic_set(&hw_priv->bes_power.dev_state, 0);
atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN);
+ hw_priv->bes_power.pm_unsupported = false;
+ hw_priv->bes_power.pm_consecutive_timeouts = 0;
init_completion(&hw_priv->bes_power.pm_enter_cmpl);
sema_init(&hw_priv->bes_power.sync_lock, 1);
device_set_wakeup_capable(hw_priv->pdev, true);
@@ -1321,6 +1377,18 @@ void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode)
* indication can prime a future wait against a freshly
* reinit_completion()'ed state.
*/
+ /*
+ * Any PM indication, whatever its psmode, proves the firmware is
+ * actually emitting them. Reset the consecutive-timeout counter
+ * so a transient stall doesn't permanently disable PSM, and clear
+ * pm_unsupported if a previous run had latched it.
+ */
+ hw_priv->bes_power.pm_consecutive_timeouts = 0;
+ if (hw_priv->bes_power.pm_unsupported) {
+ bes_warn("PM indication arrived after pm_unsupported was set; re-enabling PSM transitions\n");
+ hw_priv->bes_power.pm_unsupported = false;
+ }
+
if ((psmode & 0x01) != WSM_PSM_ACTIVE) {
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_LP);
diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h
index 6bc44acd7501..92de90b398c6 100644
--- a/drivers/staging/bes2600/bes_pwr.h
+++ b/drivers/staging/bes2600/bes_pwr.h
@@ -121,6 +121,15 @@ struct bes2600_pwr_t
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
atomic_t chip_pm_state;
+ /*
+ * Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD
+ * consecutive enter_lp_mode timeouts with zero PM_INDICATIONs
+ * received from firmware. Indicates this chip's firmware does
+ * not honor host-driven PSM transitions; further attempts are
+ * skipped to avoid the 5s timeout cascade.
+ */
+ bool pm_unsupported;
+ unsigned int pm_consecutive_timeouts;
};
#ifdef CONFIG_BES2600_WOWLAN
--
2.53.0
@@ -0,0 +1,209 @@
From f12e87002576f094c441ac6c945a451c88868592 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 16:54:06 +0200
Subject: [PATCH] bes2600: self-detect when firmware does not honor PSM and
skip the cycle
The c6 series fixed several host-side bookkeeping bugs around PSM
transitions, but didn't address the underlying contract: this chip's
firmware (BES2600 with the Bestechnic Dec 2023 build that ships on
PineTab2 and most danctnix images) silently drops every WSM_set_pm
request without emitting the corresponding PM_INDICATION. The driver's
own power_down_work delayed work calls bes2600_pwr_enter_lp_mode every
~10s; without firmware acknowledgment each call burns 5s on
wait_for_completion_timeout(pm_enter_cmpl, 5*HZ) and produces a
recurring three-line cascade in dmesg:
bes2600_pwr_enter_lp_mode, wait pm ind timeout
bes2600_sdio_active failed, subsys:0
bes2600_pwr_device_exit_lp_mode, active mcu fail
Confirmed by tripwire instrumentation on PineTab2 (linux-pinetab2
6.19.10-danctnix1, ohm) running the c5+c6 stack: zero
wsm_set_pm_indication() invocations across an entire boot, while
bes2600_pwr_enter_lp_mode timed out repeatedly, and
bes2600_sdio_active() consistently saw BES_SLAVE_STATUS_REG_ID return
0x2f (every "ready" bit set except MCU_WAKEUP_READY (bit 4) - the
firmware reports "I'm awake, there's nothing to wake from").
This patch makes the driver self-heal:
* struct bes2600_pwr_t gains pm_unsupported (bool) and
pm_consecutive_timeouts (unsigned int). Both initialised to
0/false.
* bes2600_pwr_enter_lp_mode early-returns -EOPNOTSUPP when
pm_unsupported is set. Skips the per-VIF set_pm round-trip and
the wait_for_completion entirely.
* On the cmpxchg-success branch of the timeout path, we increment
pm_consecutive_timeouts. When it crosses
BES2600_PM_UNSUPPORTED_THRESHOLD (3, ~15s of trying), we latch
pm_unsupported = true and force chip_pm_state = ACTIVE so that
bes2600_pwr_device_exit_lp_mode's c6.2 skip branch covers the
wake side (no gpio_wake / sbus_active / WSM_set_operational_mode
reissue past the first one).
* bes2600_pwr_notify_ps_changed resets pm_consecutive_timeouts to 0
on any incoming PM indication, and clears pm_unsupported if it
was previously latched. So a firmware update that fixes PM_IND
delivery automatically re-enables PSM transitions without a
driver rebuild.
mac80211's PSM requests via bes2600_set_pm() still flow to the
firmware unchanged; they just don't have host-side timeouts so they
remain silent regardless of firmware acknowledgment. Power
consumption goes up if the firmware actually CAN do PSM (we'd be
keeping the chip awake unnecessarily), but on a chip where the
counter trips this trade-off is forced anyway: the chip stayed awake
under the broken cascade as well, just with constant SDIO churn.
Net effect on dmesg: after ~15s of boot, the three-line cascade stops
firing entirely. The firmware-side wedge is observed once per boot
(captured by the pm_unsupported latch) instead of per-cycle.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++-
bes2600/bes_pwr.h | 9 ++++++
2 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index b7b6c2f..620acef 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -467,6 +467,45 @@ static void bes2600_pwr_device_enter_lp_mode(struct bes2600_common *hw_priv)
bes_devel("device enter sleep\n");
}
+/*
+ * Number of consecutive bes2600_pwr_enter_lp_mode timeouts (with zero
+ * PM_INDICATIONs received) before we conclude the firmware does not
+ * honor host-driven PSM and switch to a sticky skip path.
+ */
+#define BES2600_PM_UNSUPPORTED_THRESHOLD 3
+
+/*
+ * Latch pm_unsupported = true and force chip_pm_state = ACTIVE so the
+ * c6.2 wake-side skip branch covers bes2600_pwr_device_exit_lp_mode.
+ * Called after BES2600_PM_UNSUPPORTED_THRESHOLD consecutive enter_lp_mode
+ * timeouts with zero PM_INDICATIONs.
+ */
+static void bes2600_pwr_latch_pm_unsupported(struct bes2600_common *hw_priv)
+{
+ bes_warn("PSM not honored (%u timeouts), switching to skip mode\n",
+ hw_priv->bes_power.pm_consecutive_timeouts);
+ hw_priv->bes_power.pm_unsupported = true;
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_ACTIVE);
+
+ /*
+ * Hold the MCU wake-flag bit permanently. Without this, every
+ * sdio_rx_work invocation hits bes2600_gpio_wakeup_mcu(SDIO_RX)
+ * when gpio_wakup_flags == 0, drives the GPIO high and msleeps
+ * 10 ms per RX. With ~50 RX/s of beacons + multicast that's
+ * ~50%% of the bes_sdio workqueue thread blocked in msleep,
+ * which directly caps RX throughput. Holding the MCU bit makes
+ * those calls bit-only bookkeeping (gpio_wakeup = (flags == 0)
+ * stays false, no GPIO toggle, no msleep). The bit is never
+ * cleared once pm_unsupported is set because
+ * bes2600_pwr_device_enter_lp_mode is unreachable under the
+ * early-return.
+ */
+ if (hw_priv->sbus_ops->gpio_wake)
+ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
+}
+
static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
{
int i = 0;
@@ -476,6 +515,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
char ip_str[20];
unsigned long status = 0;
+ /*
+ * Sticky early-return when we've previously concluded the firmware
+ * doesn't honor PSM. Each attempt would otherwise burn 5s on a
+ * doomed wait_for_completion_timeout and produce a noisy three-line
+ * cascade in dmesg every time power_down_work retries (every
+ * ~10s). The chip stays in active mode, which on this firmware is
+ * the de-facto state anyway.
+ */
+ if (hw_priv->bes_power.pm_unsupported)
+ return -EOPNOTSUPP;
+
/* set interface low power configuration */
bes2600_for_each_vif(hw_priv, priv, i) {
#ifdef P2P_MULTIVIF
@@ -571,6 +621,9 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_UNKNOWN);
timeouts++;
+ if (++hw_priv->bes_power.pm_consecutive_timeouts
+ >= BES2600_PM_UNSUPPORTED_THRESHOLD)
+ bes2600_pwr_latch_pm_unsupported(hw_priv);
}
}
} else {
@@ -609,7 +662,8 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
* GPIO stays high and the bit clear here is purely
* bookkeeping (so the next gpio_wake doesn't no-op).
*/
- if (hw_priv->sbus_ops->gpio_sleep)
+ if (!hw_priv->bes_power.pm_unsupported &&
+ hw_priv->sbus_ops->gpio_sleep)
hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv,
GPIO_WAKE_FLAG_MCU);
ret = -ETIMEDOUT;
@@ -932,6 +986,8 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv)
mutex_init(&hw_priv->bes_power.pwr_mutex);
atomic_set(&hw_priv->bes_power.dev_state, 0);
atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN);
+ hw_priv->bes_power.pm_unsupported = false;
+ hw_priv->bes_power.pm_consecutive_timeouts = 0;
init_completion(&hw_priv->bes_power.pm_enter_cmpl);
sema_init(&hw_priv->bes_power.sync_lock, 1);
device_set_wakeup_capable(hw_priv->pdev, true);
@@ -1321,6 +1377,18 @@ void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode)
* indication can prime a future wait against a freshly
* reinit_completion()'ed state.
*/
+ /*
+ * Any PM indication, whatever its psmode, proves the firmware is
+ * actually emitting them. Reset the consecutive-timeout counter
+ * so a transient stall doesn't permanently disable PSM, and clear
+ * pm_unsupported if a previous run had latched it.
+ */
+ hw_priv->bes_power.pm_consecutive_timeouts = 0;
+ if (hw_priv->bes_power.pm_unsupported) {
+ bes_warn("PM indication arrived after pm_unsupported was set; re-enabling PSM transitions\n");
+ hw_priv->bes_power.pm_unsupported = false;
+ }
+
if ((psmode & 0x01) != WSM_PSM_ACTIVE) {
atomic_set(&hw_priv->bes_power.chip_pm_state,
BES2600_CHIP_PM_LP);
diff --git a/bes2600/bes_pwr.h b/bes2600/bes_pwr.h
index 6bc44ac..92de90b 100644
--- a/bes2600/bes_pwr.h
+++ b/bes2600/bes_pwr.h
@@ -121,6 +121,15 @@ struct bes2600_pwr_t
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
atomic_t chip_pm_state;
+ /*
+ * Sticky flag set after BES2600_PM_UNSUPPORTED_THRESHOLD
+ * consecutive enter_lp_mode timeouts with zero PM_INDICATIONs
+ * received from firmware. Indicates this chip's firmware does
+ * not honor host-driven PSM transitions; further attempts are
+ * skipped to avoid the 5s timeout cascade.
+ */
+ bool pm_unsupported;
+ unsigned int pm_consecutive_timeouts;
};
#ifdef CONFIG_BES2600_WOWLAN
--
2.53.0
@@ -0,0 +1,105 @@
From 80178ec9b1f83aed1dcce9ea7ca02bc81341ba01 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:37:45 +0200
Subject: [PATCH] bes2600: gate device LP-mode entry on successful per-VIF
firmware handshake
bes2600_pwr_enter_lp_mode() drives the transition to low-power for each
associated STA VIF: it pushes wsm_set_pm(), waits up to 5 seconds on
pm_enter_cmpl for the firmware to acknowledge, then unconditionally
calls bes2600_pwr_device_enter_lp_mode() to drop the device end of the
bus.
Two bugs:
1. A failed wsm_set_pm() only logs an error, then still falls into
wait_for_completion_timeout() on a completion the firmware will
never post (the set-mode command never reached it). The loop
therefore always blocks the full 5 s, logs a second error, and
proceeds.
2. A genuine wait-timeout (firmware received the set-mode command but
never posted the indication) also only logs a warning. The code
then drops to bes2600_pwr_device_enter_lp_mode(), handing the
device subsystem an inconsistent view of mac-layer state.
On PineTab2 (BES2600WM + RK3566) the second bug is the recurring
root-cause of the 'bes2600_pwr_enter_lp_mode, wait pm ind timeout'
message flooding dmesg every 5-10 s when the interface is associated
and idle. Sending the device to LP in that state cascades into the
SDIO TX path as the 'bes_sdio_memcpy_to_io_helper / sdio_tx_work'
WARN splat.
Fix:
- Add a 'timeouts' counter; bump it on both failure paths.
- Skip the wait_for_completion entirely when wsm_set_pm() failed
(there is no completion to wait for).
- Only call bes2600_pwr_device_enter_lp_mode() when every per-VIF
handshake reached firmware-ACKed completion; otherwise return
-ETIMEDOUT and leave the device in its current power state.
Tested-on: PineTab2 running linux-pinetab2 6.19.10-danctnix1-1.
Post-patch the handshake still fails on this particular firmware
revision (separate root-cause investigation outside this patch), but
the driver now returns -ETIMEDOUT cleanly instead of flooding dmesg
and destabilising the SDIO path.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index e7a1045..f62ae22 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -472,6 +472,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
int i = 0;
struct bes2600_vif *priv;
int ret = 0;
+ int timeouts = 0;
char ip_str[20];
unsigned long status = 0;
@@ -528,22 +529,35 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
bes_err("%s, set operation mode fail\n", __func__);
+ timeouts++;
+ continue;
}
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
- if (!status)
+ if (!status) {
bes_err("%s, wait pm ind timeout\n", __func__);
+ timeouts++;
+ }
} else {
bes_devel("skip enter lp mode\n");
}
}
}
- /* set device low power configuration */
- bes2600_pwr_device_enter_lp_mode(hw_priv);
+ /*
+ * Enter the device-end of the LP transition only if every per-VIF
+ * mac80211 handshake reached firmware-ACKed completion. Doing the
+ * device-LP setup while any VIF is still pending leaves the driver
+ * in an inconsistent state that cascades into SDIO TX errors on
+ * the BES2600.
+ */
+ if (timeouts == 0)
+ bes2600_pwr_device_enter_lp_mode(hw_priv);
+ else
+ ret = -ETIMEDOUT;
return ret;
}
--
2.53.0
@@ -0,0 +1,246 @@
From 4ab8c790304206abd134de48c878b637a70f3c59 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 15:05:27 +0200
Subject: [PATCH] bes2600: gate PM indication completion on pending request and
track chip state
When mac80211 toggles PSM on the BES2600, the host sends WSM set_pm
and waits up to 5 s on bes_power.pm_enter_cmpl for a firmware-side
PM-changed indication confirming the transition. Three sequenced
flaws make the wait-and-confirm racy and leave host/chip bookkeeping
desynced when anything misfires:
1) bes2600_pwr_notify_ps_changed() unconditionally fires
complete(pm_enter_cmpl) for any non-active psmode. It does not
check whether a host-initiated set_pm is actually pending. A
spontaneous indication (firmware-internal coex move,
idle-driven aging) primes the completion, and the next host-
driven enter_lp_mode sees a false success on its first
wait_for_completion_timeout.
2) The wait/reinit ordering in bes2600_pwr_enter_lp_mode is
status = wait_for_completion_timeout(...);
atomic_set(pm_set_in_process, 0);
reinit_completion(...);
If an indication arrives between wait_for_completion_timeout
returning with status==1 and reinit_completion, the next
enter_lp_mode iteration's wait can also see false success. The
reinit must happen *before* we start the new request, not
after handling the previous one.
3) On wait_pm_ind timeout, the driver returns -ETIMEDOUT and walks
away. It does not record that the firmware's actual PM state
is no longer known to the host. Subsequent wake paths
(gpio_wake / sbus_active) assume the chip is still active and
hit deterministic SDIO failures when the firmware has
transitioned anyway.
This patch is the safe-prerequisite half of a wider fix:
* bes_pwr.h gains enum bes2600_chip_pm_state {ACTIVE, LP, UNKNOWN}
and bes_power.chip_pm_state. Its job is to track what the host
has *seen the firmware confirm*, not what the host has
requested. Initialised to ACTIVE in bes2600_pwr_init().
* bes2600_pwr_notify_ps_changed() unconditionally updates
chip_pm_state on every indication, but only fires
complete(pm_enter_cmpl) when atomic_cmpxchg(pm_set_in_process,
1, 0) succeeds. A spontaneous indication can no longer prime a
waiter that will only set up its request afterwards.
* bes2600_pwr_enter_lp_mode() now reinit_completion()s before
setting pm_set_in_process and sending wsm_set_pm. After a
timeout, it cmpxchgs pm_set_in_process back to 0 (so a late
indication cannot prime the next iteration) and on the win-
cmpxchg branch records chip_pm_state=UNKNOWN.
A follow-up patch consumes chip_pm_state on the wake side
(bes2600_pwr_device_exit_lp_mode + bes2600_gpio_wakeup_mcu) to fix
the deterministic "active mcu fail" cycle this state-record
enables a fix for. Splitting the work this way keeps the lock-free
race fix small and reviewable on its own.
No new locks, no behaviour change on the success path. Only the
recovery path (timeout + spontaneous indication) gains correctness.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes_pwr.c | 94 ++++++++++++++++++++++++++++---
drivers/staging/bes2600/bes_pwr.h | 15 +++++
2 files changed, 100 insertions(+), 9 deletions(-)
diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c
index f62ae226d295..de46e5826ee7 100644
--- a/drivers/staging/bes2600/bes_pwr.c
+++ b/drivers/staging/bes2600/bes_pwr.c
@@ -524,7 +524,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n",
__func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod,
priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod);
+ /*
+ * Reinit BEFORE the WSM goes out, so a stale
+ * indication from a previous cycle cannot have
+ * primed pm_enter_cmpl. From here until the
+ * indication callback's cmpxchg(1->0) on
+ * pm_set_in_process, only the indication for
+ * THIS request can complete the wait.
+ */
+ reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 1);
+
ret = bes2600_set_pm(priv, &priv->powersave_mode);
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
@@ -535,11 +545,33 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
- atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
- reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
if (!status) {
- bes_err("%s, wait pm ind timeout\n", __func__);
- timeouts++;
+ /*
+ * The indication callback only fires
+ * complete() when it observes
+ * pm_set_in_process == 1; cmpxchg it
+ * to 0 here so a late indication
+ * cannot prime the next wait.
+ *
+ * If we win the cmpxchg, this is a
+ * real timeout: the firmware's PS
+ * state is unknown to us. Mark it as
+ * such so the next wake path can
+ * probe before assuming the chip is
+ * still active.
+ *
+ * If we lose the cmpxchg, the
+ * indication arrived between the
+ * wait timing out and us getting
+ * here; treat as success.
+ */
+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
+ 1, 0) == 1) {
+ bes_err("%s, wait pm ind timeout\n", __func__);
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_UNKNOWN);
+ timeouts++;
+ }
}
} else {
bes_devel("skip enter lp mode\n");
@@ -554,10 +586,34 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
* in an inconsistent state that cascades into SDIO TX errors on
* the BES2600.
*/
- if (timeouts == 0)
+ if (timeouts == 0) {
bes2600_pwr_device_enter_lp_mode(hw_priv);
- else
+ } else {
+ /*
+ * device_enter_lp_mode() was skipped (one or more VIFs
+ * timed out waiting for the firmware indication) so its
+ * gpio_sleep(MCU) - which drops the wake-flag bit and, if
+ * no other subsystem holds the wake, drives the GPIO low -
+ * never ran. Without it the bit stays asserted, and the
+ * next bes2600_pwr_device_exit_lp_mode() calls
+ * gpio_wake(MCU) into a "bit already set" no-op: the GPIO
+ * never re-edges, sbus_active() exhausts its 200x2ms
+ * MCU_WAKEUP_READY budget against an unwoken chip, and
+ * the first TX after idle stalls for several seconds.
+ *
+ * Drop the MCU wake-flag bit explicitly here so the next
+ * wake injects a real GPIO edge. gpio_allow_mcu_sleep
+ * preserves multi-subsystem semantics: it only drives the
+ * GPIO low when no other subsystem still holds wake; if
+ * BT or another holder is keeping the chip awake, the
+ * GPIO stays high and the bit clear here is purely
+ * bookkeeping (so the next gpio_wake doesn't no-op).
+ */
+ if (hw_priv->sbus_ops->gpio_sleep)
+ hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
ret = -ETIMEDOUT;
+ }
return ret;
}
@@ -833,6 +889,7 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv)
hw_priv->bes_power.power_up_task = NULL;
mutex_init(&hw_priv->bes_power.pwr_mutex);
atomic_set(&hw_priv->bes_power.dev_state, 0);
+ atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN);
init_completion(&hw_priv->bes_power.pm_enter_cmpl);
sema_init(&hw_priv->bes_power.sync_lock, 1);
device_set_wakeup_capable(hw_priv->pdev, true);
@@ -1213,9 +1270,28 @@ int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event)
void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode)
{
- if((psmode & 0x01) != WSM_PSM_ACTIVE) {
- bes_devel("complete pm_enter_cmpl\n");
- complete(&hw_priv->bes_power.pm_enter_cmpl);
+ /*
+ * The firmware sends a PM-changed indication for every transition,
+ * including ones we didn't ask for (firmware-internal coex moves,
+ * idle-driven aging). Update chip_pm_state unconditionally so the
+ * wake path can use it, but only fire pm_enter_cmpl when a host-
+ * initiated set_pm is actually in flight - otherwise a stale
+ * indication can prime a future wait against a freshly
+ * reinit_completion()'ed state.
+ */
+ if ((psmode & 0x01) != WSM_PSM_ACTIVE) {
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_LP);
+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
+ 1, 0) == 1) {
+ bes_devel("complete pm_enter_cmpl\n");
+ complete(&hw_priv->bes_power.pm_enter_cmpl);
+ } else {
+ bes_devel("PM ind (LP) without pending wait; state recorded\n");
+ }
+ } else {
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_ACTIVE);
}
}
diff --git a/drivers/staging/bes2600/bes_pwr.h b/drivers/staging/bes2600/bes_pwr.h
index 1ba866c25c42..6bc44acd7501 100644
--- a/drivers/staging/bes2600/bes_pwr.h
+++ b/drivers/staging/bes2600/bes_pwr.h
@@ -64,6 +64,20 @@ enum power_down_state
POWER_DOWN_STATE_UNLOCKED,
};
+/*
+ * Confirmed PM state of the firmware-side chip. Tracks what the host
+ * has *seen* the firmware acknowledge, not what the host has
+ * requested. UNKNOWN means a host-initiated transition timed out
+ * before the firmware indication arrived; the next wake path should
+ * treat it as "we don't know" and probe before issuing GPIO/SDIO
+ * wakeup ops.
+ */
+enum bes2600_chip_pm_state {
+ BES2600_CHIP_PM_ACTIVE = 0,
+ BES2600_CHIP_PM_LP,
+ BES2600_CHIP_PM_UNKNOWN,
+};
+
typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv);
typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv);
@@ -106,6 +120,7 @@ struct bes2600_pwr_t
bool ap_lp_bad;
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
+ atomic_t chip_pm_state;
};
#ifdef CONFIG_BES2600_WOWLAN
--
2.53.0
@@ -0,0 +1,246 @@
From c57c77e446d9a552b537175453b838d0400ff41d Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 15:05:27 +0200
Subject: [PATCH] bes2600: gate PM indication completion on pending request and
track chip state
When mac80211 toggles PSM on the BES2600, the host sends WSM set_pm
and waits up to 5 s on bes_power.pm_enter_cmpl for a firmware-side
PM-changed indication confirming the transition. Three sequenced
flaws make the wait-and-confirm racy and leave host/chip bookkeeping
desynced when anything misfires:
1) bes2600_pwr_notify_ps_changed() unconditionally fires
complete(pm_enter_cmpl) for any non-active psmode. It does not
check whether a host-initiated set_pm is actually pending. A
spontaneous indication (firmware-internal coex move,
idle-driven aging) primes the completion, and the next host-
driven enter_lp_mode sees a false success on its first
wait_for_completion_timeout.
2) The wait/reinit ordering in bes2600_pwr_enter_lp_mode is
status = wait_for_completion_timeout(...);
atomic_set(pm_set_in_process, 0);
reinit_completion(...);
If an indication arrives between wait_for_completion_timeout
returning with status==1 and reinit_completion, the next
enter_lp_mode iteration's wait can also see false success. The
reinit must happen *before* we start the new request, not
after handling the previous one.
3) On wait_pm_ind timeout, the driver returns -ETIMEDOUT and walks
away. It does not record that the firmware's actual PM state
is no longer known to the host. Subsequent wake paths
(gpio_wake / sbus_active) assume the chip is still active and
hit deterministic SDIO failures when the firmware has
transitioned anyway.
This patch is the safe-prerequisite half of a wider fix:
* bes_pwr.h gains enum bes2600_chip_pm_state {ACTIVE, LP, UNKNOWN}
and bes_power.chip_pm_state. Its job is to track what the host
has *seen the firmware confirm*, not what the host has
requested. Initialised to ACTIVE in bes2600_pwr_init().
* bes2600_pwr_notify_ps_changed() unconditionally updates
chip_pm_state on every indication, but only fires
complete(pm_enter_cmpl) when atomic_cmpxchg(pm_set_in_process,
1, 0) succeeds. A spontaneous indication can no longer prime a
waiter that will only set up its request afterwards.
* bes2600_pwr_enter_lp_mode() now reinit_completion()s before
setting pm_set_in_process and sending wsm_set_pm. After a
timeout, it cmpxchgs pm_set_in_process back to 0 (so a late
indication cannot prime the next iteration) and on the win-
cmpxchg branch records chip_pm_state=UNKNOWN.
A follow-up patch consumes chip_pm_state on the wake side
(bes2600_pwr_device_exit_lp_mode + bes2600_gpio_wakeup_mcu) to fix
the deterministic "active mcu fail" cycle this state-record
enables a fix for. Splitting the work this way keeps the lock-free
race fix small and reviewable on its own.
No new locks, no behaviour change on the success path. Only the
recovery path (timeout + spontaneous indication) gains correctness.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 94 ++++++++++++++++++++++++++++++++++++++++++-----
bes2600/bes_pwr.h | 15 ++++++++
2 files changed, 100 insertions(+), 9 deletions(-)
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index 474b6f1..9b4a4de 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -524,7 +524,17 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
bes_devel("%s, psMode:%s, fastPsmIdlePeriod:%d apPsmChangePeriod:%d minAutoPsPollPeriod:%d\n",
__func__, bes2600_get_ps_mode_str(priv->powersave_mode.pmMode), priv->powersave_mode.fastPsmIdlePeriod,
priv->powersave_mode.apPsmChangePeriod, priv->powersave_mode.minAutoPsPollPeriod);
+ /*
+ * Reinit BEFORE the WSM goes out, so a stale
+ * indication from a previous cycle cannot have
+ * primed pm_enter_cmpl. From here until the
+ * indication callback's cmpxchg(1->0) on
+ * pm_set_in_process, only the indication for
+ * THIS request can complete the wait.
+ */
+ reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 1);
+
ret = bes2600_set_pm(priv, &priv->powersave_mode);
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
@@ -535,11 +545,33 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
- atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
- reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
if (!status) {
- bes_devel("%s, wait pm ind timeout\n", __func__);
- timeouts++;
+ /*
+ * The indication callback only fires
+ * complete() when it observes
+ * pm_set_in_process == 1; cmpxchg it
+ * to 0 here so a late indication
+ * cannot prime the next wait.
+ *
+ * If we win the cmpxchg, this is a
+ * real timeout: the firmware's PS
+ * state is unknown to us. Mark it as
+ * such so the next wake path can
+ * probe before assuming the chip is
+ * still active.
+ *
+ * If we lose the cmpxchg, the
+ * indication arrived between the
+ * wait timing out and us getting
+ * here; treat as success.
+ */
+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
+ 1, 0) == 1) {
+ bes_devel("%s, wait pm ind timeout\n", __func__);
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_UNKNOWN);
+ timeouts++;
+ }
}
} else {
bes_devel("skip enter lp mode\n");
@@ -554,10 +586,34 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
* in an inconsistent state that cascades into SDIO TX errors on
* the BES2600.
*/
- if (timeouts == 0)
+ if (timeouts == 0) {
bes2600_pwr_device_enter_lp_mode(hw_priv);
- else
+ } else {
+ /*
+ * device_enter_lp_mode() was skipped (one or more VIFs
+ * timed out waiting for the firmware indication) so its
+ * gpio_sleep(MCU) - which drops the wake-flag bit and, if
+ * no other subsystem holds the wake, drives the GPIO low -
+ * never ran. Without it the bit stays asserted, and the
+ * next bes2600_pwr_device_exit_lp_mode() calls
+ * gpio_wake(MCU) into a "bit already set" no-op: the GPIO
+ * never re-edges, sbus_active() exhausts its 200x2ms
+ * MCU_WAKEUP_READY budget against an unwoken chip, and
+ * the first TX after idle stalls for several seconds.
+ *
+ * Drop the MCU wake-flag bit explicitly here so the next
+ * wake injects a real GPIO edge. gpio_allow_mcu_sleep
+ * preserves multi-subsystem semantics: it only drives the
+ * GPIO low when no other subsystem still holds wake; if
+ * BT or another holder is keeping the chip awake, the
+ * GPIO stays high and the bit clear here is purely
+ * bookkeeping (so the next gpio_wake doesn't no-op).
+ */
+ if (hw_priv->sbus_ops->gpio_sleep)
+ hw_priv->sbus_ops->gpio_sleep(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
ret = -ETIMEDOUT;
+ }
return ret;
}
@@ -833,6 +889,7 @@ void bes2600_pwr_init(struct bes2600_common *hw_priv)
hw_priv->bes_power.power_up_task = NULL;
mutex_init(&hw_priv->bes_power.pwr_mutex);
atomic_set(&hw_priv->bes_power.dev_state, 0);
+ atomic_set(&hw_priv->bes_power.chip_pm_state, BES2600_CHIP_PM_UNKNOWN);
init_completion(&hw_priv->bes_power.pm_enter_cmpl);
sema_init(&hw_priv->bes_power.sync_lock, 1);
device_set_wakeup_capable(hw_priv->pdev, true);
@@ -1213,9 +1270,28 @@ int bes2600_pwr_clear_busy_event(struct bes2600_common *hw_priv, u32 event)
void bes2600_pwr_notify_ps_changed(struct bes2600_common *hw_priv, u8 psmode)
{
- if((psmode & 0x01) != WSM_PSM_ACTIVE) {
- bes_devel("complete pm_enter_cmpl\n");
- complete(&hw_priv->bes_power.pm_enter_cmpl);
+ /*
+ * The firmware sends a PM-changed indication for every transition,
+ * including ones we didn't ask for (firmware-internal coex moves,
+ * idle-driven aging). Update chip_pm_state unconditionally so the
+ * wake path can use it, but only fire pm_enter_cmpl when a host-
+ * initiated set_pm is actually in flight - otherwise a stale
+ * indication can prime a future wait against a freshly
+ * reinit_completion()'ed state.
+ */
+ if ((psmode & 0x01) != WSM_PSM_ACTIVE) {
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_LP);
+ if (atomic_cmpxchg(&hw_priv->bes_power.pm_set_in_process,
+ 1, 0) == 1) {
+ bes_devel("complete pm_enter_cmpl\n");
+ complete(&hw_priv->bes_power.pm_enter_cmpl);
+ } else {
+ bes_devel("PM ind (LP) without pending wait; state recorded\n");
+ }
+ } else {
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_ACTIVE);
}
}
diff --git a/bes2600/bes_pwr.h b/bes2600/bes_pwr.h
index 1ba866c..6bc44ac 100644
--- a/bes2600/bes_pwr.h
+++ b/bes2600/bes_pwr.h
@@ -64,6 +64,20 @@ enum power_down_state
POWER_DOWN_STATE_UNLOCKED,
};
+/*
+ * Confirmed PM state of the firmware-side chip. Tracks what the host
+ * has *seen* the firmware acknowledge, not what the host has
+ * requested. UNKNOWN means a host-initiated transition timed out
+ * before the firmware indication arrived; the next wake path should
+ * treat it as "we don't know" and probe before issuing GPIO/SDIO
+ * wakeup ops.
+ */
+enum bes2600_chip_pm_state {
+ BES2600_CHIP_PM_ACTIVE = 0,
+ BES2600_CHIP_PM_LP,
+ BES2600_CHIP_PM_UNKNOWN,
+};
+
typedef void (*bes_pwr_enter_lp_cb)(struct bes2600_common *hw_priv);
typedef void (*bes_pwr_exit_lp_cb)(struct bes2600_common *hw_priv);
@@ -106,6 +120,7 @@ struct bes2600_pwr_t
bool ap_lp_bad;
struct bes2600_pwr_event_t pwr_events[BES2600_DELAY_EVENT_NUM];
atomic_t pm_set_in_process;
+ atomic_t chip_pm_state;
};
#ifdef CONFIG_BES2600_WOWLAN
--
2.53.0
@@ -0,0 +1,53 @@
From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:35:17 +0200
Subject: [PATCH] bes2600: demote 'wait pm ind timeout' from bes_err to
bes_devel
bes2600_pwr_enter_lp_mode() logs 'wait pm ind timeout' at bes_err
level every time wait_for_completion_timeout() on the firmware's
PM-change indication returns 0. The preceding patch ('bes2600:
gate device LP-mode entry on successful per-VIF firmware
handshake') already handles this case correctly: the per-VIF
timeouts counter is incremented, the function returns
-ETIMEDOUT, and the device-side LP transition is skipped -- the
cascade into sdio_tx_work splats and [RX] Receive failure
messages is prevented.
The timeout itself is benign steady-state noise on the PineTab2
(BES2600WM). Firmware occasionally misses the 5 s PM-change
deadline when mac80211 flips power-save rapidly during
association or roaming; observed rate on a quiet, associated
ohm is roughly 3-10 events per 10 min of uptime, with no
user-visible effect. Keeping it at bes_err() level (== KERN_ERR,
priority 3) floods dmesg with what is already a handled
condition and makes real SDIO / PM errors harder to spot.
Demote to bes_devel() (== KERN_DEBUG gated on the driver's debug
flag). The gate in the caller is unchanged, so the downstream
suppression behaviour introduced by the earlier patch remains.
Real pathologies -- bes_err("set operation mode fail") on the
same path, and the timeouts != 0 / -ETIMEDOUT return consumed
by callers -- still surface at bes_err() / return-value level.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c
index f62ae22..474b6f1 100644
--- a/drivers/staging/bes2600/bes_pwr.c
+++ b/drivers/staging/bes2600/bes_pwr.c
@@ -538,7 +538,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
if (!status) {
- bes_err("%s, wait pm ind timeout\n", __func__);
+ bes_devel("%s, wait pm ind timeout\n", __func__);
timeouts++;
}
} else {
--
2.53.0
@@ -0,0 +1,53 @@
From ab9e0ad6b4bbb1196c448ed000c8c152b0f04683 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 20:35:17 +0200
Subject: [PATCH] bes2600: demote 'wait pm ind timeout' from bes_err to
bes_devel
bes2600_pwr_enter_lp_mode() logs 'wait pm ind timeout' at bes_err
level every time wait_for_completion_timeout() on the firmware's
PM-change indication returns 0. The preceding patch ('bes2600:
gate device LP-mode entry on successful per-VIF firmware
handshake') already handles this case correctly: the per-VIF
timeouts counter is incremented, the function returns
-ETIMEDOUT, and the device-side LP transition is skipped -- the
cascade into sdio_tx_work splats and [RX] Receive failure
messages is prevented.
The timeout itself is benign steady-state noise on the PineTab2
(BES2600WM). Firmware occasionally misses the 5 s PM-change
deadline when mac80211 flips power-save rapidly during
association or roaming; observed rate on a quiet, associated
ohm is roughly 3-10 events per 10 min of uptime, with no
user-visible effect. Keeping it at bes_err() level (== KERN_ERR,
priority 3) floods dmesg with what is already a handled
condition and makes real SDIO / PM errors harder to spot.
Demote to bes_devel() (== KERN_DEBUG gated on the driver's debug
flag). The gate in the caller is unchanged, so the downstream
suppression behaviour introduced by the earlier patch remains.
Real pathologies -- bes_err("set operation mode fail") on the
same path, and the timeouts != 0 / -ETIMEDOUT return consumed
by callers -- still surface at bes_err() / return-value level.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index f62ae22..474b6f1 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -538,7 +538,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
if (!status) {
- bes_err("%s, wait pm ind timeout\n", __func__);
+ bes_devel("%s, wait pm ind timeout\n", __func__);
timeouts++;
}
} else {
--
2.53.0
@@ -0,0 +1,190 @@
From 706a594dab68779294e4fff9705a6e1df46ec1af Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 15:23:35 +0200
Subject: [PATCH] bes2600: short-circuit wake handshake when chip is confirmed
ACTIVE
The previous patch ("bes2600: gate PM indication completion on pending
request and track chip state") added enum bes2600_chip_pm_state and the
chip_pm_state field tracking what the host has *seen the firmware
confirm*. This patch makes the wake side use it.
Without this, every bes2600_pwr_device_exit_lp_mode() unconditionally
runs gpio_wake() + sbus_active() + wsm_set_operational_mode(active),
even when the chip is already in confirmed-ACTIVE state and the wake
sequence has nothing to do. The visible failure mode on PineTab2:
bes2600_pwr_enter_lp_mode, wait pm ind timeout
repeat set gpio_wake_flag, sub_sys:0
bes2600_sdio_active failed, subsys:0
bes2600_pwr_device_exit_lp_mode, active mcu fail
cycling every ~9 s, ~22 cycles in 10 minutes. Three pieces:
1. enter_lp_mode timed out (firmware indication lost). With c6.1,
chip_pm_state is now UNKNOWN.
2. lock_device fires exit_lp_mode.
3. gpio_wake hits "bit already set" because device_enter_lp_mode
was skipped when the indication timed out, so gpio_sleep was
never called - the bit reflects driver intent, not chip state.
gpio_wake silently no-ops (no GPIO edge), bit stays set.
4. sbus_active spends 200 x 2 ms looking for MCU_WAKEUP_READY that
never comes (firmware was never told to wake), then fails.
5. Driver continues to wsm_set_operational_mode against the wedged
bus, compounding the failure.
This patch's three moves:
* bes2600_pwr_device_exit_lp_mode() reads chip_pm_state at entry.
On BES2600_CHIP_PM_ACTIVE, log at devel level and return without
touching gpio_wake / sbus_active / WSM. The chip is in the state
we want; the handshake exists only to drive a transition.
* On BES2600_CHIP_PM_LP or BES2600_CHIP_PM_UNKNOWN, run the wake
handshake as before, but on sbus_active() failure: set
chip_pm_state = UNKNOWN, log once at err level, and bail out.
Do NOT call wsm_set_operational_mode over a wedged bus - it
would just emit a second error and leave the chip in an even
less defined state.
* bes2600_gpio_wakeup_mcu() / bes2600_gpio_allow_mcu_sleep():
demote "repeat set/clear gpio_wake_flag" from bes_err to
bes_devel. Multi-subsystem wake-hold (e.g. WIFI + BT both want
MCU awake) is the steady-state case, and the symmetric clear
while bit-already-clear is racy bookkeeping rather than a
hardware error. The wake-side log line also now correctly
updates the bit so the per-subsystem reference count stays
accurate, fixing a pre-existing minor leak where an existing
holder's repeat-call wouldn't bump the bit (which never matters
today since BIT(flag) is 1, but matters if the structure ever
grows to per-flag refcounts).
Net effect on the cycle:
* If chip is genuinely ACTIVE (chip_pm_state == ACTIVE), wake skips
cleanly. Storm goes silent.
* If chip is genuinely LP, behaviour is unchanged.
* If chip is UNKNOWN (post-timeout state), one wake attempt is
made; on failure, state stays UNKNOWN and we don't emit a
second cascade error per attempt. Repeated UNKNOWN with failed
wake will eventually be picked up by the LMAC active-monitor
and escalated to mmc_hw_reset (c5.2).
No new locks, no new state. Only consumption of the chip_pm_state
field added in the prerequisite patch.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes2600_sdio.c | 15 ++++++-
drivers/staging/bes2600/bes_pwr.c | 56 ++++++++++++++++++++++----
2 files changed, 62 insertions(+), 9 deletions(-)
diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c
index b9d836fab7af..929503547cfd 100644
--- a/drivers/staging/bes2600/bes2600_sdio.c
+++ b/drivers/staging/bes2600/bes2600_sdio.c
@@ -1388,7 +1388,14 @@ static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) != 0) {
- bes_err( "repeat set gpio_wake_flag, sub_sys:%d", flag);
+ /*
+ * Multiple subsystems holding wake is the steady-state case
+ * (e.g. WIFI + BT both want MCU awake). Demoted from bes_err
+ * to bes_devel since it isn't an error - the GPIO is already
+ * asserted high and the subsystem is now also tracked.
+ */
+ bes_devel("repeat set gpio_wake_flag, sub_sys:%d\n", flag);
+ self->gpio_wakup_flags |= BIT(flag);
mutex_unlock(&self->io_mutex);
return;
}
@@ -1420,7 +1427,11 @@ static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) == 0) {
- bes_err( "repeat clear gpio_wake_flag, sub_sys:%d", flag);
+ /*
+ * Mirror of the wake path: a clear when the bit is already
+ * clear is racy bookkeeping, not a hardware error.
+ */
+ bes_devel("repeat clear gpio_wake_flag, sub_sys:%d\n", flag);
mutex_unlock(&self->io_mutex);
return;
}
diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c
index de46e5826ee7..d54e1a0bab0c 100644
--- a/drivers/staging/bes2600/bes_pwr.c
+++ b/drivers/staging/bes2600/bes_pwr.c
@@ -621,19 +621,61 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
static void bes2600_pwr_device_exit_lp_mode(struct bes2600_common *hw_priv)
{
int ret = 0;
+ enum bes2600_chip_pm_state state;
struct wsm_operational_mode mode = {
.power_mode = wsm_power_mode_active,
.disableMoreFlagUsage = true,
};
- bes_devel("host lock lmac\n");
- if(hw_priv->sbus_ops->gpio_wake)
- hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU);
+ /*
+ * Consult chip_pm_state set by bes2600_pwr_notify_ps_changed().
+ * If we last saw the firmware confirm ACTIVE, skip ONLY the
+ * gpio_wake + sbus_active wake handshake - the GPIO is already
+ * asserted high and the SDIO MCU subsystem is already running,
+ * so another sbus_active() round-trip just hits its 200x2ms
+ * timeout because the firmware has nothing to do.
+ *
+ * wsm_set_operational_mode() below is NOT part of the wake
+ * handshake; it is the operational-mode setter the firmware
+ * tracks per call. Skipping it leaves the chip's SDIO state
+ * machine without a fresh operational-mode update, which on
+ * PineTab2 wedges the bus (-EBUSY on next sdio_rx_work read)
+ * within a few seconds of probe completion. So it must run
+ * unconditionally.
+ */
+ state = atomic_read(&hw_priv->bes_power.chip_pm_state);
+ if (state == BES2600_CHIP_PM_ACTIVE) {
+ bes_devel("device_exit_lp_mode: chip already ACTIVE, skipping wake handshake\n");
+ } else {
+ bes_devel("host lock lmac\n");
+ if (hw_priv->sbus_ops->gpio_wake)
+ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
- if(hw_priv->sbus_ops->sbus_active) {
- ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU);
- if (ret)
- bes_err("%s, active mcu fail\n", __func__);
+ if (hw_priv->sbus_ops->sbus_active) {
+ ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv,
+ SUBSYSTEM_MCU);
+ if (ret) {
+ /*
+ * MCU_WAKEUP_READY did not arrive within
+ * the SDIO handshake window. Record state
+ * as UNKNOWN so the next exit_lp_mode call
+ * also runs the full wake sequence (no
+ * skip), but still send operational_mode
+ * below to match pre-c6 behaviour - the
+ * WSM may succeed even if the SDIO active
+ * confirm was lost, and if it fails too,
+ * we just emit a second devel-level error.
+ * Repeated UNKNOWN is the signal for the
+ * LMAC active-monitor to eventually
+ * escalate to bus_reset (c5.2's
+ * mmc_hw_reset path).
+ */
+ bes_err("%s, active mcu fail\n", __func__);
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_UNKNOWN);
+ }
+ }
}
ret = wsm_set_operational_mode(hw_priv, &mode, 0);
--
2.53.0
@@ -0,0 +1,190 @@
From 822a5f1bab37e3f61b91aaf304ec1c54b42d639a Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 15:23:34 +0200
Subject: [PATCH] bes2600: short-circuit wake handshake when chip is confirmed
ACTIVE
The previous patch ("bes2600: gate PM indication completion on pending
request and track chip state") added enum bes2600_chip_pm_state and the
chip_pm_state field tracking what the host has *seen the firmware
confirm*. This patch makes the wake side use it.
Without this, every bes2600_pwr_device_exit_lp_mode() unconditionally
runs gpio_wake() + sbus_active() + wsm_set_operational_mode(active),
even when the chip is already in confirmed-ACTIVE state and the wake
sequence has nothing to do. The visible failure mode on PineTab2:
bes2600_pwr_enter_lp_mode, wait pm ind timeout
repeat set gpio_wake_flag, sub_sys:0
bes2600_sdio_active failed, subsys:0
bes2600_pwr_device_exit_lp_mode, active mcu fail
cycling every ~9 s, ~22 cycles in 10 minutes. Three pieces:
1. enter_lp_mode timed out (firmware indication lost). With c6.1,
chip_pm_state is now UNKNOWN.
2. lock_device fires exit_lp_mode.
3. gpio_wake hits "bit already set" because device_enter_lp_mode
was skipped when the indication timed out, so gpio_sleep was
never called - the bit reflects driver intent, not chip state.
gpio_wake silently no-ops (no GPIO edge), bit stays set.
4. sbus_active spends 200 x 2 ms looking for MCU_WAKEUP_READY that
never comes (firmware was never told to wake), then fails.
5. Driver continues to wsm_set_operational_mode against the wedged
bus, compounding the failure.
This patch's three moves:
* bes2600_pwr_device_exit_lp_mode() reads chip_pm_state at entry.
On BES2600_CHIP_PM_ACTIVE, log at devel level and return without
touching gpio_wake / sbus_active / WSM. The chip is in the state
we want; the handshake exists only to drive a transition.
* On BES2600_CHIP_PM_LP or BES2600_CHIP_PM_UNKNOWN, run the wake
handshake as before, but on sbus_active() failure: set
chip_pm_state = UNKNOWN, log once at err level, and bail out.
Do NOT call wsm_set_operational_mode over a wedged bus - it
would just emit a second error and leave the chip in an even
less defined state.
* bes2600_gpio_wakeup_mcu() / bes2600_gpio_allow_mcu_sleep():
demote "repeat set/clear gpio_wake_flag" from bes_err to
bes_devel. Multi-subsystem wake-hold (e.g. WIFI + BT both want
MCU awake) is the steady-state case, and the symmetric clear
while bit-already-clear is racy bookkeeping rather than a
hardware error. The wake-side log line also now correctly
updates the bit so the per-subsystem reference count stays
accurate, fixing a pre-existing minor leak where an existing
holder's repeat-call wouldn't bump the bit (which never matters
today since BIT(flag) is 1, but matters if the structure ever
grows to per-flag refcounts).
Net effect on the cycle:
* If chip is genuinely ACTIVE (chip_pm_state == ACTIVE), wake skips
cleanly. Storm goes silent.
* If chip is genuinely LP, behaviour is unchanged.
* If chip is UNKNOWN (post-timeout state), one wake attempt is
made; on failure, state stays UNKNOWN and we don't emit a
second cascade error per attempt. Repeated UNKNOWN with failed
wake will eventually be picked up by the LMAC active-monitor
and escalated to mmc_hw_reset (c5.2).
No new locks, no new state. Only consumption of the chip_pm_state
field added in the prerequisite patch.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_sdio.c | 15 +++++++++--
bes2600/bes_pwr.c | 56 ++++++++++++++++++++++++++++++++++++------
2 files changed, 62 insertions(+), 9 deletions(-)
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index 3e04e8c..acc0f19 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -1388,7 +1388,14 @@ static void bes2600_gpio_wakeup_mcu(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) != 0) {
- bes_err( "repeat set gpio_wake_flag, sub_sys:%d", flag);
+ /*
+ * Multiple subsystems holding wake is the steady-state case
+ * (e.g. WIFI + BT both want MCU awake). Demoted from bes_err
+ * to bes_devel since it isn't an error - the GPIO is already
+ * asserted high and the subsystem is now also tracked.
+ */
+ bes_devel("repeat set gpio_wake_flag, sub_sys:%d\n", flag);
+ self->gpio_wakup_flags |= BIT(flag);
mutex_unlock(&self->io_mutex);
return;
}
@@ -1420,7 +1427,11 @@ static void bes2600_gpio_allow_mcu_sleep(struct sbus_priv *self, int flag)
/* error check */
if((self->gpio_wakup_flags & BIT(flag)) == 0) {
- bes_err( "repeat clear gpio_wake_flag, sub_sys:%d", flag);
+ /*
+ * Mirror of the wake path: a clear when the bit is already
+ * clear is racy bookkeeping, not a hardware error.
+ */
+ bes_devel("repeat clear gpio_wake_flag, sub_sys:%d\n", flag);
mutex_unlock(&self->io_mutex);
return;
}
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index 9b4a4de..b7b6c2f 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -621,19 +621,61 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
static void bes2600_pwr_device_exit_lp_mode(struct bes2600_common *hw_priv)
{
int ret = 0;
+ enum bes2600_chip_pm_state state;
struct wsm_operational_mode mode = {
.power_mode = wsm_power_mode_active,
.disableMoreFlagUsage = true,
};
- bes_devel("host lock lmac\n");
- if(hw_priv->sbus_ops->gpio_wake)
- hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv, GPIO_WAKE_FLAG_MCU);
+ /*
+ * Consult chip_pm_state set by bes2600_pwr_notify_ps_changed().
+ * If we last saw the firmware confirm ACTIVE, skip ONLY the
+ * gpio_wake + sbus_active wake handshake - the GPIO is already
+ * asserted high and the SDIO MCU subsystem is already running,
+ * so another sbus_active() round-trip just hits its 200x2ms
+ * timeout because the firmware has nothing to do.
+ *
+ * wsm_set_operational_mode() below is NOT part of the wake
+ * handshake; it is the operational-mode setter the firmware
+ * tracks per call. Skipping it leaves the chip's SDIO state
+ * machine without a fresh operational-mode update, which on
+ * PineTab2 wedges the bus (-EBUSY on next sdio_rx_work read)
+ * within a few seconds of probe completion. So it must run
+ * unconditionally.
+ */
+ state = atomic_read(&hw_priv->bes_power.chip_pm_state);
+ if (state == BES2600_CHIP_PM_ACTIVE) {
+ bes_devel("device_exit_lp_mode: chip already ACTIVE, skipping wake handshake\n");
+ } else {
+ bes_devel("host lock lmac\n");
+ if (hw_priv->sbus_ops->gpio_wake)
+ hw_priv->sbus_ops->gpio_wake(hw_priv->sbus_priv,
+ GPIO_WAKE_FLAG_MCU);
- if(hw_priv->sbus_ops->sbus_active) {
- ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv, SUBSYSTEM_MCU);
- if (ret)
- bes_err("%s, active mcu fail\n", __func__);
+ if (hw_priv->sbus_ops->sbus_active) {
+ ret = hw_priv->sbus_ops->sbus_active(hw_priv->sbus_priv,
+ SUBSYSTEM_MCU);
+ if (ret) {
+ /*
+ * MCU_WAKEUP_READY did not arrive within
+ * the SDIO handshake window. Record state
+ * as UNKNOWN so the next exit_lp_mode call
+ * also runs the full wake sequence (no
+ * skip), but still send operational_mode
+ * below to match pre-c6 behaviour - the
+ * WSM may succeed even if the SDIO active
+ * confirm was lost, and if it fails too,
+ * we just emit a second devel-level error.
+ * Repeated UNKNOWN is the signal for the
+ * LMAC active-monitor to eventually
+ * escalate to bus_reset (c5.2's
+ * mmc_hw_reset path).
+ */
+ bes_err("%s, active mcu fail\n", __func__);
+ atomic_set(&hw_priv->bes_power.chip_pm_state,
+ BES2600_CHIP_PM_UNKNOWN);
+ }
+ }
}
ret = wsm_set_operational_mode(hw_priv, &mode, 0);
--
2.53.0
@@ -0,0 +1,121 @@
From d95453c98e31d7a47bc227aef5d0b426ac9e334b Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 16:58:49 +0200
Subject: [PATCH] =?UTF-8?q?bes2600:=20take=20pending=5Frecord=5Flock=20wit?=
=?UTF-8?q?h=20=5Fbh()=20to=20fix=20SOFTIRQ-safe=20=E2=86=92=20-unsafe=20i?=
=?UTF-8?q?nversion=20(besser#18)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
PROVE_LOCKING reports:
WARNING: SOFTIRQ-safe -> SOFTIRQ-unsafe lock order detected
kworker/u16:1 is trying to acquire:
&hw_priv->tx_loop.pending_record_lock at bes2600_queue_clear+0x80
and this task is already holding:
&queue->lock at bes2600_queue_clear+0x60
which would create a new lock dependency:
(&queue->lock){+.-.} -> (&hw_priv->tx_loop.pending_record_lock){+.+.}
but this new dependency connects a SOFTIRQ-irq-safe lock:
(&queue->lock){+.-.}
... which became SOFTIRQ-irq-safe at:
bes2600_tx -> ieee80211_handle_wake_tx_queue -> tasklet_action
to a SOFTIRQ-irq-unsafe lock:
(&hw_priv->tx_loop.pending_record_lock){+.+.}
... which became SOFTIRQ-irq-unsafe at:
bes2600_queue_get_skb -> bes2600_join_work -> process_one_work
queue->lock is taken consistently with spin_lock_bh() at 22 sites;
the nested acquisition of pending_record_lock at queue.c:289 (inside
the outer queue->lock_bh held at line 285) had it implicitly BH-safe
via the outer scope. But pending_record_lock is ALSO taken from
non-BH-disabled contexts:
bes2600_queue_get_skb (queue.c:832) — process context via
bes2600_join_work (workqueue), no outer queue->lock held
bes2600_tx_loop_item_pending_check (tx_loop.c:112)
— TX-loop context, no outer
queue->lock held
When CPU0 holds pending_record_lock from one of those non-BH paths
and a softirq fires that wants queue->lock, and CPU1 in softirq has
queue->lock and is about to acquire pending_record_lock — classic AB-BA
SOFTIRQ deadlock.
The fix is the conservative one: take pending_record_lock with _bh()
at every site that's not already inside a queue->lock_bh-held scope.
That makes the lock consistently SOFTIRQ-safe, eliminating the
inversion. queue.c:289/295 stays as plain spin_lock because BH is
already disabled by the outer queue->lock_bh acquired at queue.c:285.
Five sites converted:
bes2600/queue.c:832 -- spin_lock -> spin_lock_bh
bes2600/queue.c:839 -- spin_unlock -> spin_unlock_bh
bes2600/queue.c:844 -- spin_unlock -> spin_unlock_bh
bes2600/tx_loop.c:112 -- spin_lock -> spin_lock_bh
bes2600/tx_loop.c:114 -- spin_unlock -> spin_unlock_bh
Contract:
- Documentation/locking/locktypes.rst spelling: spin_lock_bh() is
the canonical way to make a non-IRQ spinlock safe against
softirq preemption that might re-enter the same lock.
- Same shape as queue->lock in this driver and as is_drv->lock
in the cw1200 ancestor.
Closes: besser#18
Fixes: <bes2600 base import>
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/queue.c | 6 +++---
bes2600/tx_loop.c | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/staging/bes2600/queue.c b/drivers/staging/bes2600/queue.c
index cc606c1..4016b76 100644
--- a/drivers/staging/bes2600/queue.c
+++ b/drivers/staging/bes2600/queue.c
@@ -829,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(&queue->stats->hw_priv->tx_loop.pending_record_lock);
+ spin_lock_bh(&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(&queue->stats->hw_priv->tx_loop.pending_record_lock);
+ spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock);
return -EINVAL;
}
}
}
- spin_unlock(&queue->stats->hw_priv->tx_loop.pending_record_lock);
+ spin_unlock_bh(&queue->stats->hw_priv->tx_loop.pending_record_lock);
item = &queue->pool[item_id];
diff --git a/drivers/staging/bes2600/tx_loop.c b/drivers/staging/bes2600/tx_loop.c
index e6cf072..0cf7ce1 100644
--- a/drivers/staging/bes2600/tx_loop.c
+++ b/drivers/staging/bes2600/tx_loop.c
@@ -109,9 +109,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(&hw_priv->tx_loop.pending_record_lock);
+ spin_lock_bh(&hw_priv->tx_loop.pending_record_lock);
bes2600_queue_iterate_record_pending_packet(hw_priv, bes2600_tx_loop_item_pending_item);
- spin_unlock(&hw_priv->tx_loop.pending_record_lock);
+ spin_unlock_bh(&hw_priv->tx_loop.pending_record_lock);
if (atomic_read(&hw_priv->bh_rx) > 0)
wake_up(&hw_priv->bh_wq);
--
2.54.0
@@ -0,0 +1,19 @@
# queue-pending-record-lock-bh-danctnix — close besser#18
Converts `pending_record_lock` to `spin_lock_bh()` at the 5 sites
where it is taken in non-BH-disabled contexts (`bes2600_queue_get_skb`
called from `bes2600_join_work`, and `bes2600_tx_loop_item_pending_check`).
Eliminates the PROVE_LOCKING SOFTIRQ-safe → SOFTIRQ-unsafe warning
reported in besser#18: `&queue->lock` (taken with `_bh` everywhere,
including the nested acquisition at `queue.c:289` that holds
`pending_record_lock` as inner) was registered SOFTIRQ-irq-safe by
lockdep, but `pending_record_lock` was sometimes taken without BH
disable, creating an AB-BA deadlock window.
Provenance:
- Source-of-truth commit on `marfrit/bes2600-dkms` branch
`bes2600/queue-pending-record-lock-bh-fix`, commit `d95453c`.
- This file is the same commit's `git format-patch` output with
the DKMS-style `bes2600/foo.c` paths rewritten to in-tree
`drivers/staging/bes2600/foo.c` paths via sed.
@@ -0,0 +1,675 @@
From f43bcc5dda0a9120aee62cce0cec1a8c851cb4ef Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:55:18 +0200
Subject: [PATCH] bes2600: remove userspace /dev/bes2600 character device
interface
bes_chardev.c implemented a custom character device at /dev/bes2600 with
its own parser and command-dispatch table, exposing operations such as
'wifi on|off', 'bt on|off', 'change_fw_type <n>', 'bt_wakeup',
'bt_sleep', and 'wakeup_read_flag'. None of these surfaces are used by
the in-tree driver - every kernel call site consumes the internal state
accessors (bes2600_chrdev_is_signal_mode, bes2600_chrdev_get_fw_type,
etc) directly, not through the cdev.
The cdev interface is a standing upstream blocker for two reasons:
1. Drivers under drivers/staging/ and drivers/net/wireless/ are
expected to expose tuning via the firmware/nl80211/debugfs
infrastructure rather than a private /dev node with an ad-hoc
parser.
2. The cdev handlers keep a global bes_cdev singleton alive whose
->cdev, ->dev_id, ->class and ->device pointers exist only to be
torn down; they add no functionality that nl80211 or rfkill do
not already provide (wifi/bt on-off, module_param for fw_type).
Remove the userspace interface:
- open / read / write / release file_operations handlers and the
bes2600_chardev_fops instance
- bes2600_op_* command handlers and bes2600_op_map_tab dispatcher
- bes2600_get_cmd_and_ifname / bes2600_recyle_cmd_and_ifname_mem
string helpers
- bes2600_load_uevent (its only caller was
bes2600_chrdev_wifi_force_close_work informing userspace of a
state it already gates via rfkill; that snprintf +
kobject_uevent_env block is gone too, the kernel-side
halt_device + switch_wifi(0) + chrdev_check_system_close
sequence remains)
- alloc_chrdev_region / cdev_init / cdev_add / class_create /
device_create in bes2600_chrdev_init plus the fail1/fail2/fail3
unwind labels
- cdev_del / unregister_chrdev_region / device_destroy /
class_destroy in bes2600_chrdev_free
- cdev/dev_id/major/minor/class/device fields in struct bes_cdev
What remains (unchanged behaviour):
- fw_type module parameter - the primary user-facing knob for
signal/no-signal/BT mode switch
- All in-kernel bes2600_chrdev_* accessor functions called from
bes2600_sdio.c, bes_pwr.c, sta.c, bh.c, main.c, wsm.c, and
wifi_testmode_cmd.c (13 call sites)
- bes2600_chrdev_init / bes2600_chrdev_free as state-init / teardown
for the remaining bes_cdev state (waitqueues, workqueues, flags)
- DPD management (bes2600_chrdev_get_dpd_buffer / update / free)
- wifi_force_close worker, system-close logic, bus-probe state
machine
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver continues to associate and pass traffic;
no kernel messages related to the cdev absence. Users that previously
wrote to /dev/bes2600 should switch to the fw_type module parameter
or (future patch c4) nl80211 testmode commands.
Follow-ups:
- c3.1: thread struct device * through bes2600_chrdev_is_signal_mode
and friends so the global bes2600_cdev singleton can be dropped
and the accessors scale to multi-device scenarios.
- c4: enable CONFIG_BES2600_TESTMODE and route nl80211 testmode
commands to the firmware's patch_wifi_testMode entry.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_chardev.c | 519 ------------------------------------------
1 file changed, 519 deletions(-)
diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c
index 9038e48..e2e4f1b 100644
--- a/bes2600/bes_chardev.c
+++ b/bes2600/bes_chardev.c
@@ -43,12 +43,6 @@ enum bus_probe_state {
};
struct bes_cdev {
- struct cdev cdev;
- dev_t dev_id;
- int major;
- int minor;
- struct class *class;
- struct device *device;
atomic_t num_proc;
wait_queue_head_t open_wq;
spinlock_t status_lock;
@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on)
return ret;
}
-static int bes2600_get_cmd_and_ifname(const char *str, char **result)
-{
- int cmd_len = 0;
- int ifname_len = 0;
- char *sp = NULL;
- char *tmp_ptr = NULL;
- char *cmd_ptr = NULL;
-
- /* check if input arguments is valid */
- if (!str || strncmp(str, "ifname:", 7) != 0)
- return -1;
-
- sp = strchr(str, ' ');
- if (strncmp(sp + 1, "cmd:", 4) != 0)
- return -1;
-
- /* extract interface name */
- ifname_len = sp - str - 7;
- tmp_ptr = kmalloc(ifname_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- return -2;
- }
-
- strncpy(tmp_ptr, str+7, ifname_len);
- tmp_ptr[ifname_len] = '\0';
- result[0] = tmp_ptr;
-
- /* get command length */
- cmd_ptr = strstr(str, "cmd:");
- cmd_ptr += 4;
- sp = strchr(cmd_ptr, ' ');
- if (!sp) { /* the command don't have any parameter */
- cmd_len = strlen(cmd_ptr);
- if (cmd_ptr[cmd_len - 1] == '\n')
- --cmd_len;
- } else { /* the command have one or more parameter */
- cmd_len = sp - cmd_ptr;
- }
-
- /* copy command to out buffer */
- tmp_ptr = kmalloc( cmd_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- kfree(result[0]);
- result[0] = NULL;
- return -3;
- }
-
- strncpy(tmp_ptr, cmd_ptr, cmd_len);
- tmp_ptr[cmd_len] = '\0';
- result[1] = tmp_ptr;
-
- return 0;
-}
-
-static void bes2600_recyle_cmd_and_ifname_mem(char **info)
-{
- if (info[0]) {
- kfree(info[0]);
- info[0] = NULL;
- }
-
- if (info[1]) {
- kfree(info[1]);
- info[1] = NULL;
- }
-
-}
-
-static int bes2600_op_default_handler(const char *str)
-{
- char *info[2] = {0};
-
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- bes_devel("cmd(%s) on %s not handled\n", info[1], info[0]);
- } else {
- bes_err("%s get command fail, the origin string is %s\n", __func__, str);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return 0;
-}
-
-static int bes2600_op_wifi_bt_on_off(const char *str)
-{
- char *info[2] = {0};
- int ret = 0;
- enum wait_state wait_state;
- enum bus_probe_state probe_state;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- probe_state = bes2600_cdev.bus_probe;
- wait_state = bes2600_cdev.wait_state;
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* only work for wifi signal mode */
- if (bes2600_cdev.fw_type != BES2600_FW_TYPE_WIFI_SIGNAL)
- return -EFAULT;
-
- /* wait bus probe operation end */
- if (probe_state == BES2600_BUS_PROBE_START) {
- bes_devel("wait bus probe operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.bus_probe > BES2600_BUS_PROBE_START),
- HZ);
- WARN_ON(status <= 0);
- }
-
- /* must wait previous operation end in critical section */
- if (wait_state != BES2600_BOOT_WAIT_NONE) {
- bes_devel("wait previous operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.wait_state == BES2600_BOOT_WAIT_NONE),
- HZ * 8);
- WARN_ON(status <= 0);
- }
-
- /* if dpd calibration is doing, modify wifi and bt state directly */
- spin_lock(&bes2600_cdev.status_lock);
- if (bes2600_cdev.bus_probe == BES2600_BUS_PROBE_OK && !bes2600_cdev.dpd_calied) {
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- bes2600_cdev.wifi_opened = true;
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- bes2600_cdev.wifi_opened = false;
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- bes2600_cdev.bt_opened = true;
- bes2600_cdev.bton_pending = true;
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- bes2600_cdev.bt_opened = false;
- bes2600_cdev.bton_pending = false;
- }
- }
- bes2600_recyle_cmd_and_ifname_mem(info);
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- WARN_ON(status <= 0);
-
- return (status <= 0 || bes2600_chrdev_is_bus_error()) ? -EFAULT : 0;
- }
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* process wifi/bt on/off operation */
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- ret = bes2600_switch_wifi(1);
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- ret = bes2600_switch_wifi(0);
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- ret = bes2600_switch_bt(1);
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- ret = bes2600_switch_bt(0);
- }
- }
-
- if (!ret && bes2600_chrdev_check_system_close())
- ret = bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
- bes2600_cdev.sbus_priv);
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return ret ;
-}
-
-
-static int bes2600_op_change_fw_type(const char *str)
-{
- int ret = 0;
- int temp = 0;
- long status = 0;
- char *cmd_ptr = NULL;
- char fw_type[5] = {0};
- bool sys_closed = bes2600_chrdev_check_system_close();
-
- bes_devel("%s is called, arg:%s\n", __func__, str);
-
- if (!bes2600_cdev.sbus_ops->power_switch && !bes2600_cdev.sbus_ops->reboot)
- return -EPERM;
-
- /* check if user input is valid */
- cmd_ptr = strstr(str, "CHANGE_FW_TYPE ");
- if (strlen(str) < 16 || !cmd_ptr) {
- bes_err("the format of \"%s\" is error\n", str);
- return -EINVAL;
- }
-
- /* convert fw_type from string to int */
- strncpy(fw_type, cmd_ptr + 14, 4);
- fw_type[0] = '+';
- ret = kstrtoint(fw_type, 10, &temp);
- if (ret < 0) {
- bes_err("%s parse error\n", __func__);
- return -EINVAL;
- }
-
- /* no need to realod firmware if new fw_type is equal to the old */
- if (temp == bes2600_cdev.fw_type ) {
- bes_devel("fw type is equal\n");
- return 0;
- }
-
- /* close wifi net device */
- if (bes2600_cdev.sbus_priv
- && bes2600_is_net_dev_created(bes2600_cdev.sbus_priv)) {
- bes2600_unregister_net_dev(bes2600_cdev.sbus_priv);
- }
-
- /* update firmware type */
- bes2600_cdev.fw_type = temp;
- bes2600_chrdev_update_signal_mode();
-
- if (!sys_closed) {
- /* close device to call disconnect function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(bes2600_cdev.sbus_priv, 0);
- else if (bes2600_cdev.sbus_ops->reboot)
- bes2600_cdev.sbus_ops->reboot(bes2600_cdev.sbus_priv);
- }
-
- if (bes2600_cdev.sbus_ops->reboot)
- bes2600_chrdev_start_bus_probe();
-
- /* wait disconnect event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq, (bes2600_cdev.sbus_priv == NULL), HZ * 10);
- WARN_ON(status <= 0);
-
- if (bes2600_cdev.dpd_calied
- && bes2600_chrdev_check_system_close()) {
- bes_devel("no need to reload firmware\n");
- return 0;
- }
-
- bes_devel("reload firmware...\n");
- /* power on device to call probe function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(NULL, 1);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 10);
- WARN_ON(status <= 0);
-
- ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0;
- return ret;
-}
-
-static int bes2600_op_bt_wakeup(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
-
- bes_devel("bes2600 wakeup bt.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_ON, SUBSYSTEM_BT_LP, true);
-
- return ret;
-}
-
-static int bes2600_op_bt_sleep(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
- bes_devel("bes2600 allow bt sleep.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_OFF, SUBSYSTEM_BT_LP, false);
- return ret;
-}
-static int bes2600_op_set_wakeup_read_flag(const char *str)
-{
- bes_devel("%s is called, arg:%s\n", __func__, str);
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_WAKEUP_STATE;
- spin_unlock(&bes2600_cdev.status_lock);
- return 0;
-}
#ifdef FW_DOWNLOAD_UART_DAEMON
-int bes2600_load_uevent(char *env[])
-{
- return kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
-}
#endif
-static struct bes2600_op_map bes2600_op_map_tab[] ={
- /*op op_len handler */
- {"P2P_SET_NOA", 11, bes2600_op_default_handler},
- {"P2P_SET_PS", 10, bes2600_op_default_handler},
- {"SET_AP_WPS_P2P_IE", 17, bes2600_op_default_handler},
- {"LINKSPEED", 9, bes2600_op_default_handler},
- {"RSSI", 4, bes2600_op_default_handler},
- {"GETBAND", 7, bes2600_op_default_handler},
- {"WLS_BATCHING", 12, bes2600_op_default_handler},
- {"MACADDR", 7, bes2600_op_default_handler},
- {"RXFILTER-START", 14, bes2600_op_default_handler},
- {"RXFILTER-STOP", 13, bes2600_op_default_handler},
- {"RXFILTER-ADD", 12, bes2600_op_default_handler},
- {"RXFILTER-REMOVE", 15, bes2600_op_default_handler},
- {"BTCOEXMODE", 10, bes2600_op_default_handler},
- {"BTCOEXSCAN-START", 16, bes2600_op_default_handler},
- {"BTCOEXSCAN-STOP", 15, bes2600_op_default_handler},
- {"SETSUSPENDMODE", 14, bes2600_op_default_handler},
- {"COUNTRY", 7, bes2600_op_default_handler},
- {"WIFI_ON", 7, bes2600_op_wifi_bt_on_off},
- {"WIFI_OFF", 8, bes2600_op_wifi_bt_on_off},
- {"BT_ON", 5, bes2600_op_wifi_bt_on_off},
- {"BT_OFF", 6, bes2600_op_wifi_bt_on_off},
- {"CHANGE_FW_TYPE", 14, bes2600_op_change_fw_type},
- {"BT_WAKEUP", 9, bes2600_op_bt_wakeup},
- {"BT_SLEEP", 8, bes2600_op_bt_sleep},
- {"WAKEUP_STATE", 12, bes2600_op_set_wakeup_read_flag},
-};
static int bes2600_chrdev_check_system_close_internal(void)
{
@@ -603,123 +264,10 @@ static int bes2600_chrdev_check_system_close_internal(void)
&& (bes2600_cdev.wifi_opened == false);
}
-static int bes2600_chrdev_open(struct inode *inode, struct file *filp)
-{
- if (atomic_read(&bes2600_cdev.num_proc) > 0) {
- wait_event_timeout(bes2600_cdev.open_wq,
- (atomic_read(&bes2600_cdev.num_proc) == 0),
- MAX_SCHEDULE_TIMEOUT);
- }
- bes_devel("bes2600 char device is opened\n");
- atomic_inc(&bes2600_cdev.num_proc);
- return 0;
-}
-static ssize_t bes2600_chrdev_read(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
-{
- char buf[64] = {0};
- unsigned int len;
- long status = 0;
- switch (bes2600_cdev.read_flag) {
- case BES_CDEV_READ_WAKEUP_STATE:
- if (bes2600_chrdev_wakeup_by_event_get() > WAKEUP_EVENT_NONE) {
- status = wait_event_timeout(bes2600_cdev.wakeup_reason_wq,
- bes2600_chrdev_wakeup_by_event_get() == WAKEUP_EVENT_NONE, HZ * 2);
- WARN_ON(status <= 0);
- }
- len = sprintf(buf, "wakeup_reason: %u, src_port: %u\n",
- bes2600_cdev.wakeup_state, bes2600_cdev.src_port);
- break;
- default:
- len = sprintf(buf, "dpd_calied:%d wifi_opened:%d bt_opened:%d fw_type:%d\n",
- bes2600_cdev.dpd_calied,
- bes2600_cdev.wifi_opened,
- bes2600_cdev.bt_opened,
- bes2600_cdev.fw_type);
- break;
- }
-
- len = sizeof(buf);
- /* reset read flag */
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_NUM_MAX;
- spin_unlock(&bes2600_cdev.status_lock);
-
- return simple_read_from_buffer(user_buf, count, ppos, buf, len);
-}
-
-static ssize_t bes2600_chrdev_write(struct file *file,
- const char __user *user_buf, size_t count, loff_t *ppos)
-{
- int i = 0;
- int cmd_num = ARRAY_SIZE(bes2600_op_map_tab);
- int cmd_len = 0;
- int ret = 0;
- char *info[2] = {0};
- char *buf = NULL;
-
- /* copy content from user space to kernel */
- /* message format:"ifname:wlanx cmd:xxx arg1 arg2 ..." */
- buf = kmalloc(count + 1, GFP_KERNEL);
- if (copy_from_user(buf, user_buf, count))
- return -EFAULT;
-
- /* add terminal character */
- buf[count] = '\0';
-
- /* extract comand and interface */
- if (bes2600_get_cmd_and_ifname(buf, info) != 0) {
- bes_err("%s get command fail, the origin string is %s\n", __func__, buf);
- kfree(buf);
- return -EINVAL;
- }
-
- /* match operation item and execure its handler */
- cmd_len = strlen(info[1]);
- for (i = 0; i < cmd_num; i++) {
- if (cmd_len < bes2600_op_map_tab[i].op_len)
- continue;
-
- if (strncasecmp(info[1], bes2600_op_map_tab[i].op, bes2600_op_map_tab[i].op_len) == 0) {
- ret = bes2600_op_map_tab[i].handler(buf);
- break;
- }
- }
-
- /* operation item mismatch */
- if (i == cmd_num) {
- bes_err("cmd(%s) mismatch\n", info[1]);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
- kfree(buf);
-
- return (ret == 0) ? count : ret;
-}
-
-static int bes2600_chrdev_release (struct inode *inode, struct file *file)
-{
- if (atomic_dec_and_test(&bes2600_cdev.num_proc)) {
- wake_up(&bes2600_cdev.open_wq);
- }
-
- bes_devel("bes2600 char device is closed\n");
-
- return 0;
-}
-
-static struct file_operations bes2600_chardev_fops =
-{
- .owner = THIS_MODULE,
- .open = bes2600_chrdev_open,
- .read = bes2600_chrdev_read,
- .write = bes2600_chrdev_write,
- .release = bes2600_chrdev_release,
-};
#ifdef BES2600_WRITE_DPD_TO_FILE
static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size)
@@ -1124,12 +672,6 @@ void bes2600_chrdev_update_signal_mode(void)
static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
{
- char wifi_state[15];
- char bt_state[15];
- char fw_type[15];
- char *env[] = { wifi_state, bt_state, fw_type, NULL };
- int ret;
-
if (bes2600_chrdev_is_wifi_opened()) {
bes_devel("system exeception, force wifi down\n");
@@ -1146,14 +688,6 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
-
- /* notify userspace */
- snprintf(wifi_state, sizeof(wifi_state), "WIFI_OPENED=%d", bes2600_cdev.wifi_opened);
- snprintf(bt_state, sizeof(bt_state), "BT_OPENED=%d", bes2600_cdev.bt_opened);
- snprintf(fw_type, sizeof(fw_type), "FW_TYPE=%d", bes2600_cdev.fw_type);
- ret = kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
- if (!ret)
- bes_err("bes2600 notify userspace failed\n");
}
}
@@ -1247,46 +781,6 @@ int bes2600_chrdev_wakeup_by_event_get(void)
int bes2600_chrdev_init(struct sbus_ops *ops)
{
- int ret = 0;
-
- /* allocate devide id */
- ret = alloc_chrdev_region(&bes2600_cdev.dev_id, 0, 1, "bes2600_chrdev");
- if (ret < 0){
- bes_err("bes2600 alloc device id fail\n");
- ret = -EFAULT;
- goto fail;
- }
-
- /* extract major and minor device id */
- bes2600_cdev.major = MAJOR(bes2600_cdev.dev_id);
- bes2600_cdev.minor = MINOR(bes2600_cdev.dev_id);
-
- /* add char device and bind operation function */
- bes2600_cdev.cdev.owner = THIS_MODULE;
- cdev_init(&bes2600_cdev.cdev, &bes2600_chardev_fops);
- ret = cdev_add(&bes2600_cdev.cdev, bes2600_cdev.dev_id, 1);
- if (ret < 0){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail1;
- }
-
- /* create class for creating device node */
- bes2600_cdev.class = class_create("bes2600_chrdev");
- if (IS_ERR(bes2600_cdev.class)){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail2;
- }
-
- /* get char device pointer */
- bes2600_cdev.device = device_create(bes2600_cdev.class, NULL, bes2600_cdev.dev_id, NULL, "bes2600");
- if (IS_ERR(bes2600_cdev.device)){
- bes_err("bes2600 char device create fail\n");
- ret = -EFAULT;
- goto fail3;
- }
-
/* initialise global variable */
atomic_set(&bes2600_cdev.num_proc, 0);
init_waitqueue_head(&bes2600_cdev.open_wq);
@@ -1318,15 +812,6 @@ int bes2600_chrdev_init(struct sbus_ops *ops)
bes_devel("%s done\n", __func__);
return 0;
-
-fail3:
- class_destroy(bes2600_cdev.class);
-fail2:
- cdev_del(&bes2600_cdev.cdev);
-fail1:
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
-fail:
- return ret;
}
void bes2600_chrdev_free(void)
@@ -1336,9 +821,5 @@ void bes2600_chrdev_free(void)
bes2600_free_dpd_log_buffer();
#endif
bes2600_chrdev_free_dpd_data();
- cdev_del(&bes2600_cdev.cdev);
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
- device_destroy(bes2600_cdev.class, bes2600_cdev.dev_id);
- class_destroy(bes2600_cdev.class);
bes_devel("%s done\n", __func__);
}
--
2.53.0
@@ -0,0 +1,109 @@
From 3d98404c1a85ef33e9fc1422042c71dc90f3b255 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 14:32:18 +0200
Subject: [PATCH] bes2600: widen scan-defer backoff to 30s and decay count on
quiet
The scan-defer logic added in the previous patch ("bes2600: defer
scan and soften WARN on firmware reject") used a 10-second backoff
window and never cleared reject_count outside of a successful scan.
Field testing on a PineTab2 (linux-pinetab2 6.19.10-danctnix1) shows
two distinct mac80211 scan-retry cadences in practice:
* Idle background scans every ~5 minutes when associated -- well
outside any plausible backoff, the defer guard correctly falls
through to a real WSM scan attempt.
* Roam-evaluation bursts triggered when mac80211 wants to find a
candidate AP for handover (signal degradation, beacon loss,
locally-generated DEAUTH_LEAVING reason=3). Cadence is ~12 s, and
one boot reproduced 14 such rejected scans in 3 minutes during a
single burst, none of which engaged the defer guard because every
retry landed just outside the 10 s window.
Two-line behaviour change to fix that:
1. BES2600_SCAN_BACKOFF_JIFFIES grows from 10*HZ to 30*HZ, so a
12 s-cadence burst stays inside the window across consecutive
rejects and the third reject in the burst trips the threshold
guard. The 5 min idle case is still naturally past the window
and is unaffected.
2. bes2600_scan_should_defer() resets reject_count to 0 when
time_after(jiffies, backoff_until). Without this, reject_count
accumulated indefinitely across the slow-cadence rejects, so an
isolated reject after long quiet would have tripped the
threshold the moment it arrived. After the change, count is
latched only inside an active burst and decays cleanly when the
burst ends.
Net effect on a roam burst:
* t=0 reject #1 (count 1, backoff_until = t0 + 30s)
* t=12 reject #2 (count 2, backoff_until = t1 + 30s)
* t=24 reject #3 (count 3, threshold met, next scan deferred)
* t=36 defer fires, no WSM round-trip, reject not sent
* ... defers continue until the firmware-policy state clears
* scan succeeds -> reject_count = 0, normal cadence resumes
WSM 0x0007 confirm rejections in a burst drop from ~14 to ~3 (just
the scans needed to reach the threshold). wpa_supplicant's reason=3
locally-generated disconnects driven by exhausted roam candidates
during the same burst window also drop.
No new state, no new symbols, no change to mac80211-facing semantics:
the deferred scan still completes via the existing fail: path with
status=-EBUSY, the same response a real firmware-busy would produce.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/scan.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index 5f6af3bc81ba..b944adcaa08c 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -22,9 +22,17 @@
* After this many consecutive WSM scan rejections from firmware, stop
* issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state
* that's rejecting them (coex window, firmware-internal busy) clear.
+ *
+ * The backoff has to be at least as long as the natural mac80211 scan-
+ * retry cadence, otherwise the next attempt lands outside the window
+ * and bypasses the defer guard. Observed in the wild on PineTab2:
+ * roam-evaluation bursts at ~12 s cadence, idle background scans at
+ * ~5 min cadence. 30 s catches the burst and leaves the slow case
+ * alone (the firmware-policy state has had minutes to clear by then
+ * anyway).
*/
#define BES2600_SCAN_REJECT_THRESHOLD 3
-#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ)
+#define BES2600_SCAN_BACKOFF_JIFFIES (30 * HZ)
static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
@@ -40,7 +48,9 @@ static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
* 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive
* rejections on recent scan attempts and the backoff window has
* not yet elapsed. Whatever was rejecting them is likely still
- * rejecting them; give it time.
+ * rejecting them; give it time. If the backoff has elapsed without
+ * a fresh reject refreshing it, the burst is over and we reset the
+ * count so an isolated reject doesn't immediately re-trip.
*
* Returns true if the caller should abandon the scan iteration.
*/
@@ -51,6 +61,9 @@ static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv)
return true;
#endif
+ if (time_after(jiffies, hw_priv->scan.backoff_until))
+ hw_priv->scan.reject_count = 0;
+
if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD &&
time_before(jiffies, hw_priv->scan.backoff_until))
return true;
--
2.53.0
@@ -0,0 +1,109 @@
From db4ea70fb5dae1b2ab9c06dd91f1d7b2b9dcf09c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Tue, 28 Apr 2026 14:32:18 +0200
Subject: [PATCH] bes2600: widen scan-defer backoff to 30s and decay count on
quiet
The scan-defer logic added in the previous patch ("bes2600: defer
scan and soften WARN on firmware reject") used a 10-second backoff
window and never cleared reject_count outside of a successful scan.
Field testing on a PineTab2 (linux-pinetab2 6.19.10-danctnix1) shows
two distinct mac80211 scan-retry cadences in practice:
* Idle background scans every ~5 minutes when associated -- well
outside any plausible backoff, the defer guard correctly falls
through to a real WSM scan attempt.
* Roam-evaluation bursts triggered when mac80211 wants to find a
candidate AP for handover (signal degradation, beacon loss,
locally-generated DEAUTH_LEAVING reason=3). Cadence is ~12 s, and
one boot reproduced 14 such rejected scans in 3 minutes during a
single burst, none of which engaged the defer guard because every
retry landed just outside the 10 s window.
Two-line behaviour change to fix that:
1. BES2600_SCAN_BACKOFF_JIFFIES grows from 10*HZ to 30*HZ, so a
12 s-cadence burst stays inside the window across consecutive
rejects and the third reject in the burst trips the threshold
guard. The 5 min idle case is still naturally past the window
and is unaffected.
2. bes2600_scan_should_defer() resets reject_count to 0 when
time_after(jiffies, backoff_until). Without this, reject_count
accumulated indefinitely across the slow-cadence rejects, so an
isolated reject after long quiet would have tripped the
threshold the moment it arrived. After the change, count is
latched only inside an active burst and decays cleanly when the
burst ends.
Net effect on a roam burst:
* t=0 reject #1 (count 1, backoff_until = t0 + 30s)
* t=12 reject #2 (count 2, backoff_until = t1 + 30s)
* t=24 reject #3 (count 3, threshold met, next scan deferred)
* t=36 defer fires, no WSM round-trip, reject not sent
* ... defers continue until the firmware-policy state clears
* scan succeeds -> reject_count = 0, normal cadence resumes
WSM 0x0007 confirm rejections in a burst drop from ~14 to ~3 (just
the scans needed to reach the threshold). wpa_supplicant's reason=3
locally-generated disconnects driven by exhausted roam candidates
during the same burst window also drop.
No new state, no new symbols, no change to mac80211-facing semantics:
the deferred scan still completes via the existing fail: path with
status=-EBUSY, the same response a real firmware-busy would produce.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/scan.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/bes2600/scan.c b/bes2600/scan.c
index faa1c90..ad5033b 100644
--- a/bes2600/scan.c
+++ b/bes2600/scan.c
@@ -22,9 +22,17 @@
* After this many consecutive WSM scan rejections from firmware, stop
* issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state
* that's rejecting them (coex window, firmware-internal busy) clear.
+ *
+ * The backoff has to be at least as long as the natural mac80211 scan-
+ * retry cadence, otherwise the next attempt lands outside the window
+ * and bypasses the defer guard. Observed in the wild on PineTab2:
+ * roam-evaluation bursts at ~12 s cadence, idle background scans at
+ * ~5 min cadence. 30 s catches the burst and leaves the slow case
+ * alone (the firmware-policy state has had minutes to clear by then
+ * anyway).
*/
#define BES2600_SCAN_REJECT_THRESHOLD 3
-#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ)
+#define BES2600_SCAN_BACKOFF_JIFFIES (30 * HZ)
static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
@@ -40,7 +48,9 @@ static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
* 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive
* rejections on recent scan attempts and the backoff window has
* not yet elapsed. Whatever was rejecting them is likely still
- * rejecting them; give it time.
+ * rejecting them; give it time. If the backoff has elapsed without
+ * a fresh reject refreshing it, the burst is over and we reset the
+ * count so an isolated reject doesn't immediately re-trip.
*
* Returns true if the caller should abandon the scan iteration.
*/
@@ -51,6 +61,9 @@ static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv)
return true;
#endif
+ if (time_after(jiffies, hw_priv->scan.backoff_until))
+ hw_priv->scan.reject_count = 0;
+
if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD &&
time_before(jiffies, hw_priv->scan.backoff_until))
return true;
--
2.53.0
@@ -0,0 +1,226 @@
From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Fri, 24 Apr 2026 21:31:45 +0200
Subject: [PATCH] bes2600: defer scan and soften WARN on firmware reject
On a BES2600-based PineTab2, mac80211's background-scan cadence
(about every 30 s when associated) triggers a two-step WARN splat
pattern, visible in dmesg roughly 30 times per 10 min of regular
WiFi use:
wsm_generic_confirm ret 2
WARNING: at wsm_handle_rx+0x8a4/0xf30 [bes2600]
... full stack trace ...
ieee80211 phy0: wsm_generic_confirm failed for request 0x0007.
WARNING: at bes2600_scan_work+0x5d4/0x810 [bes2600]
... full stack trace ...
ieee80211 phy0: [SCAN] Scan failed (-22).
0x0007 is the WSM start-scan request; status 2 is the firmware's
rejected-by-policy response, which it returns for at least two
conditions:
a) BT A2DP streaming in non-FDD coex mode -- the coex arbiter
in firmware won't grant an off-channel window while a SCO/
A2DP link is queued.
b) A firmware-internal busy state whose exact trigger the
driver cannot observe directly (confirmed on ohm with BT
disconnected -- rejection still fires). Likely transient
firmware-PM transitions.
Both are protocol-level policy responses, not kernel bugs, so the
full stack-trace WARN treatment is counterproductive: it buries
real problems and gets new users convinced the driver is broken.
Three-part fix:
1. struct bes2600_scan grows two fields -- reject_count and
backoff_until -- zero-initialised via the existing
ieee80211_alloc_hw()-provided kzalloc.
2. bes2600_scan_work() now consults bes2600_scan_should_defer()
before calling bes2600_scan_start(). The helper short-
circuits in two cases:
- coex_is_bt_a2dp() is true and coex is not in FDD mode,
since we already know the firmware will reject;
- BES2600_SCAN_REJECT_THRESHOLD (3) consecutive rejections
have fired and the BES2600_SCAN_BACKOFF_JIFFIES (10 s)
backoff window has not yet elapsed.
On defer or on a real firmware rejection, reject_count is
bumped and backoff_until is refreshed. A successful scan
clears reject_count.
3. The WARN_ON(hw_priv->scan.status) at the scan_start() call
site is replaced with a plain branch into the existing
fail: label. wsm_generic_confirm()'s WARN() becomes a
bes_devel() -- the per-request wiphy_warn in wsm_handle_rx
(which includes the offending request id) is kept, so real
debugging information is still on tape.
Net behaviour:
- Expected rejections no longer produce stack traces. The only
log line that remains on a rejected background scan is the
upstream-caller's wiphy_warn identifying request 0x0007 or
equivalent.
- The driver stops hammering the firmware with doomed scan
requests -- 3 rejections trigger a 10 s pause, during which
bes2600_scan_work() returns without issuing WSM 0x0007.
- The scan-completion path is unchanged; mac80211 sees the
scan complete with no results and reissues on its normal
cadence.
- Real protocol-layer bugs (unexpected underflow in the
confirm buffer) still WARN_ON at the 'underflow:' label.
Verified on ohm (PineTab2, linux-pinetab2 6.19.10-danctnix1-1):
WARN splat count dropped from 32 to 0 per 10 min uptime. WiFi
stays associated. No regression in other counters (KFENCE,
sdio_tx_work, RX failure, PS Mode Error, factory cali fail all
remain 0).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/scan.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-
bes2600/scan.h | 11 +++++++++
bes2600/wsm.c | 14 +++++++++++-
3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index b2c22e7..faa1c90 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -14,11 +14,50 @@
#include "scan.h"
#include "sta.h"
#include "pm.h"
+#include "epta_coex.h"
#include "epta_request.h"
#include "bes_pwr.h"
+/*
+ * After this many consecutive WSM scan rejections from firmware, stop
+ * issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state
+ * that's rejecting them (coex window, firmware-internal busy) clear.
+ */
+#define BES2600_SCAN_REJECT_THRESHOLD 3
+#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ)
+
static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
+/*
+ * Decide whether to skip sending the next WSM scan command without
+ * bothering the firmware. Two triggers:
+ *
+ * 1. BT A2DP is streaming in non-FDD coex mode. The firmware is
+ * known to reject scan requests during that window; short-
+ * circuiting here saves a WSM round-trip and avoids the
+ * wsm_generic_confirm / scan_work warning cascade that follows.
+ *
+ * 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive
+ * rejections on recent scan attempts and the backoff window has
+ * not yet elapsed. Whatever was rejecting them is likely still
+ * rejecting them; give it time.
+ *
+ * Returns true if the caller should abandon the scan iteration.
+ */
+static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv)
+{
+#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
+ if (!coex_is_fdd_mode() && coex_is_bt_a2dp())
+ return true;
+#endif
+
+ if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD &&
+ time_before(jiffies, hw_priv->scan.backoff_until))
+ return true;
+
+ return false;
+}
+
#ifdef CONFIG_BES2600_TESTMODE
static int bes2600_advance_scan_start(struct bes2600_common *hw_priv)
{
@@ -702,10 +741,29 @@ void bes2600_scan_work(struct work_struct *work)
wsm_unlock_tx(hw_priv);
} else
#endif
+ {
+ if (bes2600_scan_should_defer(hw_priv)) {
+ hw_priv->scan.status = -EBUSY;
+ hw_priv->scan.reject_count++;
+ hw_priv->scan.backoff_until =
+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES;
+ wiphy_dbg(priv->hw->wiphy,
+ "[SCAN] deferred (coex/backoff, reject_count=%u)\n",
+ hw_priv->scan.reject_count);
+ kfree(scan.ch);
+ goto fail;
+ }
hw_priv->scan.status = bes2600_scan_start(priv, &scan);
+ }
kfree(scan.ch);
- if (WARN_ON(hw_priv->scan.status))
+ if (hw_priv->scan.status) {
+ hw_priv->scan.reject_count++;
+ hw_priv->scan.backoff_until =
+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES;
+ /* Lower callers already logged the reason at wiphy_warn. */
goto fail;
+ }
+ hw_priv->scan.reject_count = 0;
hw_priv->scan.curr = it;
}
up(&hw_priv->conf_lock);
diff --git a/drivers/staging/bes2600/scan.h b/drivers/staging/bes2600/scan.h
index e50fa36..1f3adea 100644
--- a/drivers/staging/bes2600/scan.h
+++ b/drivers/staging/bes2600/scan.h
@@ -42,6 +42,17 @@ struct bes2600_scan {
struct delayed_work probe_work;
int direct_probe;
u8 if_id;
+ /*
+ * Track consecutive firmware-side WSM scan rejections so we can
+ * back off briefly instead of re-issuing the same scan on every
+ * mac80211 background-scan tick. Firmware returns WSM status != 0
+ * for a handful of transient conditions (BT A2DP active in non-
+ * FDD coex, firmware-internal busy windows) and keeps rejecting
+ * until the state clears; retrying at full cadence just floods
+ * dmesg.
+ */
+ unsigned int reject_count;
+ unsigned long backoff_until;
};
int bes2600_hw_scan(struct ieee80211_hw *hw,
diff --git a/drivers/staging/bes2600/wsm.c b/drivers/staging/bes2600/wsm.c
index d40df30..55a4e2b 100644
--- a/drivers/staging/bes2600/wsm.c
+++ b/drivers/staging/bes2600/wsm.c
@@ -134,8 +134,20 @@ static int wsm_generic_confirm(struct bes2600_common *hw_priv,
struct wsm_buf *buf)
{
u32 status = WSM_GET32(buf);
- if (WARN(status != WSM_STATUS_SUCCESS, "wsm_generic_confirm ret %u", status))
+
+ /*
+ * A non-SUCCESS status here is a firmware-side policy decision for
+ * the command whose confirm this is -- commonly WSM status 2 for
+ * scan (0x0407) rejected because of a coex window or transient
+ * firmware-busy state. It is not a driver/kernel bug, so avoid the
+ * WARN()/stack-trace treatment; the caller already emits a
+ * wiphy_warn identifying the request id and will propagate the
+ * error to mac80211.
+ */
+ if (status != WSM_STATUS_SUCCESS) {
+ bes_devel("%s ret %u\n", __func__, status);
return -EINVAL;
+ }
return 0;
underflow:
--
2.53.0
@@ -0,0 +1,226 @@
From adc6c1f332d41ee1aadd349eea11809c88139307 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Fri, 24 Apr 2026 21:31:45 +0200
Subject: [PATCH] bes2600: defer scan and soften WARN on firmware reject
On a BES2600-based PineTab2, mac80211's background-scan cadence
(about every 30 s when associated) triggers a two-step WARN splat
pattern, visible in dmesg roughly 30 times per 10 min of regular
WiFi use:
wsm_generic_confirm ret 2
WARNING: at wsm_handle_rx+0x8a4/0xf30 [bes2600]
... full stack trace ...
ieee80211 phy0: wsm_generic_confirm failed for request 0x0007.
WARNING: at bes2600_scan_work+0x5d4/0x810 [bes2600]
... full stack trace ...
ieee80211 phy0: [SCAN] Scan failed (-22).
0x0007 is the WSM start-scan request; status 2 is the firmware's
rejected-by-policy response, which it returns for at least two
conditions:
a) BT A2DP streaming in non-FDD coex mode -- the coex arbiter
in firmware won't grant an off-channel window while a SCO/
A2DP link is queued.
b) A firmware-internal busy state whose exact trigger the
driver cannot observe directly (confirmed on ohm with BT
disconnected -- rejection still fires). Likely transient
firmware-PM transitions.
Both are protocol-level policy responses, not kernel bugs, so the
full stack-trace WARN treatment is counterproductive: it buries
real problems and gets new users convinced the driver is broken.
Three-part fix:
1. struct bes2600_scan grows two fields -- reject_count and
backoff_until -- zero-initialised via the existing
ieee80211_alloc_hw()-provided kzalloc.
2. bes2600_scan_work() now consults bes2600_scan_should_defer()
before calling bes2600_scan_start(). The helper short-
circuits in two cases:
- coex_is_bt_a2dp() is true and coex is not in FDD mode,
since we already know the firmware will reject;
- BES2600_SCAN_REJECT_THRESHOLD (3) consecutive rejections
have fired and the BES2600_SCAN_BACKOFF_JIFFIES (10 s)
backoff window has not yet elapsed.
On defer or on a real firmware rejection, reject_count is
bumped and backoff_until is refreshed. A successful scan
clears reject_count.
3. The WARN_ON(hw_priv->scan.status) at the scan_start() call
site is replaced with a plain branch into the existing
fail: label. wsm_generic_confirm()'s WARN() becomes a
bes_devel() -- the per-request wiphy_warn in wsm_handle_rx
(which includes the offending request id) is kept, so real
debugging information is still on tape.
Net behaviour:
- Expected rejections no longer produce stack traces. The only
log line that remains on a rejected background scan is the
upstream-caller's wiphy_warn identifying request 0x0007 or
equivalent.
- The driver stops hammering the firmware with doomed scan
requests -- 3 rejections trigger a 10 s pause, during which
bes2600_scan_work() returns without issuing WSM 0x0007.
- The scan-completion path is unchanged; mac80211 sees the
scan complete with no results and reissues on its normal
cadence.
- Real protocol-layer bugs (unexpected underflow in the
confirm buffer) still WARN_ON at the 'underflow:' label.
Verified on ohm (PineTab2, linux-pinetab2 6.19.10-danctnix1-1):
WARN splat count dropped from 32 to 0 per 10 min uptime. WiFi
stays associated. No regression in other counters (KFENCE,
sdio_tx_work, RX failure, PS Mode Error, factory cali fail all
remain 0).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/scan.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-
bes2600/scan.h | 11 +++++++++
bes2600/wsm.c | 14 +++++++++++-
3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/bes2600/scan.c b/bes2600/scan.c
index b2c22e7..faa1c90 100644
--- a/bes2600/scan.c
+++ b/bes2600/scan.c
@@ -14,11 +14,50 @@
#include "scan.h"
#include "sta.h"
#include "pm.h"
+#include "epta_coex.h"
#include "epta_request.h"
#include "bes_pwr.h"
+/*
+ * After this many consecutive WSM scan rejections from firmware, stop
+ * issuing new scans for BES2600_SCAN_BACKOFF_JIFFIES and let the state
+ * that's rejecting them (coex window, firmware-internal busy) clear.
+ */
+#define BES2600_SCAN_REJECT_THRESHOLD 3
+#define BES2600_SCAN_BACKOFF_JIFFIES (10 * HZ)
+
static void bes2600_scan_restart_delayed(struct bes2600_vif *priv);
+/*
+ * Decide whether to skip sending the next WSM scan command without
+ * bothering the firmware. Two triggers:
+ *
+ * 1. BT A2DP is streaming in non-FDD coex mode. The firmware is
+ * known to reject scan requests during that window; short-
+ * circuiting here saves a WSM round-trip and avoids the
+ * wsm_generic_confirm / scan_work warning cascade that follows.
+ *
+ * 2. We already saw >= BES2600_SCAN_REJECT_THRESHOLD consecutive
+ * rejections on recent scan attempts and the backoff window has
+ * not yet elapsed. Whatever was rejecting them is likely still
+ * rejecting them; give it time.
+ *
+ * Returns true if the caller should abandon the scan iteration.
+ */
+static bool bes2600_scan_should_defer(struct bes2600_common *hw_priv)
+{
+#ifdef WIFI_BT_COEXIST_EPTA_ENABLE
+ if (!coex_is_fdd_mode() && coex_is_bt_a2dp())
+ return true;
+#endif
+
+ if (hw_priv->scan.reject_count >= BES2600_SCAN_REJECT_THRESHOLD &&
+ time_before(jiffies, hw_priv->scan.backoff_until))
+ return true;
+
+ return false;
+}
+
#ifdef CONFIG_BES2600_TESTMODE
static int bes2600_advance_scan_start(struct bes2600_common *hw_priv)
{
@@ -702,10 +741,29 @@ void bes2600_scan_work(struct work_struct *work)
wsm_unlock_tx(hw_priv);
} else
#endif
+ {
+ if (bes2600_scan_should_defer(hw_priv)) {
+ hw_priv->scan.status = -EBUSY;
+ hw_priv->scan.reject_count++;
+ hw_priv->scan.backoff_until =
+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES;
+ wiphy_dbg(priv->hw->wiphy,
+ "[SCAN] deferred (coex/backoff, reject_count=%u)\n",
+ hw_priv->scan.reject_count);
+ kfree(scan.ch);
+ goto fail;
+ }
hw_priv->scan.status = bes2600_scan_start(priv, &scan);
+ }
kfree(scan.ch);
- if (WARN_ON(hw_priv->scan.status))
+ if (hw_priv->scan.status) {
+ hw_priv->scan.reject_count++;
+ hw_priv->scan.backoff_until =
+ jiffies + BES2600_SCAN_BACKOFF_JIFFIES;
+ /* Lower callers already logged the reason at wiphy_warn. */
goto fail;
+ }
+ hw_priv->scan.reject_count = 0;
hw_priv->scan.curr = it;
}
up(&hw_priv->conf_lock);
diff --git a/bes2600/scan.h b/bes2600/scan.h
index e50fa36..1f3adea 100644
--- a/bes2600/scan.h
+++ b/bes2600/scan.h
@@ -42,6 +42,17 @@ struct bes2600_scan {
struct delayed_work probe_work;
int direct_probe;
u8 if_id;
+ /*
+ * Track consecutive firmware-side WSM scan rejections so we can
+ * back off briefly instead of re-issuing the same scan on every
+ * mac80211 background-scan tick. Firmware returns WSM status != 0
+ * for a handful of transient conditions (BT A2DP active in non-
+ * FDD coex, firmware-internal busy windows) and keeps rejecting
+ * until the state clears; retrying at full cadence just floods
+ * dmesg.
+ */
+ unsigned int reject_count;
+ unsigned long backoff_until;
};
int bes2600_hw_scan(struct ieee80211_hw *hw,
diff --git a/bes2600/wsm.c b/bes2600/wsm.c
index d40df30..55a4e2b 100644
--- a/bes2600/wsm.c
+++ b/bes2600/wsm.c
@@ -134,8 +134,20 @@ static int wsm_generic_confirm(struct bes2600_common *hw_priv,
struct wsm_buf *buf)
{
u32 status = WSM_GET32(buf);
- if (WARN(status != WSM_STATUS_SUCCESS, "wsm_generic_confirm ret %u", status))
+
+ /*
+ * A non-SUCCESS status here is a firmware-side policy decision for
+ * the command whose confirm this is -- commonly WSM status 2 for
+ * scan (0x0407) rejected because of a coex window or transient
+ * firmware-busy state. It is not a driver/kernel bug, so avoid the
+ * WARN()/stack-trace treatment; the caller already emits a
+ * wiphy_warn identifying the request id and will propagate the
+ * error to mac80211.
+ */
+ if (status != WSM_STATUS_SUCCESS) {
+ bes_devel("%s ret %u\n", __func__, status);
return -EINVAL;
+ }
return 0;
underflow:
--
2.53.0
@@ -0,0 +1,168 @@
From 093a5038b8b68f316d976b7cb69609ca7f24f322 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 11:27:40 +0200
Subject: [PATCH 1/2] bes2600: filter 5 GHz scans at the driver boundary
(besser#1)
The BES2600 firmware refuses WSM start-scan for 5 GHz with status 2
("rejected by policy"). This shows up in dmesg as the recurring
wsm_generic_confirm failed for request 0x0007.
[SCAN] Scan failed (-22).
pattern (besser issue #1, ~14-16/h on ohm/PineTab2 baseline).
Trace shows every reject is the second of a back-to-back pair: mac80211
splits multi-band hw_scan requests per band when the driver does not
set IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't), then re-invokes
drv_hw_scan from __ieee80211_scan_completed for each subsequent band.
The 2.4 GHz iteration succeeds; the 5 GHz iteration is what the
firmware rejects. See ieee80211_prep_hw_scan in net/mac80211/scan.c
for the loop, and the existing memory reference_bes2600_5ghz_scan_reject
for the firmware behaviour.
The 056a71a defer-on-reject patch already in this tree handles the
BT-A2DP-coex branch and the consecutive-reject backoff, but it cannot
prevent the per-band-loop reject: by the time defer_should_scan is
consulted, the per-band call is already in flight, and the reject_count
gets reset on every successful 2.4 GHz scan in between (which is
~36% of attempts), so the threshold never trips.
The fix: refuse the 5 GHz iteration upfront in bes2600_hw_scan. The
2.4 GHz scan still runs normally. The 5 GHz portion is reported as
aborted to userspace -- same outcome as today, minus the dmesg storm
and the wsm_generic_confirm WARN cascade.
5 GHz band registration is intentionally left in place: direct-BSSID
association to a known 5 GHz AP still works (no scan is needed for
that path), and a future firmware update that fixes the scan behaviour
should not be foreclosed by changing band advertisement.
Contract: per include/net/mac80211.h ieee80211_ops.hw_scan, a negative
return aborts the scan without requiring ieee80211_scan_completed().
-EOPNOTSUPP is the semantically accurate code (operation is legal,
driver can't service it on this band today).
Phase 3 evidence:
- baseline N=3: rate ~14.3-23.6/h converged at 14.3/h (matches OP)
- back-to-back scan gap: 6/6 rejected pairs <200us, 1/1 successful
pair was 114ms (single-band-only, no 5 GHz leg)
- defer log fires: 0/9 in 30-min window (056a71a structurally bypassed)
Predicted Phase 7 delta: Pattern A 14/h -> 0/h.
---
bes2600/scan.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index fb1d298..a81afb6 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -238,6 +238,28 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
/* Scan when P2P_GO corrupt firmware MiniAP mode */
if (priv->join_status == BES2600_JOIN_STATUS_AP)
return -EOPNOTSUPP;
+
+ /*
+ * Firmware refuses WSM start-scan for 5 GHz with status 2 ("rejected
+ * by policy"); see besser issue #1. mac80211 splits multi-band
+ * hw_scan requests per-band when the driver does not set
+ * IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't -- see
+ * ieee80211_hw_set() calls in bes2600_main.c), so each per-band call
+ * has req->channels[] from one band only (see ieee80211_prep_hw_scan
+ * in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver
+ * boundary so userspace gets a clean aborted-scan for that portion
+ * rather than waiting for the firmware reject to cascade up. 5 GHz
+ * band registration stays intact so direct-BSSID association to a
+ * known 5 GHz AP still works (no scan needed for that path).
+ *
+ * Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan
+ * documentation, a negative return aborts the scan without requiring
+ * ieee80211_scan_completed().
+ */
+ if (req->n_channels > 0 &&
+ req->channels[0]->band == NL80211_BAND_5GHZ)
+ return -EOPNOTSUPP;
+
#if 0
if (work_pending(&priv->offchannel_work) ||
(hw_priv->roc_if_id != -1)) {
--
2.54.0
From 8cd10f487c8144d462a510812ba0fa717b3e24df Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 15:56:34 +0200
Subject: [PATCH 2/2] bes2600: scan-filter-5ghz: allow targeted single-channel
scans (besser#1 follow-up)
The original Patch I refused EVERY 5 GHz scan request unconditionally
(req->n_channels > 0 && band == NL80211_BAND_5GHZ). This eliminated
the Pattern A storm but also broke 5 GHz association entirely:
NM / wpa_supplicant iterates a freq_list when a connection profile
specifies 802-11-wireless.band=a, issuing per-frequency single-channel
scans to find the BSS before associating. Those single-channel scans
were also refused by our guard, so the BSS was never seen and
'Wi-Fi network could not be found' was the only outcome.
Tighten the guard: refuse only multi-channel 5 GHz scans (n_channels
> 1), which is the per-band-sweep pattern mac80211 issues internally
and the only one that triggers the firmware storm at the per-band
loop boundary. Single-channel 5 GHz scans pass through to firmware,
which generally accepts them -- and when they happen to be rejected,
the failure is isolated and doesn't cascade.
Verified on ohm with pkgrel=3 (srcversion BEB625FA7443171EA8D55F7):
- Pattern A count since boot: 0 (Phase 7 prediction still holds)
- iw dev wlan0 scan freq 5180 -> allowed
- iw dev wlan0 scan freq 5180 5200 ... -> refused -EOPNOTSUPP
- NM 'nmcli connection up' with band=a -> associated to BSSID
c0:25:06:e6:5b:33 on 5240 MHz / ch.48 in ~1 second
- TX bitrate 150 Mbit/s MCS 7 40MHz short-GI (vs 72.2 Mbit/s
HT20 on 2.4 GHz) -- ~2x throughput recovered
The change is a single byte (> 0 -> > 1) plus comment update; the
test confirmation above is what motivates it.
Refs: besser#1 (closed but tracked for follow-up like this), original
Patch I sha 093a503.
---
bes2600/scan.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index a81afb6..497523b 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -248,15 +248,23 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
* has req->channels[] from one band only (see ieee80211_prep_hw_scan
* in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver
* boundary so userspace gets a clean aborted-scan for that portion
- * rather than waiting for the firmware reject to cascade up. 5 GHz
- * band registration stays intact so direct-BSSID association to a
- * known 5 GHz AP still works (no scan needed for that path).
+ * rather than waiting for the firmware reject to cascade up.
+ *
+ * Only the multi-channel case is refused (n_channels > 1): that's
+ * the per-band-sweep pattern mac80211 issues internally and the
+ * one that triggers the firmware storm at the per-band loop
+ * boundary. Single-channel 5 GHz scans (BSS verification, NM's
+ * per-freq iteration when 802-11-wireless.band=a is set) pass
+ * through to firmware, which generally accepts them since the
+ * storm is the back-to-back per-band issue, not a blanket 5 GHz
+ * reject. This preserves 5 GHz association via the
+ * "wpa_supplicant iterates freq_list per channel" path.
*
* Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan
* documentation, a negative return aborts the scan without requiring
* ieee80211_scan_completed().
*/
- if (req->n_channels > 0 &&
+ if (req->n_channels > 1 &&
req->channels[0]->band == NL80211_BAND_5GHZ)
return -EOPNOTSUPP;
--
2.54.0
@@ -0,0 +1,19 @@
# scan-filter-5ghz-danctnix — close besser#1
Refuses multi-channel 5 GHz scan requests at the driver boundary with
`-EOPNOTSUPP`, eliminating the WSM 0x0007 reject storm. Single-channel
5 GHz scans still pass through (NM `802-11-wireless.band=a` BSS
verification path stays functional).
Phase 7 baseline on ohm: Pattern A 14.3/h → 0/h (verified 2026-05-18,
30 min window). 5 GHz association achieves 150 Mbit/s MCS 7 HT40 SGI vs
72.2 on 2.4 GHz.
Single combined patch file because the two commits in the source
(initial filter + `n_channels > 1` refinement) form a 2-commit
follow-up series and git apply concatenation handles both. Splitting
into two .patch files would mean a fragile dependency on cross-file
sequencing inside the same series-dir.
Provenance: closes besser#1. Mirror of source-of-truth in
`marfrit/bes2600-dkms` branch `bes2600/scan-filter-5ghz`.
@@ -0,0 +1,168 @@
From 093a5038b8b68f316d976b7cb69609ca7f24f322 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 11:27:40 +0200
Subject: [PATCH 1/2] bes2600: filter 5 GHz scans at the driver boundary
(besser#1)
The BES2600 firmware refuses WSM start-scan for 5 GHz with status 2
("rejected by policy"). This shows up in dmesg as the recurring
wsm_generic_confirm failed for request 0x0007.
[SCAN] Scan failed (-22).
pattern (besser issue #1, ~14-16/h on ohm/PineTab2 baseline).
Trace shows every reject is the second of a back-to-back pair: mac80211
splits multi-band hw_scan requests per band when the driver does not
set IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't), then re-invokes
drv_hw_scan from __ieee80211_scan_completed for each subsequent band.
The 2.4 GHz iteration succeeds; the 5 GHz iteration is what the
firmware rejects. See ieee80211_prep_hw_scan in net/mac80211/scan.c
for the loop, and the existing memory reference_bes2600_5ghz_scan_reject
for the firmware behaviour.
The 056a71a defer-on-reject patch already in this tree handles the
BT-A2DP-coex branch and the consecutive-reject backoff, but it cannot
prevent the per-band-loop reject: by the time defer_should_scan is
consulted, the per-band call is already in flight, and the reject_count
gets reset on every successful 2.4 GHz scan in between (which is
~36% of attempts), so the threshold never trips.
The fix: refuse the 5 GHz iteration upfront in bes2600_hw_scan. The
2.4 GHz scan still runs normally. The 5 GHz portion is reported as
aborted to userspace -- same outcome as today, minus the dmesg storm
and the wsm_generic_confirm WARN cascade.
5 GHz band registration is intentionally left in place: direct-BSSID
association to a known 5 GHz AP still works (no scan is needed for
that path), and a future firmware update that fixes the scan behaviour
should not be foreclosed by changing band advertisement.
Contract: per include/net/mac80211.h ieee80211_ops.hw_scan, a negative
return aborts the scan without requiring ieee80211_scan_completed().
-EOPNOTSUPP is the semantically accurate code (operation is legal,
driver can't service it on this band today).
Phase 3 evidence:
- baseline N=3: rate ~14.3-23.6/h converged at 14.3/h (matches OP)
- back-to-back scan gap: 6/6 rejected pairs <200us, 1/1 successful
pair was 114ms (single-band-only, no 5 GHz leg)
- defer log fires: 0/9 in 30-min window (056a71a structurally bypassed)
Predicted Phase 7 delta: Pattern A 14/h -> 0/h.
---
bes2600/scan.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index fb1d298..a81afb6 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -238,6 +238,28 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
/* Scan when P2P_GO corrupt firmware MiniAP mode */
if (priv->join_status == BES2600_JOIN_STATUS_AP)
return -EOPNOTSUPP;
+
+ /*
+ * Firmware refuses WSM start-scan for 5 GHz with status 2 ("rejected
+ * by policy"); see besser issue #1. mac80211 splits multi-band
+ * hw_scan requests per-band when the driver does not set
+ * IEEE80211_HW_SINGLE_SCAN_ON_ALL_BANDS (we don't -- see
+ * ieee80211_hw_set() calls in bes2600_main.c), so each per-band call
+ * has req->channels[] from one band only (see ieee80211_prep_hw_scan
+ * in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver
+ * boundary so userspace gets a clean aborted-scan for that portion
+ * rather than waiting for the firmware reject to cascade up. 5 GHz
+ * band registration stays intact so direct-BSSID association to a
+ * known 5 GHz AP still works (no scan needed for that path).
+ *
+ * Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan
+ * documentation, a negative return aborts the scan without requiring
+ * ieee80211_scan_completed().
+ */
+ if (req->n_channels > 0 &&
+ req->channels[0]->band == NL80211_BAND_5GHZ)
+ return -EOPNOTSUPP;
+
#if 0
if (work_pending(&priv->offchannel_work) ||
(hw_priv->roc_if_id != -1)) {
--
2.54.0
From 8cd10f487c8144d462a510812ba0fa717b3e24df Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Mon, 18 May 2026 15:56:34 +0200
Subject: [PATCH 2/2] bes2600: scan-filter-5ghz: allow targeted single-channel
scans (besser#1 follow-up)
The original Patch I refused EVERY 5 GHz scan request unconditionally
(req->n_channels > 0 && band == NL80211_BAND_5GHZ). This eliminated
the Pattern A storm but also broke 5 GHz association entirely:
NM / wpa_supplicant iterates a freq_list when a connection profile
specifies 802-11-wireless.band=a, issuing per-frequency single-channel
scans to find the BSS before associating. Those single-channel scans
were also refused by our guard, so the BSS was never seen and
'Wi-Fi network could not be found' was the only outcome.
Tighten the guard: refuse only multi-channel 5 GHz scans (n_channels
> 1), which is the per-band-sweep pattern mac80211 issues internally
and the only one that triggers the firmware storm at the per-band
loop boundary. Single-channel 5 GHz scans pass through to firmware,
which generally accepts them -- and when they happen to be rejected,
the failure is isolated and doesn't cascade.
Verified on ohm with pkgrel=3 (srcversion BEB625FA7443171EA8D55F7):
- Pattern A count since boot: 0 (Phase 7 prediction still holds)
- iw dev wlan0 scan freq 5180 -> allowed
- iw dev wlan0 scan freq 5180 5200 ... -> refused -EOPNOTSUPP
- NM 'nmcli connection up' with band=a -> associated to BSSID
c0:25:06:e6:5b:33 on 5240 MHz / ch.48 in ~1 second
- TX bitrate 150 Mbit/s MCS 7 40MHz short-GI (vs 72.2 Mbit/s
HT20 on 2.4 GHz) -- ~2x throughput recovered
The change is a single byte (> 0 -> > 1) plus comment update; the
test confirmation above is what motivates it.
Refs: besser#1 (closed but tracked for follow-up like this), original
Patch I sha 093a503.
---
bes2600/scan.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/bes2600/scan.c b/drivers/staging/bes2600/scan.c
index a81afb6..497523b 100644
--- a/drivers/staging/bes2600/scan.c
+++ b/drivers/staging/bes2600/scan.c
@@ -248,15 +248,23 @@ int bes2600_hw_scan(struct ieee80211_hw *hw,
* has req->channels[] from one band only (see ieee80211_prep_hw_scan
* in net/mac80211/scan.c). Refuse the 5 GHz iteration at the driver
* boundary so userspace gets a clean aborted-scan for that portion
- * rather than waiting for the firmware reject to cascade up. 5 GHz
- * band registration stays intact so direct-BSSID association to a
- * known 5 GHz AP still works (no scan needed for that path).
+ * rather than waiting for the firmware reject to cascade up.
+ *
+ * Only the multi-channel case is refused (n_channels > 1): that's
+ * the per-band-sweep pattern mac80211 issues internally and the
+ * one that triggers the firmware storm at the per-band loop
+ * boundary. Single-channel 5 GHz scans (BSS verification, NM's
+ * per-freq iteration when 802-11-wireless.band=a is set) pass
+ * through to firmware, which generally accepts them since the
+ * storm is the back-to-back per-band issue, not a blanket 5 GHz
+ * reject. This preserves 5 GHz association via the
+ * "wpa_supplicant iterates freq_list per channel" path.
*
* Contract: per include/net/mac80211.h struct ieee80211_ops.hw_scan
* documentation, a negative return aborts the scan without requiring
* ieee80211_scan_completed().
*/
- if (req->n_channels > 0 &&
+ if (req->n_channels > 1 &&
req->channels[0]->band == NL80211_BAND_5GHZ)
return -EOPNOTSUPP;
--
2.54.0
@@ -0,0 +1,186 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 12:35:28 +0200
Subject: [PATCH 0/7] bes2600: staging-prep cleanup for PineTab2 (BES2600WM)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This series is a staging-prep cleanup for the out-of-tree Bestechnic
BES2600WM Wi-Fi/BT combo-chip driver as shipped by Mobian's bes2600-dkms
package (and in-tree at drivers/staging/bes2600/ in the danctnix
linux-pinetab2 fork). Target hardware is the Pine64 PineTab2 (RK3566
+ BES2600WM, SDIO vendor 0xBE57 / device 0x2002).
The driver descends from the ST-Ericsson CW1200 (drivers/net/wireless/
st/cw1200/) -- same author, Dmitry Tarnyagin, shared WSM host<->firmware
protocol, shared SDIO bus backend. Kconfig ancestry markers survive in
this tree today: CONFIG_BES2600_USE_STE_EXTENSIONS (STE = ST-Ericsson),
CONFIG_BES2600_WSM_DEBUG (WSM). ST-Ericsson wound down in 2013;
Bestechnic (founded 2015) appears to have inherited or licensed the
CW1200 IP. No linux-wireless RFC has ever linked the two chips.
The series fixes observable defects on a PineTab2 running linux-pinetab2
6.19.10-danctnix1-1 and removes two upstream blockers. Each patch is
independently testable and bisectable; the order below preserves
dependencies.
## What the series does
* 1/7 -- Replace filp_open() + kernel_read() in the factory-calibration
read path with request_firmware(). Repoint the FACTORY_PATH macro to
the firmware-class name (bes2600/bes2600_factory.txt, matching the
/lib/firmware/ layout). Kills a kernel-mainline anti-pattern and the
'(NULL device *): read and check /lib/firmware/bes2600_factory.txt
error' boot spam on PineTab2.
* 2/7 -- Default STANDARD_FACTORY_EFUSE_FLAG from y to n. The shipped
bes2600_factory.txt on PineTab2 contains 30 calibration fields; the
driver was expecting 31 (including a ##select_efuse_flag section
absent from this firmware). Also unguards the
wsm_save_factory_txt_to_mcu() prototype in wsm.h which was
inconsistently wrapped in '#if defined(STANDARD_FACTORY_EFUSE_FLAG)'
while its definition in wsm.c and its call site in sta.c were
ungated -- gcc -Werror=missing-prototypes broke the build with the
new default until this is fixed.
* 3/7 -- Thread struct device * through factory_section_read_file() via
a module-local setter invoked at SDIO probe. request_firmware() now
receives a real device pointer; '(NULL device *):' no longer prefixes
factory-related diagnostics.
* 4/7 -- Gate the device-end of the low-power transition on successful
per-VIF firmware handshake. Pre-patch bes2600_pwr_enter_lp_mode()
called bes2600_pwr_device_enter_lp_mode() unconditionally even when
wait_for_completion_timeout() returned 0 (firmware never posted the
PM-change indication). On PineTab2 this recurred every 5-10 s
whenever the interface was associated and idle, flooded dmesg, and
cascaded into sdio_tx_work WARN splats / [RX] Receive failure
messages. Post-patch: -ETIMEDOUT returned cleanly, dmesg silent,
SDIO stable.
* 5/7 -- Remove the custom /dev/bes2600 character-device interface.
file_operations, open/read/write/release, bes2600_op_*
command-dispatch table, bes2600_load_uevent, alloc_chrdev_region /
cdev_init / cdev_add / class_create / device_create in the init
path, and the matching teardown in bes2600_chrdev_free -- 519 lines
deleted. The in-kernel accessor functions (is_signal_mode,
get_fw_type, etc., 13 call sites) and the fw_type module parameter
are preserved; the userspace interface becomes rfkill + module_param
+ (with 6/7) nl80211 testmode.
* 6/7 -- Flip CONFIG_BES2600_TESTMODE default from n to y. The driver
already implements a mac80211 testmode_cmd dispatcher (routing to
the firmware's patch_wifi_testMode path), already gated on the flag;
CONFIG_NL80211_TESTMODE=y is common on target kernels. Enabling the
flag also exposes accumulated bit-rot -- ~41 calls to undefined
bes2600_info/err/warn/dbg/err_with_cond macros, and 3 TSM/roam-delay
helpers with external linkage but no prototype. Add shim macros to
bes_log.h rewiring the legacy calls onto the existing bes_info /
bes_err / bes_warn / bes_devel family, define BES2600_DBG_* subsystem
ids as 0 constants, and mark the 3 helpers static.
* 7/7 -- Bounce SDIO TX buffers to avoid DMA out-of-bounds reads.
sdio_tx_work() rounded the transfer length up to the SDIO block size
(align = blks * cur_blksize, or 1632) and handed that length to
dma_map_sg() via sg_set_buf(..., tx_buffer->buf, align); tx_buffer->buf
typically aliases into an skb linear head allocated to tx_buffer->len,
not the block-aligned length. The DMA engine therefore read up to one
block past the end of the skb -- KFENCE on PineTab2 fires as
'out-of-bounds read in __pi_memcpy_generic ... 704B right of
kfence-#...' with sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper in
the stack and pskb_expand_head / validate_xmit_skb / tcp_write_xmit in
the allocator stack. Besides being undefined behavior, the padding
bytes are transmitted to the peer, leaking adjacent kernel memory on
the air. Allocate a driver-owned DMA-pages bounce buffer of
MAX_SDIO_TRANSFER_LEN, memcpy each TX buffer into its slot, zero the
padding tail, and point the SG entries at the bounce. Mirrors the
pattern already used for single_gathered_buffer; kept as a separate
allocation because sdio_tx_work accumulates SG entries before claiming
the bus.
## Testing
Reference hardware: Pine64 PineTab2 (BES2600WM + Rockchip RK3566,
8a:2e:77:1f:ec:05, LAN AP newton @ 2.4 GHz ch11, tested also on
TelekomHotspot@ERGO @ 5 GHz ch36).
Host kernel: linux-pinetab2 6.19.10-danctnix1-1-pinetab2 with
CONFIG_NL80211_TESTMODE=y and CONFIG_KFENCE=y (for 7/7 verification).
Driver installed via /lib/modules/<ver>/extra/ and verified loaded via
modinfo srcversion.
Per-patch outcomes in post-reboot dmesg (full-stack applied, 11+ min
soak, 245k RX packets / 365 MB traffic):
- 1/7: 'read and check /lib/firmware/bes2600_factory.txt error' -- gone
- 2/7: 'bes2600_factory.txt parse fail' / 'factory cali data get
failed.' -- gone
- 3/7: '(NULL device *):' prefix on factory lines -- gone
- 4/7: 'bes2600_pwr_enter_lp_mode, wait pm ind timeout' (pre-patch 20-30
msgs / 5 min window) -- 0 per 5 min; '[RX] Receive failure: 4.' --
gone
- 5/7: /dev/bes2600 -- absent; driver continues to associate
- 6/7: 'iw phy0' lists 'testmode' under Supported commands; module
builds cleanly
- 7/7: 'BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic'
(pre-patch ~65 splats per 4 h of real traffic, always via
sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper+0x18c) -- 0;
'sdio_tx_work+0x2b4' WARN splat residual from 4/7's cascade -- 0
(previously recurred ~1 per reboot even with 4/7 applied);
'PS Mode Error, Reason:1' benign handshake notice at T+40s --
also gone, apparently a downstream effect of no longer DMAing
uninitialised padding bytes into firmware
Full stack: wifi associates and passes traffic across 3+ reboots.
## Known limitations / out of scope
- factory_section_write_file() in bes2600_factory.c still uses
kernel_write() + filp_open(O_CREAT) to persist per-channel
calibration updates back to /lib/firmware/bes2600_factory.txt.
Converting the write-back path to debugfs or nl80211 testmode is a
follow-up.
- bes_chardev.c still carries DPD file-read/write paths gated by
BES2600_WRITE_DPD_TO_FILE (off by default, so dead code in the
default build). Same treatment needed.
- bes_fw.c:587 unconditionally creates
/lib/firmware/bes2002_fw_write.bin via filp_open() for debug
observation. Needs to go before drivers/staging/ accepts the driver.
- bes_cdev global singleton still holds sig_mode and fw_type. Moving
those to per-hw_priv state is blocked by fw_type being a module
parameter (inherently singleton). Migrating fw_type to a per-phy
debugfs knob or nl80211 testmode command is the next step; overlaps
with 6/7's testmode plumbing.
Markus Fritsche (7):
bes2600: use request_firmware() for factory.txt read
bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for PineTab2
factory.txt format
bes2600: thread struct device * through factory request_firmware()
call
bes2600: gate device LP-mode entry on successful per-VIF firmware
handshake
bes2600: remove userspace /dev/bes2600 character device interface
bes2600: enable CONFIG_BES2600_TESTMODE by default + fix bit-rotted
testmode plumbing
bes2600: bounce SDIO TX buffers to avoid DMA OOB read
drivers/staging/bes2600/Makefile | 6 +-
drivers/staging/bes2600/bes2600_factory.c | 45 ++--
drivers/staging/bes2600/bes2600_factory.h | 3 +
drivers/staging/bes2600/bes2600_sdio.c | 43 +++-
drivers/staging/bes2600/bes_chardev.c | 519 --------------------------------------
drivers/staging/bes2600/bes_log.h | 23 ++
drivers/staging/bes2600/bes_pwr.c | 20 +-
drivers/staging/bes2600/sta.c | 6 +-
drivers/staging/bes2600/wsm.h | 2 -
9 files changed, 117 insertions(+), 550 deletions(-)
--
2.53.0
@@ -0,0 +1,147 @@
From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001
Message-ID: <d18aa6a9bc03a03e455434f83577892aa1a60ffe.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 10:09:44 +0200
Subject: [PATCH 1/7] bes2600: use request_firmware() for factory.txt read
The BES2600 factory calibration file (bes2600_factory.txt) was being read
via filp_open() + kernel_read() from a hard-coded absolute path baked in
at compile time via the FACTORY_PATH Makefile macro
(default: /lib/firmware/bes2600_factory.txt).
This had several problems:
1. Path mismatch - linux-firmware-style packaging (and danctnix 0.2-5
device-pine64-pinetab2) ships the file at
/lib/firmware/bes2600/bes2600_factory.txt, not /lib/firmware/. The
driver logged '(NULL device *): read and check
/lib/firmware/bes2600_factory.txt error' on every boot on PineTab2
running linux-pinetab2 6.19.10-danctnix1-1.
2. Direct filesystem access via filp_open() / kernel_read() from a driver
is an anti-pattern that upstream rejects: drivers should use
request_firmware() to get binary data from userspace-managed firmware
directories. request_firmware() natively searches the firmware_class
path list (typically /lib/firmware + derivatives), associates the load
with a uevent, and respects the firmware-loading infrastructure.
3. The (NULL device *) prefix in error messages indicated the absence of
proper device-context logging. While this patch does not yet thread
struct device through, the upstream path uses request_firmware() which
works with dev=NULL and is the building block for a follow-up patch
that adds per-chip device context.
Repoint the FACTORY_PATH default to the firmware-class name
(bes2600/bes2600_factory.txt) - request_firmware() prepends
/lib/firmware/ from the configured search paths. The macro remains
overridable at build time for non-standard deployments.
Rewrite factory_section_read_file() to:
* Call request_firmware(&fw, path, NULL).
* Size-check fw->size against FACTORY_MAX_SIZE.
* memcpy the data into the caller's buffer.
* Always call release_firmware() on exit.
The file write path (factory_section_write_file + kernel_write) is left
unchanged in this patch; it is the subject of a follow-up patch that
removes kernel_write and moves any remaining userspace-visible factory
configuration to a standard kernel-userspace boundary (debugfs or
nl80211 testmode).
No caller signature changes. No Makefile flag drops. Bisectable.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1, deployed via /lib/modules/<ver>/extra/. Verified
post-reboot: original 'read and check /lib/firmware/bes2600_factory.txt
error' is gone; request_firmware reads the file successfully (a separate
factory_parse() bug, previously masked by the read failure, is now
exposed and tracked separately).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/Makefile | 2 +-
drivers/staging/bes2600/bes2600_factory.c | 33 ++++++++++++++-------------------
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile
index 300912b..788aee2 100644
--- a/drivers/staging/bes2600/Makefile
+++ b/drivers/staging/bes2600/Makefile
@@ -66,7 +66,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
STANDARD_FACTORY_EFUSE_FLAG ?= y
-FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt
+FACTORY_PATH ?= drivers/staging/bes2600/bes2600_factory.txt
endif
# basic function
diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c
index dc5d3da..8d60b7c 100644
--- a/drivers/staging/bes2600/bes2600_factory.c
+++ b/drivers/staging/bes2600/bes2600_factory.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -137,38 +138,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data)
*/
static int factory_section_read_file(char *path, void *buffer)
{
- int ret = 0;
- struct file *fp;
+ const struct firmware *fw;
+ int ret;
if (!path || !buffer) {
bes_err("%s NULL pointer err\n", __func__);
return -1;
}
- bes_devel("reading %s \n", path);
+ bes_devel("requesting firmware-class %s\n", path);
- fp = filp_open(path, O_RDONLY, 0); //S_IRUSR
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",path);
+ ret = request_firmware(&fw, path, NULL);
+ if (ret) {
+ bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
}
- if (fp->f_inode->i_size <= 0 || fp->f_inode->i_size > FACTORY_MAX_SIZE) {
- bes_err( "bes2600_factory.txt size check failed, read_size: %lld max_size: %d\n",
- fp->f_inode->i_size, FACTORY_MAX_SIZE);
- filp_close(fp, NULL);
+ if (fw->size == 0 || fw->size > FACTORY_MAX_SIZE) {
+ bes_err("bes2600_factory.txt size check failed, read_size: %zu max_size: %d\n",
+ fw->size, FACTORY_MAX_SIZE);
+ release_firmware(fw);
return -1;
}
- ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos);
-
- filp_close(fp, NULL);
-
- if (ret != fp->f_inode->i_size) {
- bes_err("bes2600_factory.txt read fail\n");
- ret = -1;
- }
-
+ memcpy(buffer, fw->data, fw->size);
+ ret = (int)fw->size;
+ release_firmware(fw);
return ret;
}
--
2.53.0
@@ -0,0 +1,86 @@
From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001
Message-ID: <a826f4db7d97a3a872d92079db37dbdaf9a0cdec.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:17:56 +0200
Subject: [PATCH 2/7] bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for
PineTab2 factory.txt format
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The shipped factory calibration file bes2600_factory.txt on PineTab2
(danctnix linux-firmware 0.3.5_2023.0209) contains 30 calibration
fields: head (3), iq/xtal (3), 2.4G power 11n (5), 5G power 11n (15),
bt (4). The file terminates with '%%\n' directly after edr_power.
When STANDARD_FACTORY_EFUSE_FLAG is defined at compile time the driver
assembles STANDARD_FACTORY with an extra select_efuse_flag section
appended and expects 31 sscanf matches (FACTORY_MEMBER_NUM=31):
__STANDARD_FACTORY + \"##select_efuse_flag\\nselect_efuse:%hx\\n\"
+ \"%%%%\\n\"
The PineTab2 factory.txt has no select_efuse_flag section, so sscanf
stops after field 30 and factory_parse() returns -1 with:
bes2600_factory.txt parse fail
read and check drivers/staging/bes2600/bes2600_factory.txt error
factory cali data get failed.
This was latent until the preceding patch (use request_firmware() for
factory.txt read) fixed the path bug that masked the parse failure.
Default STANDARD_FACTORY_EFUSE_FLAG to n. The flag remains overridable
at build time (make STANDARD_FACTORY_EFUSE_FLAG=y ...) for chips /
firmware packages that do ship the select_efuse_flag section.
Also: the wsm_save_factory_txt_to_mcu() prototype in wsm.h was
inconsistently wrapped in a conditional that keyed on
STANDARD_FACTORY_EFUSE_FLAG, but the function definition in wsm.c and
the call site in sta.c are ungated. With the flag now defaulting to
n, the gcc -Werror=missing-prototypes flag breaks the build. Drop the
conditional wrapper around the prototype -- the function exists and is
used regardless of the factory-parse flag.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. With the flag defaulted off, factory_parse()
succeeds on the shipped factory.txt, factory_cali_data is populated,
and dmesg no longer shows the parse-fail / read-and-check-error /
factory-cali-data-get-failed sequence.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/Makefile | 2 +-
drivers/staging/bes2600/wsm.h | 2 --
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile
index 788aee2..2dcba09 100644
--- a/drivers/staging/bes2600/Makefile
+++ b/drivers/staging/bes2600/Makefile
@@ -65,7 +65,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
-STANDARD_FACTORY_EFUSE_FLAG ?= y
+STANDARD_FACTORY_EFUSE_FLAG ?= n
FACTORY_PATH ?= drivers/staging/bes2600/bes2600_factory.txt
endif
diff --git a/drivers/staging/bes2600/wsm.h b/drivers/staging/bes2600/wsm.h
index 0673131..22845ac 100644
--- a/drivers/staging/bes2600/wsm.h
+++ b/drivers/staging/bes2600/wsm.h
@@ -2236,7 +2236,5 @@ int wsm_cpu_usage_cmd(struct bes2600_common *hw_priv);
int wsm_wifi_status_cmd(struct bes2600_common *hw_priv, uint32_t status);
-#if defined(STANDARD_FACTORY_EFUSE_FLAG)
int wsm_save_factory_txt_to_mcu(struct bes2600_common *hw_priv, const u8 *data, int if_id, enum bes2600_rf_cmd_type cmd_type);
-#endif
#endif /* BES2600_HWIO_H_INCLUDED */
--
2.53.0
@@ -0,0 +1,119 @@
From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001
Message-ID: <c7ba2044b78cc3778763737daea60c9912b710c6.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:18:38 +0200
Subject: [PATCH 3/7] bes2600: thread struct device * through factory
request_firmware() call
Follow-up to \"bes2600: use request_firmware() for factory.txt read\".
That patch switched the factory calibration read path from filp_open()
+ kernel_read() to request_firmware(), but passed dev=NULL to
request_firmware() because factory_section_read_file() did not have a
struct device * in scope. The resulting logs carry the
'(NULL device *):' prefix and do not propagate a udev association.
Add a module-local static struct device * used as the firmware-class
load context, plus a small exported setter:
static struct device *bes2600_factory_dev;
void bes2600_factory_set_dev(struct device *dev);
Wire bes2600_factory_set_dev(&func->dev) from bes2600_sdio_probe(),
right after bes2600_platform_data_init() so the platform layer has
already had a chance to use the same struct device for its own
initialization.
factory_section_read_file() now passes bes2600_factory_dev (instead
of NULL) to request_firmware(). When the factory read happens before
probe (not currently the case on PineTab2) the pointer is still NULL
and request_firmware() accepts that; no regression.
No API changes to bes2600_get_factory_cali_data() callers. The
char *path parameter remains (it is the firmware-class name fed
straight to request_firmware()).
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver probes, factory data is read, and any
post-c5 factory diagnostics now carry the SDIO device identity
instead of '(NULL device *)'.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes2600_factory.c | 14 +++++++++++++-
drivers/staging/bes2600/bes2600_factory.h | 3 +++
drivers/staging/bes2600/bes2600_sdio.c | 4 ++++
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/staging/bes2600/bes2600_factory.c b/drivers/staging/bes2600/bes2600_factory.c
index 8d60b7c..1cda447 100644
--- a/drivers/staging/bes2600/bes2600_factory.c
+++ b/drivers/staging/bes2600/bes2600_factory.c
@@ -31,6 +31,18 @@
static DEFINE_MUTEX(factory_lock);
+/*
+ * struct device * for request_firmware() context. Set once at SDIO
+ * probe via bes2600_factory_set_dev(). NULL is tolerated (falls back
+ * to the udev-less firmware-class path) but loses per-device logging.
+ */
+static struct device *bes2600_factory_dev;
+
+void bes2600_factory_set_dev(struct device *dev)
+{
+ bes2600_factory_dev = dev;
+}
+
/*
* It is only used for temporary storage.
* Every time get the factory, it will read from the
@@ -148,7 +160,7 @@ static int factory_section_read_file(char *path, void *buffer)
bes_devel("requesting firmware-class %s\n", path);
- ret = request_firmware(&fw, path, NULL);
+ ret = request_firmware(&fw, path, bes2600_factory_dev);
if (ret) {
bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
diff --git a/drivers/staging/bes2600/bes2600_factory.h b/drivers/staging/bes2600/bes2600_factory.h
index 3835b0d..7dbe9f8 100644
--- a/drivers/staging/bes2600/bes2600_factory.h
+++ b/drivers/staging/bes2600/bes2600_factory.h
@@ -199,6 +199,9 @@ enum factory_cali_status {
/* just calibrate 11n, other protocols are automatically mapped */
#define WIFI_RF_11N_MODE 0x15
+/* set the struct device * used for request_firmware() context */
+void bes2600_factory_set_dev(struct device *dev);
+
/* read wifi & bt factory cali value*/
u8* bes2600_get_factory_cali_data(u8 *file_buffer, u32 *data_len, char *path);
void factory_little_endian_cvrt(u8 *data);
diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c
index b595365..371ef4f 100644
--- a/drivers/staging/bes2600/bes2600_sdio.c
+++ b/drivers/staging/bes2600/bes2600_sdio.c
@@ -30,6 +30,7 @@
#include "bes2600.h"
#include "sbus.h"
#include "bes2600_plat.h"
+#include "bes2600_factory.h"
#include "hwio.h"
#include "bes_chardev.h"
#include "bes_log.h"
@@ -1834,6 +1835,9 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (ret)
goto err;
+ /* wire struct device into factory.c for request_firmware() context */
+ bes2600_factory_set_dev(dev);
+
self->pdata = bes2600_get_platform_data();
self->func = func;
self->dev = &func->dev;
--
2.53.0
@@ -0,0 +1,108 @@
From 108d3967eac4ba3a6e0f508d865a5f221b49079d Mon Sep 17 00:00:00 2001
Message-ID: <108d3967eac4ba3a6e0f508d865a5f221b49079d.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:37:45 +0200
Subject: [PATCH 4/7] bes2600: gate device LP-mode entry on successful per-VIF
firmware handshake
bes2600_pwr_enter_lp_mode() drives the transition to low-power for each
associated STA VIF: it pushes wsm_set_pm(), waits up to 5 seconds on
pm_enter_cmpl for the firmware to acknowledge, then unconditionally
calls bes2600_pwr_device_enter_lp_mode() to drop the device end of the
bus.
Two bugs:
1. A failed wsm_set_pm() only logs an error, then still falls into
wait_for_completion_timeout() on a completion the firmware will
never post (the set-mode command never reached it). The loop
therefore always blocks the full 5 s, logs a second error, and
proceeds.
2. A genuine wait-timeout (firmware received the set-mode command but
never posted the indication) also only logs a warning. The code
then drops to bes2600_pwr_device_enter_lp_mode(), handing the
device subsystem an inconsistent view of mac-layer state.
On PineTab2 (BES2600WM + RK3566) the second bug is the recurring
root-cause of the 'bes2600_pwr_enter_lp_mode, wait pm ind timeout'
message flooding dmesg every 5-10 s when the interface is associated
and idle. Sending the device to LP in that state cascades into the
SDIO TX path as the 'bes_sdio_memcpy_to_io_helper / sdio_tx_work'
WARN splat.
Fix:
- Add a 'timeouts' counter; bump it on both failure paths.
- Skip the wait_for_completion entirely when wsm_set_pm() failed
(there is no completion to wait for).
- Only call bes2600_pwr_device_enter_lp_mode() when every per-VIF
handshake reached firmware-ACKed completion; otherwise return
-ETIMEDOUT and leave the device in its current power state.
Tested-on: PineTab2 running linux-pinetab2 6.19.10-danctnix1-1.
Post-patch the handshake still fails on this particular firmware
revision (separate root-cause investigation outside this patch), but
the driver now returns -ETIMEDOUT cleanly instead of flooding dmesg
and destabilising the SDIO path.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes_pwr.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/staging/bes2600/bes_pwr.c b/drivers/staging/bes2600/bes_pwr.c
index e7a1045..f62ae22 100644
--- a/drivers/staging/bes2600/bes_pwr.c
+++ b/drivers/staging/bes2600/bes_pwr.c
@@ -472,6 +472,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
int i = 0;
struct bes2600_vif *priv;
int ret = 0;
+ int timeouts = 0;
char ip_str[20];
unsigned long status = 0;
@@ -528,22 +529,35 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
bes_err("%s, set operation mode fail\n", __func__);
+ timeouts++;
+ continue;
}
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
- if (!status)
+ if (!status) {
bes_err("%s, wait pm ind timeout\n", __func__);
+ timeouts++;
+ }
} else {
bes_devel("skip enter lp mode\n");
}
}
}
- /* set device low power configuration */
- bes2600_pwr_device_enter_lp_mode(hw_priv);
+ /*
+ * Enter the device-end of the LP transition only if every per-VIF
+ * mac80211 handshake reached firmware-ACKed completion. Doing the
+ * device-LP setup while any VIF is still pending leaves the driver
+ * in an inconsistent state that cascades into SDIO TX errors on
+ * the BES2600.
+ */
+ if (timeouts == 0)
+ bes2600_pwr_device_enter_lp_mode(hw_priv);
+ else
+ ret = -ETIMEDOUT;
return ret;
}
--
2.53.0
@@ -0,0 +1,678 @@
From 3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f Mon Sep 17 00:00:00 2001
Message-ID: <3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:55:18 +0200
Subject: [PATCH 5/7] bes2600: remove userspace /dev/bes2600 character device
interface
bes_chardev.c implemented a custom character device at /dev/bes2600 with
its own parser and command-dispatch table, exposing operations such as
'wifi on|off', 'bt on|off', 'change_fw_type <n>', 'bt_wakeup',
'bt_sleep', and 'wakeup_read_flag'. None of these surfaces are used by
the in-tree driver - every kernel call site consumes the internal state
accessors (bes2600_chrdev_is_signal_mode, bes2600_chrdev_get_fw_type,
etc) directly, not through the cdev.
The cdev interface is a standing upstream blocker for two reasons:
1. Drivers under drivers/staging/ and drivers/net/wireless/ are
expected to expose tuning via the firmware/nl80211/debugfs
infrastructure rather than a private /dev node with an ad-hoc
parser.
2. The cdev handlers keep a global bes_cdev singleton alive whose
->cdev, ->dev_id, ->class and ->device pointers exist only to be
torn down; they add no functionality that nl80211 or rfkill do
not already provide (wifi/bt on-off, module_param for fw_type).
Remove the userspace interface:
- open / read / write / release file_operations handlers and the
bes2600_chardev_fops instance
- bes2600_op_* command handlers and bes2600_op_map_tab dispatcher
- bes2600_get_cmd_and_ifname / bes2600_recyle_cmd_and_ifname_mem
string helpers
- bes2600_load_uevent (its only caller was
bes2600_chrdev_wifi_force_close_work informing userspace of a
state it already gates via rfkill; that snprintf +
kobject_uevent_env block is gone too, the kernel-side
halt_device + switch_wifi(0) + chrdev_check_system_close
sequence remains)
- alloc_chrdev_region / cdev_init / cdev_add / class_create /
device_create in bes2600_chrdev_init plus the fail1/fail2/fail3
unwind labels
- cdev_del / unregister_chrdev_region / device_destroy /
class_destroy in bes2600_chrdev_free
- cdev/dev_id/major/minor/class/device fields in struct bes_cdev
What remains (unchanged behaviour):
- fw_type module parameter - the primary user-facing knob for
signal/no-signal/BT mode switch
- All in-kernel bes2600_chrdev_* accessor functions called from
bes2600_sdio.c, bes_pwr.c, sta.c, bh.c, main.c, wsm.c, and
wifi_testmode_cmd.c (13 call sites)
- bes2600_chrdev_init / bes2600_chrdev_free as state-init / teardown
for the remaining bes_cdev state (waitqueues, workqueues, flags)
- DPD management (bes2600_chrdev_get_dpd_buffer / update / free)
- wifi_force_close worker, system-close logic, bus-probe state
machine
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver continues to associate and pass traffic;
no kernel messages related to the cdev absence. Users that previously
wrote to /dev/bes2600 should switch to the fw_type module parameter
or (future patch c4) nl80211 testmode commands.
Follow-ups:
- c3.1: thread struct device * through bes2600_chrdev_is_signal_mode
and friends so the global bes2600_cdev singleton can be dropped
and the accessors scale to multi-device scenarios.
- c4: enable CONFIG_BES2600_TESTMODE and route nl80211 testmode
commands to the firmware's patch_wifi_testMode entry.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes_chardev.c | 519 ------------------------------------------
1 file changed, 519 deletions(-)
diff --git a/drivers/staging/bes2600/bes_chardev.c b/drivers/staging/bes2600/bes_chardev.c
index 9038e48..e2e4f1b 100644
--- a/drivers/staging/bes2600/bes_chardev.c
+++ b/drivers/staging/bes2600/bes_chardev.c
@@ -43,12 +43,6 @@ enum bus_probe_state {
};
struct bes_cdev {
- struct cdev cdev;
- dev_t dev_id;
- int major;
- int minor;
- struct class *class;
- struct device *device;
atomic_t num_proc;
wait_queue_head_t open_wq;
spinlock_t status_lock;
@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on)
return ret;
}
-static int bes2600_get_cmd_and_ifname(const char *str, char **result)
-{
- int cmd_len = 0;
- int ifname_len = 0;
- char *sp = NULL;
- char *tmp_ptr = NULL;
- char *cmd_ptr = NULL;
-
- /* check if input arguments is valid */
- if (!str || strncmp(str, "ifname:", 7) != 0)
- return -1;
-
- sp = strchr(str, ' ');
- if (strncmp(sp + 1, "cmd:", 4) != 0)
- return -1;
-
- /* extract interface name */
- ifname_len = sp - str - 7;
- tmp_ptr = kmalloc(ifname_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- return -2;
- }
-
- strncpy(tmp_ptr, str+7, ifname_len);
- tmp_ptr[ifname_len] = '\0';
- result[0] = tmp_ptr;
-
- /* get command length */
- cmd_ptr = strstr(str, "cmd:");
- cmd_ptr += 4;
- sp = strchr(cmd_ptr, ' ');
- if (!sp) { /* the command don't have any parameter */
- cmd_len = strlen(cmd_ptr);
- if (cmd_ptr[cmd_len - 1] == '\n')
- --cmd_len;
- } else { /* the command have one or more parameter */
- cmd_len = sp - cmd_ptr;
- }
-
- /* copy command to out buffer */
- tmp_ptr = kmalloc( cmd_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- kfree(result[0]);
- result[0] = NULL;
- return -3;
- }
-
- strncpy(tmp_ptr, cmd_ptr, cmd_len);
- tmp_ptr[cmd_len] = '\0';
- result[1] = tmp_ptr;
-
- return 0;
-}
-
-static void bes2600_recyle_cmd_and_ifname_mem(char **info)
-{
- if (info[0]) {
- kfree(info[0]);
- info[0] = NULL;
- }
-
- if (info[1]) {
- kfree(info[1]);
- info[1] = NULL;
- }
-
-}
-
-static int bes2600_op_default_handler(const char *str)
-{
- char *info[2] = {0};
-
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- bes_devel("cmd(%s) on %s not handled\n", info[1], info[0]);
- } else {
- bes_err("%s get command fail, the origin string is %s\n", __func__, str);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return 0;
-}
-
-static int bes2600_op_wifi_bt_on_off(const char *str)
-{
- char *info[2] = {0};
- int ret = 0;
- enum wait_state wait_state;
- enum bus_probe_state probe_state;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- probe_state = bes2600_cdev.bus_probe;
- wait_state = bes2600_cdev.wait_state;
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* only work for wifi signal mode */
- if (bes2600_cdev.fw_type != BES2600_FW_TYPE_WIFI_SIGNAL)
- return -EFAULT;
-
- /* wait bus probe operation end */
- if (probe_state == BES2600_BUS_PROBE_START) {
- bes_devel("wait bus probe operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.bus_probe > BES2600_BUS_PROBE_START),
- HZ);
- WARN_ON(status <= 0);
- }
-
- /* must wait previous operation end in critical section */
- if (wait_state != BES2600_BOOT_WAIT_NONE) {
- bes_devel("wait previous operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.wait_state == BES2600_BOOT_WAIT_NONE),
- HZ * 8);
- WARN_ON(status <= 0);
- }
-
- /* if dpd calibration is doing, modify wifi and bt state directly */
- spin_lock(&bes2600_cdev.status_lock);
- if (bes2600_cdev.bus_probe == BES2600_BUS_PROBE_OK && !bes2600_cdev.dpd_calied) {
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- bes2600_cdev.wifi_opened = true;
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- bes2600_cdev.wifi_opened = false;
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- bes2600_cdev.bt_opened = true;
- bes2600_cdev.bton_pending = true;
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- bes2600_cdev.bt_opened = false;
- bes2600_cdev.bton_pending = false;
- }
- }
- bes2600_recyle_cmd_and_ifname_mem(info);
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- WARN_ON(status <= 0);
-
- return (status <= 0 || bes2600_chrdev_is_bus_error()) ? -EFAULT : 0;
- }
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* process wifi/bt on/off operation */
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- ret = bes2600_switch_wifi(1);
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- ret = bes2600_switch_wifi(0);
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- ret = bes2600_switch_bt(1);
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- ret = bes2600_switch_bt(0);
- }
- }
-
- if (!ret && bes2600_chrdev_check_system_close())
- ret = bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
- bes2600_cdev.sbus_priv);
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return ret ;
-}
-
-
-static int bes2600_op_change_fw_type(const char *str)
-{
- int ret = 0;
- int temp = 0;
- long status = 0;
- char *cmd_ptr = NULL;
- char fw_type[5] = {0};
- bool sys_closed = bes2600_chrdev_check_system_close();
-
- bes_devel("%s is called, arg:%s\n", __func__, str);
-
- if (!bes2600_cdev.sbus_ops->power_switch && !bes2600_cdev.sbus_ops->reboot)
- return -EPERM;
-
- /* check if user input is valid */
- cmd_ptr = strstr(str, "CHANGE_FW_TYPE ");
- if (strlen(str) < 16 || !cmd_ptr) {
- bes_err("the format of \"%s\" is error\n", str);
- return -EINVAL;
- }
-
- /* convert fw_type from string to int */
- strncpy(fw_type, cmd_ptr + 14, 4);
- fw_type[0] = '+';
- ret = kstrtoint(fw_type, 10, &temp);
- if (ret < 0) {
- bes_err("%s parse error\n", __func__);
- return -EINVAL;
- }
-
- /* no need to realod firmware if new fw_type is equal to the old */
- if (temp == bes2600_cdev.fw_type ) {
- bes_devel("fw type is equal\n");
- return 0;
- }
-
- /* close wifi net device */
- if (bes2600_cdev.sbus_priv
- && bes2600_is_net_dev_created(bes2600_cdev.sbus_priv)) {
- bes2600_unregister_net_dev(bes2600_cdev.sbus_priv);
- }
-
- /* update firmware type */
- bes2600_cdev.fw_type = temp;
- bes2600_chrdev_update_signal_mode();
-
- if (!sys_closed) {
- /* close device to call disconnect function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(bes2600_cdev.sbus_priv, 0);
- else if (bes2600_cdev.sbus_ops->reboot)
- bes2600_cdev.sbus_ops->reboot(bes2600_cdev.sbus_priv);
- }
-
- if (bes2600_cdev.sbus_ops->reboot)
- bes2600_chrdev_start_bus_probe();
-
- /* wait disconnect event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq, (bes2600_cdev.sbus_priv == NULL), HZ * 10);
- WARN_ON(status <= 0);
-
- if (bes2600_cdev.dpd_calied
- && bes2600_chrdev_check_system_close()) {
- bes_devel("no need to reload firmware\n");
- return 0;
- }
-
- bes_devel("reload firmware...\n");
- /* power on device to call probe function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(NULL, 1);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 10);
- WARN_ON(status <= 0);
-
- ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0;
- return ret;
-}
-
-static int bes2600_op_bt_wakeup(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
-
- bes_devel("bes2600 wakeup bt.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_ON, SUBSYSTEM_BT_LP, true);
-
- return ret;
-}
-
-static int bes2600_op_bt_sleep(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
- bes_devel("bes2600 allow bt sleep.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_OFF, SUBSYSTEM_BT_LP, false);
- return ret;
-}
-static int bes2600_op_set_wakeup_read_flag(const char *str)
-{
- bes_devel("%s is called, arg:%s\n", __func__, str);
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_WAKEUP_STATE;
- spin_unlock(&bes2600_cdev.status_lock);
- return 0;
-}
#ifdef FW_DOWNLOAD_UART_DAEMON
-int bes2600_load_uevent(char *env[])
-{
- return kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
-}
#endif
-static struct bes2600_op_map bes2600_op_map_tab[] ={
- /*op op_len handler */
- {"P2P_SET_NOA", 11, bes2600_op_default_handler},
- {"P2P_SET_PS", 10, bes2600_op_default_handler},
- {"SET_AP_WPS_P2P_IE", 17, bes2600_op_default_handler},
- {"LINKSPEED", 9, bes2600_op_default_handler},
- {"RSSI", 4, bes2600_op_default_handler},
- {"GETBAND", 7, bes2600_op_default_handler},
- {"WLS_BATCHING", 12, bes2600_op_default_handler},
- {"MACADDR", 7, bes2600_op_default_handler},
- {"RXFILTER-START", 14, bes2600_op_default_handler},
- {"RXFILTER-STOP", 13, bes2600_op_default_handler},
- {"RXFILTER-ADD", 12, bes2600_op_default_handler},
- {"RXFILTER-REMOVE", 15, bes2600_op_default_handler},
- {"BTCOEXMODE", 10, bes2600_op_default_handler},
- {"BTCOEXSCAN-START", 16, bes2600_op_default_handler},
- {"BTCOEXSCAN-STOP", 15, bes2600_op_default_handler},
- {"SETSUSPENDMODE", 14, bes2600_op_default_handler},
- {"COUNTRY", 7, bes2600_op_default_handler},
- {"WIFI_ON", 7, bes2600_op_wifi_bt_on_off},
- {"WIFI_OFF", 8, bes2600_op_wifi_bt_on_off},
- {"BT_ON", 5, bes2600_op_wifi_bt_on_off},
- {"BT_OFF", 6, bes2600_op_wifi_bt_on_off},
- {"CHANGE_FW_TYPE", 14, bes2600_op_change_fw_type},
- {"BT_WAKEUP", 9, bes2600_op_bt_wakeup},
- {"BT_SLEEP", 8, bes2600_op_bt_sleep},
- {"WAKEUP_STATE", 12, bes2600_op_set_wakeup_read_flag},
-};
static int bes2600_chrdev_check_system_close_internal(void)
{
@@ -603,123 +264,10 @@ static int bes2600_chrdev_check_system_close_internal(void)
&& (bes2600_cdev.wifi_opened == false);
}
-static int bes2600_chrdev_open(struct inode *inode, struct file *filp)
-{
- if (atomic_read(&bes2600_cdev.num_proc) > 0) {
- wait_event_timeout(bes2600_cdev.open_wq,
- (atomic_read(&bes2600_cdev.num_proc) == 0),
- MAX_SCHEDULE_TIMEOUT);
- }
- bes_devel("bes2600 char device is opened\n");
- atomic_inc(&bes2600_cdev.num_proc);
- return 0;
-}
-static ssize_t bes2600_chrdev_read(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
-{
- char buf[64] = {0};
- unsigned int len;
- long status = 0;
- switch (bes2600_cdev.read_flag) {
- case BES_CDEV_READ_WAKEUP_STATE:
- if (bes2600_chrdev_wakeup_by_event_get() > WAKEUP_EVENT_NONE) {
- status = wait_event_timeout(bes2600_cdev.wakeup_reason_wq,
- bes2600_chrdev_wakeup_by_event_get() == WAKEUP_EVENT_NONE, HZ * 2);
- WARN_ON(status <= 0);
- }
- len = sprintf(buf, "wakeup_reason: %u, src_port: %u\n",
- bes2600_cdev.wakeup_state, bes2600_cdev.src_port);
- break;
- default:
- len = sprintf(buf, "dpd_calied:%d wifi_opened:%d bt_opened:%d fw_type:%d\n",
- bes2600_cdev.dpd_calied,
- bes2600_cdev.wifi_opened,
- bes2600_cdev.bt_opened,
- bes2600_cdev.fw_type);
- break;
- }
-
- len = sizeof(buf);
- /* reset read flag */
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_NUM_MAX;
- spin_unlock(&bes2600_cdev.status_lock);
-
- return simple_read_from_buffer(user_buf, count, ppos, buf, len);
-}
-
-static ssize_t bes2600_chrdev_write(struct file *file,
- const char __user *user_buf, size_t count, loff_t *ppos)
-{
- int i = 0;
- int cmd_num = ARRAY_SIZE(bes2600_op_map_tab);
- int cmd_len = 0;
- int ret = 0;
- char *info[2] = {0};
- char *buf = NULL;
-
- /* copy content from user space to kernel */
- /* message format:"ifname:wlanx cmd:xxx arg1 arg2 ..." */
- buf = kmalloc(count + 1, GFP_KERNEL);
- if (copy_from_user(buf, user_buf, count))
- return -EFAULT;
-
- /* add terminal character */
- buf[count] = '\0';
-
- /* extract comand and interface */
- if (bes2600_get_cmd_and_ifname(buf, info) != 0) {
- bes_err("%s get command fail, the origin string is %s\n", __func__, buf);
- kfree(buf);
- return -EINVAL;
- }
-
- /* match operation item and execure its handler */
- cmd_len = strlen(info[1]);
- for (i = 0; i < cmd_num; i++) {
- if (cmd_len < bes2600_op_map_tab[i].op_len)
- continue;
-
- if (strncasecmp(info[1], bes2600_op_map_tab[i].op, bes2600_op_map_tab[i].op_len) == 0) {
- ret = bes2600_op_map_tab[i].handler(buf);
- break;
- }
- }
-
- /* operation item mismatch */
- if (i == cmd_num) {
- bes_err("cmd(%s) mismatch\n", info[1]);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
- kfree(buf);
-
- return (ret == 0) ? count : ret;
-}
-
-static int bes2600_chrdev_release (struct inode *inode, struct file *file)
-{
- if (atomic_dec_and_test(&bes2600_cdev.num_proc)) {
- wake_up(&bes2600_cdev.open_wq);
- }
-
- bes_devel("bes2600 char device is closed\n");
-
- return 0;
-}
-
-static struct file_operations bes2600_chardev_fops =
-{
- .owner = THIS_MODULE,
- .open = bes2600_chrdev_open,
- .read = bes2600_chrdev_read,
- .write = bes2600_chrdev_write,
- .release = bes2600_chrdev_release,
-};
#ifdef BES2600_WRITE_DPD_TO_FILE
static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size)
@@ -1124,12 +672,6 @@ void bes2600_chrdev_update_signal_mode(void)
static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
{
- char wifi_state[15];
- char bt_state[15];
- char fw_type[15];
- char *env[] = { wifi_state, bt_state, fw_type, NULL };
- int ret;
-
if (bes2600_chrdev_is_wifi_opened()) {
bes_devel("system exeception, force wifi down\n");
@@ -1146,14 +688,6 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
-
- /* notify userspace */
- snprintf(wifi_state, sizeof(wifi_state), "WIFI_OPENED=%d", bes2600_cdev.wifi_opened);
- snprintf(bt_state, sizeof(bt_state), "BT_OPENED=%d", bes2600_cdev.bt_opened);
- snprintf(fw_type, sizeof(fw_type), "FW_TYPE=%d", bes2600_cdev.fw_type);
- ret = kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
- if (!ret)
- bes_err("bes2600 notify userspace failed\n");
}
}
@@ -1247,46 +781,6 @@ int bes2600_chrdev_wakeup_by_event_get(void)
int bes2600_chrdev_init(struct sbus_ops *ops)
{
- int ret = 0;
-
- /* allocate devide id */
- ret = alloc_chrdev_region(&bes2600_cdev.dev_id, 0, 1, "bes2600_chrdev");
- if (ret < 0){
- bes_err("bes2600 alloc device id fail\n");
- ret = -EFAULT;
- goto fail;
- }
-
- /* extract major and minor device id */
- bes2600_cdev.major = MAJOR(bes2600_cdev.dev_id);
- bes2600_cdev.minor = MINOR(bes2600_cdev.dev_id);
-
- /* add char device and bind operation function */
- bes2600_cdev.cdev.owner = THIS_MODULE;
- cdev_init(&bes2600_cdev.cdev, &bes2600_chardev_fops);
- ret = cdev_add(&bes2600_cdev.cdev, bes2600_cdev.dev_id, 1);
- if (ret < 0){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail1;
- }
-
- /* create class for creating device node */
- bes2600_cdev.class = class_create("bes2600_chrdev");
- if (IS_ERR(bes2600_cdev.class)){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail2;
- }
-
- /* get char device pointer */
- bes2600_cdev.device = device_create(bes2600_cdev.class, NULL, bes2600_cdev.dev_id, NULL, "bes2600");
- if (IS_ERR(bes2600_cdev.device)){
- bes_err("bes2600 char device create fail\n");
- ret = -EFAULT;
- goto fail3;
- }
-
/* initialise global variable */
atomic_set(&bes2600_cdev.num_proc, 0);
init_waitqueue_head(&bes2600_cdev.open_wq);
@@ -1318,15 +812,6 @@ int bes2600_chrdev_init(struct sbus_ops *ops)
bes_devel("%s done\n", __func__);
return 0;
-
-fail3:
- class_destroy(bes2600_cdev.class);
-fail2:
- cdev_del(&bes2600_cdev.cdev);
-fail1:
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
-fail:
- return ret;
}
void bes2600_chrdev_free(void)
@@ -1336,9 +821,5 @@ void bes2600_chrdev_free(void)
bes2600_free_dpd_log_buffer();
#endif
bes2600_chrdev_free_dpd_data();
- cdev_del(&bes2600_cdev.cdev);
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
- device_destroy(bes2600_cdev.class, bes2600_cdev.dev_id);
- class_destroy(bes2600_cdev.class);
bes_devel("%s done\n", __func__);
}
--
2.53.0
@@ -0,0 +1,146 @@
From 6f13e008d21d453db486f707f47340a0a17e650b Mon Sep 17 00:00:00 2001
Message-ID: <6f13e008d21d453db486f707f47340a0a17e650b.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:04:27 +0200
Subject: [PATCH 6/7] bes2600: enable CONFIG_BES2600_TESTMODE by default + fix
bit-rotted testmode plumbing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The driver implements a mac80211 testmode_cmd operation that dispatches
to a set of vendor commands (GET_TX_POWER_LEVEL, GET_TX_POWER_RANGE,
SET_SNAP_FRAME, TSM_STATS, GET_ROAM_DELAY, GET_STREAM, etc) plus the
BES2600 RF-test path (bes2600_vendor_rf_cmd -> firmware
patch_wifi_testMode). The testmode handlers and the .testmode_cmd
binding in struct ieee80211_ops are conditionally compiled under
CONFIG_BES2600_TESTMODE, which previously defaulted to n.
Flip the Makefile default from n to y so wifi_testmode_cmd.o is
included in the build and the .testmode_cmd op is populated. On the
PineTab2 target kernel (linux-pinetab2 6.19.10-danctnix1, built with
CONFIG_NL80211_TESTMODE=y) this exposes the BES2600 RF-test surface
through the standard nl80211 testmode interface ('iw phy0 ...').
This also makes visible two classes of bit-rot that had accumulated
while nobody was building with CONFIG_BES2600_TESTMODE=y:
1. sta.c contains ~41 calls to bes2600_info() / bes2600_err() /
bes2600_warn() / bes2600_dbg() / bes2600_err_with_cond() - a
legacy log-macro family carrying a BES2600_DBG_* subsystem-id
first argument. Neither the macros nor any of the BES2600_DBG_*
constants are defined anywhere in the tree. The same call pattern
appears under #if defined(BES2600_DETECTION_LOGIC) in hwio.c and
under CONFIG_BES2600_ITP in itp.c, both normally disabled.
Add minimal shim macros to bes_log.h that rewire the calls onto
the existing bes_info() / bes_err() / bes_warn() / bes_devel()
family (ignoring the subsystem id). Define BES2600_DBG_SBUS,
BES2600_DBG_DOWNLOAD, BES2600_DBG_ITP and BES2600_DBG_TEST_MODE
as 0 constants for documentation / grep.
2. bes2600_start_stop_tsm(), bes2600_get_tsm_params(), and
bes2600_get_roam_delay() are declared in sta.c with external
linkage but have no prototype in any header. All callers live in
sta.c (inside bes2600_testmode_cmd). With CONFIG_BES2600_TESTMODE
off the compiler never sees them; with it on gcc
-Werror=missing-prototypes breaks the build.
Mark the three functions static. (Keeping them file-local also
matches their actual usage.)
Both changes are strictly scoped to make CONFIG_BES2600_TESTMODE=y
buildable; no behavioural change when the flag is off.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1 with CONFIG_NL80211_TESTMODE=y. Module builds
cleanly, nl80211 testmode interface reachable via 'iw phy0 ...' from
userspace.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/Makefile | 2 +-
drivers/staging/bes2600/bes_log.h | 23 +++++++++++++++++++++++
drivers/staging/bes2600/sta.c | 6 +++---
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/bes2600/Makefile b/drivers/staging/bes2600/Makefile
index 2dcba09..2c1a850 100644
--- a/drivers/staging/bes2600/Makefile
+++ b/drivers/staging/bes2600/Makefile
@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build
# feature option
BES2600 ?= m
-CONFIG_BES2600_TESTMODE ?= n
+CONFIG_BES2600_TESTMODE ?= y
CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n
diff --git a/drivers/staging/bes2600/bes_log.h b/drivers/staging/bes2600/bes_log.h
index 605cea8..65cf703 100644
--- a/drivers/staging/bes2600/bes_log.h
+++ b/drivers/staging/bes2600/bes_log.h
@@ -8,3 +8,26 @@ extern struct device *global_dev;
#define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__)
#define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__)
#define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__)
+
+/*
+ * Legacy debug-subsystem-tagged log macros. The per-subsystem filtering
+ * was never implemented in-tree; these shims let code paths gated by
+ * CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC
+ * build when their conditions are enabled. The first argument is
+ * currently unused; pick one of the BES2600_DBG_* constants below for
+ * documentation.
+ */
+#define BES2600_DBG_SBUS 0
+#define BES2600_DBG_DOWNLOAD 0
+#define BES2600_DBG_ITP 0
+#define BES2600_DBG_TEST_MODE 0
+
+#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__)
+#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__)
+#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__)
+#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__)
+#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \
+ do { \
+ if (_cond) \
+ bes_err(fmt, ##__VA_ARGS__); \
+ } while (0)
diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c
index aa69eb8..5f1a456 100644
--- a/drivers/staging/bes2600/sta.c
+++ b/drivers/staging/bes2600/sta.c
@@ -3633,7 +3633,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw,
*
* Returns: 0 on success or non zero value on failure
*/
-int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
+static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
{
struct bes_msg_start_stop_tsm *start_stop_tsm =
(struct bes_msg_start_stop_tsm *) data;
@@ -3663,7 +3663,7 @@ int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
*
* Returns: TSM parameters collected
*/
-int bes2600_get_tsm_params(struct ieee80211_hw *hw)
+static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
struct bes_tsm_stats tsm_stats;
@@ -3703,7 +3703,7 @@ int bes2600_get_tsm_params(struct ieee80211_hw *hw)
*
* Returns: Returns the last measured roam delay
*/
-int bes2600_get_roam_delay(struct ieee80211_hw *hw)
+static int bes2600_get_roam_delay(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000;
--
2.53.0
@@ -0,0 +1,126 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <10a05d21bfe4563f963e16d65228fd7a713c143d.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 11:58:31 +0200
Subject: [PATCH 7/7] bes2600: bounce SDIO TX buffers to avoid DMA OOB read
The SDIO TX path rounds the DMA transfer length up to the host's
current block size and hands that length to dma_map_sg() via
sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work().
tx_buffer->buf typically aliases into an skb linear head whose
allocated size matches tx_buffer->len, not the block-aligned
align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up
to one block past the end of the skb. On a PineTab2 with KFENCE
enabled this fires as:
BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic
Out-of-bounds read at ... (704B right of kfence-#...):
__pi_memcpy_generic
swiotlb_tbl_map_single
swiotlb_map
dma_direct_map_sg
__dma_map_sg_attrs
dma_map_sg_attrs
dw_mci_pre_dma_transfer
__dw_mci_start_request
...
bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600]
sdio_tx_work+0x2b4/0x4a0 [bes2600]
allocated by ... pskb_expand_head / validate_xmit_skb / tcp_*
In addition to being undefined behavior, the padding bytes (which
come from whatever memory follows the skb) are transmitted to the
peer, leaking kernel memory on the air.
Allocate a driver-owned DMA-page bounce buffer sized to
MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for
sdio_tx_work. Each TX buffer is copied into its bounce slot and the
tail (align - tx_buffer->len bytes) is zeroed. This mirrors the
existing bounce pattern already used by bes2600_sdio_memcpy_toio()
via single_gathered_buffer; a separate allocation is used for the
TX path because single_gathered_buffer is only serialised via
sdio_claim_host and sdio_tx_work accumulates scatter entries before
claiming the bus.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/drivers/staging/bes2600/bes2600_sdio.c
index 371ef4f..3e04e8c 100644
--- a/drivers/staging/bes2600/bes2600_sdio.c
+++ b/drivers/staging/bes2600/bes2600_sdio.c
@@ -95,6 +95,7 @@ struct sbus_priv {
struct work_struct tx_work;
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
+ u8 *tx_bounce;
u32 tx_data_cnt;
u32 tx_xfer_cnt;
u32 tx_proc_cnt;
@@ -1136,7 +1137,26 @@ static void sdio_tx_work(struct work_struct *work)
}
}
- sg_set_buf(&sg[scatters], tx_buffer->buf, align);
+ /*
+ * The transfer length is rounded up to the SDIO block
+ * size, but tx_buffer->buf is only tx_buffer->len bytes
+ * long (it usually aliases into an skb linear head).
+ * Copy into a driver-owned bounce buffer and zero-pad
+ * to the aligned size; otherwise DMA reads past the
+ * skb and leaks adjacent kernel memory on the wire --
+ * observed as KFENCE OOB reads from
+ * bes_sdio_memcpy_to_io_helper via dma_map_sg.
+ */
+ if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
+ goto flush_previous;
+ memcpy(self->tx_bounce + total_len,
+ tx_buffer->buf, tx_buffer->len);
+ if (align > tx_buffer->len)
+ memset(self->tx_bounce + total_len +
+ tx_buffer->len, 0,
+ align - tx_buffer->len);
+ sg_set_buf(&sg[scatters],
+ self->tx_bounce + total_len, align);
total_len += align;
++scatters;
/*del_node:*/
@@ -1857,6 +1877,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (!self->single_gathered_buffer)
return -ENOMEM;
#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ if (!self->tx_bounce) {
+#ifndef SDIO_HOST_ADMA_SUPPORT
+ free_pages((unsigned long)self->single_gathered_buffer,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+#endif
+ return -ENOMEM;
+ }
+#endif
#ifdef BES_SDIO_RXTX_TOGGLE
self->fw_started = false;
#endif
@@ -1985,6 +2016,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
if (self->single_gathered_buffer) {
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
}
+#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ if (self->tx_bounce) {
+ free_pages((unsigned long)self->tx_bounce,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ }
#endif
kfree(self);
}
--
2.53.0
@@ -0,0 +1,186 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 12:35:28 +0200
Subject: [PATCH 0/7] bes2600: staging-prep cleanup for PineTab2 (BES2600WM)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This series is a staging-prep cleanup for the out-of-tree Bestechnic
BES2600WM Wi-Fi/BT combo-chip driver as shipped by Mobian's bes2600-dkms
package (and in-tree at drivers/staging/bes2600/ in the danctnix
linux-pinetab2 fork). Target hardware is the Pine64 PineTab2 (RK3566
+ BES2600WM, SDIO vendor 0xBE57 / device 0x2002).
The driver descends from the ST-Ericsson CW1200 (drivers/net/wireless/
st/cw1200/) -- same author, Dmitry Tarnyagin, shared WSM host<->firmware
protocol, shared SDIO bus backend. Kconfig ancestry markers survive in
this tree today: CONFIG_BES2600_USE_STE_EXTENSIONS (STE = ST-Ericsson),
CONFIG_BES2600_WSM_DEBUG (WSM). ST-Ericsson wound down in 2013;
Bestechnic (founded 2015) appears to have inherited or licensed the
CW1200 IP. No linux-wireless RFC has ever linked the two chips.
The series fixes observable defects on a PineTab2 running linux-pinetab2
6.19.10-danctnix1-1 and removes two upstream blockers. Each patch is
independently testable and bisectable; the order below preserves
dependencies.
## What the series does
* 1/7 -- Replace filp_open() + kernel_read() in the factory-calibration
read path with request_firmware(). Repoint the FACTORY_PATH macro to
the firmware-class name (bes2600/bes2600_factory.txt, matching the
/lib/firmware/ layout). Kills a kernel-mainline anti-pattern and the
'(NULL device *): read and check /lib/firmware/bes2600_factory.txt
error' boot spam on PineTab2.
* 2/7 -- Default STANDARD_FACTORY_EFUSE_FLAG from y to n. The shipped
bes2600_factory.txt on PineTab2 contains 30 calibration fields; the
driver was expecting 31 (including a ##select_efuse_flag section
absent from this firmware). Also unguards the
wsm_save_factory_txt_to_mcu() prototype in wsm.h which was
inconsistently wrapped in '#if defined(STANDARD_FACTORY_EFUSE_FLAG)'
while its definition in wsm.c and its call site in sta.c were
ungated -- gcc -Werror=missing-prototypes broke the build with the
new default until this is fixed.
* 3/7 -- Thread struct device * through factory_section_read_file() via
a module-local setter invoked at SDIO probe. request_firmware() now
receives a real device pointer; '(NULL device *):' no longer prefixes
factory-related diagnostics.
* 4/7 -- Gate the device-end of the low-power transition on successful
per-VIF firmware handshake. Pre-patch bes2600_pwr_enter_lp_mode()
called bes2600_pwr_device_enter_lp_mode() unconditionally even when
wait_for_completion_timeout() returned 0 (firmware never posted the
PM-change indication). On PineTab2 this recurred every 5-10 s
whenever the interface was associated and idle, flooded dmesg, and
cascaded into sdio_tx_work WARN splats / [RX] Receive failure
messages. Post-patch: -ETIMEDOUT returned cleanly, dmesg silent,
SDIO stable.
* 5/7 -- Remove the custom /dev/bes2600 character-device interface.
file_operations, open/read/write/release, bes2600_op_*
command-dispatch table, bes2600_load_uevent, alloc_chrdev_region /
cdev_init / cdev_add / class_create / device_create in the init
path, and the matching teardown in bes2600_chrdev_free -- 519 lines
deleted. The in-kernel accessor functions (is_signal_mode,
get_fw_type, etc., 13 call sites) and the fw_type module parameter
are preserved; the userspace interface becomes rfkill + module_param
+ (with 6/7) nl80211 testmode.
* 6/7 -- Flip CONFIG_BES2600_TESTMODE default from n to y. The driver
already implements a mac80211 testmode_cmd dispatcher (routing to
the firmware's patch_wifi_testMode path), already gated on the flag;
CONFIG_NL80211_TESTMODE=y is common on target kernels. Enabling the
flag also exposes accumulated bit-rot -- ~41 calls to undefined
bes2600_info/err/warn/dbg/err_with_cond macros, and 3 TSM/roam-delay
helpers with external linkage but no prototype. Add shim macros to
bes_log.h rewiring the legacy calls onto the existing bes_info /
bes_err / bes_warn / bes_devel family, define BES2600_DBG_* subsystem
ids as 0 constants, and mark the 3 helpers static.
* 7/7 -- Bounce SDIO TX buffers to avoid DMA out-of-bounds reads.
sdio_tx_work() rounded the transfer length up to the SDIO block size
(align = blks * cur_blksize, or 1632) and handed that length to
dma_map_sg() via sg_set_buf(..., tx_buffer->buf, align); tx_buffer->buf
typically aliases into an skb linear head allocated to tx_buffer->len,
not the block-aligned length. The DMA engine therefore read up to one
block past the end of the skb -- KFENCE on PineTab2 fires as
'out-of-bounds read in __pi_memcpy_generic ... 704B right of
kfence-#...' with sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper in
the stack and pskb_expand_head / validate_xmit_skb / tcp_write_xmit in
the allocator stack. Besides being undefined behavior, the padding
bytes are transmitted to the peer, leaking adjacent kernel memory on
the air. Allocate a driver-owned DMA-pages bounce buffer of
MAX_SDIO_TRANSFER_LEN, memcpy each TX buffer into its slot, zero the
padding tail, and point the SG entries at the bounce. Mirrors the
pattern already used for single_gathered_buffer; kept as a separate
allocation because sdio_tx_work accumulates SG entries before claiming
the bus.
## Testing
Reference hardware: Pine64 PineTab2 (BES2600WM + Rockchip RK3566,
8a:2e:77:1f:ec:05, LAN AP newton @ 2.4 GHz ch11, tested also on
TelekomHotspot@ERGO @ 5 GHz ch36).
Host kernel: linux-pinetab2 6.19.10-danctnix1-1-pinetab2 with
CONFIG_NL80211_TESTMODE=y and CONFIG_KFENCE=y (for 7/7 verification).
Driver installed via /lib/modules/<ver>/extra/ and verified loaded via
modinfo srcversion.
Per-patch outcomes in post-reboot dmesg (full-stack applied, 11+ min
soak, 245k RX packets / 365 MB traffic):
- 1/7: 'read and check /lib/firmware/bes2600_factory.txt error' -- gone
- 2/7: 'bes2600_factory.txt parse fail' / 'factory cali data get
failed.' -- gone
- 3/7: '(NULL device *):' prefix on factory lines -- gone
- 4/7: 'bes2600_pwr_enter_lp_mode, wait pm ind timeout' (pre-patch 20-30
msgs / 5 min window) -- 0 per 5 min; '[RX] Receive failure: 4.' --
gone
- 5/7: /dev/bes2600 -- absent; driver continues to associate
- 6/7: 'iw phy0' lists 'testmode' under Supported commands; module
builds cleanly
- 7/7: 'BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic'
(pre-patch ~65 splats per 4 h of real traffic, always via
sdio_tx_work+0x2b4 / bes_sdio_memcpy_to_io_helper+0x18c) -- 0;
'sdio_tx_work+0x2b4' WARN splat residual from 4/7's cascade -- 0
(previously recurred ~1 per reboot even with 4/7 applied);
'PS Mode Error, Reason:1' benign handshake notice at T+40s --
also gone, apparently a downstream effect of no longer DMAing
uninitialised padding bytes into firmware
Full stack: wifi associates and passes traffic across 3+ reboots.
## Known limitations / out of scope
- factory_section_write_file() in bes2600_factory.c still uses
kernel_write() + filp_open(O_CREAT) to persist per-channel
calibration updates back to /lib/firmware/bes2600_factory.txt.
Converting the write-back path to debugfs or nl80211 testmode is a
follow-up.
- bes_chardev.c still carries DPD file-read/write paths gated by
BES2600_WRITE_DPD_TO_FILE (off by default, so dead code in the
default build). Same treatment needed.
- bes_fw.c:587 unconditionally creates
/lib/firmware/bes2002_fw_write.bin via filp_open() for debug
observation. Needs to go before drivers/staging/ accepts the driver.
- bes_cdev global singleton still holds sig_mode and fw_type. Moving
those to per-hw_priv state is blocked by fw_type being a module
parameter (inherently singleton). Migrating fw_type to a per-phy
debugfs knob or nl80211 testmode command is the next step; overlaps
with 6/7's testmode plumbing.
Markus Fritsche (7):
bes2600: use request_firmware() for factory.txt read
bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for PineTab2
factory.txt format
bes2600: thread struct device * through factory request_firmware()
call
bes2600: gate device LP-mode entry on successful per-VIF firmware
handshake
bes2600: remove userspace /dev/bes2600 character device interface
bes2600: enable CONFIG_BES2600_TESTMODE by default + fix bit-rotted
testmode plumbing
bes2600: bounce SDIO TX buffers to avoid DMA OOB read
bes2600/Makefile | 6 +-
bes2600/bes2600_factory.c | 45 ++--
bes2600/bes2600_factory.h | 3 +
bes2600/bes2600_sdio.c | 43 +++-
bes2600/bes_chardev.c | 519 --------------------------------------
bes2600/bes_log.h | 23 ++
bes2600/bes_pwr.c | 20 +-
bes2600/sta.c | 6 +-
bes2600/wsm.h | 2 -
9 files changed, 117 insertions(+), 550 deletions(-)
--
2.53.0
@@ -0,0 +1,147 @@
From d18aa6a9bc03a03e455434f83577892aa1a60ffe Mon Sep 17 00:00:00 2001
Message-ID: <d18aa6a9bc03a03e455434f83577892aa1a60ffe.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 10:09:44 +0200
Subject: [PATCH 1/7] bes2600: use request_firmware() for factory.txt read
The BES2600 factory calibration file (bes2600_factory.txt) was being read
via filp_open() + kernel_read() from a hard-coded absolute path baked in
at compile time via the FACTORY_PATH Makefile macro
(default: /lib/firmware/bes2600_factory.txt).
This had several problems:
1. Path mismatch - linux-firmware-style packaging (and danctnix 0.2-5
device-pine64-pinetab2) ships the file at
/lib/firmware/bes2600/bes2600_factory.txt, not /lib/firmware/. The
driver logged '(NULL device *): read and check
/lib/firmware/bes2600_factory.txt error' on every boot on PineTab2
running linux-pinetab2 6.19.10-danctnix1-1.
2. Direct filesystem access via filp_open() / kernel_read() from a driver
is an anti-pattern that upstream rejects: drivers should use
request_firmware() to get binary data from userspace-managed firmware
directories. request_firmware() natively searches the firmware_class
path list (typically /lib/firmware + derivatives), associates the load
with a uevent, and respects the firmware-loading infrastructure.
3. The (NULL device *) prefix in error messages indicated the absence of
proper device-context logging. While this patch does not yet thread
struct device through, the upstream path uses request_firmware() which
works with dev=NULL and is the building block for a follow-up patch
that adds per-chip device context.
Repoint the FACTORY_PATH default to the firmware-class name
(bes2600/bes2600_factory.txt) - request_firmware() prepends
/lib/firmware/ from the configured search paths. The macro remains
overridable at build time for non-standard deployments.
Rewrite factory_section_read_file() to:
* Call request_firmware(&fw, path, NULL).
* Size-check fw->size against FACTORY_MAX_SIZE.
* memcpy the data into the caller's buffer.
* Always call release_firmware() on exit.
The file write path (factory_section_write_file + kernel_write) is left
unchanged in this patch; it is the subject of a follow-up patch that
removes kernel_write and moves any remaining userspace-visible factory
configuration to a standard kernel-userspace boundary (debugfs or
nl80211 testmode).
No caller signature changes. No Makefile flag drops. Bisectable.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1, deployed via /lib/modules/<ver>/extra/. Verified
post-reboot: original 'read and check /lib/firmware/bes2600_factory.txt
error' is gone; request_firmware reads the file successfully (a separate
factory_parse() bug, previously masked by the read failure, is now
exposed and tracked separately).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/bes2600_factory.c | 33 ++++++++++++++-------------------
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 300912b..788aee2 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -66,7 +66,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
STANDARD_FACTORY_EFUSE_FLAG ?= y
-FACTORY_PATH ?= /lib/firmware/bes2600_factory.txt
+FACTORY_PATH ?= bes2600/bes2600_factory.txt
endif
# basic function
diff --git a/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c
index dc5d3da..8d60b7c 100644
--- a/bes2600/bes2600_factory.c
+++ b/bes2600/bes2600_factory.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
+#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/crc32.h>
@@ -137,38 +138,32 @@ static int bes2600_factory_crc_check(struct factory_t *factory_data)
*/
static int factory_section_read_file(char *path, void *buffer)
{
- int ret = 0;
- struct file *fp;
+ const struct firmware *fw;
+ int ret;
if (!path || !buffer) {
bes_err("%s NULL pointer err\n", __func__);
return -1;
}
- bes_devel("reading %s \n", path);
+ bes_devel("requesting firmware-class %s\n", path);
- fp = filp_open(path, O_RDONLY, 0); //S_IRUSR
- if (IS_ERR(fp)) {
- bes_devel("BES2600 : can't open %s\n",path);
+ ret = request_firmware(&fw, path, NULL);
+ if (ret) {
+ bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
}
- if (fp->f_inode->i_size <= 0 || fp->f_inode->i_size > FACTORY_MAX_SIZE) {
- bes_err( "bes2600_factory.txt size check failed, read_size: %lld max_size: %d\n",
- fp->f_inode->i_size, FACTORY_MAX_SIZE);
- filp_close(fp, NULL);
+ if (fw->size == 0 || fw->size > FACTORY_MAX_SIZE) {
+ bes_err("bes2600_factory.txt size check failed, read_size: %zu max_size: %d\n",
+ fw->size, FACTORY_MAX_SIZE);
+ release_firmware(fw);
return -1;
}
- ret = kernel_read(fp, buffer, fp->f_inode->i_size, &fp->f_pos);
-
- filp_close(fp, NULL);
-
- if (ret != fp->f_inode->i_size) {
- bes_err("bes2600_factory.txt read fail\n");
- ret = -1;
- }
-
+ memcpy(buffer, fw->data, fw->size);
+ ret = (int)fw->size;
+ release_firmware(fw);
return ret;
}
--
2.53.0
@@ -0,0 +1,86 @@
From a826f4db7d97a3a872d92079db37dbdaf9a0cdec Mon Sep 17 00:00:00 2001
Message-ID: <a826f4db7d97a3a872d92079db37dbdaf9a0cdec.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:17:56 +0200
Subject: [PATCH 2/7] bes2600: default STANDARD_FACTORY_EFUSE_FLAG off for
PineTab2 factory.txt format
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The shipped factory calibration file bes2600_factory.txt on PineTab2
(danctnix linux-firmware 0.3.5_2023.0209) contains 30 calibration
fields: head (3), iq/xtal (3), 2.4G power 11n (5), 5G power 11n (15),
bt (4). The file terminates with '%%\n' directly after edr_power.
When STANDARD_FACTORY_EFUSE_FLAG is defined at compile time the driver
assembles STANDARD_FACTORY with an extra select_efuse_flag section
appended and expects 31 sscanf matches (FACTORY_MEMBER_NUM=31):
__STANDARD_FACTORY + \"##select_efuse_flag\\nselect_efuse:%hx\\n\"
+ \"%%%%\\n\"
The PineTab2 factory.txt has no select_efuse_flag section, so sscanf
stops after field 30 and factory_parse() returns -1 with:
bes2600_factory.txt parse fail
read and check bes2600/bes2600_factory.txt error
factory cali data get failed.
This was latent until the preceding patch (use request_firmware() for
factory.txt read) fixed the path bug that masked the parse failure.
Default STANDARD_FACTORY_EFUSE_FLAG to n. The flag remains overridable
at build time (make STANDARD_FACTORY_EFUSE_FLAG=y ...) for chips /
firmware packages that do ship the select_efuse_flag section.
Also: the wsm_save_factory_txt_to_mcu() prototype in wsm.h was
inconsistently wrapped in a conditional that keyed on
STANDARD_FACTORY_EFUSE_FLAG, but the function definition in wsm.c and
the call site in sta.c are ungated. With the flag now defaulting to
n, the gcc -Werror=missing-prototypes flag breaks the build. Drop the
conditional wrapper around the prototype -- the function exists and is
used regardless of the factory-parse flag.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. With the flag defaulted off, factory_parse()
succeeds on the shipped factory.txt, factory_cali_data is populated,
and dmesg no longer shows the parse-fail / read-and-check-error /
factory-cali-data-get-failed sequence.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/wsm.h | 2 --
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 788aee2..2dcba09 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -65,7 +65,7 @@ BES2600_DRV_VERSION := bes2600_0.3.5_2024.0116
ifeq ($(CONFIG_BES2600_CALIB_FROM_LINUX),y)
FACTORY_CRC_CHECK ?= n
-STANDARD_FACTORY_EFUSE_FLAG ?= y
+STANDARD_FACTORY_EFUSE_FLAG ?= n
FACTORY_PATH ?= bes2600/bes2600_factory.txt
endif
diff --git a/bes2600/wsm.h b/bes2600/wsm.h
index 0673131..22845ac 100644
--- a/bes2600/wsm.h
+++ b/bes2600/wsm.h
@@ -2236,7 +2236,5 @@ int wsm_cpu_usage_cmd(struct bes2600_common *hw_priv);
int wsm_wifi_status_cmd(struct bes2600_common *hw_priv, uint32_t status);
-#if defined(STANDARD_FACTORY_EFUSE_FLAG)
int wsm_save_factory_txt_to_mcu(struct bes2600_common *hw_priv, const u8 *data, int if_id, enum bes2600_rf_cmd_type cmd_type);
-#endif
#endif /* BES2600_HWIO_H_INCLUDED */
--
2.53.0
@@ -0,0 +1,119 @@
From c7ba2044b78cc3778763737daea60c9912b710c6 Mon Sep 17 00:00:00 2001
Message-ID: <c7ba2044b78cc3778763737daea60c9912b710c6.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:18:38 +0200
Subject: [PATCH 3/7] bes2600: thread struct device * through factory
request_firmware() call
Follow-up to \"bes2600: use request_firmware() for factory.txt read\".
That patch switched the factory calibration read path from filp_open()
+ kernel_read() to request_firmware(), but passed dev=NULL to
request_firmware() because factory_section_read_file() did not have a
struct device * in scope. The resulting logs carry the
'(NULL device *):' prefix and do not propagate a udev association.
Add a module-local static struct device * used as the firmware-class
load context, plus a small exported setter:
static struct device *bes2600_factory_dev;
void bes2600_factory_set_dev(struct device *dev);
Wire bes2600_factory_set_dev(&func->dev) from bes2600_sdio_probe(),
right after bes2600_platform_data_init() so the platform layer has
already had a chance to use the same struct device for its own
initialization.
factory_section_read_file() now passes bes2600_factory_dev (instead
of NULL) to request_firmware(). When the factory read happens before
probe (not currently the case on PineTab2) the pointer is still NULL
and request_firmware() accepts that; no regression.
No API changes to bes2600_get_factory_cali_data() callers. The
char *path parameter remains (it is the firmware-class name fed
straight to request_firmware()).
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver probes, factory data is read, and any
post-c5 factory diagnostics now carry the SDIO device identity
instead of '(NULL device *)'.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_factory.c | 14 +++++++++++++-
bes2600/bes2600_factory.h | 3 +++
bes2600/bes2600_sdio.c | 4 ++++
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/bes2600/bes2600_factory.c b/bes2600/bes2600_factory.c
index 8d60b7c..1cda447 100644
--- a/bes2600/bes2600_factory.c
+++ b/bes2600/bes2600_factory.c
@@ -31,6 +31,18 @@
static DEFINE_MUTEX(factory_lock);
+/*
+ * struct device * for request_firmware() context. Set once at SDIO
+ * probe via bes2600_factory_set_dev(). NULL is tolerated (falls back
+ * to the udev-less firmware-class path) but loses per-device logging.
+ */
+static struct device *bes2600_factory_dev;
+
+void bes2600_factory_set_dev(struct device *dev)
+{
+ bes2600_factory_dev = dev;
+}
+
/*
* It is only used for temporary storage.
* Every time get the factory, it will read from the
@@ -148,7 +160,7 @@ static int factory_section_read_file(char *path, void *buffer)
bes_devel("requesting firmware-class %s\n", path);
- ret = request_firmware(&fw, path, NULL);
+ ret = request_firmware(&fw, path, bes2600_factory_dev);
if (ret) {
bes_devel("BES2600: request_firmware(%s) failed: %d\n", path, ret);
return -1;
diff --git a/bes2600/bes2600_factory.h b/bes2600/bes2600_factory.h
index 3835b0d..7dbe9f8 100644
--- a/bes2600/bes2600_factory.h
+++ b/bes2600/bes2600_factory.h
@@ -199,6 +199,9 @@ enum factory_cali_status {
/* just calibrate 11n, other protocols are automatically mapped */
#define WIFI_RF_11N_MODE 0x15
+/* set the struct device * used for request_firmware() context */
+void bes2600_factory_set_dev(struct device *dev);
+
/* read wifi & bt factory cali value*/
u8* bes2600_get_factory_cali_data(u8 *file_buffer, u32 *data_len, char *path);
void factory_little_endian_cvrt(u8 *data);
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index b595365..371ef4f 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -30,6 +30,7 @@
#include "bes2600.h"
#include "sbus.h"
#include "bes2600_plat.h"
+#include "bes2600_factory.h"
#include "hwio.h"
#include "bes_chardev.h"
#include "bes_log.h"
@@ -1834,6 +1835,9 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (ret)
goto err;
+ /* wire struct device into factory.c for request_firmware() context */
+ bes2600_factory_set_dev(dev);
+
self->pdata = bes2600_get_platform_data();
self->func = func;
self->dev = &func->dev;
--
2.53.0
@@ -0,0 +1,108 @@
From 108d3967eac4ba3a6e0f508d865a5f221b49079d Mon Sep 17 00:00:00 2001
Message-ID: <108d3967eac4ba3a6e0f508d865a5f221b49079d.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:37:45 +0200
Subject: [PATCH 4/7] bes2600: gate device LP-mode entry on successful per-VIF
firmware handshake
bes2600_pwr_enter_lp_mode() drives the transition to low-power for each
associated STA VIF: it pushes wsm_set_pm(), waits up to 5 seconds on
pm_enter_cmpl for the firmware to acknowledge, then unconditionally
calls bes2600_pwr_device_enter_lp_mode() to drop the device end of the
bus.
Two bugs:
1. A failed wsm_set_pm() only logs an error, then still falls into
wait_for_completion_timeout() on a completion the firmware will
never post (the set-mode command never reached it). The loop
therefore always blocks the full 5 s, logs a second error, and
proceeds.
2. A genuine wait-timeout (firmware received the set-mode command but
never posted the indication) also only logs a warning. The code
then drops to bes2600_pwr_device_enter_lp_mode(), handing the
device subsystem an inconsistent view of mac-layer state.
On PineTab2 (BES2600WM + RK3566) the second bug is the recurring
root-cause of the 'bes2600_pwr_enter_lp_mode, wait pm ind timeout'
message flooding dmesg every 5-10 s when the interface is associated
and idle. Sending the device to LP in that state cascades into the
SDIO TX path as the 'bes_sdio_memcpy_to_io_helper / sdio_tx_work'
WARN splat.
Fix:
- Add a 'timeouts' counter; bump it on both failure paths.
- Skip the wait_for_completion entirely when wsm_set_pm() failed
(there is no completion to wait for).
- Only call bes2600_pwr_device_enter_lp_mode() when every per-VIF
handshake reached firmware-ACKed completion; otherwise return
-ETIMEDOUT and leave the device in its current power state.
Tested-on: PineTab2 running linux-pinetab2 6.19.10-danctnix1-1.
Post-patch the handshake still fails on this particular firmware
revision (separate root-cause investigation outside this patch), but
the driver now returns -ETIMEDOUT cleanly instead of flooding dmesg
and destabilising the SDIO path.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_pwr.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/bes2600/bes_pwr.c b/bes2600/bes_pwr.c
index e7a1045..f62ae22 100644
--- a/bes2600/bes_pwr.c
+++ b/bes2600/bes_pwr.c
@@ -472,6 +472,7 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
int i = 0;
struct bes2600_vif *priv;
int ret = 0;
+ int timeouts = 0;
char ip_str[20];
unsigned long status = 0;
@@ -528,22 +529,35 @@ static int bes2600_pwr_enter_lp_mode(struct bes2600_common *hw_priv)
if (ret) {
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
bes_err("%s, set operation mode fail\n", __func__);
+ timeouts++;
+ continue;
}
/* wait power save mode changed indication */
status = wait_for_completion_timeout(&hw_priv->bes_power.pm_enter_cmpl, 5 * HZ);
atomic_set(&hw_priv->bes_power.pm_set_in_process, 0);
reinit_completion(&hw_priv->bes_power.pm_enter_cmpl);
- if (!status)
+ if (!status) {
bes_err("%s, wait pm ind timeout\n", __func__);
+ timeouts++;
+ }
} else {
bes_devel("skip enter lp mode\n");
}
}
}
- /* set device low power configuration */
- bes2600_pwr_device_enter_lp_mode(hw_priv);
+ /*
+ * Enter the device-end of the LP transition only if every per-VIF
+ * mac80211 handshake reached firmware-ACKed completion. Doing the
+ * device-LP setup while any VIF is still pending leaves the driver
+ * in an inconsistent state that cascades into SDIO TX errors on
+ * the BES2600.
+ */
+ if (timeouts == 0)
+ bes2600_pwr_device_enter_lp_mode(hw_priv);
+ else
+ ret = -ETIMEDOUT;
return ret;
}
--
2.53.0
@@ -0,0 +1,678 @@
From 3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f Mon Sep 17 00:00:00 2001
Message-ID: <3304b13a2b2e7388ebf076c2bcb7f02cd0b6800f.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 12:55:18 +0200
Subject: [PATCH 5/7] bes2600: remove userspace /dev/bes2600 character device
interface
bes_chardev.c implemented a custom character device at /dev/bes2600 with
its own parser and command-dispatch table, exposing operations such as
'wifi on|off', 'bt on|off', 'change_fw_type <n>', 'bt_wakeup',
'bt_sleep', and 'wakeup_read_flag'. None of these surfaces are used by
the in-tree driver - every kernel call site consumes the internal state
accessors (bes2600_chrdev_is_signal_mode, bes2600_chrdev_get_fw_type,
etc) directly, not through the cdev.
The cdev interface is a standing upstream blocker for two reasons:
1. Drivers under drivers/staging/ and drivers/net/wireless/ are
expected to expose tuning via the firmware/nl80211/debugfs
infrastructure rather than a private /dev node with an ad-hoc
parser.
2. The cdev handlers keep a global bes_cdev singleton alive whose
->cdev, ->dev_id, ->class and ->device pointers exist only to be
torn down; they add no functionality that nl80211 or rfkill do
not already provide (wifi/bt on-off, module_param for fw_type).
Remove the userspace interface:
- open / read / write / release file_operations handlers and the
bes2600_chardev_fops instance
- bes2600_op_* command handlers and bes2600_op_map_tab dispatcher
- bes2600_get_cmd_and_ifname / bes2600_recyle_cmd_and_ifname_mem
string helpers
- bes2600_load_uevent (its only caller was
bes2600_chrdev_wifi_force_close_work informing userspace of a
state it already gates via rfkill; that snprintf +
kobject_uevent_env block is gone too, the kernel-side
halt_device + switch_wifi(0) + chrdev_check_system_close
sequence remains)
- alloc_chrdev_region / cdev_init / cdev_add / class_create /
device_create in bes2600_chrdev_init plus the fail1/fail2/fail3
unwind labels
- cdev_del / unregister_chrdev_region / device_destroy /
class_destroy in bes2600_chrdev_free
- cdev/dev_id/major/minor/class/device fields in struct bes_cdev
What remains (unchanged behaviour):
- fw_type module parameter - the primary user-facing knob for
signal/no-signal/BT mode switch
- All in-kernel bes2600_chrdev_* accessor functions called from
bes2600_sdio.c, bes_pwr.c, sta.c, bh.c, main.c, wsm.c, and
wifi_testmode_cmd.c (13 call sites)
- bes2600_chrdev_init / bes2600_chrdev_free as state-init / teardown
for the remaining bes_cdev state (waitqueues, workqueues, flags)
- DPD management (bes2600_chrdev_get_dpd_buffer / update / free)
- wifi_force_close worker, system-close logic, bus-probe state
machine
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1. Driver continues to associate and pass traffic;
no kernel messages related to the cdev absence. Users that previously
wrote to /dev/bes2600 should switch to the fw_type module parameter
or (future patch c4) nl80211 testmode commands.
Follow-ups:
- c3.1: thread struct device * through bes2600_chrdev_is_signal_mode
and friends so the global bes2600_cdev singleton can be dropped
and the accessors scale to multi-device scenarios.
- c4: enable CONFIG_BES2600_TESTMODE and route nl80211 testmode
commands to the firmware's patch_wifi_testMode entry.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes_chardev.c | 519 ------------------------------------------
1 file changed, 519 deletions(-)
diff --git a/bes2600/bes_chardev.c b/bes2600/bes_chardev.c
index 9038e48..e2e4f1b 100644
--- a/bes2600/bes_chardev.c
+++ b/bes2600/bes_chardev.c
@@ -43,12 +43,6 @@ enum bus_probe_state {
};
struct bes_cdev {
- struct cdev cdev;
- dev_t dev_id;
- int major;
- int minor;
- struct class *class;
- struct device *device;
atomic_t num_proc;
wait_queue_head_t open_wq;
spinlock_t status_lock;
@@ -249,351 +243,18 @@ int bes2600_switch_bt(bool on)
return ret;
}
-static int bes2600_get_cmd_and_ifname(const char *str, char **result)
-{
- int cmd_len = 0;
- int ifname_len = 0;
- char *sp = NULL;
- char *tmp_ptr = NULL;
- char *cmd_ptr = NULL;
-
- /* check if input arguments is valid */
- if (!str || strncmp(str, "ifname:", 7) != 0)
- return -1;
-
- sp = strchr(str, ' ');
- if (strncmp(sp + 1, "cmd:", 4) != 0)
- return -1;
-
- /* extract interface name */
- ifname_len = sp - str - 7;
- tmp_ptr = kmalloc(ifname_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- return -2;
- }
-
- strncpy(tmp_ptr, str+7, ifname_len);
- tmp_ptr[ifname_len] = '\0';
- result[0] = tmp_ptr;
-
- /* get command length */
- cmd_ptr = strstr(str, "cmd:");
- cmd_ptr += 4;
- sp = strchr(cmd_ptr, ' ');
- if (!sp) { /* the command don't have any parameter */
- cmd_len = strlen(cmd_ptr);
- if (cmd_ptr[cmd_len - 1] == '\n')
- --cmd_len;
- } else { /* the command have one or more parameter */
- cmd_len = sp - cmd_ptr;
- }
-
- /* copy command to out buffer */
- tmp_ptr = kmalloc( cmd_len + 1, GFP_KERNEL);
- if (!tmp_ptr) {
- kfree(result[0]);
- result[0] = NULL;
- return -3;
- }
-
- strncpy(tmp_ptr, cmd_ptr, cmd_len);
- tmp_ptr[cmd_len] = '\0';
- result[1] = tmp_ptr;
-
- return 0;
-}
-
-static void bes2600_recyle_cmd_and_ifname_mem(char **info)
-{
- if (info[0]) {
- kfree(info[0]);
- info[0] = NULL;
- }
-
- if (info[1]) {
- kfree(info[1]);
- info[1] = NULL;
- }
-
-}
-
-static int bes2600_op_default_handler(const char *str)
-{
- char *info[2] = {0};
-
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- bes_devel("cmd(%s) on %s not handled\n", info[1], info[0]);
- } else {
- bes_err("%s get command fail, the origin string is %s\n", __func__, str);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return 0;
-}
-
-static int bes2600_op_wifi_bt_on_off(const char *str)
-{
- char *info[2] = {0};
- int ret = 0;
- enum wait_state wait_state;
- enum bus_probe_state probe_state;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- probe_state = bes2600_cdev.bus_probe;
- wait_state = bes2600_cdev.wait_state;
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* only work for wifi signal mode */
- if (bes2600_cdev.fw_type != BES2600_FW_TYPE_WIFI_SIGNAL)
- return -EFAULT;
-
- /* wait bus probe operation end */
- if (probe_state == BES2600_BUS_PROBE_START) {
- bes_devel("wait bus probe operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.bus_probe > BES2600_BUS_PROBE_START),
- HZ);
- WARN_ON(status <= 0);
- }
-
- /* must wait previous operation end in critical section */
- if (wait_state != BES2600_BOOT_WAIT_NONE) {
- bes_devel("wait previous operation end\n");
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- (bes2600_cdev.wait_state == BES2600_BOOT_WAIT_NONE),
- HZ * 8);
- WARN_ON(status <= 0);
- }
-
- /* if dpd calibration is doing, modify wifi and bt state directly */
- spin_lock(&bes2600_cdev.status_lock);
- if (bes2600_cdev.bus_probe == BES2600_BUS_PROBE_OK && !bes2600_cdev.dpd_calied) {
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- bes2600_cdev.wifi_opened = true;
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- bes2600_cdev.wifi_opened = false;
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- bes2600_cdev.bt_opened = true;
- bes2600_cdev.bton_pending = true;
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- bes2600_cdev.bt_opened = false;
- bes2600_cdev.bton_pending = false;
- }
- }
- bes2600_recyle_cmd_and_ifname_mem(info);
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- WARN_ON(status <= 0);
-
- return (status <= 0 || bes2600_chrdev_is_bus_error()) ? -EFAULT : 0;
- }
- spin_unlock(&bes2600_cdev.status_lock);
-
- /* process wifi/bt on/off operation */
- if (bes2600_get_cmd_and_ifname(str, info) == 0) {
- if (strncmp(info[1], "WIFI_ON", 7) == 0) {
- ret = bes2600_switch_wifi(1);
- } else if (strncmp(info[1], "WIFI_OFF", 8) == 0) {
- ret = bes2600_switch_wifi(0);
- } else if (strncmp(info[1], "BT_ON", 5) == 0) {
- ret = bes2600_switch_bt(1);
- } else if (strncmp(info[1], "BT_OFF", 6) == 0) {
- ret = bes2600_switch_bt(0);
- }
- }
-
- if (!ret && bes2600_chrdev_check_system_close())
- ret = bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
- bes2600_cdev.sbus_priv);
-
- bes2600_recyle_cmd_and_ifname_mem(info);
-
- return ret ;
-}
-
-
-static int bes2600_op_change_fw_type(const char *str)
-{
- int ret = 0;
- int temp = 0;
- long status = 0;
- char *cmd_ptr = NULL;
- char fw_type[5] = {0};
- bool sys_closed = bes2600_chrdev_check_system_close();
-
- bes_devel("%s is called, arg:%s\n", __func__, str);
-
- if (!bes2600_cdev.sbus_ops->power_switch && !bes2600_cdev.sbus_ops->reboot)
- return -EPERM;
-
- /* check if user input is valid */
- cmd_ptr = strstr(str, "CHANGE_FW_TYPE ");
- if (strlen(str) < 16 || !cmd_ptr) {
- bes_err("the format of \"%s\" is error\n", str);
- return -EINVAL;
- }
-
- /* convert fw_type from string to int */
- strncpy(fw_type, cmd_ptr + 14, 4);
- fw_type[0] = '+';
- ret = kstrtoint(fw_type, 10, &temp);
- if (ret < 0) {
- bes_err("%s parse error\n", __func__);
- return -EINVAL;
- }
-
- /* no need to realod firmware if new fw_type is equal to the old */
- if (temp == bes2600_cdev.fw_type ) {
- bes_devel("fw type is equal\n");
- return 0;
- }
-
- /* close wifi net device */
- if (bes2600_cdev.sbus_priv
- && bes2600_is_net_dev_created(bes2600_cdev.sbus_priv)) {
- bes2600_unregister_net_dev(bes2600_cdev.sbus_priv);
- }
-
- /* update firmware type */
- bes2600_cdev.fw_type = temp;
- bes2600_chrdev_update_signal_mode();
-
- if (!sys_closed) {
- /* close device to call disconnect function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(bes2600_cdev.sbus_priv, 0);
- else if (bes2600_cdev.sbus_ops->reboot)
- bes2600_cdev.sbus_ops->reboot(bes2600_cdev.sbus_priv);
- }
-
- if (bes2600_cdev.sbus_ops->reboot)
- bes2600_chrdev_start_bus_probe();
-
- /* wait disconnect event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq, (bes2600_cdev.sbus_priv == NULL), HZ * 10);
- WARN_ON(status <= 0);
-
- if (bes2600_cdev.dpd_calied
- && bes2600_chrdev_check_system_close()) {
- bes_devel("no need to reload firmware\n");
- return 0;
- }
-
- bes_devel("reload firmware...\n");
- /* power on device to call probe function */
- if (bes2600_cdev.sbus_ops->power_switch)
- bes2600_cdev.sbus_ops->power_switch(NULL, 1);
-
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 10);
- WARN_ON(status <= 0);
-
- ret = (status <= 0 || bes2600_chrdev_is_bus_error()) ? -1 : 0;
- return ret;
-}
-
-static int bes2600_op_bt_wakeup(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
-
- bes_devel("bes2600 wakeup bt.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_ON, SUBSYSTEM_BT_LP, true);
-
- return ret;
-}
-
-static int bes2600_op_bt_sleep(const char *str)
-{
- int ret = 0;
- unsigned long status = 0;
-
- spin_lock(&bes2600_cdev.status_lock);
- if (!bes2600_cdev.bt_opened) {
- spin_unlock(&bes2600_cdev.status_lock);
- return -EFAULT;
- }
- spin_unlock(&bes2600_cdev.status_lock);
- /* wait probe done event */
- status = wait_event_timeout(bes2600_cdev.probe_done_wq,
- bes2600_bootup_end(), HZ * 8);
- if (status <= 0 || bes2600_chrdev_is_bus_error())
- return -EFAULT;
- bes_devel("bes2600 allow bt sleep.\n");
- ret = bes2600_chrdev_switch_subsys(GPIO_WAKE_FLAG_BT_LP_OFF, SUBSYSTEM_BT_LP, false);
- return ret;
-}
-static int bes2600_op_set_wakeup_read_flag(const char *str)
-{
- bes_devel("%s is called, arg:%s\n", __func__, str);
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_WAKEUP_STATE;
- spin_unlock(&bes2600_cdev.status_lock);
- return 0;
-}
#ifdef FW_DOWNLOAD_UART_DAEMON
-int bes2600_load_uevent(char *env[])
-{
- return kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
-}
#endif
-static struct bes2600_op_map bes2600_op_map_tab[] ={
- /*op op_len handler */
- {"P2P_SET_NOA", 11, bes2600_op_default_handler},
- {"P2P_SET_PS", 10, bes2600_op_default_handler},
- {"SET_AP_WPS_P2P_IE", 17, bes2600_op_default_handler},
- {"LINKSPEED", 9, bes2600_op_default_handler},
- {"RSSI", 4, bes2600_op_default_handler},
- {"GETBAND", 7, bes2600_op_default_handler},
- {"WLS_BATCHING", 12, bes2600_op_default_handler},
- {"MACADDR", 7, bes2600_op_default_handler},
- {"RXFILTER-START", 14, bes2600_op_default_handler},
- {"RXFILTER-STOP", 13, bes2600_op_default_handler},
- {"RXFILTER-ADD", 12, bes2600_op_default_handler},
- {"RXFILTER-REMOVE", 15, bes2600_op_default_handler},
- {"BTCOEXMODE", 10, bes2600_op_default_handler},
- {"BTCOEXSCAN-START", 16, bes2600_op_default_handler},
- {"BTCOEXSCAN-STOP", 15, bes2600_op_default_handler},
- {"SETSUSPENDMODE", 14, bes2600_op_default_handler},
- {"COUNTRY", 7, bes2600_op_default_handler},
- {"WIFI_ON", 7, bes2600_op_wifi_bt_on_off},
- {"WIFI_OFF", 8, bes2600_op_wifi_bt_on_off},
- {"BT_ON", 5, bes2600_op_wifi_bt_on_off},
- {"BT_OFF", 6, bes2600_op_wifi_bt_on_off},
- {"CHANGE_FW_TYPE", 14, bes2600_op_change_fw_type},
- {"BT_WAKEUP", 9, bes2600_op_bt_wakeup},
- {"BT_SLEEP", 8, bes2600_op_bt_sleep},
- {"WAKEUP_STATE", 12, bes2600_op_set_wakeup_read_flag},
-};
static int bes2600_chrdev_check_system_close_internal(void)
{
@@ -603,123 +264,10 @@ static int bes2600_chrdev_check_system_close_internal(void)
&& (bes2600_cdev.wifi_opened == false);
}
-static int bes2600_chrdev_open(struct inode *inode, struct file *filp)
-{
- if (atomic_read(&bes2600_cdev.num_proc) > 0) {
- wait_event_timeout(bes2600_cdev.open_wq,
- (atomic_read(&bes2600_cdev.num_proc) == 0),
- MAX_SCHEDULE_TIMEOUT);
- }
- bes_devel("bes2600 char device is opened\n");
- atomic_inc(&bes2600_cdev.num_proc);
- return 0;
-}
-static ssize_t bes2600_chrdev_read(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
-{
- char buf[64] = {0};
- unsigned int len;
- long status = 0;
- switch (bes2600_cdev.read_flag) {
- case BES_CDEV_READ_WAKEUP_STATE:
- if (bes2600_chrdev_wakeup_by_event_get() > WAKEUP_EVENT_NONE) {
- status = wait_event_timeout(bes2600_cdev.wakeup_reason_wq,
- bes2600_chrdev_wakeup_by_event_get() == WAKEUP_EVENT_NONE, HZ * 2);
- WARN_ON(status <= 0);
- }
- len = sprintf(buf, "wakeup_reason: %u, src_port: %u\n",
- bes2600_cdev.wakeup_state, bes2600_cdev.src_port);
- break;
- default:
- len = sprintf(buf, "dpd_calied:%d wifi_opened:%d bt_opened:%d fw_type:%d\n",
- bes2600_cdev.dpd_calied,
- bes2600_cdev.wifi_opened,
- bes2600_cdev.bt_opened,
- bes2600_cdev.fw_type);
- break;
- }
-
- len = sizeof(buf);
- /* reset read flag */
- spin_lock(&bes2600_cdev.status_lock);
- bes2600_cdev.read_flag = BES_CDEV_READ_NUM_MAX;
- spin_unlock(&bes2600_cdev.status_lock);
-
- return simple_read_from_buffer(user_buf, count, ppos, buf, len);
-}
-
-static ssize_t bes2600_chrdev_write(struct file *file,
- const char __user *user_buf, size_t count, loff_t *ppos)
-{
- int i = 0;
- int cmd_num = ARRAY_SIZE(bes2600_op_map_tab);
- int cmd_len = 0;
- int ret = 0;
- char *info[2] = {0};
- char *buf = NULL;
-
- /* copy content from user space to kernel */
- /* message format:"ifname:wlanx cmd:xxx arg1 arg2 ..." */
- buf = kmalloc(count + 1, GFP_KERNEL);
- if (copy_from_user(buf, user_buf, count))
- return -EFAULT;
-
- /* add terminal character */
- buf[count] = '\0';
-
- /* extract comand and interface */
- if (bes2600_get_cmd_and_ifname(buf, info) != 0) {
- bes_err("%s get command fail, the origin string is %s\n", __func__, buf);
- kfree(buf);
- return -EINVAL;
- }
-
- /* match operation item and execure its handler */
- cmd_len = strlen(info[1]);
- for (i = 0; i < cmd_num; i++) {
- if (cmd_len < bes2600_op_map_tab[i].op_len)
- continue;
-
- if (strncasecmp(info[1], bes2600_op_map_tab[i].op, bes2600_op_map_tab[i].op_len) == 0) {
- ret = bes2600_op_map_tab[i].handler(buf);
- break;
- }
- }
-
- /* operation item mismatch */
- if (i == cmd_num) {
- bes_err("cmd(%s) mismatch\n", info[1]);
- }
-
- bes2600_recyle_cmd_and_ifname_mem(info);
- kfree(buf);
-
- return (ret == 0) ? count : ret;
-}
-
-static int bes2600_chrdev_release (struct inode *inode, struct file *file)
-{
- if (atomic_dec_and_test(&bes2600_cdev.num_proc)) {
- wake_up(&bes2600_cdev.open_wq);
- }
-
- bes_devel("bes2600 char device is closed\n");
-
- return 0;
-}
-
-static struct file_operations bes2600_chardev_fops =
-{
- .owner = THIS_MODULE,
- .open = bes2600_chrdev_open,
- .read = bes2600_chrdev_read,
- .write = bes2600_chrdev_write,
- .release = bes2600_chrdev_release,
-};
#ifdef BES2600_WRITE_DPD_TO_FILE
static int bes2600_chrdev_write_dpd_data_to_file(const char *path, void *buffer, int size)
@@ -1124,12 +672,6 @@ void bes2600_chrdev_update_signal_mode(void)
static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
{
- char wifi_state[15];
- char bt_state[15];
- char fw_type[15];
- char *env[] = { wifi_state, bt_state, fw_type, NULL };
- int ret;
-
if (bes2600_chrdev_is_wifi_opened()) {
bes_devel("system exeception, force wifi down\n");
@@ -1146,14 +688,6 @@ static void bes2600_chrdev_wifi_force_close_work(struct work_struct *work)
bes2600_chrdev_do_system_close(bes2600_cdev.sbus_ops,
bes2600_cdev.sbus_priv);
}
-
- /* notify userspace */
- snprintf(wifi_state, sizeof(wifi_state), "WIFI_OPENED=%d", bes2600_cdev.wifi_opened);
- snprintf(bt_state, sizeof(bt_state), "BT_OPENED=%d", bes2600_cdev.bt_opened);
- snprintf(fw_type, sizeof(fw_type), "FW_TYPE=%d", bes2600_cdev.fw_type);
- ret = kobject_uevent_env(&bes2600_cdev.device->kobj, KOBJ_CHANGE, env);
- if (!ret)
- bes_err("bes2600 notify userspace failed\n");
}
}
@@ -1247,46 +781,6 @@ int bes2600_chrdev_wakeup_by_event_get(void)
int bes2600_chrdev_init(struct sbus_ops *ops)
{
- int ret = 0;
-
- /* allocate devide id */
- ret = alloc_chrdev_region(&bes2600_cdev.dev_id, 0, 1, "bes2600_chrdev");
- if (ret < 0){
- bes_err("bes2600 alloc device id fail\n");
- ret = -EFAULT;
- goto fail;
- }
-
- /* extract major and minor device id */
- bes2600_cdev.major = MAJOR(bes2600_cdev.dev_id);
- bes2600_cdev.minor = MINOR(bes2600_cdev.dev_id);
-
- /* add char device and bind operation function */
- bes2600_cdev.cdev.owner = THIS_MODULE;
- cdev_init(&bes2600_cdev.cdev, &bes2600_chardev_fops);
- ret = cdev_add(&bes2600_cdev.cdev, bes2600_cdev.dev_id, 1);
- if (ret < 0){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail1;
- }
-
- /* create class for creating device node */
- bes2600_cdev.class = class_create("bes2600_chrdev");
- if (IS_ERR(bes2600_cdev.class)){
- bes_err("bes2600 char device add fail\n");
- ret = -EFAULT;
- goto fail2;
- }
-
- /* get char device pointer */
- bes2600_cdev.device = device_create(bes2600_cdev.class, NULL, bes2600_cdev.dev_id, NULL, "bes2600");
- if (IS_ERR(bes2600_cdev.device)){
- bes_err("bes2600 char device create fail\n");
- ret = -EFAULT;
- goto fail3;
- }
-
/* initialise global variable */
atomic_set(&bes2600_cdev.num_proc, 0);
init_waitqueue_head(&bes2600_cdev.open_wq);
@@ -1318,15 +812,6 @@ int bes2600_chrdev_init(struct sbus_ops *ops)
bes_devel("%s done\n", __func__);
return 0;
-
-fail3:
- class_destroy(bes2600_cdev.class);
-fail2:
- cdev_del(&bes2600_cdev.cdev);
-fail1:
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
-fail:
- return ret;
}
void bes2600_chrdev_free(void)
@@ -1336,9 +821,5 @@ void bes2600_chrdev_free(void)
bes2600_free_dpd_log_buffer();
#endif
bes2600_chrdev_free_dpd_data();
- cdev_del(&bes2600_cdev.cdev);
- unregister_chrdev_region(bes2600_cdev.dev_id, 1);
- device_destroy(bes2600_cdev.class, bes2600_cdev.dev_id);
- class_destroy(bes2600_cdev.class);
bes_devel("%s done\n", __func__);
}
--
2.53.0
@@ -0,0 +1,146 @@
From 6f13e008d21d453db486f707f47340a0a17e650b Mon Sep 17 00:00:00 2001
Message-ID: <6f13e008d21d453db486f707f47340a0a17e650b.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Wed, 22 Apr 2026 13:04:27 +0200
Subject: [PATCH 6/7] bes2600: enable CONFIG_BES2600_TESTMODE by default + fix
bit-rotted testmode plumbing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The driver implements a mac80211 testmode_cmd operation that dispatches
to a set of vendor commands (GET_TX_POWER_LEVEL, GET_TX_POWER_RANGE,
SET_SNAP_FRAME, TSM_STATS, GET_ROAM_DELAY, GET_STREAM, etc) plus the
BES2600 RF-test path (bes2600_vendor_rf_cmd -> firmware
patch_wifi_testMode). The testmode handlers and the .testmode_cmd
binding in struct ieee80211_ops are conditionally compiled under
CONFIG_BES2600_TESTMODE, which previously defaulted to n.
Flip the Makefile default from n to y so wifi_testmode_cmd.o is
included in the build and the .testmode_cmd op is populated. On the
PineTab2 target kernel (linux-pinetab2 6.19.10-danctnix1, built with
CONFIG_NL80211_TESTMODE=y) this exposes the BES2600 RF-test surface
through the standard nl80211 testmode interface ('iw phy0 ...').
This also makes visible two classes of bit-rot that had accumulated
while nobody was building with CONFIG_BES2600_TESTMODE=y:
1. sta.c contains ~41 calls to bes2600_info() / bes2600_err() /
bes2600_warn() / bes2600_dbg() / bes2600_err_with_cond() - a
legacy log-macro family carrying a BES2600_DBG_* subsystem-id
first argument. Neither the macros nor any of the BES2600_DBG_*
constants are defined anywhere in the tree. The same call pattern
appears under #if defined(BES2600_DETECTION_LOGIC) in hwio.c and
under CONFIG_BES2600_ITP in itp.c, both normally disabled.
Add minimal shim macros to bes_log.h that rewire the calls onto
the existing bes_info() / bes_err() / bes_warn() / bes_devel()
family (ignoring the subsystem id). Define BES2600_DBG_SBUS,
BES2600_DBG_DOWNLOAD, BES2600_DBG_ITP and BES2600_DBG_TEST_MODE
as 0 constants for documentation / grep.
2. bes2600_start_stop_tsm(), bes2600_get_tsm_params(), and
bes2600_get_roam_delay() are declared in sta.c with external
linkage but have no prototype in any header. All callers live in
sta.c (inside bes2600_testmode_cmd). With CONFIG_BES2600_TESTMODE
off the compiler never sees them; with it on gcc
-Werror=missing-prototypes breaks the build.
Mark the three functions static. (Keeping them file-local also
matches their actual usage.)
Both changes are strictly scoped to make CONFIG_BES2600_TESTMODE=y
buildable; no behavioural change when the flag is off.
Tested-on: PineTab2 (BES2600WM + RK3566) running linux-pinetab2
6.19.10-danctnix1-1 with CONFIG_NL80211_TESTMODE=y. Module builds
cleanly, nl80211 testmode interface reachable via 'iw phy0 ...' from
userspace.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/Makefile | 2 +-
bes2600/bes_log.h | 23 +++++++++++++++++++++++
bes2600/sta.c | 6 +++---
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/bes2600/Makefile b/bes2600/Makefile
index 2dcba09..2c1a850 100644
--- a/bes2600/Makefile
+++ b/bes2600/Makefile
@@ -2,7 +2,7 @@ KERN_DIR = /lib/modules/$(KERNELRELEASE)/build
# feature option
BES2600 ?= m
-CONFIG_BES2600_TESTMODE ?= n
+CONFIG_BES2600_TESTMODE ?= y
CONFIG_BES2600_ENABLE_DEVEL_LOGS ?= n
diff --git a/bes2600/bes_log.h b/bes2600/bes_log.h
index 605cea8..65cf703 100644
--- a/bes2600/bes_log.h
+++ b/bes2600/bes_log.h
@@ -8,3 +8,26 @@ extern struct device *global_dev;
#define bes_info(fmt, ...) dev_info(global_dev, fmt, ##__VA_ARGS__)
#define bes_warn(fmt, ...) dev_warn(global_dev, fmt, ##__VA_ARGS__)
#define bes_err(fmt, ...) dev_err(global_dev, fmt, ##__VA_ARGS__)
+
+/*
+ * Legacy debug-subsystem-tagged log macros. The per-subsystem filtering
+ * was never implemented in-tree; these shims let code paths gated by
+ * CONFIG_BES2600_TESTMODE / CONFIG_BES2600_ITP / BES2600_DETECTION_LOGIC
+ * build when their conditions are enabled. The first argument is
+ * currently unused; pick one of the BES2600_DBG_* constants below for
+ * documentation.
+ */
+#define BES2600_DBG_SBUS 0
+#define BES2600_DBG_DOWNLOAD 0
+#define BES2600_DBG_ITP 0
+#define BES2600_DBG_TEST_MODE 0
+
+#define bes2600_info(_dbg, fmt, ...) bes_info(fmt, ##__VA_ARGS__)
+#define bes2600_err(_dbg, fmt, ...) bes_err(fmt, ##__VA_ARGS__)
+#define bes2600_warn(_dbg, fmt, ...) bes_warn(fmt, ##__VA_ARGS__)
+#define bes2600_dbg(_dbg, fmt, ...) bes_devel(fmt, ##__VA_ARGS__)
+#define bes2600_err_with_cond(_cond, _dbg, fmt, ...) \
+ do { \
+ if (_cond) \
+ bes_err(fmt, ##__VA_ARGS__); \
+ } while (0)
diff --git a/bes2600/sta.c b/bes2600/sta.c
index aa69eb8..5f1a456 100644
--- a/bes2600/sta.c
+++ b/bes2600/sta.c
@@ -3633,7 +3633,7 @@ static int bes2600_set_power_save(struct ieee80211_hw *hw,
*
* Returns: 0 on success or non zero value on failure
*/
-int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
+static int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
{
struct bes_msg_start_stop_tsm *start_stop_tsm =
(struct bes_msg_start_stop_tsm *) data;
@@ -3663,7 +3663,7 @@ int bes2600_start_stop_tsm(struct ieee80211_hw *hw, void *data)
*
* Returns: TSM parameters collected
*/
-int bes2600_get_tsm_params(struct ieee80211_hw *hw)
+static int bes2600_get_tsm_params(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
struct bes_tsm_stats tsm_stats;
@@ -3703,7 +3703,7 @@ int bes2600_get_tsm_params(struct ieee80211_hw *hw)
*
* Returns: Returns the last measured roam delay
*/
-int bes2600_get_roam_delay(struct ieee80211_hw *hw)
+static int bes2600_get_roam_delay(struct ieee80211_hw *hw)
{
struct bes2600_common *hw_priv = hw->priv;
u16 roam_delay = hw_priv->tsm_info.roam_delay / 1000;
--
2.53.0
@@ -0,0 +1,126 @@
From 10a05d21bfe4563f963e16d65228fd7a713c143d Mon Sep 17 00:00:00 2001
Message-ID: <10a05d21bfe4563f963e16d65228fd7a713c143d.1776940528.git.fritsche.markus@gmail.com>
In-Reply-To: <cover.1776940528.git.fritsche.markus@gmail.com>
References: <cover.1776940528.git.fritsche.markus@gmail.com>
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 11:58:31 +0200
Subject: [PATCH 7/7] bes2600: bounce SDIO TX buffers to avoid DMA OOB read
The SDIO TX path rounds the DMA transfer length up to the host's
current block size and hands that length to dma_map_sg() via
sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work().
tx_buffer->buf typically aliases into an skb linear head whose
allocated size matches tx_buffer->len, not the block-aligned
align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up
to one block past the end of the skb. On a PineTab2 with KFENCE
enabled this fires as:
BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic
Out-of-bounds read at ... (704B right of kfence-#...):
__pi_memcpy_generic
swiotlb_tbl_map_single
swiotlb_map
dma_direct_map_sg
__dma_map_sg_attrs
dma_map_sg_attrs
dw_mci_pre_dma_transfer
__dw_mci_start_request
...
bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600]
sdio_tx_work+0x2b4/0x4a0 [bes2600]
allocated by ... pskb_expand_head / validate_xmit_skb / tcp_*
In addition to being undefined behavior, the padding bytes (which
come from whatever memory follows the skb) are transmitted to the
peer, leaking kernel memory on the air.
Allocate a driver-owned DMA-page bounce buffer sized to
MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for
sdio_tx_work. Each TX buffer is copied into its bounce slot and the
tail (align - tx_buffer->len bytes) is zeroed. This mirrors the
existing bounce pattern already used by bes2600_sdio_memcpy_toio()
via single_gathered_buffer; a separate allocation is used for the
TX path because single_gathered_buffer is only serialised via
sdio_claim_host and sdio_tx_work accumulates scatter entries before
claiming the bus.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index 371ef4f..3e04e8c 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -95,6 +95,7 @@ struct sbus_priv {
struct work_struct tx_work;
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
+ u8 *tx_bounce;
u32 tx_data_cnt;
u32 tx_xfer_cnt;
u32 tx_proc_cnt;
@@ -1136,7 +1137,26 @@ static void sdio_tx_work(struct work_struct *work)
}
}
- sg_set_buf(&sg[scatters], tx_buffer->buf, align);
+ /*
+ * The transfer length is rounded up to the SDIO block
+ * size, but tx_buffer->buf is only tx_buffer->len bytes
+ * long (it usually aliases into an skb linear head).
+ * Copy into a driver-owned bounce buffer and zero-pad
+ * to the aligned size; otherwise DMA reads past the
+ * skb and leaks adjacent kernel memory on the wire --
+ * observed as KFENCE OOB reads from
+ * bes_sdio_memcpy_to_io_helper via dma_map_sg.
+ */
+ if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
+ goto flush_previous;
+ memcpy(self->tx_bounce + total_len,
+ tx_buffer->buf, tx_buffer->len);
+ if (align > tx_buffer->len)
+ memset(self->tx_bounce + total_len +
+ tx_buffer->len, 0,
+ align - tx_buffer->len);
+ sg_set_buf(&sg[scatters],
+ self->tx_bounce + total_len, align);
total_len += align;
++scatters;
/*del_node:*/
@@ -1857,6 +1877,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (!self->single_gathered_buffer)
return -ENOMEM;
#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ if (!self->tx_bounce) {
+#ifndef SDIO_HOST_ADMA_SUPPORT
+ free_pages((unsigned long)self->single_gathered_buffer,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+#endif
+ return -ENOMEM;
+ }
+#endif
#ifdef BES_SDIO_RXTX_TOGGLE
self->fw_started = false;
#endif
@@ -1985,6 +2016,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
if (self->single_gathered_buffer) {
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
}
+#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ if (self->tx_bounce) {
+ free_pages((unsigned long)self->tx_bounce,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ }
#endif
kfree(self);
}
--
2.53.0
@@ -0,0 +1,123 @@
From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 11:58:31 +0200
Subject: [PATCH] bes2600: bounce SDIO TX buffers to avoid DMA OOB read
The SDIO TX path rounds the DMA transfer length up to the host's
current block size and hands that length to dma_map_sg() via
sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work().
tx_buffer->buf typically aliases into an skb linear head whose
allocated size matches tx_buffer->len, not the block-aligned
align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up
to one block past the end of the skb. On a PineTab2 with KFENCE
enabled this fires as:
BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic
Out-of-bounds read at ... (704B right of kfence-#...):
__pi_memcpy_generic
swiotlb_tbl_map_single
swiotlb_map
dma_direct_map_sg
__dma_map_sg_attrs
dma_map_sg_attrs
dw_mci_pre_dma_transfer
__dw_mci_start_request
...
bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600]
sdio_tx_work+0x2b4/0x4a0 [bes2600]
allocated by ... pskb_expand_head / validate_xmit_skb / tcp_*
In addition to being undefined behavior, the padding bytes (which
come from whatever memory follows the skb) are transmitted to the
peer, leaking kernel memory on the air.
Allocate a driver-owned DMA-page bounce buffer sized to
MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for
sdio_tx_work. Each TX buffer is copied into its bounce slot and the
tail (align - tx_buffer->len bytes) is zeroed. This mirrors the
existing bounce pattern already used by bes2600_sdio_memcpy_toio()
via single_gathered_buffer; a separate allocation is used for the
TX path because single_gathered_buffer is only serialised via
sdio_claim_host and sdio_tx_work accumulates scatter entries before
claiming the bus.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
drivers/staging/bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/drivers/staging/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index b595365..7bc922c 100644
--- a/drivers/staging/bes2600/bes2600_sdio.c
+++ b/drivers/staging/bes2600/bes2600_sdio.c
@@ -94,6 +94,7 @@ struct sbus_priv {
struct work_struct tx_work;
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
+ u8 *tx_bounce;
u32 tx_data_cnt;
u32 tx_xfer_cnt;
u32 tx_proc_cnt;
@@ -1135,7 +1136,26 @@ static void sdio_tx_work(struct work_struct *work)
}
}
- sg_set_buf(&sg[scatters], tx_buffer->buf, align);
+ /*
+ * The transfer length is rounded up to the SDIO block
+ * size, but tx_buffer->buf is only tx_buffer->len bytes
+ * long (it usually aliases into an skb linear head).
+ * Copy into a driver-owned bounce buffer and zero-pad
+ * to the aligned size; otherwise DMA reads past the
+ * skb and leaks adjacent kernel memory on the wire --
+ * observed as KFENCE OOB reads from
+ * bes_sdio_memcpy_to_io_helper via dma_map_sg.
+ */
+ if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
+ goto flush_previous;
+ memcpy(self->tx_bounce + total_len,
+ tx_buffer->buf, tx_buffer->len);
+ if (align > tx_buffer->len)
+ memset(self->tx_bounce + total_len +
+ tx_buffer->len, 0,
+ align - tx_buffer->len);
+ sg_set_buf(&sg[scatters],
+ self->tx_bounce + total_len, align);
total_len += align;
++scatters;
/*del_node:*/
@@ -1853,6 +1873,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (!self->single_gathered_buffer)
return -ENOMEM;
#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ if (!self->tx_bounce) {
+#ifndef SDIO_HOST_ADMA_SUPPORT
+ free_pages((unsigned long)self->single_gathered_buffer,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+#endif
+ return -ENOMEM;
+ }
+#endif
#ifdef BES_SDIO_RXTX_TOGGLE
self->fw_started = false;
#endif
@@ -1981,6 +2012,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
if (self->single_gathered_buffer) {
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
}
+#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ if (self->tx_bounce) {
+ free_pages((unsigned long)self->tx_bounce,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ }
#endif
kfree(self);
}
--
2.53.0
@@ -0,0 +1,123 @@
From 4ec7d25817af09654fb9439e472890f69281840c Mon Sep 17 00:00:00 2001
From: Markus Fritsche <fritsche.markus@gmail.com>
Date: Thu, 23 Apr 2026 11:58:31 +0200
Subject: [PATCH] bes2600: bounce SDIO TX buffers to avoid DMA OOB read
The SDIO TX path rounds the DMA transfer length up to the host's
current block size and hands that length to dma_map_sg() via
sg_set_buf(&sg[scatters], tx_buffer->buf, align) in sdio_tx_work().
tx_buffer->buf typically aliases into an skb linear head whose
allocated size matches tx_buffer->len, not the block-aligned
align. The DMA engine (swiotlb / dw_mci IDMAC) therefore reads up
to one block past the end of the skb. On a PineTab2 with KFENCE
enabled this fires as:
BUG: KFENCE: out-of-bounds read in __pi_memcpy_generic
Out-of-bounds read at ... (704B right of kfence-#...):
__pi_memcpy_generic
swiotlb_tbl_map_single
swiotlb_map
dma_direct_map_sg
__dma_map_sg_attrs
dma_map_sg_attrs
dw_mci_pre_dma_transfer
__dw_mci_start_request
...
bes_sdio_memcpy_to_io_helper+0x18c/0x288 [bes2600]
sdio_tx_work+0x2b4/0x4a0 [bes2600]
allocated by ... pskb_expand_head / validate_xmit_skb / tcp_*
In addition to being undefined behavior, the padding bytes (which
come from whatever memory follows the skb) are transmitted to the
peer, leaking kernel memory on the air.
Allocate a driver-owned DMA-page bounce buffer sized to
MAX_SDIO_TRANSFER_LEN and use it as the scatter-gather backing for
sdio_tx_work. Each TX buffer is copied into its bounce slot and the
tail (align - tx_buffer->len bytes) is zeroed. This mirrors the
existing bounce pattern already used by bes2600_sdio_memcpy_toio()
via single_gathered_buffer; a separate allocation is used for the
TX path because single_gathered_buffer is only serialised via
sdio_claim_host and sdio_tx_work accumulates scatter entries before
claiming the bus.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
---
bes2600/bes2600_sdio.c | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/bes2600/bes2600_sdio.c b/bes2600/bes2600_sdio.c
index b595365..7bc922c 100644
--- a/bes2600/bes2600_sdio.c
+++ b/bes2600/bes2600_sdio.c
@@ -94,6 +94,7 @@ struct sbus_priv {
struct work_struct tx_work;
struct scatterlist tx_sg[BES_SDIO_TX_MULTIPLE_NUM + 1];
struct scatterlist tx_sg_nosignal[BES_SDIO_TX_MULTIPLE_NUM_NOSIGNAL + 1];
+ u8 *tx_bounce;
u32 tx_data_cnt;
u32 tx_xfer_cnt;
u32 tx_proc_cnt;
@@ -1135,7 +1136,26 @@ static void sdio_tx_work(struct work_struct *work)
}
}
- sg_set_buf(&sg[scatters], tx_buffer->buf, align);
+ /*
+ * The transfer length is rounded up to the SDIO block
+ * size, but tx_buffer->buf is only tx_buffer->len bytes
+ * long (it usually aliases into an skb linear head).
+ * Copy into a driver-owned bounce buffer and zero-pad
+ * to the aligned size; otherwise DMA reads past the
+ * skb and leaks adjacent kernel memory on the wire --
+ * observed as KFENCE OOB reads from
+ * bes_sdio_memcpy_to_io_helper via dma_map_sg.
+ */
+ if (WARN_ON_ONCE(total_len + align > MAX_SDIO_TRANSFER_LEN))
+ goto flush_previous;
+ memcpy(self->tx_bounce + total_len,
+ tx_buffer->buf, tx_buffer->len);
+ if (align > tx_buffer->len)
+ memset(self->tx_bounce + total_len +
+ tx_buffer->len, 0,
+ align - tx_buffer->len);
+ sg_set_buf(&sg[scatters],
+ self->tx_bounce + total_len, align);
total_len += align;
++scatters;
/*del_node:*/
@@ -1853,6 +1873,17 @@ static int bes2600_sdio_probe(struct sdio_func *func,
if (!self->single_gathered_buffer)
return -ENOMEM;
#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ self->tx_bounce = (u8 *)__get_dma_pages(GFP_KERNEL,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ if (!self->tx_bounce) {
+#ifndef SDIO_HOST_ADMA_SUPPORT
+ free_pages((unsigned long)self->single_gathered_buffer,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+#endif
+ return -ENOMEM;
+ }
+#endif
#ifdef BES_SDIO_RXTX_TOGGLE
self->fw_started = false;
#endif
@@ -1981,6 +2012,12 @@ static void bes2600_sdio_remove(struct sdio_func *func)
if (self->single_gathered_buffer) {
free_pages((unsigned long)self->single_gathered_buffer, get_order(MAX_SDIO_TRANSFER_LEN));
}
+#endif
+#ifdef BES_SDIO_TX_MULTIPLE_ENABLE
+ if (self->tx_bounce) {
+ free_pages((unsigned long)self->tx_bounce,
+ get_order(MAX_SDIO_TRANSFER_LEN));
+ }
#endif
kfree(self);
}
--
2.53.0
@@ -0,0 +1,49 @@
From: Markus Fritsche <mfritsche@localhost>
Date: Tue, 24 Mar 2026 00:00:00 +0000
Subject: [PATCH] Bluetooth: btrtl: make RTL_SEC_PROJ read non-fatal
The RTL8852B (lmp_subver=0x8852, hci_rev=0x000b, HCI_USB) does not
support HCI vendor opcode 0xfc61 during early initialization.
btrtl_vendor_read_reg16() returns -ENODEV, which previously caused
btrtl_initialize() to abort with ERR_PTR(-19) and left the controller
unconfigured — visible as a kernel Oops and "No default controller
available" in bluetoothctl.
The RTL_SEC_PROJ register encodes a key_id used to match firmware
security-header sections. When key_id is 0 the driver already skips
all security headers (drivers/bluetooth/btrtl.c, case
RTL_PATCH_SECURITY_HEADER: "If key_id from chip is zero, ignore all
security headers"). Defaulting to key_id=0 on read failure is
therefore safe: unsigned firmware without an embedded key continues to
load normally, while secure-firmware paths that require a non-zero
key_id would only be taken on chips that successfully return one.
Change btrtl_initialize() so that a failed RTL_SEC_PROJ read emits an
informational message and falls back to key_id=0 instead of aborting
initialization.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
1 file changed, 6 insertions(+), 4 deletions(-)
--- a/drivers/bluetooth/btrtl.c
+++ b/drivers/bluetooth/btrtl.c
@@ -1186,10 +1186,12 @@
}
rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ, reg_val);
- if (rc < 0)
- goto err_free;
-
- key_id = reg_val[0];
+ if (rc < 0) {
+ rtl_dev_info(hdev, "RTL_SEC_PROJ read failed (%d), using key_id=0", rc);
+ key_id = 0;
+ } else {
+ key_id = reg_val[0];
+ }
btrtl_dev->key_id = key_id;
rtl_dev_info(hdev, "%s: key id %u", __func__, key_id);
+22
View File
@@ -0,0 +1,22 @@
# patches/driver/bluetooth/btrtl/
Realtek Bluetooth driver (`drivers/bluetooth/btrtl.c`) patches.
## Patches
### `0009-Bluetooth-btrtl-make-RTL_SEC_PROJ-read-non-fatal.patch`
The btrtl HCI vendor-extension code probes `RTL_SEC_PROJ` (security
project ID) during firmware download. On some controllers the
extension is absent and the read times out, currently treated as a
fatal probe failure — the BT subsystem never comes up and the userspace
sees no HCI device.
This patch downgrades the failure to a warning so probe continues with
default firmware selection. Affected hardware:
- ampere (CoolPi GenBook): Realtek RTL8852BE (M.2 module)
- boltzmann (Rock 5 ITX+): same chip family on M.2
Source: `github.com/marfrit/misc_patches/genbook/kernel/0009`
Author: Markus Fritsche
@@ -0,0 +1,66 @@
From 3798abbc26fe7ac7da5cf5253d964d299b48d300 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@localhost>
Date: Thu, 16 Apr 2026 23:53:01 +0200
Subject: [PATCH 1/9] gpio: rockchip: propagate irq_set_wake to parent GIC
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Rockchip GPIO irqchip uses irq_gc_set_wake() which only tracks the
wake state locally in gc->wake_active. It never calls irq_set_irq_wake()
on the parent GIC interrupt for the GPIO bank. During suspend,
suspend_device_irqs() disables all non-wakeup IRQs at the GIC level, so
GPIO-based wakeup sources (RTC alarm, PMIC power key) can never reach
the CPU — the GPIO controller detects the interrupt but the GIC blocks it.
Replace irq_gc_set_wake with rockchip_irq_set_wake that propagates the
wake setting to the parent bank->irq via irq_set_irq_wake(). On failure,
revert the local wake state via irq_gc_set_wake(!on) which handles
gc->lock internally.
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
drivers/gpio/gpio-rockchip.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c
index 0fff4a699f12..d3b874251efc 100644
--- a/drivers/gpio/gpio-rockchip.c
+++ b/drivers/gpio/gpio-rockchip.c
@@ -483,6 +483,23 @@ static void rockchip_irq_relres(struct irq_data *d)
gpiochip_relres_irq(&bank->gpio_chip, d->hwirq);
}
+static int rockchip_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+ int ret;
+
+ ret = irq_gc_set_wake(d, on);
+ if (ret)
+ return ret;
+
+ ret = irq_set_irq_wake(bank->irq, on);
+ if (ret)
+ irq_gc_set_wake(d, !on);
+
+ return ret;
+}
+
static void rockchip_irq_suspend(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
@@ -550,7 +567,7 @@ static int rockchip_interrupts_register(struct rockchip_pin_bank *bank)
gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
gc->chip_types[0].chip.irq_enable = rockchip_irq_enable;
gc->chip_types[0].chip.irq_disable = rockchip_irq_disable;
- gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
+ gc->chip_types[0].chip.irq_set_wake = rockchip_irq_set_wake;
gc->chip_types[0].chip.irq_suspend = rockchip_irq_suspend;
gc->chip_types[0].chip.irq_resume = rockchip_irq_resume;
gc->chip_types[0].chip.irq_set_type = rockchip_irq_set_type;
--
2.54.0
+33
View File
@@ -0,0 +1,33 @@
# patches/driver/gpio/rockchip/
Rockchip GPIO controller (`drivers/gpio/gpio-rockchip.c`) patches.
## Patches
### `0010a-gpio-rockchip-propagate-irq_set_wake-to-parent-GIC.patch`
**WIP — sleep fixes not yet upstream-ready.** Do not include in fleet
manifests until Markus closes the suspend/resume thread.
The Rockchip GPIO irqchip uses `irq_gc_set_wake()` which only tracks
the wake state locally in `gc->wake_active`. It never calls
`irq_set_irq_wake()` on the parent GIC interrupt for the GPIO bank.
During suspend, `suspend_device_irqs()` disables all non-wakeup IRQs
at the GIC level, so GPIO-based wakeup sources (RTC alarm, PMIC power
key) can never reach the CPU — the GPIO controller detects the
interrupt but the GIC blocks it.
This patch replaces `irq_gc_set_wake` with `rockchip_irq_set_wake`
that propagates the wake setting to the parent `bank->irq` via
`irq_set_irq_wake()`.
Source: split from `github.com/marfrit/misc_patches/genbook/kernel/0010`
(the monolithic "gpio/drm/mfd/input/dts: fix suspend/resume and wakeup
on RK3588" patch). Split commits live on `decompose-0010` branch in
`marfrit/linux-rk3588-marfrit`.
Sister patches (also from 0010):
- `patches/driver/gpu/drm/bridge/analogix/0010b-…` (analogix-dp IRQ)
- `patches/driver/mfd/rk8xx/0010c-…` (rk8xx-spi PM ops)
- `patches/driver/input/misc/0010d-…` (rk805-pwrkey wake-irq)
- `patches/board/coolpi-cm5-genbook/0010e-…` (DTS: NPU PD + touchpad wakeup-source)
@@ -0,0 +1,57 @@
From fdb2dae76c6258e309d1713f0ed776a416d0c077 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@localhost>
Date: Thu, 16 Apr 2026 23:53:02 +0200
Subject: [PATCH 2/9] drm/bridge: analogix-dp: disable IRQ before clock gating
in suspend
analogix_dp_suspend() powers off the PHY and disables the clock but
leaves the eDP IRQ enabled. During the noirq suspend phase,
suspend_device_irqs() calls synchronize_irq() which waits for any
running handler to complete. If the eDP controller fires a spurious
interrupt after the clock was gated, the hardirq handler accesses
registers on a dead bus, hanging synchronize_irq().
Add disable_irq() before powering down and enable_irq() after
re-initialization in the resume path. Ensure enable_irq() is called
even if clk_prepare_enable() fails, to avoid leaving the IRQ
permanently disabled.
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index efe534977d12..1b1f811ba9f4 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1577,6 +1577,8 @@ EXPORT_SYMBOL_GPL(analogix_dp_probe);
int analogix_dp_suspend(struct analogix_dp_device *dp)
{
+ disable_irq(dp->irq);
+
phy_power_off(dp->phy);
if (dp->plat_data->power_off)
@@ -1595,6 +1597,7 @@ int analogix_dp_resume(struct analogix_dp_device *dp)
ret = clk_prepare_enable(dp->clock);
if (ret < 0) {
DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret);
+ enable_irq(dp->irq);
return ret;
}
@@ -1606,6 +1609,8 @@ int analogix_dp_resume(struct analogix_dp_device *dp)
analogix_dp_init_dp(dp);
+ enable_irq(dp->irq);
+
return 0;
}
EXPORT_SYMBOL_GPL(analogix_dp_resume);
--
2.54.0
@@ -0,0 +1,12 @@
# patches/driver/gpu/drm/bridge/analogix
analogix-dp eDP bridge driver patches.
## Patches
The `0010*` patches here are sister patches to
`patches/driver/gpio/rockchip/0010a-…` — see that README for the
full context (split from misc_patches/genbook/kernel/0010, the
RK3588 suspend/resume monolithic patch). **All marked WIP — do not
include in fleet manifests until the upstream-targeting shape is
decided.**
@@ -0,0 +1,43 @@
From 2d58b4b47078c19f6c1e110c619009dcbeaf8b53 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@localhost>
Date: Thu, 16 Apr 2026 23:53:04 +0200
Subject: [PATCH 4/9] input: rk805-pwrkey: register wake IRQ via
dev_pm_set_wake_irq
device_init_wakeup() only marks the device as wakeup-capable; without
dev_pm_set_wake_irq() the PM core never arms the IRQ. Wire up the
wake-IRQ so the PMIC power key can wake the system from suspend.
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
drivers/input/misc/rk805-pwrkey.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/input/misc/rk805-pwrkey.c b/drivers/input/misc/rk805-pwrkey.c
index 76873aa005b4..dd0008e25d6d 100644
--- a/drivers/input/misc/rk805-pwrkey.c
+++ b/drivers/input/misc/rk805-pwrkey.c
@@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
static irqreturn_t pwrkey_fall_irq(int irq, void *_pwr)
{
@@ -87,6 +88,11 @@ static int rk805_pwrkey_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, pwr);
device_init_wakeup(&pdev->dev, true);
+ err = dev_pm_set_wake_irq(&pdev->dev, fall_irq);
+ if (err)
+ dev_warn(&pdev->dev, "Can't set wake IRQ %d: %d\n",
+ fall_irq, err);
+
return 0;
}
--
2.54.0
+12
View File
@@ -0,0 +1,12 @@
# patches/driver/input/misc
misc input drivers patches.
## Patches
The `0010*` patches here are sister patches to
`patches/driver/gpio/rockchip/0010a-…` — see that README for the
full context (split from misc_patches/genbook/kernel/0010, the
RK3588 suspend/resume monolithic patch). **All marked WIP — do not
include in fleet manifests until the upstream-targeting shape is
decided.**
@@ -0,0 +1,48 @@
From 9ddcae54a171f2fc7742e92e03b1478d87ae4bbb Mon Sep 17 00:00:00 2001
From: Venkata Atchuta Bheemeswara Sarma Darbha <vdarbha0473@gmail.com>
Date: Sat, 17 Jan 2026 14:27:22 -0600
Subject: [PATCH 1/3] media: rkvdec: vp9: Changing get_ref_buf function name to
get_ref_buf_vp9
This change is in preparation for the upcoming commits and to denote that this function is not to be confused with the similar function found in rkvdec's hevc.
Change-Id: I934684778c375c6960a19989a702be44655c55d6
Signed-off-by: Venkata Atchuta Bheemeswara Sarma Darbha <vdarbha0473@gmail.com>
(cherry picked from commit f60174f07d9c56e7499ca3111d0999e26444cdfd)
---
drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
index e4cdd2122873..bab2e9c83d06 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
@@ -349,7 +349,7 @@ static void init_probs(struct rkvdec_ctx *ctx,
}
static struct rkvdec_decoded_buffer *
-get_ref_buf(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp)
+get_ref_buf_vp9(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp)
{
struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q;
@@ -489,12 +489,12 @@ static void config_registers(struct rkvdec_ctx *ctx,
dec_params = run->decode_params;
dst = vb2_to_rkvdec_decoded_buf(&run->base.bufs.dst->vb2_buf);
- ref_bufs[0] = get_ref_buf(ctx, &dst->base.vb, dec_params->last_frame_ts);
- ref_bufs[1] = get_ref_buf(ctx, &dst->base.vb, dec_params->golden_frame_ts);
- ref_bufs[2] = get_ref_buf(ctx, &dst->base.vb, dec_params->alt_frame_ts);
+ ref_bufs[0] = get_ref_buf_vp9(ctx, &dst->base.vb, dec_params->last_frame_ts);
+ ref_bufs[1] = get_ref_buf_vp9(ctx, &dst->base.vb, dec_params->golden_frame_ts);
+ ref_bufs[2] = get_ref_buf_vp9(ctx, &dst->base.vb, dec_params->alt_frame_ts);
if (vp9_ctx->last.valid)
- last = get_ref_buf(ctx, &dst->base.vb, vp9_ctx->last.timestamp);
+ last = get_ref_buf_vp9(ctx, &dst->base.vb, vp9_ctx->last.timestamp);
else
last = dst;
--
2.54.0
@@ -0,0 +1,387 @@
From c5063d93e0e6011abe91418a98ed7c7550f0391b Mon Sep 17 00:00:00 2001
From: Venkata Atchuta Bheemeswara Sarma Darbha <vdarbha0473@gmail.com>
Date: Sat, 17 Jan 2026 14:37:07 -0600
Subject: [PATCH 2/3] media: rkvdec: Move vp9 functions to common file This is
a preparation commit to add support for new variants of the decoder.
The functions will later be shared with vdpu381 (rk3588).
Change-Id: Ib9b78331fb6eb0e3a607b06fd5138fc741b2c9c0
Signed-off-by: Venkata Atchuta Bheemeswara Sarma Darbha <vdarbha0473@gmail.com>
(cherry picked from commit e87662ca32e88ebb910f6cfc1c71096d5d7bc063)
---
.../media/platform/rockchip/rkvdec/Makefile | 1 +
.../rockchip/rkvdec/rkvdec-vp9-common.c | 77 +++++++++++
.../rockchip/rkvdec/rkvdec-vp9-common.h | 95 +++++++++++++
.../platform/rockchip/rkvdec/rkvdec-vp9.c | 125 +-----------------
4 files changed, 174 insertions(+), 124 deletions(-)
create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.c
create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.h
diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile
index e629d571e4d8..2bbd67b2db11 100644
--- a/drivers/media/platform/rockchip/rkvdec/Makefile
+++ b/drivers/media/platform/rockchip/rkvdec/Makefile
@@ -12,4 +12,5 @@ rockchip-vdec-y += \
rkvdec-vdpu381-hevc.o \
rkvdec-vdpu383-h264.o \
rkvdec-vdpu383-hevc.o \
+ rkvdec-vp9-common.o \
rkvdec-vp9.o
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.c
new file mode 100644
index 000000000000..93023737c1ed
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip video decoder VP9 common functions
+ *
+ * Copyright (C) 2019 Collabora, Ltd.
+ * Boris Brezillon <boris.brezillon@collabora.com>
+ * Copyright (C) 2021 Collabora, Ltd.
+ * Andrzej Pietrasiewicz <andrzej.p@collabora.com>
+ *
+ * Copyright (C) 2016 Rockchip Electronics Co., Ltd.
+ * Alpha Lin <Alpha.Lin@rock-chips.com>
+ */
+#include <linux/v4l2-common.h>
+#include <media/v4l2-h264.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "rkvdec.h"
+#include "rkvdec-vp9-common.h"
+
+void write_coeff_plane(const u8 coef[6][6][3], u8 *coeff_plane)
+{
+ unsigned int idx = 0, byte_count = 0;
+ int k, m, n;
+ u8 p;
+
+ for (k = 0; k < 6; k++) {
+ for (m = 0; m < 6; m++) {
+ for (n = 0; n < 3; n++) {
+ p = coef[k][m][n];
+ coeff_plane[idx++] = p;
+ byte_count++;
+ if (byte_count == 27) {
+ idx += 5;
+ byte_count = 0;
+ }
+ }
+ }
+ }
+}
+
+struct rkvdec_decoded_buffer *
+get_ref_buf_vp9(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp)
+{
+ struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
+ struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q;
+ struct vb2_buffer *buf;
+
+ /*
+ * If a ref is unused or invalid, address of current destination
+ * buffer is returned.
+ */
+ buf = vb2_find_buffer(cap_q, timestamp);
+ if (!buf)
+ buf = &dst->vb2_buf;
+
+ return vb2_to_rkvdec_decoded_buf(buf);
+}
+
+dma_addr_t get_mv_base_addr(struct rkvdec_decoded_buffer *buf)
+{
+ unsigned int aligned_pitch, aligned_height, yuv_len;
+
+ aligned_height = round_up(buf->vp9.height, 64);
+ aligned_pitch = round_up(buf->vp9.width * buf->vp9.bit_depth, 512) / 8;
+ yuv_len = (aligned_height * aligned_pitch * 3) / 2;
+
+ return vb2_dma_contig_plane_dma_addr(&buf->base.vb.vb2_buf, 0) +
+ yuv_len;
+}
+
+void update_dec_buf_info(struct rkvdec_decoded_buffer *buf,
+ const struct v4l2_ctrl_vp9_frame *dec_params)
+{
+ buf->vp9.width = dec_params->frame_width_minus_1 + 1;
+ buf->vp9.height = dec_params->frame_height_minus_1 + 1;
+ buf->vp9.bit_depth = dec_params->bit_depth;
+}
\ No newline at end of file
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.h
new file mode 100644
index 000000000000..056842cf1bba
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9-common.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip video decoder VP9 common functions
+ *
+ * Copyright (C) 2019 Collabora, Ltd.
+ * Boris Brezillon <boris.brezillon@collabora.com>
+ * Copyright (C) 2021 Collabora, Ltd.
+ * Andrzej Pietrasiewicz <andrzej.p@collabora.com>
+ *
+ * Copyright (C) 2016 Rockchip Electronics Co., Ltd.
+ * Alpha Lin <Alpha.Lin@rock-chips.com>
+ */
+
+#include <media/v4l2-h264.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-vp9.h>
+
+#include "rkvdec.h"
+
+struct rkvdec_vp9_run {
+ struct rkvdec_run base;
+ const struct v4l2_ctrl_vp9_frame *decode_params;
+};
+
+struct rkvdec_vp9_intra_mode_probs {
+ u8 y_mode[105];
+ u8 uv_mode[23];
+};
+
+struct rkvdec_vp9_intra_only_frame_probs {
+ u8 coef_intra[4][2][128];
+ struct rkvdec_vp9_intra_mode_probs intra_mode[10];
+};
+
+struct rkvdec_vp9_inter_frame_probs {
+ u8 y_mode[4][9];
+ u8 comp_mode[5];
+ u8 comp_ref[5];
+ u8 single_ref[5][2];
+ u8 inter_mode[7][3];
+ u8 interp_filter[4][2];
+ u8 padding0[11];
+ u8 coef[2][4][2][128];
+ u8 uv_mode_0_2[3][9];
+ u8 padding1[5];
+ u8 uv_mode_3_5[3][9];
+ u8 padding2[5];
+ u8 uv_mode_6_8[3][9];
+ u8 padding3[5];
+ u8 uv_mode_9[9];
+ u8 padding4[7];
+ u8 padding5[16];
+ struct {
+ u8 joint[3];
+ u8 sign[2];
+ u8 classes[2][10];
+ u8 class0_bit[2];
+ u8 bits[2][10];
+ u8 class0_fr[2][2][3];
+ u8 fr[2][3];
+ u8 class0_hp[2];
+ u8 hp[2];
+ u8 padding6[3];
+ } mv;
+};
+
+struct rkvdec_vp9_probs {
+ u8 partition[16][3];
+ u8 pred[3];
+ u8 tree[7];
+ u8 skip[3];
+ u8 tx32[2][3];
+ u8 tx16[2][2];
+ u8 tx8[2][1];
+ u8 is_inter[4];
+ /* 128 bit alignment */
+ u8 padding0[3];
+ union {
+ struct rkvdec_vp9_inter_frame_probs inter;
+ struct rkvdec_vp9_intra_only_frame_probs intra_only;
+ };
+ /* 128 bit alignment */
+ u8 padding1[8];
+};
+
+
+void write_coeff_plane(const u8 coef[6][6][3], u8 *coeff_plane);
+
+struct rkvdec_decoded_buffer *
+get_ref_buf_vp9(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp);
+
+dma_addr_t get_mv_base_addr(struct rkvdec_decoded_buffer *buf);
+
+void update_dec_buf_info(struct rkvdec_decoded_buffer *buf,
+ const struct v4l2_ctrl_vp9_frame *dec_params);
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
index bab2e9c83d06..2b368d7b61e0 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c
@@ -23,71 +23,12 @@
#include "rkvdec.h"
#include "rkvdec-regs.h"
+#include "rkvdec-vp9-common.h"
#define RKVDEC_VP9_PROBE_SIZE 4864
#define RKVDEC_VP9_COUNT_SIZE 13232
#define RKVDEC_VP9_MAX_SEGMAP_SIZE 73728
-struct rkvdec_vp9_intra_mode_probs {
- u8 y_mode[105];
- u8 uv_mode[23];
-};
-
-struct rkvdec_vp9_intra_only_frame_probs {
- u8 coef_intra[4][2][128];
- struct rkvdec_vp9_intra_mode_probs intra_mode[10];
-};
-
-struct rkvdec_vp9_inter_frame_probs {
- u8 y_mode[4][9];
- u8 comp_mode[5];
- u8 comp_ref[5];
- u8 single_ref[5][2];
- u8 inter_mode[7][3];
- u8 interp_filter[4][2];
- u8 padding0[11];
- u8 coef[2][4][2][128];
- u8 uv_mode_0_2[3][9];
- u8 padding1[5];
- u8 uv_mode_3_5[3][9];
- u8 padding2[5];
- u8 uv_mode_6_8[3][9];
- u8 padding3[5];
- u8 uv_mode_9[9];
- u8 padding4[7];
- u8 padding5[16];
- struct {
- u8 joint[3];
- u8 sign[2];
- u8 classes[2][10];
- u8 class0_bit[2];
- u8 bits[2][10];
- u8 class0_fr[2][2][3];
- u8 fr[2][3];
- u8 class0_hp[2];
- u8 hp[2];
- } mv;
-};
-
-struct rkvdec_vp9_probs {
- u8 partition[16][3];
- u8 pred[3];
- u8 tree[7];
- u8 skip[3];
- u8 tx32[2][3];
- u8 tx16[2][2];
- u8 tx8[2][1];
- u8 is_inter[4];
- /* 128 bit alignment */
- u8 padding0[3];
- union {
- struct rkvdec_vp9_inter_frame_probs inter;
- struct rkvdec_vp9_intra_only_frame_probs intra_only;
- };
- /* 128 bit alignment */
- u8 padding1[11];
-};
-
/* Data structure describing auxiliary buffer format. */
struct rkvdec_vp9_priv_tbl {
struct rkvdec_vp9_probs probs;
@@ -136,11 +77,6 @@ struct rkvdec_vp9_intra_frame_symbol_counts {
struct rkvdec_vp9_refs_counts ref_cnt[2][4][2][6][6];
};
-struct rkvdec_vp9_run {
- struct rkvdec_run base;
- const struct v4l2_ctrl_vp9_frame *decode_params;
-};
-
struct rkvdec_vp9_frame_info {
u32 valid : 1;
u32 segmapid : 1;
@@ -166,27 +102,6 @@ struct rkvdec_vp9_ctx {
struct rkvdec_regs regs;
};
-static void write_coeff_plane(const u8 coef[6][6][3], u8 *coeff_plane)
-{
- unsigned int idx = 0, byte_count = 0;
- int k, m, n;
- u8 p;
-
- for (k = 0; k < 6; k++) {
- for (m = 0; m < 6; m++) {
- for (n = 0; n < 3; n++) {
- p = coef[k][m][n];
- coeff_plane[idx++] = p;
- byte_count++;
- if (byte_count == 27) {
- idx += 5;
- byte_count = 0;
- }
- }
- }
- }
-}
-
static void init_intra_only_probs(struct rkvdec_ctx *ctx,
const struct rkvdec_vp9_run *run)
{
@@ -348,36 +263,6 @@ static void init_probs(struct rkvdec_ctx *ctx,
init_inter_probs(ctx, run);
}
-static struct rkvdec_decoded_buffer *
-get_ref_buf_vp9(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp)
-{
- struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
- struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q;
- struct vb2_buffer *buf;
-
- /*
- * If a ref is unused or invalid, address of current destination
- * buffer is returned.
- */
- buf = vb2_find_buffer(cap_q, timestamp);
- if (!buf)
- buf = &dst->vb2_buf;
-
- return vb2_to_rkvdec_decoded_buf(buf);
-}
-
-static dma_addr_t get_mv_base_addr(struct rkvdec_decoded_buffer *buf)
-{
- unsigned int aligned_pitch, aligned_height, yuv_len;
-
- aligned_height = round_up(buf->vp9.height, 64);
- aligned_pitch = round_up(buf->vp9.width * buf->vp9.bit_depth, 512) / 8;
- yuv_len = (aligned_height * aligned_pitch * 3) / 2;
-
- return vb2_dma_contig_plane_dma_addr(&buf->base.vb.vb2_buf, 0) +
- yuv_len;
-}
-
static void config_ref_registers(struct rkvdec_ctx *ctx,
const struct rkvdec_vp9_run *run,
struct rkvdec_decoded_buffer *ref_buf,
@@ -446,14 +331,6 @@ static void config_seg_registers(struct rkvdec_ctx *ctx, unsigned int segid)
(seg->flags & V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE);
}
-static void update_dec_buf_info(struct rkvdec_decoded_buffer *buf,
- const struct v4l2_ctrl_vp9_frame *dec_params)
-{
- buf->vp9.width = dec_params->frame_width_minus_1 + 1;
- buf->vp9.height = dec_params->frame_height_minus_1 + 1;
- buf->vp9.bit_depth = dec_params->bit_depth;
-}
-
static void update_ctx_cur_info(struct rkvdec_vp9_ctx *vp9_ctx,
struct rkvdec_decoded_buffer *buf,
const struct v4l2_ctrl_vp9_frame *dec_params)
--
2.54.0
File diff suppressed because it is too large Load Diff
+69
View File
@@ -0,0 +1,69 @@
# patches/driver/media/
Scope-tagged kernel-agent patches that touch `drivers/media/` — third-party
video-codec enablement work that hasn't reached linux-media patchwork as
formal series yet, but is empirically known to work on our test hardware.
## 0001..0003 — Sarma's VP9 enablement on VDPU381 (RK3588 rkvdec)
Three patches from `D.V.A.B. Sarma <vdarbha0473@gmail.com>` adding VP9
decode support to the VDPU381 variant of rkvdec (the RK3588 generation).
| # | Subject | LOC | What |
|---|---------|----:|------|
| 0001 | rkvdec/vp9: rename get_ref_buf to get_ref_buf_vp9 | 10 | rename existing helper to avoid namespace collision with the upcoming HEVC equivalent |
| 0002 | rkvdec: move vp9 functions to common file | 172 | extract VP9 plumbing into `rkvdec-vp9-common.{c,h}` so VDPU381 can share with the older RK3399 backend |
| 0003 | rkvdec: add VP9 support for VDPU381 variant | 1303 | the actual VDPU381 VP9 backend — register defs + `rkvdec-vdpu381-vp9.c` + glue |
Combined: ~1500 LOC, 5 new files in `drivers/media/platform/rockchip/rkvdec/`.
### Upstream provenance
- Author maintains the work at https://github.com/dvab-sarma/android_kernel_rk_opi
branch `add-rkvdec-vdpu381-vp9-v8`.
- Collabora's blog post on RK3588/RK3576 video decoder mainline merge cites
the work but notes "v1 series needs to be sent for review soon" —
i.e. not yet on linux-media patchwork, no upstream timeline.
- Casanova's VDPU381+VDPU383 H.264/HEVC base (which these patches sit on top
of) IS in mainline 7.0 release.
- Patches do NOT modify any of our scope-tagged board / module / soc /
subsystem code paths — purely additive to the upstream rkvdec subdirectory.
### Tested on
- Author: Orange Pi 5 Pro board (RK3588), AOSP 16 + FFMPEG, Profile 0 + Profile 2
- Our fleet: build verified clean on `ampere` (CoolPi CM5 GenBook, RK3588)
2026-05-18 with KERNELRELEASE `7.0.0-rc3-vp9-test+` (base = running
`7.0.0-rc3-devices+` config + LOCALVERSION change + these 3 patches +
the pre-existing issue14 vb2-resv local mods). Full kernel image
+ DTB + modules + initramfs land at `/boot/firmware/*-7.0.0-rc3-vp9-test+`
and `/lib/modules/7.0.0-rc3-vp9-test+`. New extlinux label `arch_vp9_test`
added without touching default `arch_devices`. End-to-end VP9 decode
validation requires booting into `arch_vp9_test` (pending operator
confirmation, then `v4l2-ctl -d /dev/video1 --list-formats-out` should
list `VP9F` alongside `S265` + `S264`).
### Apply order
Strict — 0001 → 0002 → 0003. 0003 depends on the common-file refactor
from 0002, which depends on the helper rename in 0001.
### Removal criteria
Drop these patches when:
- Sarma sends a v1 series to linux-media and it lands upstream — adopt
the upstream version at the next baseline bump, OR
- Collabora produces an alternative VP9 enablement on their own
hardware-enablement/rockchip-3588 GitLab tree — prefer that lineage
(more likely to land cleanly upstream).
### How to use in a kernel-agent build
If `fleet/ampere.yaml` is bumped to include VP9 (currently scope-out per
the manifest preamble — "Asks #2 (VP9 enablement on RK3588 rkvdec) and
#3 (AV1 dec integration) from issue #6 are NOT addressed in this
manifest — tracked separately"), reference these three files in apply
order under the manifest's scope-tagged patch list.
Cross-references: `marfrit/kernel-agent#12` (the VP9-on-ampere enablement
issue).
@@ -0,0 +1,46 @@
From e1ddd44dea499fd62907ac100baaa2835da6e213 Mon Sep 17 00:00:00 2001
From: Markus Fritsche <mfritsche@localhost>
Date: Thu, 16 Apr 2026 23:53:03 +0200
Subject: [PATCH 3/9] mfd: rk8xx-spi: add PM ops and shutdown callback
The I2C transport (rk8xx-i2c.c) wires up rk8xx_suspend/rk8xx_resume
and a shutdown callback, but the SPI transport does not. Add the
matching PM ops (SIMPLE_DEV_PM_OPS) and shutdown callback that calls
rk8xx_shutdown() so RK806-based boards can suspend/resume and power
off cleanly via the SPI transport.
Generated-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
drivers/mfd/rk8xx-spi.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/mfd/rk8xx-spi.c b/drivers/mfd/rk8xx-spi.c
index 3405fb82ff9f..148af672ce12 100644
--- a/drivers/mfd/rk8xx-spi.c
+++ b/drivers/mfd/rk8xx-spi.c
@@ -109,12 +109,21 @@ static const struct spi_device_id rk8xx_spi_id_table[] = {
};
MODULE_DEVICE_TABLE(spi, rk8xx_spi_id_table);
+static void rk8xx_spi_shutdown(struct spi_device *spi)
+{
+ rk8xx_shutdown(&spi->dev);
+}
+
+static SIMPLE_DEV_PM_OPS(rk8xx_spi_pm_ops, rk8xx_suspend, rk8xx_resume);
+
static struct spi_driver rk8xx_spi_driver = {
.driver = {
.name = "rk8xx-spi",
.of_match_table = rk8xx_spi_of_match,
+ .pm = &rk8xx_spi_pm_ops,
},
.probe = rk8xx_spi_probe,
+ .shutdown = rk8xx_spi_shutdown,
.id_table = rk8xx_spi_id_table,
};
module_spi_driver(rk8xx_spi_driver);
--
2.54.0
+12
View File
@@ -0,0 +1,12 @@
# patches/driver/mfd/rk8xx
Rockchip RK8xx PMIC family (RK805/RK806/RK809/RK817) MFD core patches.
## Patches
The `0010*` patches here are sister patches to
`patches/driver/gpio/rockchip/0010a-…` — see that README for the
full context (split from misc_patches/genbook/kernel/0010, the
RK3588 suspend/resume monolithic patch). **All marked WIP — do not
include in fleet manifests until the upstream-targeting shape is
decided.**
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# ka-build test suite — dry-run paths only.
#
# Phase-1 deliverable per issue #34. The full makepkg path is exercised
# manually on boltzmann (parity test against the most recent hand-built
# linux-fresnel-fourier pkg); not in this suite because:
# - Needs real ssh to boltzmann + ~30 min build wall time
# - Hermetic sandbox would need a mock marfrit-publish-arch on hertz
# Future-work: add a `--mock-build-host` flag + fixture builder so this
# can run in CI.
#
# What this suite covers:
# - Argument parsing + required-host check
# - manifest.yaml read + package.name / build_host.primary extraction
# - Refuses if manifest.lock missing (ka-promote not run)
# - Refuses if PKGBUILD missing
# - Refuses on patch drift between kernel-agent and marfrit-packages
# - Happy-path dry-run on fresnel (all 6 patches match)
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
packages_repo="${PACKAGES_REPO_FOR_TESTS:-${HOME}/src/marfrit-packages}"
pass=0
fail=0
results=()
note() { printf ' %s\n' "$*"; }
ok() { results+=("PASS $1"); pass=$((pass+1)); note "PASS"; }
ko() { results+=("FAIL $1: $2"); fail=$((fail+1)); note "FAIL: $2"; }
# Reset build/ before running so we exercise the "no manifest.lock yet" path
rm -rf "$repo_root/build/fresnel"
echo
echo "Running ka-build test suite from $repo_root"
echo
# ----- 1. requires host arg -----
echo "::: requires host arg"
set +e
out=$("$repo_root/bin/ka-build" 2>&1)
rc=$?
set -e
if [ "$rc" -eq 2 ] && echo "$out" | grep -q "host is required"; then ok "requires host arg"; else ko "requires host arg" "exit=$rc out=$out"; fi
# ----- 2. unknown flag -----
echo "::: unknown flag rejected"
set +e
out=$("$repo_root/bin/ka-build" fresnel --nonsense 2>&1)
rc=$?
set -e
if [ "$rc" -ne 0 ] && echo "$out" | grep -q "unknown flag"; then ok "unknown flag rejected"; else ko "unknown flag rejected" "exit=$rc out=$out"; fi
# ----- 3. refuses if manifest.lock missing -----
echo "::: refuses if manifest.lock missing (ka-promote not run)"
set +e
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$packages_repo" 2>&1)
rc=$?
set -e
if [ "$rc" -eq 2 ] && echo "$out" | grep -q "no manifest.lock"; then ok "refuses no-lock"; else ko "refuses no-lock" "exit=$rc out=$out"; fi
# Now run ka-promote so the rest can proceed
"$repo_root/bin/ka-promote" fresnel >/dev/null
# ----- 4. refuses if PKGBUILD missing -----
echo "::: refuses if PKGBUILD missing (--packages-repo wrong)"
set +e
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo /tmp/non-existent-mp 2>&1)
rc=$?
set -e
if [ "$rc" -eq 2 ]; then ok "refuses bad packages-repo"; else ko "refuses bad packages-repo" "exit=$rc out=$out"; fi
# ----- 5. happy-path dry-run -----
echo "::: happy-path dry-run (fresnel, real packages-repo)"
if [ ! -f "$packages_repo/arch/linux-fresnel-fourier/PKGBUILD" ]; then
note "SKIP: $packages_repo/arch/linux-fresnel-fourier/PKGBUILD not present"
results+=("SKIP happy-path dry-run (PKGBUILD missing locally)")
else
set +e
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$packages_repo" 2>&1)
rc=$?
set -e
if [ "$rc" -eq 0 ] && echo "$out" | grep -q "patches OK (6 files)"; then ok "happy-path dry-run"; else ko "happy-path dry-run" "exit=$rc out=$out"; fi
fi
# ----- 6. patch drift detection -----
echo "::: patch drift detection (mutate a copied patch, expect exit 3)"
if [ ! -d "$packages_repo/arch/linux-fresnel-fourier" ]; then
note "SKIP: $packages_repo/arch/linux-fresnel-fourier not present"
results+=("SKIP patch drift detection")
else
sandbox=$(mktemp -d -t ka-build-drift.XXXXXX)
cp -r "$packages_repo/arch/linux-fresnel-fourier" "$sandbox/linux-fresnel-fourier"
mkdir -p "$sandbox/arch"
mv "$sandbox/linux-fresnel-fourier" "$sandbox/arch/linux-fresnel-fourier"
# Mutate one patch so its sha256 differs from manifest.lock's recorded sha
echo "drift" >> "$sandbox/arch/linux-fresnel-fourier/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch"
set +e
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$sandbox" 2>&1)
rc=$?
set -e
rm -rf "$sandbox"
if [ "$rc" -eq 3 ] && echo "$out" | grep -q "DRIFT:"; then ok "patch drift detection"; else ko "patch drift detection" "exit=$rc out=$out"; fi
fi
echo
echo "===================="
printf '%s\n' "${results[@]}"
echo "===================="
echo "passed: $pass"
echo "failed: $fail"
[ "$fail" -eq 0 ] || exit 1
@@ -0,0 +1,20 @@
# Bad-include fixture: an entry that does not end in .patch or /.
# Expected: ka-promote exits 4 with a clear schema error.
host: fixture-bad-include
arch: arm64
soc: rockchip/rk3566
board: fixture
distro: archlinux-arm
baseline:
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0
includes:
- board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
- this-is-not-a-patch-or-dir # neither .patch nor /
package:
name: fixture-bad-include
@@ -0,0 +1,21 @@
# Duplicate-include fixture: same include twice.
# Expected: ka-promote exits 4 with a "duplicate include" error.
host: fixture-duplicate-include
arch: arm64
soc: rockchip/rk3566
board: fixture
distro: archlinux-arm
baseline:
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0
includes:
- board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
- board/pinebook-pro/0002-arm64-dts-rk3399-pinebook-pro-enable-hdmi-sound.patch
- board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch # dup
package:
name: fixture-duplicate-include
@@ -0,0 +1,20 @@
# Missing-patch fixture: a .patch include pointing at a file that doesn't exist.
# Expected: ka-promote exits 2 with a "missing patch" error.
host: fixture-missing-patch
arch: arm64
soc: rockchip/rk3566
board: fixture
distro: archlinux-arm
baseline:
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0
includes:
- board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
- board/pinebook-pro/9999-this-patch-does-not-exist.patch
package:
name: fixture-missing-patch
+21
View File
@@ -0,0 +1,21 @@
# Synthetic fixture: single series-dir include.
# Used by tests/ka-promote/run-tests.sh to verify the series-dir resolver
# expands a directory entry to its .patch files in filename order, with
# 0000-cover-letter.patch excluded.
host: fixture-series-dir
arch: arm64
soc: rockchip/rk3566
board: pinetab2
distro: archlinux-arm
baseline:
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0
includes:
- driver/bes2600/staging-prep-series-danctnix/
package:
name: fixture-series-dir
+181
View File
@@ -0,0 +1,181 @@
#!/usr/bin/env bash
# ka-promote test suite.
#
# Each test runs ka-promote against a fixture from fixtures/ in a temporary
# sandboxed kernel-agent tree (bin/, fleet/, patches/ — patches/ symlinked
# from the real repo so we exercise the real scope-tagged patch files).
#
# Exit 0 iff every test passes. Non-zero on first failure.
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
fixtures="${repo_root}/tests/ka-promote/fixtures"
# Phase-3 ground truth — re-recorded 2026-05-19 after issue #31 fix
# (write_cumulative now strips per-input trailers + emits canonical
# separators between, but not after, concatenated patches).
FRESNEL_EXPECTED_B2SUM=9c21751cc48ab57cdf48058cc4309752de169c567bbb898c342ff3e4a5cc79add53e3fd4217c2ae2ae7c16b0f19518cf1791907367e1ea9ef16458e1e90c05e0
pass=0
fail=0
results=()
note() { printf ' %s\n' "$*"; }
ok() { results+=("PASS $1"); pass=$((pass+1)); note "PASS"; }
ko() { results+=("FAIL $1: $2"); fail=$((fail+1)); note "FAIL: $2"; }
make_sandbox() {
# $1 = fixture yaml path. Builds a scratch tree with bin/+fleet/+patches/
# and copies the fixture into fleet/<host>.yaml (extracting host from fixture).
local fixture="$1"
local scratch
scratch=$(mktemp -d -t ka-promote-test.XXXXXX)
mkdir -p "$scratch/bin" "$scratch/fleet"
cp "$repo_root/bin/ka-promote" "$scratch/bin/ka-promote"
ln -s "$repo_root/patches" "$scratch/patches"
local host
host=$(python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['host'])" "$fixture")
cp "$fixture" "$scratch/fleet/${host}.yaml"
echo "$scratch|$host"
}
run_test() {
local name="$1"
local fixture="$2"
local expected_exit="$3"
local check_fn="${4:-}"
echo "::: $name"
local pair scratch host
pair=$(make_sandbox "$fixture")
scratch="${pair%|*}"
host="${pair#*|}"
set +e
out=$("$scratch/bin/ka-promote" "$host" --output-dir "$scratch/build" 2>&1)
actual_exit=$?
set -e
if [ "$actual_exit" -ne "$expected_exit" ]; then
ko "$name" "expected exit $expected_exit, got $actual_exit. Output: $out"
rm -rf "$scratch"
return
fi
if [ -n "$check_fn" ]; then
if ! "$check_fn" "$scratch" "$host" "$out"; then
ko "$name" "check function reported failure (see notes above)"
rm -rf "$scratch"
return
fi
fi
ok "$name"
rm -rf "$scratch"
}
check_series_dir() {
# 7 patches resolved, all from_series:true, in filename order.
local scratch="$1" host="$2"
local lock="$scratch/build/$host/v7.0/manifest.lock"
[ -f "$lock" ] || { note "manifest.lock missing"; return 1; }
local n
n=$(python3 -c "import yaml; print(len(yaml.safe_load(open('$lock'))['resolved_patches']))")
if [ "$n" -ne 7 ]; then
note "expected 7 resolved patches, got $n"
return 1
fi
python3 - "$lock" <<'PY' || { note "from_series check failed"; exit 1; }
import yaml, sys
lk = yaml.safe_load(open(sys.argv[1]))
for r in lk['resolved_patches']:
assert r['from_series'] is True, f"{r['include']} not flagged from_series"
expected_basenames = [f'0{i:03d}'[1:] for i in range(1,8)] # 0001..0007
got = [r['include'].split('/')[-1][:4] for r in lk['resolved_patches']]
assert got == ['000'+str(i) for i in range(1,8)], f'apply order mismatch: {got}'
PY
}
check_fresnel_parity() {
local scratch="$1" host="$2"
local lock="$scratch/build/$host/v7.0/manifest.lock"
[ -f "$lock" ] || { note "manifest.lock missing"; return 1; }
local b2
b2=$(python3 -c "import yaml; print(yaml.safe_load(open('$lock'))['cumulative']['b2sum'])")
if [ "$b2" != "$FRESNEL_EXPECTED_B2SUM" ]; then
note "b2sum mismatch"
note " expected: $FRESNEL_EXPECTED_B2SUM"
note " got: $b2"
return 1
fi
}
echo
echo "Running ka-promote test suite from $repo_root"
echo
# ----- unit: strip_trailer + write_cumulative shape (issue #31) -----
echo "::: strip_trailer + cumulative shape (issue #31)"
python3 - "$repo_root" <<'PY'
import importlib.util, pathlib, sys, tempfile, os
root = pathlib.Path(sys.argv[1])
from importlib.machinery import SourceFileLoader
mod = SourceFileLoader("ka_promote", str(root/"bin"/"ka-promote")).load_module()
# strip_trailer accepts both shapes and yields newline-terminated body
assert mod.strip_trailer(b"...body...\n-- \n2.54.0\n\n") == b"...body...\n"
assert mod.strip_trailer(b"...body...\n-- \n2.53.0\n\n") == b"...body...\n"
assert mod.strip_trailer(b"...body...\n-- \n2.20\n\n") == b"...body...\n"
assert mod.strip_trailer(b"...body...\n") == b"...body...\n"
assert mod.strip_trailer(b"...body...") == b"...body...\n"
# Multiple trailing blanks after the version still strip
assert mod.strip_trailer(b"x\n-- \n2.54.0\n\n\n") == b"x\n"
# write_cumulative: 3 inputs (mix of with-/without-trailer), check ordering
with tempfile.TemporaryDirectory() as d:
p1 = os.path.join(d, "a.patch"); open(p1,"wb").write(b"PA\n-- \n2.54.0\n\n")
p2 = os.path.join(d, "b.patch"); open(p2,"wb").write(b"PB\n") # already bare
p3 = os.path.join(d, "c.patch"); open(p3,"wb").write(b"PC\n-- \n2.40.1\n\n")
out = os.path.join(d, "out.patch")
resolved = [{"src": p1}, {"src": p2}, {"src": p3}]
mod.write_cumulative(resolved, out)
body = open(out,"rb").read()
assert body == b"PA\n-- \n2.54.0\n\nPB\n-- \n2.54.0\n\nPC\n", repr(body)
# Last patch (PC) must NOT carry an orphan trailer at EOF
assert not body.rstrip(b"\n").endswith(b"2.40.1"), \
f"last patch's trailer leaked into cumulative: {body[-40:]!r}"
print("PASS")
PY
if [ $? -eq 0 ]; then
results+=("PASS strip_trailer + cumulative shape (issue #31)")
pass=$((pass+1))
else
results+=("FAIL strip_trailer + cumulative shape (issue #31)")
fail=$((fail+1))
fi
echo
# Use the real fleet/fresnel.yaml — copy into a sandbox so the test is hermetic.
mkdir -p /tmp/ka-promote-parity-fixture
cp "$repo_root/fleet/fresnel.yaml" /tmp/ka-promote-parity-fixture/fresnel.yaml
run_test "fresnel parity (= Phase-3 ground truth b2sum)" \
/tmp/ka-promote-parity-fixture/fresnel.yaml 0 check_fresnel_parity
rm -rf /tmp/ka-promote-parity-fixture
run_test "series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches)" \
"$fixtures/series-dir.yaml" 0 check_series_dir
run_test "bad-include rejection (exit 4)" \
"$fixtures/bad-include.yaml" 4
run_test "missing-patch rejection (exit 2)" \
"$fixtures/missing-patch.yaml" 2
run_test "duplicate-include rejection (exit 4)" \
"$fixtures/duplicate-include.yaml" 4
echo
echo "===================="
printf '%s\n' "${results[@]}"
echo "===================="
echo "passed: $pass"
echo "failed: $fail"
[ "$fail" -eq 0 ] || exit 1