forked from marfrit/kernel-agent
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84cdb5b4ee | |||
| 3d15c5367d | |||
| 588350c4da | |||
| cc6f2378ab | |||
| dd631fd3c7 | |||
| 38fd672940 | |||
| 8b356aa11f | |||
| 443f5e992e | |||
| 2f119a3fb7 | |||
| 7a86ebb587 | |||
| 731e98e079 | |||
| bae99da612 | |||
| 3d10a2c21a | |||
| 3ee0ef7d86 | |||
| 878e86f103 | |||
| 4d98a8169d | |||
| 84734ba527 | |||
| c9e9ad973c | |||
| a840f76907 | |||
| 989b8842fb | |||
| f203b70f4f | |||
| a254b6f0bb | |||
| 43c8f0cba8 | |||
| 42b0c5042a | |||
| 4c80458d1f | |||
| 96af34d775 | |||
| 95be39ef80 | |||
| 9092d9aaaa | |||
| 44c6c3fa4f | |||
| 3c6d3567f0 | |||
| 91fe815c4c | |||
| 0e1075fbdd | |||
| f8d56d8635 | |||
| fb4bfdc2ff | |||
| 2d2b9ee8ba | |||
| 7f60a3a37c | |||
| d5893e65f0 | |||
| 2783d5ec60 | |||
| 91617fae14 | |||
| ea3b2fb813 | |||
| 62a6f88abd | |||
| ad7c61a81b | |||
| a6549605f0 | |||
| e53db55959 | |||
| 18da673ccc |
@@ -0,0 +1,7 @@
|
||||
# ka-promote / ka-build output
|
||||
/build/
|
||||
|
||||
# transient
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
+36
@@ -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
|
||||
+70
@@ -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";
|
||||
+};
|
||||
|
||||
+104
@@ -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";
|
||||
|
||||
+226
@@ -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 */
|
||||
|
||||
+94
@@ -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";
|
||||
};
|
||||
|
||||
|
||||
+50
@@ -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
|
||||
|
||||
+66
@@ -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.
|
||||
@@ -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
|
||||
+4322
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
+38
@@ -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
|
||||
|
||||
+293
@@ -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
|
||||
|
||||
+293
@@ -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
|
||||
|
||||
+168
@@ -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
|
||||
|
||||
+168
@@ -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
|
||||
|
||||
+143
@@ -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
|
||||
|
||||
+156
@@ -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
|
||||
|
||||
+156
@@ -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
|
||||
|
||||
+144
@@ -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
|
||||
|
||||
+83
@@ -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
|
||||
|
||||
+116
@@ -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
|
||||
|
||||
+131
@@ -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)
|
||||
+251
@@ -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
|
||||
|
||||
+251
@@ -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
|
||||
|
||||
+209
@@ -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
|
||||
|
||||
+209
@@ -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
|
||||
|
||||
+105
@@ -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
|
||||
|
||||
+246
@@ -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
|
||||
|
||||
+246
@@ -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
|
||||
|
||||
+53
@@ -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
|
||||
|
||||
+53
@@ -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
|
||||
|
||||
+190
@@ -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
|
||||
|
||||
+190
@@ -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
|
||||
|
||||
+121
@@ -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.
|
||||
+675
@@ -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
|
||||
|
||||
+109
@@ -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
|
||||
|
||||
+109
@@ -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
|
||||
|
||||
+226
@@ -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
|
||||
|
||||
+226
@@ -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
|
||||
|
||||
+168
@@ -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`.
|
||||
+168
@@ -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
|
||||
|
||||
+147
@@ -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
|
||||
|
||||
+86
@@ -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
|
||||
|
||||
+119
@@ -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
|
||||
|
||||
+108
@@ -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
|
||||
|
||||
+678
@@ -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
|
||||
|
||||
+146
@@ -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
|
||||
|
||||
+126
@@ -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
|
||||
|
||||
+147
@@ -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
|
||||
|
||||
+86
@@ -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
|
||||
|
||||
+119
@@ -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
|
||||
|
||||
+108
@@ -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
|
||||
|
||||
+678
@@ -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
|
||||
|
||||
+146
@@ -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
|
||||
|
||||
+126
@@ -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
|
||||
|
||||
+123
@@ -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
|
||||
|
||||
+123
@@ -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
|
||||
|
||||
+49
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
+66
@@ -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
|
||||
|
||||
@@ -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)
|
||||
+57
@@ -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.**
|
||||
+43
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.**
|
||||
+29
@@ -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>;
|
||||
|
||||
@@ -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.
|
||||
+52
@@ -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 {
|
||||
|
||||
@@ -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-...`).
|
||||
+356
@@ -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
|
||||
|
||||
+95
@@ -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
|
||||
|
||||
+117
@@ -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).
|
||||
Executable
+113
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user