Luci-app-wifimgr v2.0.0 — WiFi 7 / MLO manager for BPI-R4 (MT7988A)

Note for moderators / readers: This thread may appear to be a duplicate of the earlier v1.1.1 release thread. It is intentionally separate. Version 1.1.1 did not include any protection against adding mutually incompatible WiFi networks — doing so could silently lock the MT7996 hardware semaphore register, which survives soft reboots and requires a power-off of at least 15 minutes (to fully discharge the BPI-R4 board capacitors) to recover. Version 2.0.0 addresses this at the wizard level and warrants its own thread.


luci-app-wifimgr v2.0.0 — WiFi 7 / MLO manager for BPI-R4 (MT7988A)

A LuCI-based WiFi manager for the Banana Pi BPI-R4 (MT7988A SoC, MT7996 tri-band WiFi 7).

Designed for the current MTK SDK stack — kernel 6.12.x, MT7996 firmware, mt76 driver — with full support for Multi-Link Operation (MLO), per-radio TX power management, and all supported client modes.

Requires an MTK SDK build from bpi-r4-deploy. Not compatible with mainline OpenWrt — interface naming (ap-mld-*), MLD UCI configuration, and hostapd vendor extensions differ significantly.


Hardware

Component Details
Board Banana Pi BPI-R4, BPI-R4 PoE
SoC MediaTek MT7988A (Filogic 880)
WiFi chip MT7996 tri-band (2.4 GHz / 5 GHz / 6 GHz)
WiFi standard 802.11be (WiFi 7), MLO
RAM variants 4 GB / 8 GB

Why this matters — the hardware semaphore problem (and why v2.0.0 fixes it)

The MT7996 chip contains an internal MCU with a hardware semaphore register. Under certain conditions — specifically, adding a second legacy AP to a radio that is already part of an MLO group using wifi reload — the driver triggers an EDCCA crash that locks this register.

The locked state survives soft reboots and short power cycles because the on-board capacitors on the BPI-R4 keep the chip powered for several seconds after shutdown.

Symptomdmesg shows:

mt7996e: Failed to start patch
mt7996e: Failed to release patch semaphore
mt7996e: probe with driver mt7996e failed with error -11

WiFi is completely dead. The only fix is to disconnect from power for at least 15 minutes to fully discharge the board capacitors and reset all MCU hardware registers.

v2.0.0 prevents this entirely. All wizards now detect and block the configurations that trigger the crash before they are applied:

  • Adding a second AP to an MLO radio → blocked with an inline error
  • Adding an MLO AP when radios are already in another MLO group → blocked
  • Adding an MLO STA when a local MLO AP or another MLO STA is already active → blocked
  • WDS / Repeater uplink on a radio that is part of an MLO group → blocked
  • Applying an AP to an MLO radio → always triggers reboot instead of wifi reload, even if the UI guard is somehow bypassed

Installation

Included in firmware (recommended)

luci-app-wifimgr is included by default in all BPI-R4 firmware images built via bpi-r4-deploy — covering all hardware variants (standard, PoE, 4 GB, 8 GB).

No additional steps needed after flashing.

Standalone APK

Download the latest APK from the Releases page and install:

# Copy APK to router
scp luci-app-wifimgr-2.0.0-r20260517.apk [email protected]:/tmp/

# Install (no internet required)
ssh [email protected] 'apk add --allow-untrusted --no-network /tmp/luci-app-wifimgr-*.apk'

Then open LuCI → Network → WiFi Manager.

Before upgrading — back up your WiFi config

Before any sysupgrade or APK reinstall, export your wireless configuration from the Diagnostics tab:

Network → WiFi Manager → Diagnostics → Wireless Backup / Restore → Download backup

This saves your current /etc/config/wireless as a file. After upgrading, use the same tab to restore it — networks come back exactly as configured, without manual reconfiguration.

UCI config normally survives an APK reinstall (apk del + apk add) without a backup. For sysupgrade, the backup is essential — the wireless config is wiped along with the rest of the overlay filesystem.


Changelog

v2.0.0 (2026-05-17)

New features

  • Channel advisor — “Scan channels” button in each radio card. Scans nearby APs and survey noise, computes interference-weighted score, and recommends top 3 channels as color-coded buttons (green/yellow/red). Click to apply immediately.
  • Scan in wizards — Station, WDS, and Repeater wizards now have a live scan button that lists nearby networks. Selecting one auto-fills SSID and encryption.
  • All-band nearby scan — Diagnostics “Nearby Networks” now scans all three bands simultaneously.
  • Preamble puncturing — per-radio subchannel exclusion (EHT). Visual 20 MHz bitmap — click to puncture/restore individual subchannels for DFS coexistence.
  • Version badge — UI header shows current package version.

Safety / crash protection

  • EDCCA crash prevention in wizardAP — Radios that are part of an MLO group and already have one legacy AP are disabled in the selector (“— at limit”) and show a blocking error.
  • EDCCA crash prevention in wizardMLO — Link toggle buttons for radios already in an existing MLO group are disabled.
  • MLO radio always reboots — wizard_ap() detects if the target radio is part of an MLO group and triggers a reboot instead of wifi reload, eliminating the risk of EDCCA crash even if the UI guard is bypassed.
  • MLO conflict blocking in wizardStation — MLO STA mode is blocked if a local MLO AP or another MLO STA is already active.
  • MLO conflict blocking in wizardWDS / wizardRepeater — Radios that are part of any MLO group (AP or STA) are disabled as uplink options.

Bug fixes

  • Fixed misleading “DFS scan in progress” message shown on 2.4G interfaces (which have no DFS).
  • Fixed TX power display in Radios tab — manual mode now shows the configured UCI value instead of the driver-reported regulatory maximum.
  • Fixed scan interface selection — channel advisor and uplink scan now correctly derive the phy interface from radio_id.

v1.1.1 (2026-05-14)

  • Sysupgrade to OpenWrt 99211b26fb (kernel 6.12.x, MTK SDK May 2026)
  • First-run UCI defaults (country=CZ, renamed default SSID)
  • Hotplug TX power fix for MT7996 cold-boot TMAC=0 bug on band0/band2

v1.0.0 (2026-05-10)

  • Initial public release
  • All wizards: MLO AP, legacy AP, Station (incl. MLO STA), WDS, relayd, Repeater, Country
  • Networks / Radios / Clients / Diagnostics tabs
  • TX power modes: Regulatory / eFuse max / Manual
  • Channel utilization, noise, thermal, MLO internals in Diagnostics

Screenshots

Networks Radios
Networks tab Radios tab
Clients Diagnostics
Clients tab Diagnostics tab

Safety blocks — wizards actively prevent incompatible configurations:

Station Wizard MLO Wizard blocked AP Wizard blocked
wizardStation wizardMLO blocked wizardAP blocked
WDS / Bridge blocked Repeater blocked
wizardWDS blocked wizardRepeater blocked

Features overview

Wizards — guided network setup

Wizard Description
MLO AP Creates a Multi-Link Operation AP across 2 or 3 bands. Supports 2G+5G, 2G+6G, 5G+6G, or all three. WPA3 enforced.
AP Creates a standard single-band AP on any radio. Full control over channel, width, encryption, SSID isolation, hidden SSID, client limits. DFS supported with CAC progress indication.
Station Connects to an upstream WiFi network on 2.4G or 5G. MLO STA mode supported — multi-band client connecting to an upstream MLO AP.
WDS / Bridge Wireless bridge using 4-address WDS mode or L2 relayd ARP proxy.
Repeater Uplink STA connection on one radio + local AP on a separate radio (L3 NAT).
Country Changes regulatory domain. Requires reboot to apply kernel regulatory database.

Networks tab

Live list of all configured networks with status indicators, band pills, encryption labels, and client counts. Each row expands to show the full configuration. Inline edit and remove with wifi reload.

Radios tab

Per-radio configuration (channel, bandwidth, country) and TX power management:

Mode Description
Regulatory Country SKU table applied. sku_idx=0, no manual txpower override.
eFuse max Driver runs at hardware eFuse maximum. Requires reboot.
Manual Per-radio dBm cap. sku_idx=0 + txpower=N.

Channel advisor and preamble puncturing are also accessible here.

Clients tab

Live client list showing signal (color-coded), WiFi generation badge (WiFi 4/5/6/7), bitrate, and per-link data for MLO clients. Disconnect button.

Diagnostics tab

Firmware version, CPU and WiFi chip temperatures, per-radio channel utilization / noise / TX stats, MLO internals (MLD address, active links, EMLSR/STR status), log download, all-band nearby scan, and wireless backup / restore.


Supported network combinations

Combination Works Notes
MLO AP (2 or 3 bands) :white_check_mark: Requires reboot
MLO AP + legacy AP on each MLO radio :white_check_mark: Max 1 extra per radio, requires reboot
MLO AP + STA uplink (radio0 or radio1) :white_check_mark: wifi reload OK
MLO AP + WDS bridge :white_check_mark: radio0 or radio1
MLO AP + L2 relayd :white_check_mark: radio0 or radio1
Legacy APs only (no MLO) :white_check_mark: Up to ~4 per radio, wifi reload
Legacy AP + STA uplink on same radio :white_check_mark: radio0 or radio1 only
Legacy AP + WDS bridge :white_check_mark: radio0 or radio1
MLO STA (all 3 bands) :white_check_mark: Requires reboot
MLO STA + legacy APs :white_check_mark: No EDCCA risk — MLO STA is not an AP
Repeater (STA + local AP on different radio) :white_check_mark:
MLO AP + MLO STA :x: Same 3 radios — physically impossible
MLO AP + 2 or more extra APs on same radio :x: EDCCA crash — wizard blocks this
6 GHz STA (non-MLO) :x: Driver limitation — see below
Multiple MLO AP groups :x: Single chip / one wiphy
Repeater with STA and AP on the same radio :x: Wizard blocks this
WDS/relayd on a radio in an MLO group :x: Driver limitation — wizard blocks this
Two MLO STA connections simultaneously :x: One wpa_supplicant instance per router

Known limitations

  • 6G STA (non-MLO): Not supported. MT7996 always routes band2 through the MLD code path — a standalone 6G STA scan returns no results. 6G uplink is only possible via MLO STA.
  • Per-link RSSI on secondary MLO links: Driver reports signal only for the primary data link. Secondary links show . Driver limitation, not fixable in software.
  • 6G channel utilization: Always n/a — driver bug: mbssid=1 causes hostapd to report 126% utilization. Discarded in layer2.

Requirements

  • OpenWrt with MediaTek SDK (MTK SDK) — kernel 6.12.x, MT7996 firmware, mt76 driver
  • Board: Banana Pi BPI-R4 or BPI-R4 PoE (MT7988A / MT7996)
  • LuCI installed

Not compatible with mainline OpenWrt.


Links

1 Like

Thanks for your work and tests,but why do youcreate a new thread and not using your existing one?

Hi Frank,

the main reason is that the semaphore crash causes complete and seemingly permanent WiFi failure — users who hit it will search for help and need to find the fix quickly. A reply buried in a v1.1.1 thread would likely be missed.

If it’s possible, I’d actually prefer to delete the v1.1.1 thread entirely (it’s now obsolete) and remove the duplicate note from this one. Happy to follow whatever process the forum has for that.

Good afternoon, Wozi. The new Banana Pi Pro 8X router has that error. The problem is that it only has one fork, and no commits have been made to it. It’s this one:

GitHub - BPI-SINOVOIP/BPI-R4PRO-8X-OPENWRT-V24.10.0-Master-Devel · GitHub, with kernel 6.6.93, and it describes the exact problem you’re showing.

Here are the key boot log lines showing the failure (mt7996e patch timeouts and probe -11):

[ 56.165611] mt7996e 0000:01:00.0: Message 00000007 (seq 12) timeout
[ 76.645602] mt7996e 0000:01:00.0: Message 00000007 (seq 13) timeout
[ 97.131852] mt7996e 0000:01:00.0: Failed to start patch
[158.571764] mt7996e 0000:01:00.0: Failed to release patch semaphore
[159.460364] mt7996e: probe of 0000:01:00.0 failed with error -11
[159.004909] Trying to free already-free IRQ 104

And they haven’t provided any solution.

I sent them a video demonstrating the error you mentioned, in which I solved the semaphore problem using a DIY workaround. As long as you don’t disconnect the router from the power supply, you can reboot it as many times as you want, and it will always recognize the card. The problem is that when you cut the power, the issue comes back.

The CRC error occurs in BL2, before U-Boot even loads

The problem is related to MMC bus instability during early boot

When the WiFi card is ON during this phase, it causes interference on the MMC bus

Evidence 2 – The workaround (works 100% of the time) This is the procedure that works every time:

Power off the router completely (disconnect power cable)

With the WiFi card set to OFF, apply power

Wait for the U-Boot menu to appear

At this moment (or even a few seconds later, as the kernel is loading), turn the WiFi card ON

I have recorded a video demonstrating this procedure.

What happens after this:

:white_check_mark: The BE14 WiFi card is detected and works perfectly

:white_check_mark: You can reboot via SSH or the web interface and the WiFi card remains detected

What happens if you remove power completely and start with WiFi ON:

:x: The WiFi card is NOT detected

What this proves: :white_check_mark: The hardware is fully functional :white_check_mark: The issue is software / boot timing :white_check_mark: The fix requires a patch in U-Boot or a delay in the PCIe / MMC initialization

What I need from you: An official patch for U-Boot to solve this problem permanently

Or, at minimum, official documentation of this workaround for users until a proper fix is released

I have recorded a full video showing:

The CRC error on fast power cycle

The OFF→ON workaround (including that it works even if you turn WiFi ON a few seconds later, not exactly at the menu)

The successful boot with WiFi detected and working

Subject: Reproducible mt7996e probe -11 on original NAND image —

They replied that they would look into it, but I haven’t received any further response from them. The problem is that there is only one fork, they haven’t made any commits since its release, and this error has been present since day one.

I don’t know if you can help solve it. I’m sharing my repository with you, where I’ve released images for this router, but of course the problem comes from the original fork I’m working from, and I’m not in a position to fix it myself.

Here’s the thread for you to read, and I would appreciate any help you can offer:

be14000 mt7996e

my repository

Best regards, and thank you

Hi,

Thanks for the detailed writeup — interesting problem. Based on what you described, our guess is that the root cause is not the MMC bus interference itself, but rather the PCIe card not receiving a proper hardware reset (PERST#) during the boot sequence in ATF/U-Boot. When the card is physically switched OFF, the MCU resets cleanly; when it isn’t, the patch semaphore from the previous session stays locked, and the driver hits the familiar -11 on probe.

A proper fix would likely be in ATF or U-Boot: ensuring PERST# is toggled correctly before PCIe enumeration. That’s squarely BPI/Sinovoip territory for the R4 Pro 8X.

We’d be happy to dig into this further, but without being able to test on the actual board there’s only so much we can do — and unfortunately we don’t have the R4 Pro 8X available. It’s worth pushing BPI directly, especially since their repo hasn’t seen any commits since November 2025.

Good luck!

v3.0.0 — Link Policy tab: open-source MLO band steering + traffic steering

Following up on the earlier discussions about MLMR-STR capabilities and Neg-TTLM negotiation — we’ve now put both to practical use. v3.0.0 adds a Link Policy tab with a working MLO link steering daemon and a live control UI.

GitHub: GitHub - woziwrt/mt7996-wifi7-manager: WiFi 7 / MLO manager for BPI-R4 (MT7996) — LuCI package for OpenWrt with MTK SDK · GitHub

APK: Release v3.0.0 — Link Policy: MLO band steering + Neg-TTLM · woziwrt/mt7996-wifi7-manager · GitHub


Background

The MTK SDK hostapd already exposes the full API for dynamic link management — SET_ATTLM for AP-side link disable/enable, and negotiated_ttlm for TID-to-link mapping negotiation. We’ve been discussing these in earlier posts at the protocol level. mlo-steerd is a shell daemon that now sits on top of that API and makes real-time decisions.

The script lives at /root/mlo-steerd.sh on the AP router, autostarted via procd with respawn on crash. Source is in the bpi-r4-deploy repo, my_files/mlo-steerd.sh.


Algorithm 1 — Band steering (SET_ATTLM)

The daemon polls every 10 seconds and computes a weighted score per link:

score = SNR_normalized × 60%  +  (100 − tx_retries%) × 30%  +  (100 − channel_busy%) × 10%

SNR is normalized over a per-link soft zone ([SNR_HARD_LOW .. SNR_HARD_HIGH]), giving a 0–10000 integer. The decision table:

Condition Action
SNR < hard_low (2 dB for 6G, 0 dB for 5G) Immediate disable
SNR > hard_high AND retries < 15% Immediate enable
score < 4000 Disable
score > 6000 Enable
4000 ≤ score ≤ 6000 No change (hysteresis)

Priority order: 6G evaluated first, 5G only if 6G already disabled — client is never left with no link. 30-second cooldown between actions.

For EMLSR clients (iPhone, max_simul_links=1) this is the only mechanism — band steering via SET_ATTLM, no Neg-TTLM.


Algorithm 2 — Traffic steering (Neg-TTLM)

When all links are up and an MLMR client is connected (max_simul_links > 1, in our setup the BPI-R4 running as MLO STA), the daemon sends a negotiated_ttlm request mapping traffic classes to optimal bands:

AC TIDs Links Rationale
Background (BK) 1, 2 2.4G + 5G Bulk transfers — spare 6G for latency-sensitive
Best Effort (BE) 0, 3 All General traffic — full capacity
Video (VI) 4, 5 5G + 6G High throughput, low latency — skip 2.4G
Voice (VO) 6, 7 5G only Minimum latency — stable mid-band, no 6G range risk

Verified live on MT7996 (2026-05-27). The client accepts and routes accordingly:

hostapd_cli -i ap-mld-1 get_neg_ttlm 3e:35:54:dc:99:02
Link Mapping:   uplink  downlink
TID 0:          0x0007  0x0007   ← BE → all links
TID 1:          0x0003  0x0003   ← BK → 2.4G+5G
TID 2:          0x0003  0x0003
TID 3:          0x0007  0x0007
TID 4:          0x0006  0x0006   ← VI → 5G+6G
TID 5:          0x0006  0x0006
TID 6:          0x0002  0x0002   ← VO → 5G only
TID 7:          0x0002  0x0002

One implementation detail worth noting: SET_ATTLM and Neg-TTLM cannot be active simultaneously — issuing negotiated_ttlm request while A-TTLM is running returns “Busy: A-TTLM is on-going”. The daemon handles this correctly: before any SET_ATTLM call it tears down active Neg-TTLM mappings, and re-applies them when all links come back up.


Link Policy tab

The new tab (only shown when an MLO AP is configured):

Live view: MLMR client (BPI-R4 STA, 3/3 links, Neg-TTLM active) + EMLSR client (iPhone, 6G only)

  • Daemon control — start/stop, status dot + PID
  • Steering override — three buttons: Auto / All links ON / 5G only, written to UCI mlo-steerd.global.mode, picked up on next poll cycle without restart
  • Link status — live noise floor per band (iw survey)
  • MLO clients — EMLSR vs MLMR, per-link signal, active Neg-TTLM mapping per AC for MLMR clients
  • Daemon log — last 25 lines of /tmp/steerd.log

Install

scp luci-app-wifimgr-3.0.0-r20260528.apk [email protected]:/tmp/
ssh [email protected] 'apk add --allow-untrusted --no-network /tmp/luci-app-wifimgr-*.apk'

Requires OpenWrt with MTK SDK (kernel 6.12.x, mt76, MLO-capable hostapd).


The thresholds are currently hardcoded in the daemon script — the next logical step would be UCI-configurable values exposed in the UI. If anyone tests this in different RF environments or with other MLMR clients, would be interested to hear how the algorithm holds up.

1 Like

The weighted score logic looks very well-balanced, especially giving 60% weight to SNR while still accounting for channel congestion. It is great to see a practical implementation of mlo-steerd that handles EMLSR and MLMR clients differently. This level of granular control over link steering is a significant improvement for the MT7996 on the BPI-R4. Thanks for the detailed breakdown of the algorithm and the updated APK.