Skip to content

Commit f4d0a66

Browse files
fabiiw05claude
andcommitted
fix(wifi): add reconnect watchdog for brcmfmac roam/SAE wedge
Power save (fixed in #8) was not the only WiFi drop. On a WPA2/WPA3- transition, band-steering AP (one SSID, several BSSIDs across 2.4/5 GHz), NetworkManager's background scan roams to another BSSID and brcmfmac fails SAE external auth on the transition BSSID (kernel: brcmf_cfg80211_external_ auth: External authentication failed: status=1). The link then dies -- or stays "connected" while passing no traffic -- until a manual nmcli reconnect. Verified on hardware; power_save was already off. Install a WiFi recovery watchdog (customize.sh): /usr/local/sbin/uconsole-wifi-watchdog driven by uconsole-wifi-watchdog.timer (every 30s). If wlan0 is disconnected, or connected but the default gateway is unreachable across two probes, it forces a NetworkManager reconnect. It only acts when an autoconnect wifi profile exists (never fights a deliberate disconnect) and uses only NetworkManager + iproute2 + iputils (all in base). Document both drop causes and the per-device 5 GHz band-pin mitigation in MAINTAINING (EN/JA). The band pin is network-specific, so it stays a device tweak, not an image default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d471f09 commit f4d0a66

3 files changed

Lines changed: 170 additions & 24 deletions

File tree

MAINTAINING.ja.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,29 +203,62 @@ timedatectl timesync-status # ServerName=ntp.nict.jp / synced になれば OK
203203
systemd-networkd-wait-online` を実行し、上記 `timesyncd.conf.d` を配置しておけば、
204204
以降のイメージはこの問題を最初から回避できる。
205205

206-
### WiFi 不安定(brcmfmac パワーセーブ
206+
### WiFi 不安定(brcmfmac)
207207

208208
**症状**: WiFi が稀に切断される。`nmcli` で再接続すると復旧する。
209209

210-
**原因**: `brcmfmac` ドライバ(CM4 内蔵 WiFi)のデフォルトで省電力
211-
モード(`Power save: on`)が有効になっており、省電力状態でチップが
212-
スリープに入り、復帰に失敗して再接続が必要になる
210+
`brcmfmac`(CM4 内蔵 WiFi)は少なくとも2種類の理由でハングする。いずれも
211+
手動再接続まで復帰しないため、イメージには予防策(パワーセーブ無効)と
212+
復旧策(ウォッチドッグ)の両方を入れている
213213

214-
**対処(実機)**: NetworkManager でパワーセーブを恒久無効化する。
214+
**原因1 — パワーセーブ。** `brcmfmac` はデフォルトで省電力(`Power save:
215+
on`)。この状態でチップがスリープに入り、復帰に失敗することがある。
215216

216-
```sh
217-
# 即時(再起動まで有効)
218-
sudo iw dev wlan0 set power_save off
217+
対処 — NetworkManager でパワーセーブを恒久無効化:
219218

220-
# 恒久(現在・将来の全接続に適用)
221-
sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf <<'EOF'
219+
```sh
220+
sudo iw dev wlan0 set power_save off # 即時(再起動まで)
221+
sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf <<'EOF' # 恒久
222222
[connection]
223223
wifi.powersave=2
224224
EOF
225225
```
226226

227-
**イメージ側で根治済み**: `scripts/customize.sh` がビルド時に上記の
228-
設定ファイルを書き込むため、新規イメージでは最初から無効化されている。
227+
**原因2 — ローミング失敗。** WPA2/WPA3 transition かつ band-steering の AP
228+
(1 SSID・2.4/5GHz に複数 BSSID)では、NetworkManager のバックグラウンド
229+
スキャン(`bgscan simple:30:-65:300`)が別 BSSID へローミングし、transition
230+
BSSID で `brcmfmac` が SAE 外部認証に失敗する:
231+
232+
```
233+
kernel: brcmf_cfg80211_external_auth: External authentication failed: status=1
234+
```
235+
236+
その後リンクが死ぬ(または "connected" のまま無通信になる)まで手動再接続が
237+
必要になる。パワーセーブは無効なので、これは別バグ。
238+
239+
対処 — バンド固定でローミング自体を止め(実機・接続ごと)、残るケースは
240+
下記ウォッチドッグで自動復旧させる:
241+
242+
```sh
243+
# この接続を 5GHz に固定し、cross-band steering を封じる
244+
sudo nmcli connection modify <SSID> 802-11-wireless.band a
245+
sudo nmcli connection up <SSID>
246+
# (任意・最も強力: AP を1台に固定)
247+
# sudo nmcli connection modify <SSID> 802-11-wireless.bssid AA:BB:CC:DD:EE:FF
248+
```
249+
250+
バンド固定は SSID/バンドに依存するネットワーク固有設定なので、イメージ
251+
デフォルトではなく実機ごとの調整として残す。
252+
253+
**イメージ側で根治済み**:
254+
- `scripts/customize.sh` がビルド時に上記パワーセーブ設定を書き込む。
255+
- 併せて **WiFi 復旧ウォッチドッグ** を導入 —
256+
`/usr/local/sbin/uconsole-wifi-watchdog``uconsole-wifi-watchdog.timer`
257+
(30秒毎)で駆動。`wlan0``disconnected`、または `connected` でも
258+
デフォルトゲートウェイに2回連続到達不可なら、NetworkManager 再接続を強制
259+
する。autoconnect な wifi プロファイルが存在する時だけ動作(意図的な切断
260+
とは競合しない)。依存は NetworkManager + iproute2 + iputils のみ(すべて
261+
base に同梱)。ログは `journalctl -t uconsole-wifi-watchdog` で確認。
229262

230263
## 今後の方向性
231264

MAINTAINING.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,29 +216,62 @@ NetworkManager enablement, also run `systemctl disable systemd-networkd
216216
systemd-networkd.socket systemd-networkd-wait-online` and drop the
217217
`timesyncd.conf.d` file above, so future images avoid this out of the box.
218218

219-
### WiFi instability (brcmfmac power save)
219+
### WiFi instability (brcmfmac)
220220

221221
**Symptom**: WiFi occasionally drops; reconnecting via `nmcli` restores it.
222222

223-
**Root cause**: the `brcmfmac` driver (CM4 onboard WiFi) has power saving
224-
enabled by default (`Power save: on`). In this state the driver occasionally
225-
puts the chip to sleep and fails to recover, requiring a manual reconnect.
223+
The `brcmfmac` driver (CM4 onboard WiFi) wedges in at least two distinct ways.
224+
Both leave the link dead until a manual reconnect, so the image now ships both a
225+
preventive fix (power save off) and a recovery net (a watchdog).
226226

227-
**Fix (on device)**: disable power save permanently via NetworkManager.
227+
**Cause 1 — power save.** `brcmfmac` ships with power saving on (`Power save:
228+
on`); in this state it occasionally puts the chip to sleep and fails to wake.
228229

229-
```sh
230-
# Immediate (until reboot)
231-
sudo iw dev wlan0 set power_save off
230+
Fix — disable power save permanently via NetworkManager:
232231

233-
# Permanent (applies to all connections, current and future)
234-
sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf <<'EOF'
232+
```sh
233+
sudo iw dev wlan0 set power_save off # immediate, until reboot
234+
sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf <<'EOF' # permanent
235235
[connection]
236236
wifi.powersave=2
237237
EOF
238238
```
239239

240-
**Fixed in the image**: `scripts/customize.sh` writes the config file above
241-
at build time, so fresh images ship with WiFi power saving disabled.
240+
**Cause 2 — a failed roam.** On a WPA2/WPA3-transition, band-steering AP (one
241+
SSID, several BSSIDs across 2.4/5 GHz), NetworkManager's background scan
242+
(`bgscan simple:30:-65:300`) roams to another BSSID, and `brcmfmac` fails SAE
243+
external auth on the transition BSSID:
244+
245+
```
246+
kernel: brcmf_cfg80211_external_auth: External authentication failed: status=1
247+
```
248+
249+
The link then dies (or stays "connected" while passing no traffic) until a
250+
manual `nmcli` reconnect. Power save is already off, so this is a separate bug.
251+
252+
Fix — cut the failing roam by pinning the band (per-connection, on device), and
253+
rely on the watchdog below to auto-recover the residual cases:
254+
255+
```sh
256+
# Lock this connection to 5 GHz so cross-band steering can't trigger the roam.
257+
sudo nmcli connection modify <SSID> 802-11-wireless.band a
258+
sudo nmcli connection up <SSID>
259+
# (Optional, most aggressive: pin one AP outright)
260+
# sudo nmcli connection modify <SSID> 802-11-wireless.bssid AA:BB:CC:DD:EE:FF
261+
```
262+
263+
The band pin is network-specific (it names your SSID/band), so it stays a
264+
per-device tweak, not an image default.
265+
266+
**Fixed in the image**:
267+
- `scripts/customize.sh` writes the power-save config above at build time.
268+
- It also installs a **WiFi recovery watchdog**`/usr/local/sbin/uconsole-wifi-watchdog`
269+
driven by `uconsole-wifi-watchdog.timer` (every 30 s). If `wlan0` is
270+
`disconnected`, or `connected` but the default gateway is unreachable across
271+
two probes, it forces a NetworkManager reconnect. It only acts when an
272+
autoconnect wifi profile exists (never fights a deliberate disconnect) and
273+
uses only NetworkManager + iproute2 + iputils (all already in the base).
274+
Inspect with `journalctl -t uconsole-wifi-watchdog`.
242275

243276
## Future directions
244277

scripts/customize.sh

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,86 @@ cat > /etc/NetworkManager/conf.d/wifi-powersave-off.conf <<'EOF'
166166
wifi.powersave=2
167167
EOF
168168

169+
# WiFi recovery watchdog: power save is not the only way the brcmfmac link
170+
# wedges. On WPA2/WPA3-transition, band-steering APs a background-scan roam to
171+
# another BSSID can fail SAE external auth (kernel: "brcmf_cfg80211_external_auth:
172+
# External authentication failed"), and the driver sometimes stays "connected"
173+
# while passing no traffic -- in both cases WiFi is dead until a manual `nmcli`
174+
# reconnect. A 30s timer detects the wedged state and forces NetworkManager to
175+
# reconnect wlan0, so the link self-heals. Uses only NetworkManager + iproute2 +
176+
# iputils (all already present). See MAINTAINING "WiFi instability".
177+
echo "==> [chroot] installing WiFi recovery watchdog (systemd timer)"
178+
mkdir -p /usr/local/sbin
179+
cat > /usr/local/sbin/uconsole-wifi-watchdog <<'WD'
180+
#!/usr/bin/env bash
181+
# Recover the CM4 onboard brcmfmac WiFi when it wedges (see customize.sh).
182+
set -u
183+
IFACE=wlan0
184+
185+
# Nothing to do if the interface is absent or the radio is switched off.
186+
[[ -e "/sys/class/net/${IFACE}" ]] || exit 0
187+
[[ "$(nmcli -t -f WIFI radio 2>/dev/null)" == enabled ]] || exit 0
188+
189+
# Only manage the link if an autoconnectable wifi profile exists, so we never
190+
# fight a user who deliberately disconnected.
191+
if ! nmcli -t -f TYPE,AUTOCONNECT connection show 2>/dev/null \
192+
| grep -q '^802-11-wireless:yes'; then
193+
exit 0
194+
fi
195+
196+
state=$(nmcli -t -f DEVICE,STATE device 2>/dev/null \
197+
| awk -F: -v i="$IFACE" '$1==i{print $2}')
198+
199+
case "${state}" in
200+
connected)
201+
# L2 is up; confirm L3 by pinging the default gateway (LAN-local, cheap). A
202+
# wedged brcmfmac often stays "connected" while passing no traffic. Require
203+
# two failed probes a few seconds apart to avoid acting on a single loss.
204+
gw=$(ip -4 route show default dev "${IFACE}" 2>/dev/null | awk '{print $3; exit}')
205+
[[ -n "${gw}" ]] || exit 0
206+
ping -c1 -W2 -I "${IFACE}" "${gw}" >/dev/null 2>&1 && exit 0
207+
sleep 3
208+
ping -c2 -W2 -I "${IFACE}" "${gw}" >/dev/null 2>&1 && exit 0
209+
logger -t uconsole-wifi-watchdog "gateway ${gw} unreachable; reconnecting ${IFACE}"
210+
nmcli device reconnect "${IFACE}" >/dev/null 2>&1 || true
211+
;;
212+
disconnected)
213+
# NM failed to recover on its own; force a reconnect to the best BSSID.
214+
logger -t uconsole-wifi-watchdog "device ${IFACE} disconnected; forcing connect"
215+
nmcli device connect "${IFACE}" >/dev/null 2>&1 || true
216+
;;
217+
*)
218+
# connecting / unavailable / unmanaged: an attempt is in progress or the
219+
# device is not ours to touch -- leave it alone.
220+
exit 0
221+
;;
222+
esac
223+
WD
224+
chmod +x /usr/local/sbin/uconsole-wifi-watchdog
225+
cat > /etc/systemd/system/uconsole-wifi-watchdog.service <<'UNIT'
226+
[Unit]
227+
Description=uConsole WiFi recovery watchdog (recover wedged brcmfmac link)
228+
After=NetworkManager.service
229+
Wants=NetworkManager.service
230+
231+
[Service]
232+
Type=oneshot
233+
ExecStart=/usr/local/sbin/uconsole-wifi-watchdog
234+
UNIT
235+
cat > /etc/systemd/system/uconsole-wifi-watchdog.timer <<'UNIT'
236+
[Unit]
237+
Description=Run the uConsole WiFi recovery watchdog periodically
238+
239+
[Timer]
240+
OnBootSec=60
241+
OnUnitActiveSec=30
242+
AccuracySec=5s
243+
244+
[Install]
245+
WantedBy=timers.target
246+
UNIT
247+
systemctl enable uconsole-wifi-watchdog.timer || true
248+
169249
# --- Internal speaker amplifier enable -------------------------------
170250
# The uConsole's onboard speaker amplifier is gated by an enable GPIO
171251
# (BCM11 / gpiochip0 line 11). Headphones bypass the amp, so without this the

0 commit comments

Comments
 (0)