wifi: mac80211: fix per-station PHY capability bandwidth

When a (link) station connected to an AP interface is not
capable of EHT, it's possible that the AP interface is in
160 MHz but the HE channel is narrower, e.g. when EHT has
puncturing.  In this case, the code doesn't correctly set
the STAs bandwidth, the station might be capable of using
160 MHz, but it can't use EHT 160 MHz with puncturing, so
it must be set to narrower.

Track the AP's 'he_and_lower_bw' bandwidth, use that when
calculating the maximum bandwidth to transmit to/from any
station not capable of EHT, and update all stations and
the chanctx min_def when it changes.

Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260415144514.d41efd67876c.I29ef615e6ab049a56c20f3226b5e953859f890b0@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Johannes Berg 2026-04-15 14:42:18 +02:00
parent 2abfda2ad2
commit c9b62ab791
4 changed files with 170 additions and 6 deletions

View File

@ -1312,6 +1312,120 @@ ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst,
return offset;
}
static enum ieee80211_sta_rx_bandwidth
ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
{
const struct ieee80211_vht_operation *vht_oper = params->vht_oper;
int ccfs0, ccfs1;
if (params->he_oper) {
const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
if (params->he_oper->he_oper_params &
cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO))
vht_oper = (void *)params->he_oper->optional;
he_6ghz_oper = ieee80211_he_6ghz_oper(params->he_oper);
if (he_6ghz_oper) {
switch (u8_get_bits(he_6ghz_oper->control,
IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH)) {
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_20MHZ:
return IEEE80211_STA_RX_BW_20;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_40MHZ:
return IEEE80211_STA_RX_BW_40;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_80MHZ:
return IEEE80211_STA_RX_BW_80;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ:
return IEEE80211_STA_RX_BW_160;
}
}
}
if (vht_oper) {
switch (vht_oper->chan_width) {
case IEEE80211_VHT_CHANWIDTH_USE_HT:
/* check for HT (or fall down to 20) below */
break;
case IEEE80211_VHT_CHANWIDTH_160MHZ:
case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
/* deprecated encodings */
return IEEE80211_STA_RX_BW_160;
case IEEE80211_VHT_CHANWIDTH_80MHZ:
/*
* See IEEE 802.11-2020 Table 9-352-BSS bandwidth
* when the VHT Operation Information field Channel
* Width subfield is 1
*
* (IEEE80211_VHT_CHANWIDTH_80MHZ == 1)
*/
ccfs0 = vht_oper->center_freq_seg0_idx;
ccfs1 = vht_oper->center_freq_seg1_idx;
if (!ccfs0)
return IEEE80211_STA_RX_BW_80;
if (ccfs1 && abs(ccfs1 - ccfs0) == 8)
return IEEE80211_STA_RX_BW_160;
/* 80+80 - RX BW doesn't cover that / uses 160 */
if (ccfs1 && abs(ccfs1 - ccfs0) > 16)
return IEEE80211_STA_RX_BW_160;
fallthrough;
default:
/* reserved encoding - assume 80 */
return IEEE80211_STA_RX_BW_80;
}
}
if (params->ht_oper) {
switch (u8_get_bits(params->ht_oper->ht_param,
IEEE80211_HT_PARAM_CHA_SEC_OFFSET)) {
case IEEE80211_HT_PARAM_CHA_SEC_NONE:
default: /* invalid values */
return IEEE80211_STA_RX_BW_20;
case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
return IEEE80211_STA_RX_BW_40;
}
}
/* nothing found, must be 20 MHz */
return IEEE80211_STA_RX_BW_20;
}
static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
struct cfg80211_beacon_data *params)
{
struct ieee80211_local *local = link->sdata->local;
struct ieee80211_chanctx_conf *chanctx_conf;
struct ieee80211_chanctx *chanctx;
/*
* Updating the beacon might, without even changing the channel, cause
* the usable bandwidth for some stations to be changed, for example
* if the beacon configuration is EHT with 160 MHz, HE could change
* between 20, 40, 80 and 160 MHz, and HE (or lower) clients need to
* be handled accordingly.
* Calculate the HE and lower bandwidth and apply that to all stations.
*
* In the future, this also needs to calculate EHT bandwidth and apply
* it to all stations not using UHR DBE, since the chandef would then
* include DBE.
*/
if (link->conf->chanreq.oper.chan->band == NL80211_BAND_S1GHZ)
return;
link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params);
chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
/*
* Note: this relies on ieee80211_recalc_chanctx_min_def() having
* the side effect of updating all stations, if they changed; that
* was normally for when the chandef changed but is used here too.
*/
ieee80211_recalc_chanctx_min_def(local, chanctx);
}
static int
ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
@ -1450,6 +1564,8 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
if (old)
kfree_rcu(old, rcu_head);
ieee80211_update_ap_bandwidth(link, params);
*changed |= _changed;
return 0;
}

View File

@ -718,6 +718,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
* recalc the min required chan width of the channel context, which is
* the max of min required widths of all the interfaces bound to this
* channel context.
*
* Note: ieee80211_update_ap_bandwidth() relies on this iterating all
* affected stations, even if min_def didn't change.
*/
static void
_ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
@ -728,13 +731,11 @@ _ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
u32 changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
check_reserved);
if (!changed)
return;
/* check is BW narrowed */
ieee80211_chan_bw_change(local, ctx, false, true);
drv_change_chanctx(local, ctx, changed);
if (changed)
drv_change_chanctx(local, ctx, changed);
/* check is BW wider */
ieee80211_chan_bw_change(local, ctx, false, false);

View File

@ -1133,6 +1133,10 @@ struct ieee80211_link_data {
struct ieee80211_bss_conf *conf;
struct {
enum ieee80211_sta_rx_bandwidth he_and_lower;
} bss_bw;
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif

View File

@ -3623,6 +3623,49 @@ ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
return IEEE80211_STA_RX_BW_80;
}
/**
* ieee80211_sta_usable_bw - get STA's usable bandwidth capability
* @link_sta: the (link) STA to get the capability for
* @band: the band to get the capability on
*
* If the STA is on an AP interface, take into account the AP's
* bandwidth corresponding to this station's PHY capability
*
* Return: the maximum bandwidth supported by the STA on the
* connection to the interface it's connected to
*/
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
enum nl80211_band band)
{
struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
enum ieee80211_sta_rx_bandwidth bw;
struct ieee80211_link_data *link;
bw = ieee80211_sta_bw_capability(link_sta, band);
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
sdata = get_bss_sdata(sdata);
/* for a STA to exist on VLAN, it must have AP */
if (WARN_ON(!sdata))
return IEEE80211_STA_RX_BW_20;
}
if (sdata->vif.type != NL80211_IFTYPE_AP)
return bw;
/* for a link STA to exist, vif must have the link */
link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
if (WARN_ON(!link))
return IEEE80211_STA_RX_BW_20;
if (link_sta->pub->eht_cap.has_eht)
return bw;
return min(bw, link->bss_bw.he_and_lower);
}
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
@ -3637,7 +3680,7 @@ ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
* at a higher bandwidth first while reducing bandwidth, and
* change the chanctx only after the peer accepts, etc.)
*/
return min(ieee80211_sta_bw_capability(link_sta, chandef->chan->band),
return min(ieee80211_sta_usable_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
}
@ -3653,7 +3696,7 @@ ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
bss_width = chandef->width;
band = chandef->chan->band;
bw = ieee80211_sta_bw_capability(link_sta, band);
bw = ieee80211_sta_usable_bw(link_sta, band);
bw = min(bw, link_sta->op_mode_bw);
/* also limit to RX OMI bandwidth we TX to the STA */
bw = min(bw, link_sta->rx_omi_bw_tx);