openwrt / openwrt/mt76

mt7915: firmware rate control pinned to HE40/1SS/MCS1 when 80 MHz is unusable

Open
#1,114 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
888
Forks
436
PR merge metrics
No merged PRs in 30d

Description

Disclaimer on authorship. This report was written by Claude (Anthropic's
Claude Opus 5, running in Claude Code), not by the human submitting it. The
firmware disassembly, the diagnostic debugfs instrumentation and the test
sequences described below were also carried out by the assistant, on the
submitter's own hardware and at their direction. The submitter is posting it
and can answer questions or re-run tests, but should not be assumed to have
independently verified every address, offset or bit-field claim below. Please
treat the firmware-internal details as findings to be checked rather than
established fact — the measured throughput results and the
STA_REC_RA bw = 40 MHz behaviour are the parts reproduced most directly.

Summary

When an MT7915 AP declares 80 MHz for a station but 80 MHz PPDUs to that station
are not acknowledged, the WM firmware falls back to 40 MHz — which is correct —
but in that downgraded state its rate adaptation stays pinned at one spatial
stream and MCS 1
and never probes upward. Throughput collapses to ~24 Mbit/s.

The fallback itself is not the bug. The bug is that the downgraded-bandwidth
state appears to be a second-class path in which NSS and MCS selection are
effectively disabled, even though the peer's full two-stream MCS 0-11 map is
present in the firmware station record the entire time.

Handing firmware a STA_REC_RA with bw = 40 MHz, so that no downgrade is
needed, makes it transmit at the same physical width with two streams and
climb normally to MCS 11 — 541.6 Mbit/s instead of 32.4.

This looks like a firmware defect rather than an mt76 bug, so the ask here is
mainly to confirm the analysis and, if possible, escalate to MediaTek. I have
firmware-internal addresses and a decoded state layout below that should make it
directly actionable.

Environment

  • Xiaomi Redmi Router AX6S (xiaomi,redmi-router-ax6s), MT7622 + PCIe MT7915
  • OpenWrt SNAPSHOT r35679-e9a6e45556, kernel 6.18.41
  • mt76 59676919ea408b0b13a9d23f2e2e1a1ab407fba1 (2026-07-01) plus local
    read-only diagnostics
  • Firmware: MT7915_MP_7_4_2045-20240429200502; WM build 20240429200752,
    WA 20240429200812
  • Regulatory domain AU. AP on channel 136 / HE80 (center 5690, spanning
    5650-5730 MHz), which AU permits (5650 - 5730 @ 80, DFS)
  • Peer: Nintendo Switch 2. Reproduced with this one client model only, but the
    mechanism below is client-independent as far as the firmware state shows.

Why 80 MHz is unusable here (context, not the bug)

The client appears to be certified only to 5700 MHz in this band, so the upper
half of the 132/136/140/144 block is outside its range. Consistent with that:

  • it clears IEEE80211_VHT_CAP_SHORT_GI_80 (0x20) only on this block —
    0x03907191 on ch136 versus 0x039071b1 on ch108
  • its own uplink also drops to 40 MHz on this block
  • moving the same AP to ch108 fixes it completely (HE80/2SS MCS11,
    1134 Mbit/s), and moving the other AP onto ch136 reproduces it — so the
    behaviour follows the channel, not the unit

The mt76-relevant point is only that 80 MHz is genuinely unusable while 40 MHz
works. Anything that makes 80 MHz fail should trigger the same defect.

Reproduction

  1. AP on a 80 MHz channel where the peer cannot use the full width.
  2. Associate the peer. mac80211 reports bandwidth=2 (80 MHz), rx_nss=2.
  3. Within ~1 s of association firmware settles on HE40/1SS MCS1, 32.4 Mbit/s,
    expected throughput 29.23 Mbit/s.
  4. It stays there indefinitely. Across ~34 MB of downlink traffic there was not
    one upward rate transition, while the peer's uplink reached HE40/2SS MCS10.

Sanity checks confirming the link itself is fine:

forced HE40/2SS MCS3:  30/30 replies, 0% loss, 1.1-1.3 ms   -> two streams work
forced HE80/1SS MCS3:   0/30 replies, 100% loss             -> 80 MHz does not

Firmware state

Read via a local read-only debugfs node using mt7915_memcpy_fromio() /
mt76_rr(). Addresses are for the WM build above.

StaRec       = 0x00401180 + wcid * 0x98     (accessor 0xe002a274)
HERA context = 0x0032b02c + wcid * 0xc8
RA handle    = *(StaRec + 0x0c)

StaRec+0x38  bandwidth from sta_rec_ra.bw
StaRec+0x48  peer VHT capability
StaRec+0x50  composed VHT operating-mode byte
StaRec+0x66  peer HE MCS/NSS map
ctx+0x0d     declared/working bandwidth = min(StaRec+0x38, RAHandle+0xc7)
             computed at 0xe002e222
ctx+0x1c     upward probe candidate rate
ctx+0x28/2c/36/38   committed rate

The rate word layout, from the builder at 0x0022b498 and its accessors
(0x22b4fe NSS, 0x22b506 mode, 0x22b526 MCS):

rate = mcs | ((nss - 1) << 6) | (mode << 9) | (arg4 ? 0x10 : 0)

bits[3:0]    MCS / rate index   (bits[5:0] when mode == 2)
bits[8:6]    NSS - 1
bits[12:9]   PHY mode, matching mt76's MT_PHY_TYPE_* (2=HT, 4=VHT, 8=HE_SU)
bit[13]      set when transmitting narrower than the declared bandwidth

The mode field matching MT_PHY_TYPE_*, and the 6-bit MCS special case for
mode 2 being exactly HT's MCS 0-31, are independent confirmation of the layout.

Observed states
state                        ctx_0d      actual  nss  mode  rate word  bit13
peer, failing (ch136)        2 (80 MHz)  40 MHz   1    8     0x3001      1
peer, sta_rec_ra bw = 40     1 (40 MHz)  40 MHz   2    8     0x104b      0
peer, healthy (ch108)        2 (80 MHz)  80 MHz   2    8     0x1042      0
unrelated VHT client         2 (80 MHz)  80 MHz   1    4     0x0807      0
unrelated HT client          0 (20 MHz)  20 MHz   1    2     0x0407      0

The VHT client is the control that identifies bit 13: one spatial stream but no
bandwidth downgrade, and the bit is clear. So bit 13 tracks the downgrade, not
one-stream/STBC operation.

Note StaRec+0x66 is 0xfffa — full two-stream HE MCS 0-11 — in every state
including the failing one. Firmware has the capability information and does not
use it while downgraded.

What fixes it, and what does not

Only one thing helped: sending STA_REC_RA with bw/phy.bw set to 40 MHz
while leaving the peer's HE MCS/NSS map untouched. Firmware then climbed
HE40/2SS MCS 3 -> 4 -> 7 -> 10 -> 11, settling at MCS 11, 541.6 Mbit/s, expected
throughput 465.250 Mbit/s, 60/60 large pings at 0% loss. An application-level
speed test on the client went from ~24 Mbit/s to 274 Mbit/s.

Ineffective:

  • resending the unchanged STA_REC_RA station constraints (MCU returns success,
    some WTBL rate rows reset, selected rate unchanged)
  • RATE_PARAM_AUTO followed by resending the constraints
  • STA_REC_RA_UPDATE with field = 1, the operating-mode path (see below)
  • disabling VHT/HE transmit beamforming
  • nl80211 bitrate masks restricting NSS

Aside: an unused firmware operating-mode input

While tracing this I found that the firmware implements a STA_REC_RA_UPDATE
sub-command that no mt76 driver sends. For field == 1 the handler at
0xe002f1ae consumes op_mode, op_vht_chan_width, op_vht_rx_nss and
op_vht_rx_nss_type from sta_rec_ra_fixed, packs them into StaRec+0x50
(bits[1:0] width, bits[6:4] Rx NSS, bit[7] NSS type) and sets an enable bit in
ctx+0x00 bit 3. 0xe002e222 then overrides the working bandwidth with
StaRec+0x50 & 0x3, and the HERA core additionally clamps bandwidth and NSS at
0x0022d70e.

Those four fields are declared in struct sta_rec_ra / struct sta_rec_ra_fixed but are written by no driver in the tree, and mt7915 only ever
sends field values 3, 5, 7, 8, 11 and 20. I tested it: the command is accepted,
StaRec+0x50 and the enable bit land exactly as decoded — but it did not
change the selected rate or ctx+0x0d for this HE station. The clamp at
0x0022d70e is in the mode == 4 VHT branch, which may explain why.

Two notes if anyone wants to use this path: a full STA_REC_RA update clears
StaRec+0x50 and the enable bit, so operating mode must be sent afterwards; and
firmware compares op_vht_rx_nss against a stream count, not the standard's
nss - 1.

Question for maintainers

Is there a supported way to tell this firmware "operate at 40 MHz" without
misrepresenting the peer's capability in STA_REC_RA? If not, and if the
downgraded-bandwidth rate search is confirmed broken, this probably needs a
firmware fix.

A driver-side workaround is conceivable — detect a persistent mismatch between
negotiated and achieved bandwidth and re-send a capped STA_REC_RA — but the
trigger and recovery policy are the hard part, and I would not want an
identity-based quirk. Guidance welcome on whether that direction is acceptable
at all.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the mt7915 STA_REC_RA handling and the declared struct sta_rec_ra/sta_rec_ra_fixed fields, then reproduce the HE40 downgrade using the reported debugfs observations. Done means establishing whether the firmware path is broken and documenting a supported driver-side workaround or firmware escalation; no concrete patch scope is specified.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
networking
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.