45 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
marfrit ea3b2fb813 Merge pull request 'fleet/ampere: pin baseline to torvalds v7.0-rc3 (working) instead of broken marfrit tip' (#10) from claude-noether/kernel-agent:baseline-fix into main 2026-05-16 06:07:50 +00:00
claude-noether 62a6f88abd fleet/ampere: pin baseline to torvalds v7.0-rc3 (working) instead of broken marfrit tip
2026-05-16 bisect found that linux-rk3588-marfrit @ f8f3ad9 (the
previous baseline.ref) black-screens ampere — eDP connector reports
connected/enabled/dpms On + SDDM starts + backlight on, but the panel
shows no pixels. Decomposing the suspend/resume patch 0010 into 5
atomic sub-commits and reverting all of them did NOT recover display,
ruling out 0010 as the offender.

The 6 patches already listed in fleet/ampere.yaml's includes apply
cleanly on top of v7.0-rc3 mainline and produce a kernel that boots
with working display + power-off. That's the verified-working baseline,
captured here.

Regression source is in one of the 12 remaining commits f8f3ad9 has on
top of v7.0-rc3. Top suspect: 55d1b3dcc05e "clk: rockchip: rk3588:
Drop CLK_SET_RATE_PARENT from DCLK_VOP2_SRC" (touches display
controller clock parent rate). Bisect campaign separately.

For consumers who want a ready-to-fetch ref instead of
patches-on-mainline, the verified-working tree is on
git.reauktion.de/marfrit/linux-rk3588-marfrit @ ampere-minimal-devices
(7c241f2e2835).

No change to the includes list — same 6 patches as #8, just retargeted
to a baseline they can actually be applied to.

Generated-by: Claude Opus 4.7 <claude@reauktion.de>
2026-05-16 06:07:39 +00:00
marfrit ad7c61a81b phase 2 ask #1: bootstrap ampere kernel-agent recipe (CoolPi GenBook RK3588) (#8) 2026-05-15 16:47:13 +00:00
claude-noether a6549605f0 phase 2 ask #1: bootstrap ampere kernel-agent recipe (CoolPi GenBook RK3588)
Brings the second customized fleet host into kernel-agent's
scope-tagged tree. Sibling PR coming in marfrit-packages with the
arch/linux-ampere-fourier/ PKGBUILD + flat patch + config + extlinux
hook (build-tree-ready form).

Issue #6 ask #1 only — VP9 enablement (ask #2) and AV1 dec integration
(ask #3) are explicitly deferred to a separate session per the user's
direction. The ampere.yaml manifest documents what's excluded and why.

## Patches promoted

Six patches from boltzmann:~/src/misc_patches/genbook/kernel/ get
scope-tagged into kernel-agent's tree:

  soc/rockchip/rk3588/
    0001-...Add-pwm15-pinctrl-entries.patch         (prereq for 0002)
  module/coolpi-cm5/
    0003-...Fix-power-off-by-enabling-RK806-as-system-power-controller.patch
  board/coolpi-cm5-genbook/
    0002-...Add-pwm-fan.patch
    0004-...Enable-speaker-output-via-audio-graph-card.patch
    0005-...Enable-USB-C-PD-charging-via-FUSB302.patch
    0008-...Add-lid-switch-and-USB3-PHY-lane-config.patch

Each new scope dir gets a README.md documenting what it carries and
why the scope-level granularity makes sense (pwm15 is SoC-wide,
RK806 power-off is SoM-level not board-level, the rest are
board-specific GenBook quirks).

Note on uncommitted-modifications flag in issue body: patches 0001-0004
have working-tree-only changes in misc_patches that fix malformed
'From: PATCH 000X/000Y' headers (placeholder text instead of an
RFC-2822 author identity). The working-tree versions with proper
'From: Markus Fritsche <mfritsche@localhost>' headers are what we ship
here — the unfixed-on-disk variants would fail patch -i header parsing
on stricter implementations and are user-side cleanup that hasn't been
committed back to misc_patches yet. Markus can commit the cleanup
to that personal repo separately; this PR's ingestion does not depend
on it.

Patches 0006 (HAVE_GCC_PLUGINS Kconfig), 0009 (Bluetooth btrtl),
0010 (multi-driver suspend/wakeup) from the misc_patches series are
intentionally NOT promoted here — they need different scope tags
(arch/arm64/, driver/bluetooth/, soc/rockchip/rk3588/ + driver/ split)
and will follow when their respective campaigns demand them. The
ampere.yaml manifest documents the explicit-defer for each.

## fleet/ampere.yaml manifest

Same shape as fleet/fresnel.yaml. Baseline pinned at marfrit/linux-rockchip
branch linux-rk3588-marfrit @ f8f3ad934433 (the working tree on
boltzmann; 18 commits ahead of v7.0-rc3, current 2026-05-15 tip).
Six scope-tagged patch includes per the apply-order in the package
(pwm15 pinctrl must precede the pwm-fan node consumer).

## ampere-specific bootloader path

Documented in manifest. ampere boots from /boot/firmware/ (vfat
partition on mmcblk0p1), not /boot/ (root partition) like fresnel.
The marfrit-packages PKGBUILD's extlinux-add hook needs to write to
/boot/firmware/extlinux/extlinux.conf, not /boot/extlinux/.

## ampere as 2nd aarch64 build host

Per the README update in PR #7, ampere is now a secondary aarch64
build host. The manifest's build_host: section pins ampere as
self-hosting primary for its own kernel, with boltzmann as secondary
and fermi as fallback. This is the first manifest that has its own
host listed as the primary build target — not ideologically pretty,
but pragmatic: native arch, same uarch, full RAM, no cross-compile
step, no need to wake another host.
2026-05-15 16:03:00 +00:00
marfrit e53db55959 Merge pull request 'phase 1: promote vb2_dma_resv RFC v2 + add ka-status + ampere as 2nd aarch64 host' (#7) from claude-noether/kernel-agent:noether/phase1-promote-vb2-dma-resv into main
Reviewed-on: marfrit/kernel-agent#7
2026-05-15 15:36:56 +00:00
claude-noether 18da673ccc phase 1: promote vb2_dma_resv RFC v2 + add ka-status + ampere as 2nd aarch64 host
Three changes that together flip kernel-agent from spec'd to operational
in the manual-orchestrated form. Real ka-* CLI verbs come in later phases;
this commit gets a first iteration through the pipeline and proves the
flow at the artifact level.

1. Promote vb2_dma_resv RFC v2 series into the scope-tagged tree

Markus iterated v2 locally on boltzmann (kernel-agent-bootstrap dir,
reaching linux-fresnel-fourier pkgrel=14). v2 attaches the producer
fence at device_run in slept-OK context per Dufresne's v1 review on
linux-media. The three patches land under
patches/subsystem/media/videobuf2/dma-resv-release-fence/:

  - 0004 (helper) — opt-in vb2 dma_resv producer-fence helper
  - 0005 (driver opt-in) — hantro device_run attach
  - 0006 (driver opt-in) — rockchip-rga device_run attach

Numbered 4/5/6 because the fresnel build PKGBUILD applies them after
the three 0001/0002/0003 PBP DTS patches; this directory's numbering
follows that apply-order, not the upstream lore series numbering.

README at the scope dir documents fleet eligibility, decision history,
and the v1 → v2 design pivot.

2. Update fleet/fresnel.yaml to include the v2 series

Pre-v2 manifest had a comment block 'Explicitly NOT included … vb2
dma-resv-release-fence … defer until v2 lands'. v2 has landed. Move
those three lines from 'excluded' to 'includes', annotate the decision
inline.

3. README updates

- Build hosts table: add ampere (CoolPi GenBook, RK3588 32GB) as
  secondary aarch64 host. Same uarch as boltzmann, on-demand wake via
  His. Gives the fleet a second native build target for when boltzmann
  is busy (e.g. carrying a firefox-fourier 4h build).
- 'Out of scope this round' bootstrap section: mark vb2_dma_resv as
  resolved 2026-05-15, keep panfrost IOMMU_CACHE deferred.

4. First ka-* CLI verb implemented: bin/ka-status

bash, ~120 lines. Reads fleet/*.yaml manifests, queries Gitea for
open [ka:*] issues, probes each reachable host for the installed
kernel-package version. Read-only — no sudo, no host writes. Picks
GITEA_TOKEN from /opt/herding/etc/claude-identities/<host>.creds or
env override.

Proves the agent's Gitea-API + manifest-parsing skeleton works
end-to-end without committing to a full ka-promote/build/install
implementation. Smoke-tested locally:

  $ bin/ka-status
  kernel-agent status (repo: marfrit/kernel-agent)
  open [ka:*] issues total: 1
  ══ fresnel ══
    manifest: arch=arm64 soc=rockchip/rk3399 board=pinebook-pro
    package:  linux-fresnel-fourier
    installed: host-down            # (fresnel is currently powered off)
    open ka-issues: (none for this host)

No PKGBUILD update in this PR — that lives in marfrit-packages and
ships as a sibling PR (the actual linux-fresnel-fourier-7.0-14 publish).
2026-05-15 15:32:00 +00:00
101 changed files with 18740 additions and 34 deletions
+7
View File
@@ -0,0 +1,7 @@
# ka-promote / ka-build output
/build/
# transient
*.pyc
__pycache__/
.DS_Store
+44 -24
View File
@@ -106,20 +106,29 @@ 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>
ka-snooze <issue-id> [--for <duration>]
ka-debug <job-id> # shells into the same container that ran the build
ka-status # per-host one-liner with drift/pending state
ka-status # per-host one-liner with drift/pending state [bin/ka-status — implemented Phase 1]
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.
@@ -162,11 +171,13 @@ manifest rewrite); paths stable otherwise.
## Build hosts
```
Host Where Role Wake? Notes
Host Where Role Wake? Notes
──────────────────────────────────────────────────────────────────────────
boltzmann Rock 5 ITX+ aarch64 primary always container kbuild-aarch64
fermi hertz LXD aarch64 fallback always matches kbuild-aarch64 profile
kbuild-x86 data CT x86_64 on-demand wakes via His; idle 30 min → release
boltzmann Rock 5 ITX+ aarch64 primary always container kbuild-aarch64
ampere CoolPi GenBook aarch64 secondary on-demand RK3588 32GB; same uarch as boltzmann,
wakes via His; idle 30 min → release
fermi hertz LXD aarch64 fallback always matches kbuild-aarch64 profile
kbuild-x86 data CT x86_64 on-demand wakes via His; idle 30 min → release
```
Native make on the assigned build host. **No distcc** for kernel-agent
@@ -224,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`
@@ -247,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
@@ -288,10 +302,16 @@ build.
### Out of scope this round (explicit defer)
- **vb2 dma_resv RFC v2** + panfrost IOMMU_CACHE for RK3399 — would have closed
the fresnel-fourier campaign criterion-4 readback transitive-proof gap, but
v2 isn't implemented (RFC v1 rejected upstream). Deferred to a follow-up
build once v2 lands. See `marfrit/dmabuf-modifier-triage#3`.
- **vb2 dma_resv RFC v2** *resolved 2026-05-15.* Markus iterated v2 locally
on boltzmann reaching pkgrel=14; the v2 series attaches the fence at
`device_run` (slept-OK context per Dufresne's v1 review). Now carried in
`patches/subsystem/media/videobuf2/dma-resv-release-fence/` and included
in `fleet/fresnel.yaml`. Still in scope for upstream targeting; default
remains "build-tree only, no PR until explicitly asked"
(`feedback_no_upstream.md`).
- **panfrost IOMMU_CACHE for RK3399** — sibling kernel work that targets the
readback transitive-proof gap that vb2_dma_resv alone doesn't close.
Still deferred until that lands; ship together when ready.
- **Replace** `linux-eos-arm` rather than coexist alongside — preserves easy
rollback at u-boot. Can flip to `provides=(linux-eos-arm) conflicts=(...)`
later once burn-in proves the OC kernel reliable.
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())
Executable
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
#
# ka-status — per-host kernel-agent state summary.
#
# Reads fleet/*.yaml manifests + queries Gitea for open [ka:*] issues +
# probes each host (where reachable) for the installed kernel-package
# version. Designed to give a first-look "what's the state" before any
# ka-promote / ka-install action.
#
# Usage:
# ka-status # summary across all manifests
# ka-status <host> # detail for one host
#
# Read-only. Never mutates state. No sudo. No SSH-into-host writes.
#
# Phase 1 deliverable. Future ka-* CLI verbs (ka-promote / ka-close /
# ka-install) build on the same Gitea-API + manifest-parsing skeleton.
set -euo pipefail
GITEA_URL="${GITEA_URL:-https://git.reauktion.de}"
REPO="${KERNEL_AGENT_REPO:-marfrit/kernel-agent}"
TOKEN_FILE="${KERNEL_AGENT_TOKEN_FILE:-/opt/herding/etc/claude-identities/noether.creds}"
# Resolve token from per-host claude-identity creds, or env override.
token=""
if [ -n "${GITEA_TOKEN:-}" ]; then
token="$GITEA_TOKEN"
elif [ -r "$TOKEN_FILE" ]; then
token="$(grep -E '^GITEA_TOKEN=' "$TOKEN_FILE" | head -1 | cut -d= -f2)"
fi
# Locate fleet/ — script lives in bin/ next to fleet/.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
fleet_dir="${script_dir}/../fleet"
[ -d "$fleet_dir" ] || { echo "fleet/ not found relative to $script_dir" >&2; exit 2; }
api_get() {
local path="$1"
local args=(--silent --show-error --max-time 15)
[ -n "$token" ] && args+=(-H "Authorization: token $token")
curl "${args[@]}" "${GITEA_URL}/api/v1/${path}"
}
# Open ka-prefixed issues, JSON array on stdout.
fetch_ka_issues() {
api_get "repos/${REPO}/issues?state=open&type=issues&limit=50" \
| python3 -c '
import json, sys
issues = json.load(sys.stdin)
def kind(t):
if not t.startswith("["): return None
p = t.find("]")
return t[1:p] if p > 0 else None
out = [{
"number": i["number"],
"title": i["title"],
"kind": kind(i["title"]),
} for i in issues if kind(i["title"]) and kind(i["title"]).startswith("ka:")]
print(json.dumps(out))
' 2>/dev/null || echo "[]"
}
# Parse a fleet/<host>.yaml manifest (very-narrow YAML subset).
manifest_field() {
local file="$1" key="$2"
grep -E "^[[:space:]]*${key}:" "$file" | head -1 | sed -E "s/^[^:]*:[[:space:]]*//; s/[[:space:]]+#.*//; s/^[\"']//; s/[\"']$//"
}
manifest_pkgname() {
local file="$1"
awk '/^package:/{p=1; next} p && /^[a-z]/{p=0} p && /^[[:space:]]+name:/{sub(/^[[:space:]]+name:[[:space:]]*/,""); print; exit}' "$file"
}
# Probe a host for installed kernel-package version (best-effort, non-blocking).
probe_installed() {
local host="$1" pkg="$2"
ssh -o ConnectTimeout=3 -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
"${host}.fritz.box" "pacman -Q '$pkg' 2>/dev/null || dpkg-query -W -f='\${Package} \${Version}\n' '$pkg' 2>/dev/null || echo 'host-up:not-installed'" 2>/dev/null \
|| echo "host-down"
}
issues_json="$(fetch_ka_issues)"
# Per-host issue grouping — match on title containing the host name (cheap heuristic;
# proper kernel-agent will tag issues with a host label).
issues_for_host() {
local host="$1"
echo "$issues_json" | python3 -c "
import json, sys
host = '$host'
issues = json.load(sys.stdin)
hits = [i for i in issues if host in i['title'].lower()]
for h in hits:
print(f\" #{h['number']} [{h['kind']}] {h['title']}\")
" 2>/dev/null
}
print_host() {
local file="$1"
local host pkg
host="$(basename "$file" .yaml)"
pkg="$(manifest_pkgname "$file")"
local arch="$(manifest_field "$file" arch)"
local soc="$(manifest_field "$file" soc)"
local board="$(manifest_field "$file" board)"
printf '\n══ %s ══\n' "$host"
printf ' manifest: arch=%s soc=%s board=%s\n' "$arch" "$soc" "$board"
printf ' package: %s\n' "$pkg"
if [ -n "$pkg" ]; then
printf ' installed: %s\n' "$(probe_installed "$host" "$pkg")"
fi
local n=0
while IFS= read -r line; do
[ -z "$line" ] && continue
if [ $n -eq 0 ]; then printf ' open ka-issues:\n'; fi
printf '%s\n' "$line"
n=$((n+1))
done < <(issues_for_host "$host")
[ $n -eq 0 ] && printf ' open ka-issues: (none for this host)\n'
}
if [ $# -ge 1 ]; then
f="${fleet_dir}/${1}.yaml"
[ -r "$f" ] || { echo "no manifest for host '$1'" >&2; exit 2; }
print_host "$f"
else
printf 'kernel-agent status (repo: %s)\n' "$REPO"
printf 'open [ka:*] issues total: %s\n' "$(echo "$issues_json" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))')"
for f in "$fleet_dir"/*.yaml; do
[ -e "$f" ] || continue
print_host "$f"
done
fi
+119
View File
@@ -0,0 +1,119 @@
# kernel-agent manifest for ampere (CoolPi GenBook / Rockchip RK3588)
#
# Status: bootstrap. ka-promote / ka-build / ka-install CLI not yet
# implemented; the canonical patch set + baseline below is the input
# for the manual flow that produces linux-ampere-fourier (same shape
# as fresnel's bootstrap reference build).
#
# Asks #2 (VP9 enablement on RK3588 rkvdec) and #3 (AV1 dec integration)
# from kernel-agent issue #6 are NOT addressed in this manifest —
# tracked separately for a follow-up sprint.
host: ampere
arch: arm64
soc: rockchip/rk3588
module: coolpi-cm5
board: coolpi-cm5-genbook
distro: archlinux-arm
baseline:
tree: torvalds/linux
url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ref: v7.0-rc3
upstream_compat: linux-7.0-rc3
# 2026-05-16 bisect note: the linux-rk3588-marfrit branch tip @ f8f3ad9
# (18 commits ahead of v7.0-rc3) BLACK-SCREENS ampere — connector
# reports connected/enabled/dpms On + SDDM starts + backlight on, but
# panel shows no pixels. Decomposing the suspend/resume patch (0010
# family) into 5 atomic sub-commits and reverting all 5 did NOT recover
# display (test-arch-plus-pcie3 branch). The 6 patches listed below
# applied on top of v7.0-rc3 directly DO boot with working display
# (ampere-minimal-devices branch @ 7c241f2). Regression source is in
# one of the remaining 12 commits in f8f3ad9 — top suspect:
# 55d1b3dcc05e "clk: rockchip: rk3588: Drop CLK_SET_RATE_PARENT from
# DCLK_VOP2_SRC" (touches display controller clock parent rate). Other
# excluded commits: Shawn Lin pcie3 phy series, Cristian Ciocaltea
# clk/dts/dw-dp fixes, Sebastian Reichel hdmirx Rock 5 ITX, Pedro Alves
# btrtl, and the suspend/resume patch 0010 family. Bisect campaign
# separately.
#
# Branch ampere-minimal-devices on git.reauktion.de/marfrit/linux-rk3588-marfrit
# is the verified-working tip if you want a ready-to-fetch ref instead
# of patches-on-mainline.
# Scope-tagged patch includes. Resolve to patches/<scope>/<file>.patch.
# Apply order matters: pwm15 pinctrl (soc) must precede the genbook
# pwm-fan node consumer.
includes:
- soc/rockchip/rk3588/0001-arm64-dts-rockchip-rk3588-Add-pwm15-pinctrl-entries.patch
- board/coolpi-cm5-genbook/0002-arm64-dts-rockchip-rk3588-coolpi-cm5-genbook-Add-pwm-fan.patch
- module/coolpi-cm5/0003-arm64-dts-rockchip-rk3588-coolpi-cm5-Fix-power-off-by-enabling-RK806-as-system-power-controller.patch
- 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):
# - 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.
# - misc_patches/genbook/kernel/0006 (arm64 Kconfig: do not select
# HAVE_GCC_PLUGINS). Local-only build-config; not for upstream and
# possibly redundant with the linux-rk3588-marfrit branch already.
# - misc_patches/genbook/kernel/0009 (Bluetooth btrtl RTL_SEC_PROJ
# non-fatal). Different subsystem; promote under driver/bluetooth/
# when a campaign demands.
# - misc_patches/genbook/kernel/0010 (multi-driver suspend/wakeup fix).
# Promote under soc/rockchip/rk3588/ or split per affected driver
# once the RockHard campaign decides the upstream-targeting shape.
config:
source: /proc/config.gz on running ampere kernel (7.0.0-rc3-ARCH+, hand-managed before this bootstrap)
strategy: snapshot, fold to baseline, accept-new with rationale on diff
package:
name: linux-ampere-fourier
versioning: "${baseline_ref}.kafr${pkgrel}" # 7.0.rc3.kafr1 etc.
install_mode: alongside
conflicts: []
provides: []
kernel_suffix: -ampere-fourier # /boot/firmware/Image-7.0.0-rc3-ampere-fourier
bootloader: extlinux
bootloader_path: /boot/firmware/extlinux/extlinux.conf # vfat partition on mmcblk0p1
boot_path: /boot/firmware/ # vfat, 1.5G, ~1G free
# ampere boots from a separate FAT partition (mmcblk0p1), unlike fresnel which
# uses the root partition's /boot/. The extlinux-add hook needs to write to
# /boot/firmware/extlinux/extlinux.conf, not /boot/extlinux/.
verify:
bar1_ssh_heartbeat: required
bar2_pkg_version: required
bar3_dtb_match:
- power-off via 'shutdown -h' actually powers down (RK806 system-power-controller wired)
- pwm15 pinctrl exposes group3-pwm15-m0..m3 entries
- audio: speakers driven via ES8316 graph (not just headphone)
bar4_per_patch_probe: opt-in
bar5_burn_in: skip # laptop, runtime not constant
build_host:
primary: ampere # self-host, 8 cores RK3588, 32 GB RAM, native arch
secondary: boltzmann # also RK3588 32 GB, kbuild-aarch64 container surrogate
fallback: fermi # hertz LXD, ALARM aarch64
# No distcc for kernel-agent builds — native make on the assigned host only.
# ampere is self-hosting today because boltzmann was busy with userspace
# builds when the bootstrap ran; either host can take the work.
backup:
pre_install: hertz:/sparfuxdata/kernel-agent-backups/ampere/${replaced_version}/
+17 -10
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.
@@ -23,13 +26,17 @@ 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/0003-arm64-dts-rk3399-pinebook-pro-spi1-max-freq-10MHz.patch
# vb2_dma_resv RFC v2 series — added 2026-05-15 (Markus iterated v2 locally
# on boltzmann reaching pkgrel=14; pre-v2 decision was "defer". v2 attaches
# the fence at device_run in slept-OK context per Dufresne's v1 review).
- subsystem/media/videobuf2/dma-resv-release-fence/0004-media-videobuf2-add-opt-in-dma_resv-producer-fence-h.patch
- subsystem/media/videobuf2/dma-resv-release-fence/0005-media-hantro-attach-dma_resv-release-fence-at-device.patch
- subsystem/media/videobuf2/dma-resv-release-fence/0006-media-rockchip-rga-attach-dma_resv-release-fence-at-.patch
# Explicitly NOT included (tracked elsewhere, decision logged):
# - subsystem/media/videobuf2/dma-resv-release-fence/ (RFC v1 rejected;
# v2 in design — see marfrit/dmabuf-modifier-triage#3. Skip until v2 lands
# or we explicitly accept v1-shape parity with ohm.)
# - driver/panfrost/iommu-cache-rk3399/ (sibling kernel work; ship together
# with vb2_dma_resv when it lands.)
# once it lands. Targets the readback transitive-proof gap that vb2_dma_resv
# alone doesn't close.)
config:
source: /proc/config.gz on running fresnel kernel (linux-eos-arm 6.19.9-99)
+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,70 @@
From: Markus Fritsche <mfritsche@localhost>
Subject: [PATCH 2/2] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: Add pwm-fan with thermal cooling
The CoolPi CM5 GenBook has a PWM-controlled fan connected to pwm15
(mux m3, GPIO1_D7). Add a pwm-fan node driven at 20 kHz (50000 ns
period) with six cooling levels, and wire it into the package thermal
zone with two trip points at 55 °C and 65 °C.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
1 file changed, 43 insertions(+)
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -23,6 +23,13 @@
pwms = <&pwm6 0 25000 0>;
};
+ fan: pwm-fan {
+ compatible = "pwm-fan";
+ #cooling-cells = <2>;
+ pwms = <&pwm15 0 50000 0>;
+ cooling-levels = <1 50 100 150 200 254>;
+ };
+
battery: battery {
compatible = "simple-battery";
charge-full-design-microamp-hours = <9800000>;
@@ -443,3 +450,39 @@
remote-endpoint = <&edp1_in_vp2>;
};
};
+
+&package_thermal {
+ polling-delay = <1000>;
+
+ trips {
+ package_fan0: package-fan0 {
+ temperature = <55000>;
+ hysteresis = <2000>;
+ type = "active";
+ };
+
+ package_fan1: package-fan1 {
+ temperature = <65000>;
+ hysteresis = <2000>;
+ type = "active";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&package_fan0>;
+ cooling-device = <&fan THERMAL_NO_LIMIT 2>;
+ };
+
+ map1 {
+ trip = <&package_fan1>;
+ cooling-device = <&fan 3 THERMAL_NO_LIMIT>;
+ };
+ };
+};
+
+&pwm15 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm15m3_pins>;
+ status = "okay";
+};
@@ -0,0 +1,104 @@
From: Markus Fritsche <mfritsche@localhost>
Subject: [PATCH 4/4] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: enable speaker output
The GenBook carrier board routes the ES8316 HPOL/HPOR outputs to both a
headphone jack and an external speaker amplifier. The amplifier is
enabled by GPIO1_A6 (active-high) and headphone insertion is detected by
GPIO1_B5 (active-high, pull-up).
Add a label to the shared analog-sound node in the CM5 DTSI so the
GenBook DTS can extend it, then override the node to:
- add pa-gpios for the speaker amplifier enable line (GPIO1_A6)
- add hp-det-gpios for headphone jack detection (GPIO1_B5)
- extend widgets/routing to include the Speaker path through the
audio-graph-card built-in "Amplifier" DAPM output-driver widget,
which gates the pa-gpios GPIO on widget power-up/down
- add the hp-det pinctrl group for GPIO1_B5
The "Amplifier" DAPM widget (snd_soc_dapm_out_drv) is provided by
audio-graph-card.c and registered at card level. Its event handler
drives pa-gpios high on SND_SOC_DAPM_POST_PMU and low on
SND_SOC_DAPM_PRE_PMD, giving automatic speaker enable/disable in step
with DAPM power management.
DAPM path for speaker output:
ES8316 AIF1RX (DAI) -> Left/Right DAC -> Left/Right Headphone Mixer
-> Left/Right Headphone Driver -> HPOL/HPOR [codec OUTPUT pins]
-> Amplifier [card OUT_DRV, fires pa-gpios] -> Speaker [SPK terminal]
The Left/Right Headphone Mixer Left/Right DAC Switch controls, which
gate the DAC-to-mixer connections in the DAPM graph, are set on by the
UCM BootSequence in the rk3588-es8316 ALSA UCM profile and must remain
enabled for the path to be traversable.
The HPOL/HPOR codec output pins also feed the Headphones HP widget:
HPOL/HPOR -> Headphones [HP terminal, jack-controlled via hp-det-gpios]
Both the Speaker and Headphones paths are active whenever a PCM stream
is running. Speaker-muting when headphones are inserted is handled at
the userspace (UCM) level via JackHWMute on the Speaker UCM device: when
PipeWire routes audio away from the Speaker sink on headphone insertion,
the absence of an active PCM consumer causes DAPM to power down the
Amplifier widget and drive GPIO1_A6 low.
Note: full speaker output also requires a Speaker SectionDevice in the
rk3588-es8316 ALSA UCM HiFi.conf. Without it the HiFi profile's only
playback port is Headphones (jack-controlled), causing the profile to be
reported as "not available" when no headphones are inserted, and
PipeWire falls back to the pro-audio profile with no speaker sub-device.
A separate patch to alsa-ucm-conf adds the missing Speaker device.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
2 files changed, 25 insertions(+), 1 deletion(-)
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -283,7 +283,31 @@
status = "okay";
};
+&analog_sound {
+ pa-gpios = <&gpio1 RK_PA6 GPIO_ACTIVE_HIGH>;
+ hp-det-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&hp_det>;
+
+ widgets = "Microphone", "Mic Jack",
+ "Headphone", "Headphones",
+ "Speaker", "Speaker";
+
+ routing = "MIC2", "Mic Jack",
+ "Headphones", "HPOL",
+ "Headphones", "HPOR",
+ "Amplifier", "HPOL",
+ "Amplifier", "HPOR",
+ "Speaker", "Amplifier";
+};
+
&pinctrl {
+ headphone {
+ hp_det: hp-det {
+ rockchip,pins = <1 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
lcd {
lcdpwr_en: lcdpwr-en {
rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>;
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5.dtsi
@@ -21,7 +21,7 @@
serial2 = &uart2;
};
- analog-sound {
+ analog_sound: analog-sound {
compatible = "audio-graph-card";
dais = <&i2s0_8ch_p0>;
label = "rk3588-es8316";
@@ -0,0 +1,226 @@
From: Markus Fritsche <mfritsche@localhost>
Date: Tue, 24 Mar 2026 00:00:00 +0000
Subject: [PATCH] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: add USB-C PD charging
The GenBook carrier board exposes a USB Type-C port driven by the
RK3588 USB3OTG0 controller (usb_host0_xhci / usbdp_phy0 combo). The
port uses a Fairchild FUSB302 (I²C address 0x22, bus i2c4) for USB
Power Delivery negotiation. Hardware signals:
GPIO0_PD5 FUSB302 interrupt (active-low, shared with CC logic)
GPIO0_PA0 VBUS switch enable (active-high)
Without these changes the kernel registers usb_host0_xhci in
peripheral-only, high-speed mode, which prevents PD negotiation and
leaves the port unable to charge the battery.
Changes:
* Add #include <dt-bindings/usb/pd.h> for PDO_FIXED macros.
* Add vcc5v0_otg regulator: GPIO0_PA0 active-high switch that gates
VBUS; used as vbus-supply for the FUSB302 and connected via the new
typec5v_pwren pinctrl entry. The previously defined but unreferenced
usb_otg_pwren entry (same pin, wrong pull direction) is removed.
* Add usbc0 (FUSB302) node inside &i2c4:
- compatible "fcs,fusb302", reg 0x22
- interrupt GPIO0_PD5 (IRQ_TYPE_LEVEL_LOW), pinctrl usbc0_int
- usb-c-connector child with:
data-role / power-role "dual", try-power-role "sink"
pd-revision 2.0 Ver 1.2 (maximum supported by FUSB302)
sink-pdos: 5 V/3 A, 9 V/3 A, 12 V/3 A, 15 V/3 A
source-pdos: 5 V/3 A
DisplayPort alt-mode (SVID 0xff01) declared for orientation
switching; three connector ports linking HS (→ usb_host0_xhci),
SS (→ usbdp_phy0) and SBU (→ usbdp_phy0) endpoints.
* Expand &usbdp_phy0 to add mode-switch and orientation-switch
capabilities; register endpoint@0 (SS, linked to usbc0_ss) and
endpoint@1 (SBU, linked to usbc0_sbu).
* Replace the &usb_host0_xhci override:
- remove dr_mode "peripheral" and maximum-speed "high-speed"
(the base DTSI already sets dr_mode "otg")
- add usb-role-switch and the port endpoint linked to usbc0_hs
This allows the TCPM stack to switch the controller between host
and device role as the PD contract dictates.
* Add pinctrl group usb-typec with:
- usbc0_int: GPIO0_PD5 pull-up (FUSB302 /INT)
- typec5v_pwren: GPIO0_PA0 pull-down (VBUS switch, default off)
DAPM / power flow:
USB-C charger → FUSB302 CC negotiation → TCPM requests VBUS →
vcc5v0_otg regulator enables GPIO0_PA0 → VBUS present on port →
DWC3 OTG detects VBUS, enters device/host mode per PD data-role.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
1 file changed, 106 insertions(+), 6 deletions(-)
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -8,6 +8,7 @@
#include <dt-bindings/leds/common.h>
#include <dt-bindings/soc/rockchip,vop2.h>
+#include <dt-bindings/usb/pd.h>
#include "rk3588-coolpi-cm5.dtsi"
/ {
@@ -153,6 +154,18 @@
pinctrl-0 = <&usb_host_pwren>;
vin-supply = <&vcc5v0_usb>;
};
+
+ vcc5v0_otg: regulator-vcc5v0-otg {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc5v0_otg";
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ enable-active-high;
+ gpio = <&gpio0 RK_PA0 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&typec5v_pwren>;
+ vin-supply = <&vcc5v0_sys>;
+ };
};
&edp1 {
@@ -239,6 +252,65 @@
monitored-battery = <&battery>;
power-supplies = <&charger>;
};
+
+ usbc0: usb-typec@22 {
+ compatible = "fcs,fusb302";
+ reg = <0x22>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PD5 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&usbc0_int>;
+ vbus-supply = <&vcc5v0_otg>;
+
+ usb_con: connector {
+ compatible = "usb-c-connector";
+ label = "USB-C";
+ data-role = "dual";
+ power-role = "dual";
+ try-power-role = "sink";
+ op-sink-microwatt = <1000000>;
+ /* FUSB302 supports PD Rev 2.0 Ver 1.2 */
+ pd-revision = /bits/ 8 <0x2 0x0 0x1 0x2>;
+ sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>,
+ <PDO_FIXED(9000, 3000, PDO_FIXED_USB_COMM)>,
+ <PDO_FIXED(12000, 3000, PDO_FIXED_USB_COMM)>,
+ <PDO_FIXED(15000, 3000, PDO_FIXED_USB_COMM)>;
+ source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
+
+ altmodes {
+ displayport {
+ svid = /bits/ 16 <0xff01>;
+ vdo = <0xffffffff>;
+ };
+ };
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ usbc0_hs: endpoint {
+ remote-endpoint = <&usb_host0_xhci_to_usbc0>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ usbc0_ss: endpoint {
+ remote-endpoint = <&usbdp_phy0_ss>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ usbc0_sbu: endpoint {
+ remote-endpoint = <&usbdp_phy0_sbu>;
+ };
+ };
+ };
+ };
+ };
};
&i2c5 {
@@ -323,15 +395,21 @@
rockchip,pins = <1 RK_PD5 RK_FUNC_GPIO &pcfg_pull_up>;
};
- usb_otg_pwren: usb-otg-pwren {
- rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;
- };
-
usb_host_pwren: usb-host-pwren {
rockchip,pins = <1 RK_PA7 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
+ usb-typec {
+ usbc0_int: usbc0-int {
+ rockchip,pins = <0 RK_PD5 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+
+ typec5v_pwren: typec5v-pwren {
+ rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+ };
+
wifi {
bt_pwron: bt-pwron {
rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>;
@@ -387,7 +465,24 @@
};
&usbdp_phy0 {
+ mode-switch;
+ orientation-switch;
status = "okay";
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ usbdp_phy0_ss: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&usbc0_ss>;
+ };
+
+ usbdp_phy0_sbu: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usbc0_sbu>;
+ };
+ };
};
&u2phy1 {
@@ -431,9 +526,14 @@
/* Type C port */
&usb_host0_xhci {
- dr_mode = "peripheral";
- maximum-speed = "high-speed";
+ usb-role-switch;
status = "okay";
+
+ port {
+ usb_host0_xhci_to_usbc0: endpoint {
+ remote-endpoint = <&usbc0_hs>;
+ };
+ };
};
/* connected to a HUB for camera and BT */
@@ -0,0 +1,94 @@
From: Markus Fritsche <mfritsche@localhost>
Date: Tue, 24 Mar 2026 00:00:00 +0000
Subject: [PATCH] arm64: dts: rockchip: rk3588-coolpi-cm5-genbook: add lid switch and USB3 PHY lane config
The GenBook laptop has two features missing from the mainline DTS:
1. Lid switch (MH248 hall-effect sensor on GPIO0_PB0)
The lid state is reported via a gpio-keys node using SW_LID / EV_SW.
The GPIO is active-low (low = lid closed) and pulled up.
wakeup-source is set so that opening the lid can wake the system from
suspend. A sensor pinctrl group is added for the GPIO configuration.
2. usbdp_phy1 lane mux (rockchip,dp-lane-mux = <2 3>)
The RK3588 usbdp_phy1 is a combo USB3 + DisplayPort PHY shared between
the USB3 host1 port (USB-A connector) and the DisplayPort output on the
GenBook board. Lanes 0+1 carry USB3 SuperSpeed to the USB-A socket;
lanes 2+3 are routed to the DP connector.
Without this property the phy-rockchip-usbdp driver selects
UDPHY_MODE_USB and configures all four lanes as USB3. That conflicts
with the physical routing on this board: lanes 2+3 are not connected to
the USB-A socket, so the 4-lane USB3 training fails silently and the
USB3 host1 DWC3 controller (usb_host1_xhci) cannot enumerate devices.
Setting rockchip,dp-lane-mux = <2 3> restricts USB3 to lanes 0+1,
matching the physical wiring, and enables correct SuperSpeed operation
on the USB-A port.
The USB2 EHCI host1 controller (usb_host1_ehci) shares the USB power
domain with usb_host1_xhci. A stalled USB3 DWC3 probe can delay
power-domain activation in a way that prevents the EHCI hub (which
carries the integrated webcam) from being enumerated. With the correct
lane mux both controllers probe cleanly and the webcam is detected.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@localhost>
---
1 file changed, 23 insertions(+)
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts
@@ -8,6 +8,7 @@
#include <dt-bindings/leds/common.h>
#include <dt-bindings/soc/rockchip,vop2.h>
+#include <dt-bindings/input/linux-event-codes.h>
#include <dt-bindings/usb/pd.h>
#include "rk3588-coolpi-cm5.dtsi"
@@ -166,6 +167,21 @@
pinctrl-0 = <&typec5v_pwren>;
vin-supply = <&vcc5v0_sys>;
};
+
+ gpio-key-lid {
+ compatible = "gpio-keys";
+ pinctrl-names = "default";
+ pinctrl-0 = <&mh248_irq_gpio>;
+
+ lid {
+ debounce-interval = <20>;
+ gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_LOW>;
+ label = "Lid";
+ linux,code = <SW_LID>;
+ linux,input-type = <EV_SW>;
+ wakeup-source;
+ };
+ };
};
&edp1 {
@@ -431,6 +447,12 @@
rockchip,pins = <4 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
+
+ sensor {
+ mh248_irq_gpio: mh248-irq-gpio {
+ rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
};
&pwm6 {
@@ -512,6 +534,7 @@
};
&usbdp_phy1 {
+ rockchip,dp-lane-mux = <2 3>;
status = "okay";
};
@@ -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
@@ -0,0 +1,33 @@
# board/coolpi-cm5-genbook
Patches specific to the CoolPi GenBook laptop carrier (RK3588 +
CoolPi-CM5 SoM, ARM laptop form factor). The board adds an
ES8316-routed audio path, FUSB302 USB-C-PD controller, lid switch,
USB3 phy-lane config, and a pwm-controlled fan — none of which are
in mainline upstream because the board ships outside Rockchip's
official reference designs.
## Patches
| File | Adds |
|---|---|
| `0002-...Add-pwm-fan.patch` | pwm15-driven thermal cooling fan node (depends on `soc/rockchip/rk3588/0001-...pwm15-pinctrl-entries.patch`) |
| `0004-...Enable-speaker-output-via-audio-graph-card.patch` | rewires the ES8316 sound graph so HPOL/HPOR also drive the laptop speakers |
| `0005-...Enable-USB-C-PD-charging-via-FUSB302.patch` | FUSB302 controller binding for USB-C PD charging |
| `0008-...Add-lid-switch-and-USB3-PHY-lane-config.patch` | lid GPIO + USB3 phy-lane config |
Numbered 2/4/5/8 to match the upstream-series numbering Markus uses in
`misc_patches/genbook/kernel/` — gap at 0006 (an SoC-wide
`arm64/Kconfig` build-flag patch tracked elsewhere) and 0007 (skipped
in the upstream series). 0009 (Bluetooth btrtl) and 0010 (multi-driver
suspend/wakeup fix) are tracked in other scopes (driver/bluetooth,
soc/rockchip/rk3588) and will be promoted as their respective
campaigns demand.
## Fleet eligibility
- **ampere** (CoolPi CM5 GenBook): primary target, all 4 patches
included in `fleet/ampere.yaml`.
No other current fleet host uses the CoolPi CM5 GenBook carrier, so
no second consumer yet.
+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.**
@@ -0,0 +1,29 @@
From: Markus Fritsche <mfritsche@localhost>
Subject: [PATCH 3/3] arm64: dts: rockchip: rk3588-coolpi-cm5: fix power-off by enabling RK806 as system power controller
Without the system-power-controller property the rk8xx-core driver never
registers its sys_off handler (rk808_power_off), which writes the DEV_OFF
bit to RK806_SYS_CFG3 at shutdown time. As a result the RK806 PMIC does
not cut power and the board remains partially active after "poweroff" —
the heartbeat LED stops but internal activity continues.
All other mainline RK3588 boards that use the RK806 carry this property
(NanoPC-T6, Rock 5A, OrangePi 5, Jaguar, ...). Add it to the CoolPi CM5
PMIC node to restore proper power-off behaviour.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
1 file changed, 1 insertion(+)
--- a/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5.dtsi
@@ -346,6 +346,7 @@
interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
gpio-controller;
#gpio-cells = <2>;
+ system-power-controller;
pinctrl-names = "default";
pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>,
<&rk806_dvs2_null>, <&rk806_dvs3_null>;
+15
View File
@@ -0,0 +1,15 @@
# module/coolpi-cm5
Patches that target the CoolPi CM5 system-on-module (RK3588-based,
sold as a carrier-board-agnostic compute module). They apply to any
carrier board that hosts the CM5 — currently only the GenBook in our
fleet, but the scope tag exists so that a hypothetical second carrier
wouldn't have to clone the patch under `board/<other-carrier>/`.
## Patches
- `0003-arm64-dts-rockchip-rk3588-coolpi-cm5-Fix-power-off-by-enabling-RK806-as-system-power-controller.patch`
marks the on-module RK806 PMIC as `system-power-controller` so the
rk8xx-core driver registers it for the `pm_power_off` hook. Without
this, `poweroff` reaches `arm_pm_restart` instead and the CM5 reboots
on shutdown.
@@ -0,0 +1,52 @@
From: Markus Fritsche <mfritsche@localhost>
Subject: [PATCH 1/2] arm64: dts: rockchip: rk3588: Add pwm15 pinctrl entries
Add pinctrl entries for all four mux options of the RK3588 pwm15
controller (m0-m3) to rk3588-extra-pinctrl.dtsi, marked with
/omit-if-no-ref/ so they are only compiled in when referenced.
Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
1 file changed, 30 insertions(+)
--- a/arch/arm64/boot/dts/rockchip/rk3588-extra-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-extra-pinctrl.dtsi
@@ -342,6 +342,36 @@
};
};
+ pwm15 {
+ /omit-if-no-ref/
+ pwm15m0_pins: pwm15m0-pins {
+ rockchip,pins =
+ /* pwm15_ir_m0 */
+ <3 RK_PC3 11 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ pwm15m1_pins: pwm15m1-pins {
+ rockchip,pins =
+ /* pwm15_ir_m1 */
+ <4 RK_PB3 11 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ pwm15m2_pins: pwm15m2-pins {
+ rockchip,pins =
+ /* pwm15_ir_m2 */
+ <1 RK_PC6 11 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ pwm15m3_pins: pwm15m3-pins {
+ rockchip,pins =
+ /* pwm15_ir_m3 */
+ <1 RK_PD7 11 &pcfg_pull_none>;
+ };
+ };
+
sdio {
/omit-if-no-ref/
sdiom0_pins: sdiom0-pins {
+14
View File
@@ -0,0 +1,14 @@
# soc/rockchip/rk3588
SoC-wide RK3588 patches that any board built on this SoC benefits from.
Distinct from `board/<name>/` (specific board configuration) and
`module/<som-name>/` (specific SoM configuration that several boards
share).
## Patches
- `0001-arm64-dts-rockchip-rk3588-Add-pwm15-pinctrl-entries.patch`
fills in the four mux options for RK3588's `pwm15`. Cosmetic
upstream-quality fix; required as a *prerequisite* by board patches
that want to wire pwm15 to a real consumer (e.g. CoolPi GenBook's
pwm-fan, see `patches/board/coolpi-cm5-genbook/0002-...`).
@@ -0,0 +1,356 @@
From a202de1646d4c8f8ee2ebc2e4c100b621975754a Mon Sep 17 00:00:00 2001
In-Reply-To: <20260429195306.239666-1-mfritsche@reauktion.de>
References: <20260429195306.239666-1-mfritsche@reauktion.de>
From: Markus Fritsche <mfritsche@reauktion.de>
Date: Sat, 9 May 2026 16:16:07 +0200
Subject: [PATCH RFC v2] media: videobuf2: add opt-in dma_resv producer fence
helper
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
V4L2 producers historically don't propagate buffer-state-done into
the dmabuf's dma_resv exclusive fence. Userspace consumers that
import V4L2-produced dmabufs and wait on the dmabuf's implicit-sync
fence (poll(POLLIN), DMA_BUF_IOCTL_EXPORT_SYNC_FILE,
EGL_LINUX_DMA_BUF_EXT) currently see either zero fences or a stub
fence from dma_fence_get_stub(). This is correct by accident for the
common DQBUF-then-import case but represents a contract gap that
breaks Wayland compositors importing CAPTURE buffers from a stateless
H.264 decoder under continuous playback on implicit-sync GPU stacks
(observed on RK3566 + hantro VPU + Mali-G52 panfrost; manifests as
green frames -- BT.709 limited-range YUV(0,0,0) -> RGB(0,77,0) -- when
the GPU samples the dmabuf before the producer's decode completes).
Add an opt-in API gated by both a per-driver runtime flag
(vb2_queue::supports_release_fences) and a Kconfig
(CONFIG_VIDEOBUF2_RELEASE_FENCES, default n) that lets producers
populate a real dma_resv exclusive write fence on the dmabufs they
export. Drivers call vb2_buffer_attach_release_fence(vb) at a
finite-time-fenced point in their pipeline (typically m2m
device_run, just before the HW kick); vb2_buffer_done() signals and
puts the fence as part of its state transition.
The publish and signal paths are wrapped in
dma_fence_begin_signalling() / dma_fence_end_signalling() so
PROVE_LOCKING can validate that nothing taken in those critical
sections deadlocks against the signal path. dma_resv_lock is
sleepable but not taken on the signal path, so taking it inside the
publish critical section is safe under lockdep.
Skips planes whose vb2_plane.dbuf is NULL -- buffers never exported
via VIDIOC_EXPBUF (or imported via V4L2_MEMORY_DMABUF) have no
dmabuf for userspace to wait on.
Drivers that don't opt in pay nothing: the helper is a no-op stub
when CONFIG_VIDEOBUF2_RELEASE_FENCES=n, and an early-return check
of supports_release_fences when =y but the flag is unset.
Validated on RK3566 PineTab2 with PROVE_LOCKING enabled: 30s of
bbb_1080p30 H.264 stateless decode + zero-copy panfrost EGL import
via dmabuf-wayland (mpv 0.41 + KWin 6.6.4 + Mesa panfrost 26.0.5)
produces 31,816 dma_fence init/signal pairs across 5,724 vb2 buffer
cycles with zero lockdep splats from videobuf2 / dma_resv code paths.
Subsequent patches in this series opt the hantro and rockchip-rga
drivers in.
Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Christian König <christian.koenig@amd.com>
Cc: Nicolas Dufresne <nicolas@ndufresne.ca>
Cc: Sumit Semwal <sumit.semwal@linaro.org>
Cc: Hans Verkuil <hverkuil@xs4all.nl>
Cc: Tomasz Figa <tfiga@chromium.org>
Cc: linux-media@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: linaro-mm-sig@lists.linaro.org
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
drivers/media/common/videobuf2/Kconfig | 29 ++++
.../media/common/videobuf2/videobuf2-core.c | 135 ++++++++++++++++++
include/media/videobuf2-core.h | 51 +++++++
3 files changed, 215 insertions(+)
diff --git a/drivers/media/common/videobuf2/Kconfig b/drivers/media/common/videobuf2/Kconfig
index d2223a12c..bbfa26984 100644
--- a/drivers/media/common/videobuf2/Kconfig
+++ b/drivers/media/common/videobuf2/Kconfig
@@ -30,3 +30,32 @@ config VIDEOBUF2_DMA_SG
config VIDEOBUF2_DVB
tristate
select VIDEOBUF2_CORE
+
+config VIDEOBUF2_RELEASE_FENCES
+ bool "videobuf2: opt-in dma_resv producer fences for V4L2 dmabuf exports"
+ depends on VIDEOBUF2_CORE
+ depends on DMA_SHARED_BUFFER
+ default n
+ help
+ Enables an opt-in API that lets vb2 producers populate a dma_resv
+ exclusive write fence on the dmabufs they export to userspace.
+ The fence is signalled when the buffer transitions to
+ VB2_BUF_STATE_DONE.
+
+ This gives userspace consumers that import V4L2-produced dmabufs
+ and wait on the dmabuf's implicit-sync fence (poll(POLLIN),
+ DMA_BUF_IOCTL_EXPORT_SYNC_FILE, EGL_LINUX_DMA_BUF_EXT) a real
+ producer fence to wait on, instead of a stub fence from
+ dma_fence_get_stub() that the dma_buf core substitutes when
+ dma_resv is empty.
+
+ Drivers individually opt in by setting
+ vb2_queue::supports_release_fences = true and calling
+ vb2_buffer_attach_release_fence() at the right point in their
+ pipeline (typically m2m device_run, just before HW kick).
+
+ Distributors leave this off unless targeting Wayland/EGL
+ consumers of V4L2 stateless decoder output on
+ implicit-sync-only GPU stacks (e.g. mainline panfrost).
+
+ If unsure, say N.
diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c
index adf668b21..85d7fddbd 100644
--- a/drivers/media/common/videobuf2/videobuf2-core.c
+++ b/drivers/media/common/videobuf2/videobuf2-core.c
@@ -26,6 +26,12 @@
#include <linux/freezer.h>
#include <linux/kthread.h>
+#ifdef CONFIG_VIDEOBUF2_RELEASE_FENCES
+#include <linux/dma-fence.h>
+#include <linux/dma-resv.h>
+#include <linux/dma-buf.h>
+#endif
+
#include <media/videobuf2-core.h>
#include <media/v4l2-mc.h>
@@ -1173,6 +1179,120 @@ void *vb2_plane_cookie(struct vb2_buffer *vb, unsigned int plane_no)
}
EXPORT_SYMBOL_GPL(vb2_plane_cookie);
+#ifdef CONFIG_VIDEOBUF2_RELEASE_FENCES
+/*
+ * dma_resv release-fence integration.
+ *
+ * Optional, opt-in path that lets producers publish a real
+ * dma_fence on their CAPTURE-side dmabufs so userspace consumers
+ * (compositors, EGL importers) get spec-clean implicit-sync
+ * semantics instead of the dma_buf core's stub fence. Drivers
+ * call vb2_buffer_attach_release_fence() at a finite-time-fenced
+ * point (typically m2m device_run) and the fence is signalled by
+ * vb2_buffer_done(). Gated at runtime by
+ * vb2_queue::supports_release_fences and at compile time by
+ * CONFIG_VIDEOBUF2_RELEASE_FENCES.
+ */
+
+static const char *vb2_dma_resv_get_driver_name(struct dma_fence *fence)
+{
+ return "videobuf2";
+}
+
+static const char *vb2_dma_resv_get_timeline_name(struct dma_fence *fence)
+{
+ return "vb2-release-fence";
+}
+
+static const struct dma_fence_ops vb2_dma_resv_fence_ops = {
+ .get_driver_name = vb2_dma_resv_get_driver_name,
+ .get_timeline_name = vb2_dma_resv_get_timeline_name,
+};
+
+int vb2_buffer_attach_release_fence(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ struct dma_fence *fence;
+ unsigned int plane;
+ bool cookie;
+
+ if (!q->supports_release_fences)
+ return 0;
+
+ if (WARN_ON(vb->release_fence))
+ return -EINVAL;
+
+ fence = kzalloc(sizeof(*fence), GFP_KERNEL);
+ if (!fence)
+ return -ENOMEM;
+
+ dma_fence_init(fence, &vb2_dma_resv_fence_ops, &q->dma_resv_fence_lock,
+ q->dma_resv_fence_context,
+ atomic64_inc_return(&q->dma_resv_fence_seqno));
+
+ /*
+ * Annotate the publish-side critical section. Per
+ * Documentation/driver-api/dma-buf.rst, lockdep validates
+ * that nothing taken in this region can deadlock against
+ * the signal path in vb2_buffer_signal_release_fence().
+ * dma_resv_lock is sleepable but is not taken on the signal
+ * path, so taking it inside the critical section is safe.
+ */
+ cookie = dma_fence_begin_signalling();
+ for (plane = 0; plane < vb->num_planes; plane++) {
+ struct dma_buf *dbuf = vb->planes[plane].dbuf;
+
+ if (!dbuf)
+ continue;
+
+ dma_resv_lock(dbuf->resv, NULL);
+ dma_resv_add_fence(dbuf->resv, fence, DMA_RESV_USAGE_WRITE);
+ dma_resv_unlock(dbuf->resv);
+ }
+ dma_fence_end_signalling(cookie);
+
+ /* One reference for the eventual signal in vb2_buffer_done. */
+ vb->release_fence = dma_fence_get(fence);
+
+ /* The dma_resv held its own reference per plane. Drop ours. */
+ dma_fence_put(fence);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vb2_buffer_attach_release_fence);
+
+static void vb2_buffer_signal_release_fence(struct vb2_buffer *vb,
+ enum vb2_buffer_state state)
+{
+ struct dma_fence *fence = vb->release_fence;
+ bool cookie;
+
+ if (!fence)
+ return;
+
+ cookie = dma_fence_begin_signalling();
+ if (state == VB2_BUF_STATE_ERROR)
+ dma_fence_set_error(fence, -EIO);
+ dma_fence_signal(fence);
+ dma_fence_end_signalling(cookie);
+
+ dma_fence_put(fence);
+ vb->release_fence = NULL;
+}
+#else /* !CONFIG_VIDEOBUF2_RELEASE_FENCES */
+
+int vb2_buffer_attach_release_fence(struct vb2_buffer *vb)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vb2_buffer_attach_release_fence);
+
+static inline void vb2_buffer_signal_release_fence(struct vb2_buffer *vb,
+ enum vb2_buffer_state state)
+{
+}
+#endif /* CONFIG_VIDEOBUF2_RELEASE_FENCES */
+
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{
struct vb2_queue *q = vb->vb2_queue;
@@ -1199,6 +1319,9 @@ void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
if (state != VB2_BUF_STATE_QUEUED)
__vb2_buf_mem_finish(vb);
+ if (state != VB2_BUF_STATE_QUEUED)
+ vb2_buffer_signal_release_fence(vb, state);
+
spin_lock_irqsave(&q->done_lock, flags);
if (state == VB2_BUF_STATE_QUEUED) {
vb->state = VB2_BUF_STATE_QUEUED;
@@ -2651,6 +2774,18 @@ int vb2_core_queue_init(struct vb2_queue *q)
mutex_init(&q->mmap_lock);
init_waitqueue_head(&q->done_wq);
+#ifdef CONFIG_VIDEOBUF2_RELEASE_FENCES
+ /*
+ * Per-queue dma_resv release-fence context. Drivers that
+ * opt in via supports_release_fences and call
+ * vb2_buffer_attach_release_fence() use these to allocate
+ * fences on a single per-queue timeline.
+ */
+ q->dma_resv_fence_context = dma_fence_context_alloc(1);
+ atomic64_set(&q->dma_resv_fence_seqno, 0);
+ spin_lock_init(&q->dma_resv_fence_lock);
+#endif
+
q->memory = VB2_MEMORY_UNKNOWN;
if (q->buf_struct_size == 0)
diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h
index 4424d481d..766ff2194 100644
--- a/include/media/videobuf2-core.h
+++ b/include/media/videobuf2-core.h
@@ -288,6 +288,16 @@ struct vb2_buffer {
unsigned int skip_cache_sync_on_finish:1;
struct vb2_plane planes[VB2_MAX_PLANES];
+#ifdef CONFIG_VIDEOBUF2_RELEASE_FENCES
+ /*
+ * Producer release fence published on each plane's
+ * dmabuf->resv when the driver opts in via
+ * vb2_buffer_attach_release_fence(). Signalled and put by
+ * vb2_buffer_done() on transition to DONE/ERROR. NULL when
+ * the driver did not opt in for this buffer.
+ */
+ struct dma_fence *release_fence;
+#endif
struct list_head queued_entry;
struct list_head done_entry;
#ifdef CONFIG_VIDEO_ADV_DEBUG
@@ -648,6 +658,19 @@ struct vb2_queue {
spinlock_t done_lock;
wait_queue_head_t done_wq;
+#ifdef CONFIG_VIDEOBUF2_RELEASE_FENCES
+ /*
+ * dma_resv release-fence context. Drivers that set
+ * supports_release_fences and call
+ * vb2_buffer_attach_release_fence() use these to allocate
+ * fences on a per-queue timeline.
+ */
+ u64 dma_resv_fence_context;
+ atomic64_t dma_resv_fence_seqno;
+ spinlock_t dma_resv_fence_lock;
+#endif
+
+ unsigned int supports_release_fences:1;
unsigned int streaming:1;
unsigned int start_streaming_called:1;
unsigned int error:1;
@@ -735,6 +758,34 @@ void *vb2_plane_cookie(struct vb2_buffer *vb, unsigned int plane_no);
*/
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state);
+/**
+ * vb2_buffer_attach_release_fence() - opt-in dma_resv release fence.
+ * @vb: the buffer being committed to the producer.
+ *
+ * Drivers that have set vb2_queue::supports_release_fences may call
+ * this from any sleepable context where they have committed to
+ * running the operation in finite time -- typically m2m
+ * device_run(), just before the HW kick. The helper allocates a
+ * dma_fence on the queue's per-queue timeline, attaches it as
+ * DMA_RESV_USAGE_WRITE on each plane's dmabuf->resv, and stashes
+ * it in vb->release_fence. vb2_buffer_done() signals and puts the
+ * fence as part of the buffer's state transition.
+ *
+ * Skips planes whose vb2_plane.dbuf is NULL -- buffers never
+ * exported via VIDIOC_EXPBUF (or imported via V4L2_MEMORY_DMABUF)
+ * have no dmabuf for userspace to wait on.
+ *
+ * No-op when vb2_queue::supports_release_fences is not set
+ * (regardless of CONFIG_VIDEOBUF2_RELEASE_FENCES). When
+ * CONFIG_VIDEOBUF2_RELEASE_FENCES=n, this is a stub that returns 0.
+ *
+ * Returns 0 on success or when the no-op stub is in effect,
+ * negative errno on allocation failure when fence publishing was
+ * attempted. Best-effort: drivers should ignore the return value
+ * unless they want diagnostics.
+ */
+int vb2_buffer_attach_release_fence(struct vb2_buffer *vb);
+
/**
* vb2_discard_done() - discard all buffers marked as DONE.
* @q: pointer to &struct vb2_queue with videobuf2 queue.
--
2.53.0
@@ -0,0 +1,95 @@
From 1844c263bde8dd244d7db46f8c508e7c70da459c Mon Sep 17 00:00:00 2001
In-Reply-To: <20260429195306.239666-1-mfritsche@reauktion.de>
References: <20260429195306.239666-1-mfritsche@reauktion.de>
From: Markus Fritsche <mfritsche@reauktion.de>
Date: Sat, 9 May 2026 16:24:01 +0200
Subject: [PATCH RFC v2] media: hantro: attach dma_resv release fence at
device_run
Opt the hantro driver into the new vb2 release-fence helper so its
CAPTURE-side dmabufs carry a real producer fence that wayland
compositors and other implicit-sync consumers can wait on, instead
of the dma_buf core's stub fence.
Attach point is m2m device_run, immediately after
v4l2_m2m_buf_copy_metadata() and before ctx->codec_ops->run().
Per Nicolas Dufresne's v1 review (lore.kernel.org/linux-media/
3d8deeb15581b754e4c061d4c4a13657aa08bc3c.camel@ndufresne.ca/),
this satisfies the dma_fence finite-time contract: the m2m core
has committed to running the job by this point, codec_ops->run
either kicks the HW (decode-complete signals the fence via
vb2_buffer_done) or fails immediately (job_finish with
VB2_BUF_STATE_ERROR signals with -EIO). PM and clocks are already
up by this point, so no allocation context restrictions.
The CAPTURE queue is opted in with supports_release_fences=true at
queue_init.
Userspace consumers that import hantro CAPTURE dmabufs and wait on
their implicit-sync fence (Wayland zwp_linux_dmabuf_v1 +
panfrost EGL_LINUX_DMA_BUF_EXT) now wait on a real fence
representing the producer's actual completion, fixing green-frame
corruption observed on RK3566 PineTab2 + Mali-G52 panfrost (the
GPU was sampling zero pages because the dmabuf's implicit fence
was the dma_buf core's pre-signalled stub).
Validated end-to-end on PineTab2 (RK3566 / hantro G1 / Mali-G52
mainline panfrost): 30s of bbb_1080p30 H.264 stateless decode +
zero-copy panfrost EGL import via dmabuf-wayland (mpv 0.41 +
KWin 6.6.4 + Mesa panfrost 26.0.5) renders correctly with no
green-frame corruption and no PROVE_LOCKING splats.
Cc: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Nicolas Dufresne <nicolas@ndufresne.ca>
Cc: linux-media@vger.kernel.org
Cc: linux-rockchip@lists.infradead.org
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
.../media/platform/verisilicon/hantro_drv.c | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c
index 2e81877f6..6a66c47ed 100644
--- a/drivers/media/platform/verisilicon/hantro_drv.c
+++ b/drivers/media/platform/verisilicon/hantro_drv.c
@@ -186,6 +186,22 @@ static void device_run(void *priv)
v4l2_m2m_buf_copy_metadata(src, dst);
+ /*
+ * Attach a producer fence on the CAPTURE-side dmabuf so userspace
+ * importers (e.g. Wayland compositors) get spec-clean implicit-sync
+ * semantics. Called from device_run rather than buf_queue: the
+ * dma_fence finite-time contract requires that once a fence is
+ * published, the producer must signal it in finite time. By the
+ * time we reach device_run, the m2m core has committed to running
+ * this job, and the next hop (codec_ops->run) either kicks the HW
+ * (decode-complete signals the fence via vb2_buffer_done) or
+ * fails immediately (job_finish with VB2_BUF_STATE_ERROR signals
+ * the fence with -EIO). Either path resolves the fence in finite
+ * time. Best-effort: a NOMEM here means we lose implicit-sync
+ * precision for this frame, no functional regression.
+ */
+ (void)vb2_buffer_attach_release_fence(&dst->vb2_buf);
+
if (ctx->codec_ops->run(ctx))
goto err_cancel_job;
@@ -249,6 +265,13 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
dst_vq->lock = &ctx->dev->vpu_mutex;
dst_vq->dev = ctx->dev->v4l2_dev.dev;
+ /*
+ * Opt the CAPTURE queue into vb2 release-fence publishing.
+ * No-op unless CONFIG_VIDEOBUF2_RELEASE_FENCES=y; runtime cost
+ * is one extra fence allocation + dma_resv update per device_run.
+ */
+ dst_vq->supports_release_fences = true;
+
return vb2_queue_init(dst_vq);
}
--
2.53.0
@@ -0,0 +1,117 @@
From 2c63a63bf65739763051dc4ce7ce2ffaf2d514c4 Mon Sep 17 00:00:00 2001
In-Reply-To: <20260429195306.239666-1-mfritsche@reauktion.de>
References: <20260429195306.239666-1-mfritsche@reauktion.de>
From: Markus Fritsche <mfritsche@reauktion.de>
Date: Sat, 9 May 2026 16:50:51 +0200
Subject: [PATCH RFC v2] media: rockchip-rga: attach dma_resv release fence at
device_run
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Opt the rockchip-rga driver into the new vb2 release-fence helper.
Same shape as the hantro patch: attach a producer fence on the
CAPTURE-side dmabuf at m2m device_run, signalled by
vb2_buffer_done() when RGA completes the m2m operation.
Differs from hantro in one mechanical detail: rga's device_run
wraps the entire body in spin_lock_irqsave(&rga->ctrl_lock). Our
helper calls dma_resv_lock(), which is sleepable, so the
buffer-fetch + fence-attach sequence has to run above the spinlock.
Restructure device_run so:
- v4l2_m2m_next_src_buf / next_dst_buf,
- src->sequence increment,
- vb2_buffer_attach_release_fence(&dst->vb2_buf)
run before spin_lock_irqsave; only the rga->curr assignment and
rga_hw_start() (the actual HW kick) remain inside the spinlock.
This is safe under the m2m-job ownership model: by the time
device_run is called, the m2m core has selected this context and
serializes one device_run per context, so v4l2_m2m_next_*_buf
returns stable pointers until the corresponding *_buf_remove in
rga_isr. ctrl_lock was previously protecting per-device state
(rga->curr) and the HW register access, neither of which depends on
the buffer-fetch happening inside the lock.
The CAPTURE queue is opted in with supports_release_fences=true at
queue_init.
Userspace consumers of RGA-produced dmabufs (image-processing
pipelines, screen-rotation servers, gstreamer flows on Rockchip
boards) get spec-clean implicit-sync semantics, matching what
hantro does in the previous patch in this series.
Sven Püschel's ongoing "media: platform: rga: Add RGA3 support"
v5 series (linux-rockchip 2026-04-28) restructures rga.c
substantially. If that lands first, the device_run restructure
here will need a rebase against the new shape; the locking story
itself is invariant.
Cc: Jacob Chen <jacob-chen@iotwrt.com>
Cc: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
Cc: Sven Püschel <s.pueschel@pengutronix.de>
Cc: Heiko Stuebner <heiko@sntech.de>
Cc: Hans Verkuil <hverkuil@xs4all.nl>
Cc: linux-media@vger.kernel.org
Cc: linux-rockchip@lists.infradead.org
Signed-off-by: Markus Fritsche <mfritsche@reauktion.de>
---
drivers/media/platform/rockchip/rga/rga.c | 27 +++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index fea63b94c..03030c7ea 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -38,15 +38,28 @@ static void device_run(void *prv)
struct vb2_v4l2_buffer *src, *dst;
unsigned long flags;
- spin_lock_irqsave(&rga->ctrl_lock, flags);
-
- rga->curr = ctx;
-
+ /*
+ * Fetch the next-job buffers and (best-effort) attach a producer
+ * fence on CAPTURE before taking ctrl_lock below.
+ * vb2_buffer_attach_release_fence() takes dma_resv_lock, which is
+ * sleepable; ctrl_lock is taken with spin_lock_irqsave so any
+ * sleepable call must happen above it. Buffer ownership is
+ * already committed at this point: the m2m core has selected
+ * this context for device_run and serializes one device_run per
+ * context, so v4l2_m2m_next_*_buf returns stable pointers until
+ * the corresponding *_buf_remove in rga_isr.
+ */
src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
src->sequence = ctx->osequence++;
dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ (void)vb2_buffer_attach_release_fence(&dst->vb2_buf);
+
+ spin_lock_irqsave(&rga->ctrl_lock, flags);
+
+ rga->curr = ctx;
+
rga_hw_start(rga, vb_to_rga(src), vb_to_rga(dst));
spin_unlock_irqrestore(&rga->ctrl_lock, flags);
@@ -123,6 +136,12 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
dst_vq->lock = &ctx->rga->mutex;
dst_vq->dev = ctx->rga->v4l2_dev.dev;
+ /*
+ * Opt the CAPTURE queue into vb2 release-fence publishing.
+ * Compile-time gated by CONFIG_VIDEOBUF2_RELEASE_FENCES.
+ */
+ dst_vq->supports_release_fences = true;
+
return vb2_queue_init(dst_vq);
}
--
2.53.0
@@ -0,0 +1,61 @@
# vb2_dma_resv release-fence — RFC v2 series
Three-patch series that opts V4L2 m2m drivers into attaching real
producer fences to CAPTURE-side dmabufs, so implicit-sync GPU
consumers (Wayland / panfrost / panthor) wait correctly on the
producer's decode-completion rather than seeing the dma_buf core's
stub fence.
| Patch | Subject |
|---|---|
| `0004` | media: videobuf2: add opt-in dma_resv producer fence helper |
| `0005` | media: hantro: attach dma_resv release fence at device_run |
| `0006` | media: rockchip-rga: attach dma_resv release fence at device_run |
Numbered with the leading 4/5/6 because the fresnel build series carries
these alongside the 3 board-scoped `0001/0002/0003` Pinebook Pro DTS
patches; the numbers reflect apply-order in the PKGBUILD, not the
upstream lore series ordering (which starts at 1/4..4/4 with a cover).
## Status
**RFC v2.** Iterated on `lore.kernel.org/linux-media` after v1 was
rejected over the dma_fence finite-time contract gap and bus-locked
allocation issues. v2 attaches the fence at `device_run` instead of
QBUF, which puts allocation in slept-OK context (PM and clocks up,
job committed) — per Nicolas Dufresne's v1 review feedback.
Cover-letter reference: `marfrit/dmabuf-modifier-triage#3` (campaign
session that owns the upstream-targeting work; this directory ships
the build-tree-ready form for kernel-agent fleet consumption).
## Scope
`subsystem/media/videobuf2/` for the helper (0004), with two
driver opt-ins (0005/0006) shipped together because hantro and
rockchip-rga both need the helper to be useful on the RK35xx fleet.
Splitting 0005/0006 into `driver/hantro/` and `driver/rockchip-rga/`
was considered but rejected: they're a single contract series, and
their apply-order matters (0004 must precede). Series-as-unit beats
per-driver promote eligibility here.
## Fleet eligibility
- **fresnel** (RK3399 + hantro + Mali-G52): eligible. Carried in
`fleet/fresnel.yaml` since 2026-05-15 (decision flipped from "defer
to v2" to "include — v2 in this tree").
- **ohm** (RK3566 + hantro + Mali-G52): eligible. Was the original
reproducer for the green-frames symptom. Will be carried in
`fleet/ohm.yaml` once that manifest lands.
- **ampere** (RK3588 + hantro for some codecs): eligibility deferred —
RK3588 uses rkvdec2 for primary decode, hantro role is narrower.
Re-assess when `fleet/ampere.yaml` lands per issue #6.
- **boltzmann** (RK3588): same as ampere — defer.
## Upstream targeting
Not yet posted to linux-media in v2 form. Per `feedback_no_upstream.md`
the default is "build-tree only, wait for explicit ask". When/if the
upstream submission happens, this directory's `0004/0005/0006` are the
canonical source — they include the v2 commit headers (`PATCH RFC v2`,
`In-Reply-To` chain to the v1 cover-letter Message-Id).
+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

Some files were not shown because too many files have changed in this diff Show More