// SPDX-License-Identifier: GPL-2.0-only
/*
* BSS client mode implementation
* Copyright 2003-2008, Jouni Malinen <j@w1.fi>
* Copyright 2004, Instant802 Networks, Inc.
* Copyright 2005, Devicescape Software, Inc.
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
* Copyright (C) 2018 - 2025 Intel Corporation
*/
#include <linux/delay.h>
#include <linux/fips.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/etherdevice.h>
#include <linux/moduleparam.h>
#include <linux/rtnetlink.h>
#include <linux/crc32.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <net/mac80211.h>
#include <linux/unaligned.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "rate.h"
#include "led.h"
#include "fils_aead.h"
#include <kunit/static_stub.h>
#define IEEE80211_AUTH_TIMEOUT (HZ / 5)
#define IEEE80211_AUTH_TIMEOUT_LONG (HZ / 2)
#define IEEE80211_AUTH_TIMEOUT_SHORT (HZ / 10)
#define IEEE80211_AUTH_TIMEOUT_SAE (HZ * 2)
#define IEEE80211_AUTH_MAX_TRIES 3
#define IEEE80211_AUTH_WAIT_ASSOC (HZ * 5)
#define IEEE80211_AUTH_WAIT_SAE_RETRY (HZ * 2)
#define IEEE80211_ASSOC_TIMEOUT (HZ / 5)
#define IEEE80211_ASSOC_TIMEOUT_LONG (HZ / 2)
#define IEEE80211_ASSOC_TIMEOUT_SHORT (HZ / 10)
#define IEEE80211_ASSOC_MAX_TRIES 3
#define IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS (100 * USEC_PER_MSEC)
#define IEEE80211_ADV_TTLM_ST_UNDERFLOW 0xff00
#define IEEE80211_NEG_TTLM_REQ_TIMEOUT (HZ / 5)
static int max_nullfunc_tries = 2;
module_param(max_nullfunc_tries,
int, 0644);
MODULE_PARM_DESC(max_nullfunc_tries,
"Maximum nullfunc tx tries before disconnecting (reason 4).");
static int max_probe_tries = 5;
module_param(max_probe_tries,
int, 0644);
MODULE_PARM_DESC(max_probe_tries,
"Maximum probe tries before disconnecting (reason 4).");
/*
* Beacon loss timeout is calculated as N frames times the
* advertised beacon interval. This may need to be somewhat
* higher than what hardware might detect to account for
* delays in the host processing frames. But since we also
* probe on beacon miss before declaring the connection lost
* default to what we want.
*/
static int beacon_loss_count = 7;
module_param(beacon_loss_count,
int, 0644);
MODULE_PARM_DESC(beacon_loss_count,
"Number of beacon intervals before we decide beacon was lost.");
/*
* Time the connection can be idle before we probe
* it to see if we can still talk to the AP.
*/
#define IEEE80211_CONNECTION_IDLE_TIME (30 * HZ)
/*
* Time we wait for a probe response after sending
* a probe request because of beacon loss or for
* checking the connection still works.
*/
static int probe_wait_ms = 500;
module_param(probe_wait_ms,
int, 0644);
MODULE_PARM_DESC(probe_wait_ms,
"Maximum time(ms) to wait for probe response"
" before disconnecting (reason 4).");
/*
* How many Beacon frames need to have been used in average signal strength
* before starting to indicate signal change events.
*/
#define IEEE80211_SIGNAL_AVE_MIN_COUNT 4
/*
* We can have multiple work items (and connection probing)
* scheduling this timer, but we need to take care to only
* reschedule it when it should fire _earlier_ than it was
* asked for before, or if it's not pending right now. This
* function ensures that. Note that it then is required to
* run this function for all timeouts after the first one
* has happened -- the work that runs from this timer will
* do that.
*/
static void run_again(
struct ieee80211_sub_if_data *sdata,
unsigned long timeout)
{
lockdep_assert_wiphy(sdata->local->hw.wiphy);
if (!timer_pending(&sdata->u.mgd.timer) ||
time_before(timeout, sdata->u.mgd.timer.expires))
mod_timer(&sdata->u.mgd.timer, timeout);
}
void ieee80211_sta_reset_beacon_monitor(
struct ieee80211_sub_if_data *sdata)
{
if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER)
return;
if (ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR))
return;
mod_timer(&sdata->u.mgd.bcn_mon_timer,
round_jiffies_up(jiffies + sdata->u.mgd.beacon_timeout));
}
void ieee80211_sta_reset_conn_monitor(
struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
if (unlikely(!ifmgd->associated))
return;
if (ifmgd->probe_send_count)
ifmgd->probe_send_count = 0;
if (ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR))
return;
mod_timer(&ifmgd->conn_mon_timer,
round_jiffies_up(jiffies + IEEE80211_CONNECTION_IDLE_TIME));
}
static int ecw2cw(
int ecw)
{
return (1 << ecw) - 1;
}
static enum ieee80211_conn_mode
ieee80211_determine_ap_chan(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel *channel,
u32 vht_cap_info,
const struct ieee802_11_elems *elems,
bool ignore_ht_channel_mismatch,
const struct ieee80211_conn_settings *conn,
struct cfg80211_chan_def *chandef)
{
const struct ieee80211_ht_operation *ht_oper = elems->ht_operation;
const struct ieee80211_vht_operation *vht_oper = elems->vht_operation;
const struct ieee80211_he_operation *he_oper = elems->he_operation;
const struct ieee80211_eht_operation *eht_oper = elems->eht_operation;
struct ieee80211_supported_band *sband =
sdata->local->hw.wiphy->bands[channel->band];
struct cfg80211_chan_def vht_chandef;
bool no_vht =
false;
u32 ht_cfreq;
if (ieee80211_hw_check(&sdata->local->hw, STRICT))
ignore_ht_channel_mismatch =
false;
*chandef = (
struct cfg80211_chan_def) {
.chan = channel,
.width = NL80211_CHAN_WIDTH_20_NOHT,
.center_freq1 = channel->center_freq,
.freq1_offset = channel->freq_offset,
};
/* get special S1G case out of the way */
if (sband->band == NL80211_BAND_S1GHZ) {
if (!ieee80211_chandef_s1g_oper(elems->s1g_oper, chandef)) {
sdata_info(sdata,
"Missing S1G Operation Element? Trying operating == primary\n");
chandef->width = ieee80211_s1g_channel_width(channel);
}
return IEEE80211_CONN_MODE_S1G;
}
/* get special 6 GHz case out of the way */
if (sband->band == NL80211_BAND_6GHZ) {
enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_EHT;
/* this is an error */
if (conn->mode < IEEE80211_CONN_MODE_HE)
return IEEE80211_CONN_MODE_LEGACY;
if (!elems->he_6ghz_capa || !elems->he_cap) {
sdata_info(sdata,
"HE 6 GHz AP is missing HE/HE 6 GHz band capability\n");
return IEEE80211_CONN_MODE_LEGACY;
}
if (!eht_oper || !elems->eht_cap) {
eht_oper = NULL;
mode = IEEE80211_CONN_MODE_HE;
}
if (!ieee80211_chandef_he_6ghz_oper(sdata->local, he_oper,
eht_oper, chandef)) {
sdata_info(sdata,
"bad HE/EHT 6 GHz operation\n");
return IEEE80211_CONN_MODE_LEGACY;
}
return mode;
}
/* now we have the progression HT, VHT, ... */
if (conn->mode < IEEE80211_CONN_MODE_HT)
return IEEE80211_CONN_MODE_LEGACY;
if (!ht_oper || !elems->ht_cap_elem)
return IEEE80211_CONN_MODE_LEGACY;
chandef->width = NL80211_CHAN_WIDTH_20;
ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan,
channel->band);
/* check that channel matches the right operating channel */
if (!ignore_ht_channel_mismatch && channel->center_freq != ht_cfreq) {
/*
* It's possible that some APs are confused here;
* Netgear WNDR3700 sometimes reports 4 higher than
* the actual channel in association responses, but
* since we look at probe response/beacon data here
* it should be OK.
*/
sdata_info(sdata,
"Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n",
channel->center_freq, ht_cfreq,
ht_oper->primary_chan, channel->band);
return IEEE80211_CONN_MODE_LEGACY;
}
ieee80211_chandef_ht_oper(ht_oper, chandef);
if (conn->mode < IEEE80211_CONN_MODE_VHT)
return IEEE80211_CONN_MODE_HT;
vht_chandef = *chandef;
/*
* having he_cap/he_oper parsed out implies we're at
* least operating as HE STA
*/
if (elems->he_cap && he_oper &&
he_oper->he_oper_params & cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO)) {
struct ieee80211_vht_operation he_oper_vht_cap;
/*
* Set only first 3 bytes (other 2 aren't used in
* ieee80211_chandef_vht_oper() anyway)
*/
memcpy(&he_oper_vht_cap, he_oper->optional, 3);
he_oper_vht_cap.basic_mcs_set = cpu_to_le16(0);
if (!ieee80211_chandef_vht_oper(&sdata->local->hw, vht_cap_info,
&he_oper_vht_cap, ht_oper,
&vht_chandef)) {
sdata_info(sdata,
"HE AP VHT information is invalid, disabling HE\n");
/* this will cause us to re-parse as VHT STA */
return IEEE80211_CONN_MODE_VHT;
}
}
else if (!vht_oper || !elems->vht_cap_elem) {
if (sband->band == NL80211_BAND_5GHZ) {
sdata_info(sdata,
"VHT information is missing, disabling VHT\n");
return IEEE80211_CONN_MODE_HT;
}
no_vht =
true;
}
else if (sband->band == NL80211_BAND_2GHZ) {
no_vht =
true;
}
else if (!ieee80211_chandef_vht_oper(&sdata->local->hw,
vht_cap_info,
vht_oper, ht_oper,
&vht_chandef)) {
sdata_info(sdata,
"AP VHT information is invalid, disabling VHT\n");
return IEEE80211_CONN_MODE_HT;
}
if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
sdata_info(sdata,
"AP VHT information doesn't match HT, disabling VHT\n");
return IEEE80211_CONN_MODE_HT;
}
*chandef = vht_chandef;
/* stick to current max mode if we or the AP don't have HE */
if (conn->mode < IEEE80211_CONN_MODE_HE ||
!elems->he_operation || !elems->he_cap) {
if (no_vht)
return IEEE80211_CONN_MODE_HT;
return IEEE80211_CONN_MODE_VHT;
}
/* stick to HE if we or the AP don't have EHT */
if (conn->mode < IEEE80211_CONN_MODE_EHT ||
!eht_oper || !elems->eht_cap)
return IEEE80211_CONN_MODE_HE;
/*
* handle the case that the EHT operation indicates that it holds EHT
* operation information (in case that the channel width differs from
* the channel width reported in HT/VHT/HE).
*/
if (eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT) {
struct cfg80211_chan_def eht_chandef = *chandef;
ieee80211_chandef_eht_oper((
const void *)eht_oper->optional,
&eht_chandef);
eht_chandef.punctured =
ieee80211_eht_oper_dis_subchan_bitmap(eht_oper);
if (!cfg80211_chandef_valid(&eht_chandef)) {
sdata_info(sdata,
"AP EHT information is invalid, disabling EHT\n");
return IEEE80211_CONN_MODE_HE;
}
if (!cfg80211_chandef_compatible(chandef, &eht_chandef)) {
sdata_info(sdata,
"AP EHT information doesn't match HT/VHT/HE, disabling EHT\n");
return IEEE80211_CONN_MODE_HE;
}
*chandef = eht_chandef;
}
return IEEE80211_CONN_MODE_EHT;
}
static bool
ieee80211_verify_sta_ht_mcs_support(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct ieee80211_ht_operation *ht_op)
{
struct ieee80211_sta_ht_cap sta_ht_cap;
int i;
if (sband->band == NL80211_BAND_6GHZ)
return true;
if (!ht_op)
return false;
memcpy(&sta_ht_cap, &sband->ht_cap,
sizeof(sta_ht_cap));
ieee80211_apply_htcap_overrides(sdata, &sta_ht_cap);
/*
* P802.11REVme/D7.0 - 6.5.4.2.4
* ...
* If the MLME of an HT STA receives an MLME-JOIN.request primitive
* with the SelectedBSS parameter containing a Basic HT-MCS Set field
* in the HT Operation parameter that contains any unsupported MCSs,
* the MLME response in the resulting MLME-JOIN.confirm primitive shall
* contain a ResultCode parameter that is not set to the value SUCCESS.
* ...
*/
/* Simply check that all basic rates are in the STA RX mask */
for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
if ((ht_op->basic_set[i] & sta_ht_cap.mcs.rx_mask[i]) !=
ht_op->basic_set[i])
return false;
}
return true;
}
static bool
ieee80211_verify_sta_vht_mcs_support(
struct ieee80211_sub_if_data *sdata,
int link_id,
struct ieee80211_supported_band *sband,
const struct ieee80211_vht_operation *vht_op)
{
struct ieee80211_sta_vht_cap sta_vht_cap;
u16 ap_min_req_set, sta_rx_mcs_map, sta_tx_mcs_map;
int nss;
if (sband->band != NL80211_BAND_5GHZ)
return true;
if (!vht_op)
return false;
memcpy(&sta_vht_cap, &sband->vht_cap,
sizeof(sta_vht_cap));
ieee80211_apply_vhtcap_overrides(sdata, &sta_vht_cap);
ap_min_req_set = le16_to_cpu(vht_op->basic_mcs_set);
sta_rx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.rx_mcs_map);
sta_tx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.tx_mcs_map);
/*
* Many APs are incorrectly advertising an all-zero value here,
* which really means MCS 0-7 are required for 1-8 streams, but
* they don't really mean it that way.
* Some other APs are incorrectly advertising 3 spatial streams
* with MCS 0-7 are required, but don't really mean it that way
* and we'll connect only with HT, rather than even HE.
* As a result, unfortunately the VHT basic MCS/NSS set cannot
* be used at all, so check it only in strict mode.
*/
if (!ieee80211_hw_check(&sdata->local->hw, STRICT))
return true;
/*
* P802.11REVme/D7.0 - 6.5.4.2.4
* ...
* If the MLME of a VHT STA receives an MLME-JOIN.request primitive
* with a SelectedBSS parameter containing a Basic VHT-MCS And NSS Set
* field in the VHT Operation parameter that contains any unsupported
* <VHT-MCS, NSS> tuple, the MLME response in the resulting
* MLME-JOIN.confirm primitive shall contain a ResultCode parameter
* that is not set to the value SUCCESS.
* ...
*/
for (nss = 8; nss > 0; nss--) {
u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
u8 sta_rx_val;
u8 sta_tx_val;
if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
sta_rx_val = (sta_rx_mcs_map >> (2 * (nss - 1))) & 3;
sta_tx_val = (sta_tx_mcs_map >> (2 * (nss - 1))) & 3;
if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
sta_rx_val < ap_op_val || sta_tx_val < ap_op_val) {
link_id_info(sdata, link_id,
"Missing mandatory rates for %d Nss, rx %d, tx %d oper %d, disable VHT\n",
nss, sta_rx_val, sta_tx_val, ap_op_val);
return false;
}
}
return true;
}
static bool
ieee80211_verify_peer_he_mcs_support(
struct ieee80211_sub_if_data *sdata,
int link_id,
const struct ieee80211_he_cap_elem *he_cap,
const struct ieee80211_he_operation *he_op)
{
struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
u16 mcs_80_map_tx, mcs_80_map_rx;
u16 ap_min_req_set;
int nss;
if (!he_cap)
return false;
/* mcs_nss is right after he_cap info */
he_mcs_nss_supp = (
void *)(he_cap + 1);
mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80);
/* P802.11-REVme/D0.3
* 27.1.1 Introduction to the HE PHY
* ...
* An HE STA shall support the following features:
* ...
* Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all
* supported channel widths for HE SU PPDUs
*/
if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) {
link_id_info(sdata, link_id,
"Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n",
mcs_80_map_tx, mcs_80_map_rx);
return false;
}
if (!he_op)
return true;
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
/*
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
return true;
/* make sure the AP is consistent with itself
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* A STA that is operating in an HE BSS shall be able to receive and
* transmit at each of the <HE-MCS, NSS> tuple values indicated by the
* Basic HE-MCS And NSS Set field of the HE Operation parameter of the
* MLME-START.request primitive and shall be able to receive at each of
* the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and
* NSS Set field in the HE Capabilities parameter of the MLMESTART.request
* primitive
*/
for (nss = 8; nss > 0; nss--) {
u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
u8 ap_rx_val;
u8 ap_tx_val;
if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3;
ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3;
if (ap_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
ap_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
ap_rx_val < ap_op_val || ap_tx_val < ap_op_val) {
link_id_info(sdata, link_id,
"Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n",
nss, ap_rx_val, ap_tx_val, ap_op_val);
return false;
}
}
return true;
}
static bool
ieee80211_verify_sta_he_mcs_support(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct ieee80211_he_operation *he_op)
{
const struct ieee80211_sta_he_cap *sta_he_cap =
ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
u16 ap_min_req_set;
int i;
if (!sta_he_cap || !he_op)
return false;
ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);
/*
* Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
* zeroes, which is nonsense, and completely inconsistent with itself
* (it doesn't have 8 streams). Accept the settings in this case anyway.
*/
if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
return true;
/* Need to go over for 80MHz, 160MHz and for 80+80 */
for (i = 0; i < 3; i++) {
const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp =
&sta_he_cap->he_mcs_nss_supp;
u16 sta_mcs_map_rx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]);
u16 sta_mcs_map_tx =
le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]);
u8 nss;
bool verified =
true;
/*
* For each band there is a maximum of 8 spatial streams
* possible. Each of the sta_mcs_map_* is a 16-bit struct built
* of 2 bits per NSS (1-8), with the values defined in enum
* ieee80211_he_mcs_support. Need to make sure STA TX and RX
* capabilities aren't less than the AP's minimum requirements
* for this HE BSS per SS.
* It is enough to find one such band that meets the reqs.
*/
for (nss = 8; nss > 0; nss--) {
u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3;
u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3;
u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
continue;
/*
* Make sure the HE AP doesn't require MCSs that aren't
* supported by the client as required by spec
*
* P802.11-REVme/D0.3
* 26.17.1 Basic HE BSS operation
*
* An HE STA shall not attempt to join * (MLME-JOIN.request primitive)
* a BSS, unless it supports (i.e., is able to both transmit and
* receive using) all of the <HE-MCS, NSS> tuples in the basic
* HE-MCS and NSS set.
*/
if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
(ap_val > sta_rx_val) || (ap_val > sta_tx_val)) {
verified =
false;
break;
}
}
if (verified)
return true;
}
/* If here, STA doesn't meet AP's HE min requirements */
return false;
}
static u8
ieee80211_get_eht_cap_mcs_nss(
const struct ieee80211_sta_he_cap *sta_he_cap,
const struct ieee80211_sta_eht_cap *sta_eht_cap,
unsigned int idx,
int bw)
{
u8 he_phy_cap0 = sta_he_cap->he_cap_elem.phy_cap_info[0];
u8 eht_phy_cap0 = sta_eht_cap->eht_cap_elem.phy_cap_info[0];
/* handle us being a 20 MHz-only EHT STA - with four values
* for MCS 0-7, 8-9, 10-11, 12-13.
*/
if (!(he_phy_cap0 & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL))
return sta_eht_cap->eht_mcs_nss_supp.only_20mhz.rx_tx_max_nss[idx];
/* the others have MCS 0-9 together, rather than separately from 0-7 */
if (idx > 0)
idx--;
switch (bw) {
case 0:
return sta_eht_cap->eht_mcs_nss_supp.bw._80.rx_tx_max_nss[idx];
case 1:
if (!(he_phy_cap0 &
(IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)))
return 0xff;
/* pass check */
return sta_eht_cap->eht_mcs_nss_supp.bw._160.rx_tx_max_nss[idx];
case 2:
if (!(eht_phy_cap0 & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ))
return 0xff;
/* pass check */
return sta_eht_cap->eht_mcs_nss_supp.bw._320.rx_tx_max_nss[idx];
}
WARN_ON(1);
return 0;
}
static bool
ieee80211_verify_sta_eht_mcs_support(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
const struct ieee80211_eht_operation *eht_op)
{
const struct ieee80211_sta_he_cap *sta_he_cap =
ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
const struct ieee80211_sta_eht_cap *sta_eht_cap =
ieee80211_get_eht_iftype_cap_vif(sband, &sdata->vif);
const struct ieee80211_eht_mcs_nss_supp_20mhz_only *req;
unsigned int i;
if (!sta_he_cap || !sta_eht_cap || !eht_op)
return false;
req = &eht_op->basic_mcs_nss;
for (i = 0; i < ARRAY_SIZE(req->rx_tx_max_nss); i++) {
u8 req_rx_nss, req_tx_nss;
unsigned int bw;
req_rx_nss = u8_get_bits(req->rx_tx_max_nss[i],
IEEE80211_EHT_MCS_NSS_RX);
req_tx_nss = u8_get_bits(req->rx_tx_max_nss[i],
IEEE80211_EHT_MCS_NSS_TX);
for (bw = 0; bw < 3; bw++) {
u8 have, have_rx_nss, have_tx_nss;
have = ieee80211_get_eht_cap_mcs_nss(sta_he_cap,
sta_eht_cap,
i, bw);
have_rx_nss = u8_get_bits(have,
IEEE80211_EHT_MCS_NSS_RX);
have_tx_nss = u8_get_bits(have,
IEEE80211_EHT_MCS_NSS_TX);
if (req_rx_nss > have_rx_nss ||
req_tx_nss > have_tx_nss)
return false;
}
}
return true;
}
static void ieee80211_get_rates(
struct ieee80211_supported_band *sband,
const u8 *supp_rates,
unsigned int supp_rates_len,
const u8 *ext_supp_rates,
unsigned int ext_supp_rates_len,
u32 *rates, u32 *basic_rates,
unsigned long *unknown_rates_selectors,
bool *have_higher_than_11mbit,
int *min_rate,
int *min_rate_index)
{
int i, j;
for (i = 0; i < supp_rates_len + ext_supp_rates_len; i++) {
u8 supp_rate = i < supp_rates_len ?
supp_rates[i] :
ext_supp_rates[i - supp_rates_len];
int rate = supp_rate & 0x7f;
bool is_basic = !!(supp_rate & 0x80);
if ((rate * 5) > 110 && have_higher_than_11mbit)
*have_higher_than_11mbit =
true;
/*
* Skip membership selectors since they're not rates.
*
* Note: Even though the membership selector and the basic
* rate flag share the same bit, they are not exactly
* the same.
*/
if (is_basic && rate >= BSS_MEMBERSHIP_SELECTOR_MIN) {
if (unknown_rates_selectors)
set_bit(rate, unknown_rates_selectors);
continue;
}
for (j = 0; j < sband->n_bitrates; j++) {
struct ieee80211_rate *br;
int brate;
br = &sband->bitrates[j];
brate = DIV_ROUND_UP(br->bitrate, 5);
if (brate == rate) {
if (rates)
*rates |= BIT(j);
if (is_basic && basic_rates)
*basic_rates |= BIT(j);
if (min_rate && (rate * 5) < *min_rate) {
*min_rate = rate * 5;
if (min_rate_index)
*min_rate_index = j;
}
break;
}
}
/* Handle an unknown entry as if it is an unknown selector */
if (is_basic && unknown_rates_selectors && j == sband->n_bitrates)
set_bit(rate, unknown_rates_selectors);
}
}
static bool ieee80211_chandef_usable(
struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 prohibited_flags)
{
if (!cfg80211_chandef_usable(sdata->local->hw.wiphy,
chandef, prohibited_flags))
return false;
if (chandef->punctured &&
ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING))
return false;
return true;
}
static int ieee80211_chandef_num_subchans(
const struct cfg80211_chan_def *c)
{
if (c->width == NL80211_CHAN_WIDTH_80P80)
return 4 + 4;
return cfg80211_chandef_get_width(c) / 20;
}
static int ieee80211_chandef_num_widths(
const struct cfg80211_chan_def *c)
{
switch (c->width) {
case NL80211_CHAN_WIDTH_20:
case NL80211_CHAN_WIDTH_20_NOHT:
return 1;
case NL80211_CHAN_WIDTH_40:
return 2;
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_80:
return 3;
case NL80211_CHAN_WIDTH_160:
return 4;
case NL80211_CHAN_WIDTH_320:
return 5;
default:
WARN_ON(1);
return 0;
}
}
VISIBLE_IF_MAC80211_KUNIT
int
ieee80211_calc_chandef_subchan_offset(
const struct cfg80211_chan_def *ap,
u8 n_partial_subchans)
{
int n = ieee80211_chandef_num_subchans(ap);
struct cfg80211_chan_def tmp = *ap;
int offset = 0;
/*
* Given a chandef (in this context, it's the AP's) and a number
* of subchannels that we want to look at ('n_partial_subchans'),
* calculate the offset in number of subchannels between the full
* and the subset with the desired width.
*/
/* same number of subchannels means no offset, obviously */
if (n == n_partial_subchans)
return 0;
/* don't WARN - misconfigured APs could cause this if their N > width */
if (n < n_partial_subchans)
return 0;
while (ieee80211_chandef_num_subchans(&tmp) > n_partial_subchans) {
u32 prev = tmp.center_freq1;
ieee80211_chandef_downgrade(&tmp, NULL);
/*
* if center_freq moved up, half the original channels
* are gone now but were below, so increase offset
*/
if (prev < tmp.center_freq1)
offset += ieee80211_chandef_num_subchans(&tmp);
}
/*
* 80+80 with secondary 80 below primary - four subchannels for it
* (we cannot downgrade *to* 80+80, so no need to consider 'tmp')
*/
if (ap->width == NL80211_CHAN_WIDTH_80P80 &&
ap->center_freq2 < ap->center_freq1)
offset += 4;
return offset;
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_calc_chandef_subchan_offset);
VISIBLE_IF_MAC80211_KUNIT
void
ieee80211_rearrange_tpe_psd(
struct ieee80211_parsed_tpe_psd *psd,
const struct cfg80211_chan_def *ap,
const struct cfg80211_chan_def *used)
{
u8 needed = ieee80211_chandef_num_subchans(used);
u8 have = ieee80211_chandef_num_subchans(ap);
u8 tmp[IEEE80211_TPE_PSD_ENTRIES_320MHZ];
u8 offset;
if (!psd->valid)
return;
/* if N is zero, all defaults were used, no point in rearranging */
if (!psd->n)
goto out;
BUILD_BUG_ON(
sizeof(tmp) !=
sizeof(psd->power));
/*
* This assumes that 'N' is consistent with the HE channel, as
* it should be (otherwise the AP is broken).
*
* In psd->power we have values in the order 0..N, 0..K, where
* N+K should cover the entire channel per 'ap', but even if it
* doesn't then we've pre-filled 'unlimited' as defaults.
*
* But this is all the wrong order, we want to have them in the
* order of the 'used' channel.
*
* So for example, we could have a 320 MHz EHT AP, which has the
* HE channel as 80 MHz (e.g. due to puncturing, which doesn't
* seem to be considered for the TPE), as follows:
*
* EHT 320: | | | | | | | | | | | | | | | | |
* HE 80: | | | | |
* used 160: | | | | | | | | |
*
* N entries: |--|--|--|--|
* K entries: |--|--|--|--|--|--|--|--| |--|--|--|--|
* power idx: 4 5 6 7 8 9 10 11 0 1 2 3 12 13 14 15
* full chan: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
* used chan: 0 1 2 3 4 5 6 7
*
* The idx in the power array ('power idx') is like this since it
* comes directly from the element's N and K entries in their
* element order, and those are this way for HE compatibility.
*
* Rearrange them as desired here, first by putting them into the
* 'full chan' order, and then selecting the necessary subset for
* the 'used chan'.
*/
/* first reorder according to AP channel */
offset = ieee80211_calc_chandef_subchan_offset(ap, psd->n);
for (
int i = 0; i < have; i++) {
if (i < offset)
tmp[i] = psd->power[i + psd->n];
else if (i < offset + psd->n)
tmp[i] = psd->power[i - offset];
else
tmp[i] = psd->power[i];
}
/*
* and then select the subset for the used channel
* (set everything to defaults first in case a driver is confused)
*/
memset(psd->power, IEEE80211_TPE_PSD_NO_LIMIT,
sizeof(psd->power));
offset = ieee80211_calc_chandef_subchan_offset(ap, needed);
for (
int i = 0; i < needed; i++)
psd->power[i] = tmp[offset + i];
out:
/* limit, but don't lie if there are defaults in the data */
if (needed < psd->count)
psd->count = needed;
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_rearrange_tpe_psd);
static void ieee80211_rearrange_tpe(
struct ieee80211_parsed_tpe *tpe,
const struct cfg80211_chan_def *ap,
const struct cfg80211_chan_def *used)
{
/* ignore this completely for narrow/invalid channels */
if (!ieee80211_chandef_num_subchans(ap) ||
!ieee80211_chandef_num_subchans(used)) {
ieee80211_clear_tpe(tpe);
return;
}
for (
int i = 0; i < 2; i++) {
int needed_pwr_count;
ieee80211_rearrange_tpe_psd(&tpe->psd_local[i], ap, used);
ieee80211_rearrange_tpe_psd(&tpe->psd_reg_client[i], ap, used);
/* limit this to the widths we actually need */
needed_pwr_count = ieee80211_chandef_num_widths(used);
if (needed_pwr_count < tpe->max_local[i].count)
tpe->max_local[i].count = needed_pwr_count;
if (needed_pwr_count < tpe->max_reg_client[i].count)
tpe->max_reg_client[i].count = needed_pwr_count;
}
}
/*
* The AP part of the channel request is used to distinguish settings
* to the device used for wider bandwidth OFDMA. This is used in the
* channel context code to assign two channel contexts even if they're
* both for the same channel, if the AP bandwidths are incompatible.
* If not EHT (or driver override) then ap.chan == NULL indicates that
* there's no wider BW OFDMA used.
*/
static void ieee80211_set_chanreq_ap(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_chan_req *chanreq,
struct ieee80211_conn_settings *conn,
struct cfg80211_chan_def *ap_chandef)
{
chanreq->ap.chan = NULL;
if (conn->mode < IEEE80211_CONN_MODE_EHT)
return;
if (sdata->vif.driver_flags & IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW)
return;
chanreq->ap = *ap_chandef;
}
VISIBLE_IF_MAC80211_KUNIT
struct ieee802_11_elems *
ieee80211_determine_chan_mode(
struct ieee80211_sub_if_data *sdata,
struct ieee80211_conn_settings *conn,
struct cfg80211_bss *cbss,
int link_id,
struct ieee80211_chan_req *chanreq,
struct cfg80211_chan_def *ap_chandef,
unsigned long *userspace_selectors)
{
const struct cfg80211_bss_ies *ies = rcu_dereference(cbss->ies);
struct ieee80211_bss *bss = (
void *)cbss->priv;
struct ieee80211_channel *channel = cbss->channel;
struct ieee80211_elems_parse_params parse_params = {
.link_id = -1,
.from_ap =
true,
.start = ies->data,
.len = ies->len,
};
struct ieee802_11_elems *elems;
struct ieee80211_supported_band *sband;
enum ieee80211_conn_mode ap_mode;
unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
int ret;
again:
parse_params.mode = conn->mode;
elems = ieee802_11_parse_elems_full(&parse_params);
if (!elems)
return ERR_PTR(-ENOMEM);
ap_mode = ieee80211_determine_ap_chan(sdata, channel, bss->vht_cap_info,
elems,
false, conn, ap_chandef);
/* this should be impossible since parsing depends on our mode */
if (WARN_ON(ap_mode > conn->mode)) {
ret = -EINVAL;
goto free;
}
if (conn->mode != ap_mode) {
conn->mode = ap_mode;
kfree(elems);
goto again;
}
mlme_link_id_dbg(sdata, link_id,
"determined AP %pM to be %s\n",
cbss->bssid, ieee80211_conn_mode_str(ap_mode));
sband = sdata->local->hw.wiphy->bands[channel->band];
ieee80211_get_rates(sband, elems->supp_rates, elems->supp_rates_len,
elems->ext_supp_rates, elems->ext_supp_rates_len,
NULL, NULL, unknown_rates_selectors, NULL, NULL,
NULL);
switch (channel->band) {
case NL80211_BAND_S1GHZ:
if (WARN_ON(ap_mode != IEEE80211_CONN_MODE_S1G)) {
ret = -EINVAL;
goto free;
}
return elems;
case NL80211_BAND_6GHZ:
if (ap_mode < IEEE80211_CONN_MODE_HE) {
link_id_info(sdata, link_id,
"Rejecting non-HE 6/7 GHz connection");
ret = -EINVAL;
goto free;
}
break;
default:
if (WARN_ON(ap_mode == IEEE80211_CONN_MODE_S1G)) {
ret = -EINVAL;
goto free;
}
}
switch (ap_mode) {
case IEEE80211_CONN_MODE_S1G:
WARN_ON(1);
ret = -EINVAL;
goto free;
case IEEE80211_CONN_MODE_LEGACY:
conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
break;
case IEEE80211_CONN_MODE_HT:
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_40);
break;
case IEEE80211_CONN_MODE_VHT:
case IEEE80211_CONN_MODE_HE:
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_160);
break;
case IEEE80211_CONN_MODE_EHT:
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_320);
break;
}
chanreq->oper = *ap_chandef;
bitmap_copy(sta_selectors, userspace_selectors, 128);
if (conn->mode >= IEEE80211_CONN_MODE_HT)
set_bit(BSS_MEMBERSHIP_SELECTOR_HT_PHY, sta_selectors);
if (conn->mode >= IEEE80211_CONN_MODE_VHT)
set_bit(BSS_MEMBERSHIP_SELECTOR_VHT_PHY, sta_selectors);
if (conn->mode >= IEEE80211_CONN_MODE_HE)
set_bit(BSS_MEMBERSHIP_SELECTOR_HE_PHY, sta_selectors);
if (conn->mode >= IEEE80211_CONN_MODE_EHT)
set_bit(BSS_MEMBERSHIP_SELECTOR_EHT_PHY, sta_selectors);
/*
* We do not support EPD or GLK so never add them.
* SAE_H2E is handled through userspace_selectors.
*/
/* Check if we support all required features */
if (!bitmap_subset(unknown_rates_selectors, sta_selectors, 128)) {
link_id_info(sdata, link_id,
"required basic rate or BSS membership selectors not supported or disabled, rejecting connection\n");
ret = -EINVAL;
goto free;
}
ieee80211_set_chanreq_ap(sdata, chanreq, conn, ap_chandef);
while (!ieee80211_chandef_usable(sdata, &chanreq->oper,
IEEE80211_CHAN_DISABLED)) {
if (WARN_ON(chanreq->oper.width == NL80211_CHAN_WIDTH_20_NOHT)) {
ret = -EINVAL;
goto free;
}
ieee80211_chanreq_downgrade(chanreq, conn);
}
if (conn->mode >= IEEE80211_CONN_MODE_HE &&
!cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
IEEE80211_CHAN_NO_HE)) {
conn->mode = IEEE80211_CONN_MODE_VHT;
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_160);
}
if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
!cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
IEEE80211_CHAN_NO_EHT)) {
conn->mode = IEEE80211_CONN_MODE_HE;
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_160);
}
if (chanreq->oper.width != ap_chandef->width || ap_mode != conn->mode)
link_id_info(sdata, link_id,
"regulatory prevented using AP config, downgraded\n");
if (conn->mode >= IEEE80211_CONN_MODE_HT &&
!ieee80211_verify_sta_ht_mcs_support(sdata, sband,
elems->ht_operation)) {
conn->mode = IEEE80211_CONN_MODE_LEGACY;
conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
link_id_info(sdata, link_id,
"required MCSes not supported, disabling HT\n");
}
if (conn->mode >= IEEE80211_CONN_MODE_VHT &&
!ieee80211_verify_sta_vht_mcs_support(sdata, link_id, sband,
elems->vht_operation)) {
conn->mode = IEEE80211_CONN_MODE_HT;
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_40);
link_id_info(sdata, link_id,
"required MCSes not supported, disabling VHT\n");
}
if (conn->mode >= IEEE80211_CONN_MODE_HE &&
(!ieee80211_verify_peer_he_mcs_support(sdata, link_id,
(
void *)elems->he_cap,
elems->he_operation) ||
!ieee80211_verify_sta_he_mcs_support(sdata, sband,
elems->he_operation))) {
conn->mode = IEEE80211_CONN_MODE_VHT;
link_id_info(sdata, link_id,
"required MCSes not supported, disabling HE\n");
}
if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
!ieee80211_verify_sta_eht_mcs_support(sdata, sband,
elems->eht_operation)) {
conn->mode = IEEE80211_CONN_MODE_HE;
conn->bw_limit = min_t(
enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_160);
link_id_info(sdata, link_id,
"required MCSes not supported, disabling EHT\n");
}
if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
channel->band != NL80211_BAND_2GHZ &&
conn->bw_limit == IEEE80211_CONN_BW_LIMIT_40) {
conn->mode = IEEE80211_CONN_MODE_HE;
link_id_info(sdata, link_id,
"required bandwidth not supported, disabling EHT\n");
}
/* the mode can only decrease, so this must terminate */
if (ap_mode != conn->mode) {
kfree(elems);
goto again;
}
mlme_link_id_dbg(sdata, link_id,
"connecting with %s mode, max bandwidth %d MHz\n",
ieee80211_conn_mode_str(conn->mode),
20 * (1 << conn->bw_limit));
if (WARN_ON_ONCE(!cfg80211_chandef_valid(&chanreq->oper))) {
ret = -EINVAL;
goto free;
}
return elems;
free:
kfree(elems);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode);
static int ieee80211_config_bw(
struct ieee80211_link_data *link,
struct ieee802_11_elems *elems,
bool update, u64 *changed, u16 stype)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chan_req chanreq = {};
struct cfg80211_chan_def ap_chandef;
enum ieee80211_conn_mode ap_mode;
const char *frame;
u32 vht_cap_info = 0;
u16 ht_opmode;
int ret;
switch (stype) {
case IEEE80211_STYPE_BEACON:
frame =
"beacon";
break;
case IEEE80211_STYPE_ASSOC_RESP:
frame =
"assoc response";
break;
case IEEE80211_STYPE_REASSOC_RESP:
frame =
"reassoc response";
break;
case IEEE80211_STYPE_ACTION:
/* the only action frame that gets here */
frame =
"ML reconf response";
break;
default:
return -EINVAL;
}
/* don't track any bandwidth changes in legacy/S1G modes */
if (link->u.mgd.conn.mode == IEEE80211_CONN_MODE_LEGACY ||
link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G)
return 0;
if (elems->vht_cap_elem)
vht_cap_info = le32_to_cpu(elems->vht_cap_elem->vht_cap_info);
ap_mode = ieee80211_determine_ap_chan(sdata, channel, vht_cap_info,
elems,
true, &link->u.mgd.conn,
&ap_chandef);
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
"AP %pM appears to change mode (expected %s, found %s) in %s, disconnect\n",
link->u.mgd.bssid,
ieee80211_conn_mode_str(link->u.mgd.conn.mode),
ieee80211_conn_mode_str(ap_mode), frame);
return -EINVAL;
}
chanreq.oper = ap_chandef;
ieee80211_set_chanreq_ap(sdata, &chanreq, &link->u.mgd.conn,
&ap_chandef);
/*
* if HT operation mode changed store the new one -
* this may be applicable even if channel is identical
*/
if (elems->ht_operation) {
ht_opmode = le16_to_cpu(elems->ht_operation->operation_mode);
if (link->conf->ht_operation_mode != ht_opmode) {
*changed |= BSS_CHANGED_HT;
link->conf->ht_operation_mode = ht_opmode;
}
}
/*
* Downgrade the new channel if we associated with restricted
* bandwidth capabilities. For example, if we associated as a
* 20 MHz STA to a 40 MHz AP (due to regulatory, capabilities
* or config reasons) then switching to a 40 MHz channel now
* won't do us any good -- we couldn't use it with the AP.
*/
while (link->u.mgd.conn.bw_limit <
ieee80211_min_bw_limit_from_chandef(&chanreq.oper))
ieee80211_chandef_downgrade(&chanreq.oper, NULL);
/* TPE element is not present in (re)assoc/ML reconfig response */
if (stype == IEEE80211_STYPE_BEACON &&
ap_chandef.chan->band == NL80211_BAND_6GHZ &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HE) {
ieee80211_rearrange_tpe(&elems->tpe, &ap_chandef,
&chanreq.oper);
if (memcmp(&link->conf->tpe, &elems->tpe,
sizeof(elems->tpe))) {
link->conf->tpe = elems->tpe;
*changed |= BSS_CHANGED_TPE;
}
}
if (ieee80211_chanreq_identical(&chanreq, &link->conf->chanreq))
return 0;
link_info(link,
"AP %pM changed bandwidth in %s, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
link->u.mgd.bssid, frame, chanreq.oper.chan->center_freq,
chanreq.oper.chan->freq_offset, chanreq.oper.width,
chanreq.oper.center_freq1, chanreq.oper.freq1_offset,
chanreq.oper.center_freq2);
if (!cfg80211_chandef_valid(&chanreq.oper)) {
sdata_info(sdata,
"AP %pM changed caps/bw in %s in a way we can't support - disconnect\n",
link->u.mgd.bssid, frame);
return -EINVAL;
}
if (!update) {
link->conf->chanreq = chanreq;
return 0;
}
/*
* We're tracking the current AP here, so don't do any further checks
* here. This keeps us from playing ping-pong with regulatory, without
* it the following can happen (for example):
* - connect to an AP with 80 MHz, world regdom allows 80 MHz
* - AP advertises regdom US
* - CRDA loads regdom US with 80 MHz prohibited (old database)
* - we detect an unsupported channel and disconnect
* - disconnect causes CRDA to reload world regdomain and the game
* starts anew.
* (see https://bugzilla.kernel.org/show_bug.cgi?id=70881)
*
* It seems possible that there are still scenarios with CSA or real
* bandwidth changes where a this could happen, but those cases are
* less common and wouldn't completely prevent using the AP.
*/
ret = ieee80211_link_change_chanreq(link, &chanreq, changed);
if (ret) {
sdata_info(sdata,
"AP %pM changed bandwidth in %s to incompatible one - disconnect\n",
link->u.mgd.bssid, frame);
return ret;
}
cfg80211_schedule_channels_check(&sdata->wdev);
return 0;
}
/* frame sending functions */
static void ieee80211_add_ht_ie(
struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, u8 ap_ht_param,
struct ieee80211_supported_band *sband,
struct ieee80211_channel *channel,
enum ieee80211_smps_mode smps,
const struct ieee80211_conn_settings *conn)
{
u8 *pos;
u32 flags = channel->flags;
u16 cap;
struct ieee80211_sta_ht_cap ht_cap;
BUILD_BUG_ON(
sizeof(ht_cap) !=
sizeof(sband->ht_cap));
memcpy(&ht_cap, &sband->ht_cap,
sizeof(ht_cap));
ieee80211_apply_htcap_overrides(sdata, &ht_cap);
/* determine capability flags */
cap = ht_cap.cap;
switch (ap_ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
if (flags & IEEE80211_CHAN_NO_HT40PLUS) {
cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
cap &= ~IEEE80211_HT_CAP_SGI_40;
}
break;
case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
if (flags & IEEE80211_CHAN_NO_HT40MINUS) {
cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
cap &= ~IEEE80211_HT_CAP_SGI_40;
}
break;
}
/*
* If 40 MHz was disabled associate as though we weren't
* capable of 40 MHz -- some broken APs will never fall
* back to trying to transmit in 20 MHz.
*/
if (conn->bw_limit <= IEEE80211_CONN_BW_LIMIT_20) {
cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
cap &= ~IEEE80211_HT_CAP_SGI_40;
}
/* set SM PS mode properly */
cap &= ~IEEE80211_HT_CAP_SM_PS;
switch (smps) {
case IEEE80211_SMPS_AUTOMATIC:
case IEEE80211_SMPS_NUM_MODES:
WARN_ON(1);
fallthrough;
case IEEE80211_SMPS_OFF:
cap |= WLAN_HT_CAP_SM_PS_DISABLED <<
IEEE80211_HT_CAP_SM_PS_SHIFT;
break;
case IEEE80211_SMPS_STATIC:
cap |= WLAN_HT_CAP_SM_PS_STATIC <<
IEEE80211_HT_CAP_SM_PS_SHIFT;
break;
case IEEE80211_SMPS_DYNAMIC:
cap |= WLAN_HT_CAP_SM_PS_DYNAMIC <<
IEEE80211_HT_CAP_SM_PS_SHIFT;
break;
}
/* reserve and fill IE */
pos = skb_put(skb,
sizeof(
struct ieee80211_ht_cap) + 2);
ieee80211_ie_build_ht_cap(pos, &ht_cap, cap);
}
/* This function determines vht capability flags for the association
* and builds the IE.
* Note - the function returns true to own the MU-MIMO capability
*/
static bool ieee80211_add_vht_ie(
struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb,
struct ieee80211_supported_band *sband,
struct ieee80211_vht_cap *ap_vht_cap,
const struct ieee80211_conn_settings *conn)
{
struct ieee80211_local *local = sdata->local;
u8 *pos;
u32 cap;
struct ieee80211_sta_vht_cap vht_cap;
u32 mask, ap_bf_sts, our_bf_sts;
bool mu_mimo_owner =
false;
BUILD_BUG_ON(
sizeof(vht_cap) !=
sizeof(sband->vht_cap));
memcpy(&vht_cap, &sband->vht_cap,
sizeof(vht_cap));
ieee80211_apply_vhtcap_overrides(sdata, &vht_cap);
/* determine capability flags */
cap = vht_cap.cap;
if (conn->bw_limit <= IEEE80211_CONN_BW_LIMIT_80) {
cap &= ~IEEE80211_VHT_CAP_SHORT_GI_160;
cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
}
/*
* Some APs apparently get confused if our capabilities are better
* than theirs, so restrict what we advertise in the assoc request.
*/
if (!ieee80211_hw_check(&local->hw, STRICT)) {
if (!(ap_vht_cap->vht_cap_info &
cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
else if (!(ap_vht_cap->vht_cap_info &
cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)))
cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
}
/*
* If some other vif is using the MU-MIMO capability we cannot associate
* using MU-MIMO - this will lead to contradictions in the group-id
* mechanism.
* Ownership is defined since association request, in order to avoid
* simultaneous associations with MU-MIMO.
*/
if (cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) {
bool disable_mu_mimo =
false;
struct ieee80211_sub_if_data *other;
list_for_each_entry(other, &local->interfaces, list) {
if (other->vif.bss_conf.mu_mimo_owner) {
disable_mu_mimo =
true;
break;
}
}
if (disable_mu_mimo)
cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
else
mu_mimo_owner =
true;
}
mask = IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK;
ap_bf_sts = le32_to_cpu(ap_vht_cap->vht_cap_info) & mask;
our_bf_sts = cap & mask;
if (ap_bf_sts < our_bf_sts) {
cap &= ~mask;
cap |= ap_bf_sts;
}
/* reserve and fill IE */
pos = skb_put(skb,
sizeof(
struct ieee80211_vht_cap) + 2);
ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);
return mu_mimo_owner;
}
static void ieee80211_assoc_add_rates(
struct ieee80211_local *local,
struct sk_buff *skb,
enum nl80211_chan_width width,
struct ieee80211_supported_band *sband,
struct ieee80211_mgd_assoc_data *assoc_data)
{
u32 rates;
if (assoc_data->supp_rates_len &&
!ieee80211_hw_check(&local->hw, STRICT)) {
/*
* Get all rates supported by the device and the AP as
* some APs don't like getting a superset of their rates
* in the association request (e.g. D-Link DAP 1353 in
* b-only mode)...
*/
ieee80211_parse_bitrates(width, sband,
assoc_data->supp_rates,
assoc_data->supp_rates_len,
&rates);
}
else {
/*
* In case AP not provide any supported rates information
* before association, we send information element(s) with
* all rates that we support.
*/
rates = ~0;
}
ieee80211_put_srates_elem(skb, sband, 0, ~rates,
WLAN_EID_SUPP_RATES);
ieee80211_put_srates_elem(skb, sband, 0, ~rates,
WLAN_EID_EXT_SUPP_RATES);
}
static size_t ieee80211_add_before_ht_elems(
struct sk_buff *skb,
const u8 *elems,
size_t elems_len,
size_t offset)
{
size_t noffset;
static const u8 before_ht[] = {
WLAN_EID_SSID,
WLAN_EID_SUPP_RATES,
WLAN_EID_EXT_SUPP_RATES,
WLAN_EID_PWR_CAPABILITY,
WLAN_EID_SUPPORTED_CHANNELS,
WLAN_EID_RSN,
WLAN_EID_QOS_CAPA,
WLAN_EID_RRM_ENABLED_CAPABILITIES,
WLAN_EID_MOBILITY_DOMAIN,
WLAN_EID_FAST_BSS_TRANSITION,
/* reassoc only */
WLAN_EID_RIC_DATA,
/* reassoc only */
WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
};
static const u8 after_ric[] = {
WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
WLAN_EID_HT_CAPABILITY,
WLAN_EID_BSS_COEX_2040,
/* luckily this is almost always there */
WLAN_EID_EXT_CAPABILITY,
WLAN_EID_QOS_TRAFFIC_CAPA,
WLAN_EID_TIM_BCAST_REQ,
WLAN_EID_INTERWORKING,
/* 60 GHz (Multi-band, DMG, MMS) can't happen */
WLAN_EID_VHT_CAPABILITY,
WLAN_EID_OPMODE_NOTIF,
};
if (!elems_len)
return offset;
noffset = ieee80211_ie_split_ric(elems, elems_len,
before_ht,
ARRAY_SIZE(before_ht),
after_ric,
ARRAY_SIZE(after_ric),
offset);
skb_put_data(skb, elems + offset, noffset - offset);
return noffset;
}
static size_t ieee80211_add_before_vht_elems(
struct sk_buff *skb,
const u8 *elems,
size_t elems_len,
size_t offset)
{
static const u8 before_vht[] = {
/*
* no need to list the ones split off before HT
* or generated here
*/
WLAN_EID_BSS_COEX_2040,
WLAN_EID_EXT_CAPABILITY,
WLAN_EID_QOS_TRAFFIC_CAPA,
WLAN_EID_TIM_BCAST_REQ,
WLAN_EID_INTERWORKING,
/* 60 GHz (Multi-band, DMG, MMS) can't happen */
};
size_t noffset;
if (!elems_len)
return offset;
/* RIC already taken care of in ieee80211_add_before_ht_elems() */
noffset = ieee80211_ie_split(elems, elems_len,
before_vht, ARRAY_SIZE(before_vht),
offset);
skb_put_data(skb, elems + offset, noffset - offset);
return noffset;
}
static size_t ieee80211_add_before_he_elems(
struct sk_buff *skb,
const u8 *elems,
size_t elems_len,
size_t offset)
{
static const u8 before_he[] = {
/*
* no need to list the ones split off before VHT
* or generated here
*/
WLAN_EID_OPMODE_NOTIF,
WLAN_EID_EXTENSION, WLAN_EID_EXT_FUTURE_CHAN_GUIDANCE,
/* 11ai elements */
WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_SESSION,
WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_PUBLIC_KEY,
WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_KEY_CONFIRM,
WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_HLP_CONTAINER,
WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN,
/* TODO: add 11ah/11aj/11ak elements */
};
size_t noffset;
if (!elems_len)
return offset;
/* RIC already taken care of in ieee80211_add_before_ht_elems() */
noffset = ieee80211_ie_split(elems, elems_len,
before_he, ARRAY_SIZE(before_he),
offset);
skb_put_data(skb, elems + offset, noffset - offset);
return noffset;
}
static size_t ieee80211_add_before_reg_conn(
struct sk_buff *skb,
const u8 *elems, size_t elems_len,
size_t offset)
{
static const u8 before_reg_conn[] = {
/*
* no need to list the ones split off before HE
* or generated here
*/
WLAN_EID_EXTENSION, WLAN_EID_EXT_DH_PARAMETER,
WLAN_EID_EXTENSION, WLAN_EID_EXT_KNOWN_STA_IDENTIFCATION,
};
size_t noffset;
if (!elems_len)
return offset;
noffset = ieee80211_ie_split(elems, elems_len, before_reg_conn,
ARRAY_SIZE(before_reg_conn), offset);
skb_put_data(skb, elems + offset, noffset - offset);
return noffset;
}
#define PRESENT_ELEMS_MAX 8
#define PRESENT_ELEM_EXT_OFFS 0x100
static void
ieee80211_assoc_add_ml_elem(
struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, u16 capab,
const struct element *ext_capa,
const u16 *present_elems,
struct ieee80211_mgd_assoc_data *assoc_data);
static size_t
ieee80211_add_link_elems(
struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, u16 *capab,
const struct element *ext_capa,
const u8 *extra_elems,
size_t extra_elems_len,
unsigned int link_id,
struct ieee80211_link_data *link,
u16 *present_elems,
struct ieee80211_mgd_assoc_data *assoc_data)
{
enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
struct ieee80211_channel *chan = cbss->channel;
const struct ieee80211_sband_iftype_data *iftd;
struct ieee80211_local *local = sdata->local;
struct ieee80211_supported_band *sband;
enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
struct ieee80211_chanctx_conf *chanctx_conf;
enum ieee80211_smps_mode smps_mode;
u16 orig_capab = *capab;
size_t offset = 0;
int present_elems_len = 0;
u8 *pos;
int i;
#define ADD_PRESENT_ELEM(id)
do { \
/* need a last for termination - we use 0 == SSID */ \
if (!WARN_ON(present_elems_len >= PRESENT_ELEMS_MAX - 1)) \
present_elems[present_elems_len++] = (id); \
}
while (0)
#define ADD_PRESENT_EXT_ELEM(id) ADD_PRESENT_ELEM(PRESENT_ELEM_EXT_OFFS | (id))
if (link)
smps_mode = link->smps_mode;
else if (sdata->u.mgd.powersave)
smps_mode = IEEE80211_SMPS_DYNAMIC;
else
smps_mode = IEEE80211_SMPS_OFF;
if (link) {
/*
* 5/10 MHz scenarios are only viable without MLO, in which
* case this pointer should be used ... All of this is a bit
* unclear though, not sure this even works at all.
*/
rcu_read_lock();
chanctx_conf = rcu_dereference(link->conf->chanctx_conf);
if (chanctx_conf)
width = chanctx_conf->def.width;
rcu_read_unlock();
}
sband = local->hw.wiphy->bands[chan->band];
iftd = ieee80211_get_sband_iftype_data(sband, iftype);
if (sband->band == NL80211_BAND_2GHZ) {
*capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
*capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
}
if ((cbss->capability & WLAN_CAPABILITY_SPECTRUM_MGMT) &&
ieee80211_hw_check(&local->hw, SPECTRUM_MGMT))
*capab |= WLAN_CAPABILITY_SPECTRUM_MGMT;
if (sband->band != NL80211_BAND_S1GHZ)
ieee80211_assoc_add_rates(local, skb, width, sband, assoc_data);
if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT ||
*capab & WLAN_CAPABILITY_RADIO_MEASURE) {
struct cfg80211_chan_def chandef = {
.width = width,
.chan = chan,
};
pos = skb_put(skb, 4);
*pos++ = WLAN_EID_PWR_CAPABILITY;
*pos++ = 2;
*pos++ = 0;
/* min tx power */
/* max tx power */
*pos++ = ieee80211_chandef_max_power(&chandef);
ADD_PRESENT_ELEM(WLAN_EID_PWR_CAPABILITY);
}
/*
* Per spec, we shouldn't include the list of channels if we advertise
* support for extended channel switching, but we've always done that;
* (for now?) apply this restriction only on the (new) 6 GHz band.
*/
if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT &&
(sband->band != NL80211_BAND_6GHZ ||
!ext_capa || ext_capa->datalen < 1 ||
!(ext_capa->data[0] & WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING))) {
/* TODO: get this in reg domain format */
pos = skb_put(skb, 2 * sband->n_channels + 2);
*pos++ = WLAN_EID_SUPPORTED_CHANNELS;
*pos++ = 2 * sband->n_channels;
for (i = 0; i < sband->n_channels; i++) {
int cf = sband->channels[i].center_freq;
*pos++ = ieee80211_frequency_to_channel(cf);
*pos++ = 1;
/* one channel in the subband*/
}
ADD_PRESENT_ELEM(WLAN_EID_SUPPORTED_CHANNELS);
}
/* if present, add any custom IEs that go before HT */
offset = ieee80211_add_before_ht_elems(skb, extra_elems,
extra_elems_len,
offset);
if (sband->band != NL80211_BAND_6GHZ &&
assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_HT) {
ieee80211_add_ht_ie(sdata, skb,
assoc_data->link[link_id].ap_ht_param,
sband, chan, smps_mode,
&assoc_data->link[link_id].conn);
ADD_PRESENT_ELEM(WLAN_EID_HT_CAPABILITY);
}
/* if present, add any custom IEs that go before VHT */
offset = ieee80211_add_before_vht_elems(skb, extra_elems,
extra_elems_len,
offset);
if (sband->band != NL80211_BAND_6GHZ &&
assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_VHT &&
sband->vht_cap.vht_supported) {
bool mu_mimo_owner =
ieee80211_add_vht_ie(sdata, skb, sband,
&assoc_data->link[link_id].ap_vht_cap,
&assoc_data->link[link_id].conn);
if (link)
link->conf->mu_mimo_owner = mu_mimo_owner;
ADD_PRESENT_ELEM(WLAN_EID_VHT_CAPABILITY);
}
/* if present, add any custom IEs that go before HE */
offset = ieee80211_add_before_he_elems(skb, extra_elems,
extra_elems_len,
offset);
if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_HE) {
ieee80211_put_he_cap(skb, sdata, sband,
&assoc_data->link[link_id].conn);
ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_HE_CAPABILITY);
if (sband->band == NL80211_BAND_6GHZ)
ieee80211_put_he_6ghz_cap(skb, sdata, smps_mode);
}
/*
* if present, add any custom IEs that go before regulatory
* connectivity element
*/
offset = ieee80211_add_before_reg_conn(skb, extra_elems,
extra_elems_len, offset);
if (sband->band == NL80211_BAND_6GHZ) {
/*
* as per Section E.2.7 of IEEE 802.11 REVme D7.0, non-AP STA
* capable of operating on the 6 GHz band shall transmit
* regulatory connectivity element.
*/
ieee80211_put_reg_conn(skb, chan->flags);
}
/*
* careful - need to know about all the present elems before
* calling ieee80211_assoc_add_ml_elem(), so add this one if
* we're going to put it after the ML element
*/
if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_EHT_CAPABILITY);
if (link_id == assoc_data->assoc_link_id)
ieee80211_assoc_add_ml_elem(sdata, skb, orig_capab, ext_capa,
present_elems, assoc_data);
/* crash if somebody gets it wrong */
present_elems = NULL;
if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
ieee80211_put_eht_cap(skb, sdata, sband,
&assoc_data->link[link_id].conn);
if (sband->band == NL80211_BAND_S1GHZ) {
ieee80211_add_aid_request_ie(sdata, skb);
ieee80211_add_s1g_capab_ie(sdata, &sband->s1g_cap, skb);
}
if (iftd && iftd->vendor_elems.data && iftd->vendor_elems.len)
skb_put_data(skb, iftd->vendor_elems.data, iftd->vendor_elems.len);
return offset;
}
static void ieee80211_add_non_inheritance_elem(
struct sk_buff *skb,
const u16 *outer,
const u16 *inner)
{
unsigned int skb_len = skb->len;
bool at_extension =
false;
bool added =
false;
int i, j;
u8 *len, *list_len = NULL;
skb_put_u8(skb, WLAN_EID_EXTENSION);
len = skb_put(skb, 1);
skb_put_u8(skb, WLAN_EID_EXT_NON_INHERITANCE);
for (i = 0; i < PRESENT_ELEMS_MAX && outer[i]; i++) {
u16 elem = outer[i];
bool have_inner =
false;
/* should at least be sorted in the sense of normal -> ext */
WARN_ON(at_extension && elem < PRESENT_ELEM_EXT_OFFS);
/* switch to extension list */
if (!at_extension && elem >= PRESENT_ELEM_EXT_OFFS) {
at_extension =
true;
if (!list_len)
skb_put_u8(skb, 0);
list_len = NULL;
}
for (j = 0; j < PRESENT_ELEMS_MAX && inner[j]; j++) {
if (elem == inner[j]) {
have_inner =
true;
break;
}
}
if (have_inner)
continue;
if (!list_len) {
list_len = skb_put(skb, 1);
*list_len = 0;
}
*list_len += 1;
skb_put_u8(skb, (u8)elem);
added =
true;
}
/* if we added a list but no extension list, make a zero-len one */
if (added && (!at_extension || !list_len))
skb_put_u8(skb, 0);
/* if nothing added remove extension element completely */
if (!added)
skb_trim(skb, skb_len);
else
*len = skb->len - skb_len - 2;
}
static void
ieee80211_assoc_add_ml_elem(
struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, u16 capab,
const struct element *ext_capa,
const u16 *outer_present_elems,
struct ieee80211_mgd_assoc_data *assoc_data)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_multi_link_elem *ml_elem;
struct ieee80211_mle_basic_common_info *common;
const struct wiphy_iftype_ext_capab *ift_ext_capa;
__le16 eml_capa = 0, mld_capa_ops = 0;
unsigned int link_id;
u8 *ml_elem_len;
void *capab_pos;
if (!ieee80211_vif_is_mld(&sdata->vif))
return;
ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy,
ieee80211_vif_type_p2p(&sdata->vif));
if (ift_ext_capa) {
eml_capa = cpu_to_le16(ift_ext_capa->eml_capabilities);
mld_capa_ops = cpu_to_le16(ift_ext_capa->mld_capa_and_ops);
}
skb_put_u8(skb, WLAN_EID_EXTENSION);
ml_elem_len = skb_put(skb, 1);
skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK);
ml_elem = skb_put(skb,
sizeof(*ml_elem));
ml_elem->control =
cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_BASIC |
IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP);
common = skb_put(skb,
sizeof(*common));
common->len =
sizeof(*common) +
2;
/* MLD capa/ops */
memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);
/* add EML_CAPA only if needed, see Draft P802.11be_D2.1, 35.3.17 */
if (eml_capa &
cpu_to_le16((IEEE80211_EML_CAP_EMLSR_SUPP |
IEEE80211_EML_CAP_EMLMR_SUPPORT))) {
common->len += 2;
/* EML capabilities */
ml_elem->control |=
cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EML_CAPA);
skb_put_data(skb, &eml_capa,
sizeof(eml_capa));
}
skb_put_data(skb, &mld_capa_ops,
sizeof(mld_capa_ops));
if (assoc_data->ext_mld_capa_ops) {
ml_elem->control |=
cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP);
common->len += 2;
skb_put_data(skb, &assoc_data->ext_mld_capa_ops,
sizeof(assoc_data->ext_mld_capa_ops));
}
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
u16 link_present_elems[PRESENT_ELEMS_MAX] = {};
const u8 *extra_elems;
size_t extra_elems_len;
size_t extra_used;
u8 *subelem_len = NULL;
__le16 ctrl;
if (!assoc_data->link[link_id].bss ||
link_id == assoc_data->assoc_link_id)
continue;
extra_elems = assoc_data->link[link_id].elems;
extra_elems_len = assoc_data->link[link_id].elems_len;
skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE);
subelem_len = skb_put(skb, 1);
ctrl = cpu_to_le16(link_id |
IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE |
IEEE80211_MLE_STA_CONTROL_STA_MAC_ADDR_PRESENT);
skb_put_data(skb, &ctrl,
sizeof(ctrl));
skb_put_u8(skb, 1 + ETH_ALEN);
/* STA Info Length */
skb_put_data(skb, assoc_data->link[link_id].addr,
ETH_ALEN);
/*
* Now add the contents of the (re)association request,
* but the "listen interval" and "current AP address"
* (if applicable) are skipped. So we only have
* the capability field (remember the position and fill
* later), followed by the elements added below by
* calling ieee80211_add_link_elems().
*/
capab_pos = skb_put(skb, 2);
extra_used = ieee80211_add_link_elems(sdata, skb, &capab,
ext_capa,
extra_elems,
extra_elems_len,
link_id, NULL,
link_present_elems,
assoc_data);
if (extra_elems)
skb_put_data(skb, extra_elems + extra_used,
extra_elems_len - extra_used);
put_unaligned_le16(capab, capab_pos);
ieee80211_add_non_inheritance_elem(skb, outer_present_elems,
link_present_elems);
ieee80211_fragment_element(skb, subelem_len,
IEEE80211_MLE_SUBELEM_FRAGMENT);
}
ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
}
static int
ieee80211_link_common_elems_size(
struct ieee80211_sub_if_data *sdata,
enum nl80211_iftype iftype,
struct cfg80211_bss *cbss,
size_t elems_len)
{
struct ieee80211_local *local = sdata->local;
const struct ieee80211_sband_iftype_data *iftd;
struct ieee80211_supported_band *sband;
size_t size = 0;
if (!cbss)
return size;
sband = local->hw.wiphy->bands[cbss->channel->band];
/* add STA profile elements length */
size += elems_len;
/* and supported rates length */
size += 4 + sband->n_bitrates;
/* supported channels */
size += 2 + 2 * sband->n_channels;
iftd = ieee80211_get_sband_iftype_data(sband, iftype);
if (iftd)
size += iftd->vendor_elems.len;
/* power capability */
size += 4;
/* HT, VHT, HE, EHT */
size += 2 +
sizeof(
struct ieee80211_ht_cap);
size += 2 +
sizeof(
struct ieee80211_vht_cap);
size += 2 + 1 +
sizeof(
struct ieee80211_he_cap_elem) +
sizeof(
struct ieee80211_he_mcs_nss_supp) +
IEEE80211_HE_PPE_THRES_MAX_LEN;
if (sband->band == NL80211_BAND_6GHZ) {
size += 2 + 1 +
sizeof(
struct ieee80211_he_6ghz_capa);
/* reg connection */
size += 4;
}
size += 2 + 1 +
sizeof(
struct ieee80211_eht_cap_elem) +
sizeof(
struct ieee80211_eht_mcs_nss_supp) +
IEEE80211_EHT_PPE_THRES_MAX_LEN;
return size;
}
static int ieee80211_send_assoc(
struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
struct ieee80211_link_data *link;
struct sk_buff *skb;
struct ieee80211_mgmt *mgmt;
u8 *pos, qos_info, *ie_start;
size_t offset, noffset;
u16 capab = 0, link_capab;
__le16 listen_int;
struct element *ext_capa = NULL;
enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
struct ieee80211_prep_tx_info info = {};
unsigned int link_id, n_links = 0;
u16 present_elems[PRESENT_ELEMS_MAX] = {};
void *capab_pos;
size_t size;
int ret;
/* we know it's writable, cast away the const */
if (assoc_data->ie_len)
ext_capa = (
void *)cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY,
assoc_data->ie,
assoc_data->ie_len);
lockdep_assert_wiphy(sdata->local->hw.wiphy);
size = local->hw.extra_tx_headroom +
sizeof(*mgmt) +
/* bit too much but doesn't matter */
2 + assoc_data->ssid_len +
/* SSID */
--> --------------------
--> maximum size reached
--> --------------------