The reserved VHT channel-width pair was the refusal -- clamping it associates 8/8
scope: soc:msm8998 · severity: finding · confidence: proven · subsystem: radio
The question – a UniFi AP answered status=18
(ASSOC_DENIED_UNSUPP_RATE) to this phone while a laptop, and the user’s
Android on the same handset, associated fine. Four sessions diffed the
association request, proved the Supported Rates element byte-identical to the
accepted laptop’s, and concluded the frame was clean and the AP was at fault.
The last one was about to hand the question to a network administrator.
The answer – it was our frame all along. ath10k advertises a VHT capability pair that IEEE 802.11-2016 Table 9-250 marks reserved, and this AP refuses it. Clamp it and the AP accepts, repeatably.
ath10k_create_vht_cap(): vht_cap.cap = ar->vht_cap_info; /* verbatim */WCN3990 firmware reports Supported Channel Width Set = 2 (“160 and 80+80 MHz”)
together with Extended NSS BW Support = 1. That pair is reserved. It is also
untrue of the hardware: the WCN3990 ath10k_hw_params entry declares no
vht160_mcs_rx_highest / vht160_mcs_tx_highest at all, so the MCS set
simultaneously says “not 160-capable”. The frame contradicts itself, and
ath10k passes the firmware word through without ever looking at it.
The fix is to clamp to what the hardware actually declares:
if (!hw->vht160_mcs_rx_highest && !hw->vht160_mcs_tx_highest) vht_cap.cap &= ~(IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK | IEEE80211_VHT_CAP_EXT_NSS_BW_MASK);
before: VHT Capabilities (0x738139fa) Supported Channel Width: (reserved)after: VHT Capabilities (0x338139f2) Supported Channel Width: neither 160 nor 80+80The result, counted rather than asserted
Section: The result, counted rather than assertedAcceptance here was known to be intermittent – 3 acceptances in ~170 pinned attempts with the stock frame, all clustered in one window (ap-accepts-us-intermittently). So one success proves nothing. With the clamp, cold pinned attempts each with a fresh MAC:
attempt 1..8 -> ASSOCIATED(status=0)TOTAL accepted=8 refused18=0 silent=0 of 8then a real NetworkManager activation with the real PSK: 4-way completed,
inet <lan-ip>/24, traffic flowing. Under the old base rate 8/8 is on the
order of 1e-14.
Why four sessions ruled VHT out and were wrong
Section: Why four sessions ruled VHT out and were wrongBecause the knob they tested with does nothing.
vht-capa-overrides-cannot-touch-channel-width is the whole story:
wpa_supplicant’s vht_capa / vht_capa_mask are parsed, logged as applied,
and then silently dropped by mac80211, whose
ieee80211_apply_vhtcap_overrides() has no case for either field. Every one
of those twelve “counterbalanced attempts” transmitted the identical reserved
bytes. The negative result looked like evidence and was noise.
The lesson, and it is the expensive one: a variant that cannot be shown to differ from the control ON THE AIR is not a variant. Capture the frame.
Downstream does not do this, which is why Android works
Section: Downstream does not do this, which is why Android worksqcacld-3.0 never puts the firmware word on the air. It assembles the VHT capability field by field from its own CFG store and uses the firmware’s capabilities only to clamp downward:
parser_api.c:906– forcessupportedChannelWidthSet = 0whenever the session is below 160 MHz, which is every ordinary 80 MHz associationparser_api.c:1040–pDot11f->reserved1 = 0, hard-zeroing bits 30-31; Extended NSS BW does not exist as a concept in that driverwlan_hdd_main.c:1495– init value on WCN3990 isVHT_CAP_80_SUPP(0)
So Android emits (0, 0) and mainline emitted (2, 1), on the same chip with
the same firmware. The user’s working Android on the same handset was the
control that broke the case open – it made “the AP is at fault” untenable
and forced the search back onto our own frame.
The second ath10k wart, still unfixed
Section: The second ath10k wart, still unfixedath10k_get_ht_cap() sets IEEE80211_HT_CAP_DSSSCCK40 unconditionally,
advertising DSSS/CCK in 40 MHz on 5 GHz where it is meaningless. Downstream
leaves it clear (WNI_CFG_HT_CAP_INFO default 0x016C, bit 12 clear). Not
causal here – the clamp alone gives 8/8 – but it is real and worth fixing.
What would overturn this: a long run of cold pinned attempts WITH the
clamp that returns status=18. One would reopen it.
How it was established
Section: How it was establishedtools/wifi-mgmt-capture.py on a monitor vif for every frame; each variant
confirmed on the air before being believed. Module pushed on the mod rung
(see a-tree-built-module-carries-btf-the-running-kernel-rejects – the
rung does NOT strip BTF, and the load fails as “Symbolic link loop”).
