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,
if (old)
kfree_rcu(old, rcu_head);
+ ieee80211_update_ap_bandwidth(link, params);
+
*changed |= _changed;
return 0;
}
* 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,
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);
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)
* 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);
}
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);