blob: 96b70006b7fc0b11b12f423fb74ec32a030d91af [file] [log] [blame]
</
// 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 - 2024 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 <asm/unaligned.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "rate.h"
#include "led.h"
#include "fils_aead.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 msecs_to_jiffies(100)
#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;
*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_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
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) {
sdata_info(sdata,
"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 (!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) {
sdata_info(sdata,
"Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n",
nss, ap_rx_val, ap_rx_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 (!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 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 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)
{
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,
.mode = conn->mode,
};
struct ieee802_11_elems *elems;
struct ieee80211_supported_band *sband;
struct cfg80211_chan_def ap_chandef;
enum ieee80211_conn_mode ap_mode;
int ret;
again:
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);
mlme_link_id_dbg(sdata, link_id, "determined AP %pM to be %s\n",
cbss->bssid, ieee80211_conn_mode_str(ap_mode));
/* this should be impossible since parsing depends on our mode */
if (WARN_ON(ap_mode > conn->mode)) {
ret = -EINVAL;
goto free;
}
sband = sdata->local->hw.wiphy->bands[channel->band];
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) {
sdata_info(sdata,
"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;
}
conn->mode = ap_mode;
chanreq->oper = ap_chandef;
/* wider-bandwidth OFDMA is only done in EHT */
if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
!(sdata->vif.driver_flags & IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW))
chanreq->ap = ap_chandef;
else
chanreq->ap.chan = NULL;
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)
sdata_info(sdata,
"regulatory prevented using AP config, downgraded\n");
if (conn->mode >= IEEE80211_CONN_MODE_HE &&
(!ieee80211_verify_peer_he_mcs_support(sdata, (void *)elems->he_cap,
elems->he_operation) ||
!ieee80211_verify_sta_he_mcs_support(sdata, sband,
elems->he_operation))) {
conn->mode = IEEE80211_CONN_MODE_VHT;
sdata_info(sdata, "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);
sdata_info(sdata, "required MCSes not supported, disabling EHT\n");
}
/* the mode can only decrease, so this must terminate */
if (ap_mode != conn->mode)
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);
}
static int ieee80211_config_bw(struct ieee80211_link_data *link,
struct ieee802_11_elems *elems,
bool update, u64 *changed)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chan_req chanreq = {};
enum ieee80211_conn_mode ap_mode;
u32 vht_cap_info = 0;
u16 ht_opmode;
int ret;
/* 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,
&chanreq.ap);
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
"AP appears to change mode (expected %s, found %s), disconnect\n",
ieee80211_conn_mode_str(link->u.mgd.conn.mode),
ieee80211_conn_mode_str(ap_mode));
return -EINVAL;
}
chanreq.oper = chanreq.ap;
if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT ||
sdata->vif.driver_flags & IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW)
chanreq.ap.chan = NULL;
/*
* 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);
if (ieee80211_chanreq_identical(&chanreq, &link->conf->chanreq))
return 0;
link_info(link,
"AP %pM changed bandwidth, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
link->u.mgd.bssid, 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 a way we can't support - disconnect\n",
link->u.mgd.bssid);
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 to incompatible one - disconnect\n",
link->u.mgd.bssid);
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 (!(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_rcu(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 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) {
/*
* 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, 0, ~rates,
WLAN_EID_SUPP_RATES);
ieee80211_put_srates_elem(skb, sband, 0, 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;
}
#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);
static size_t ieee80211_assoc_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)
{
enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
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(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);
ieee80211_put_he_6ghz_cap(skb, sdata, smps_mode);
}
/*
* 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);
/* 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_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
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));
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_assoc_link_elems().
*/
capab_pos = skb_put(skb, 2);
extra_used = ieee80211_assoc_link_elems(sdata, skb, &capab,
ext_capa,
extra_elems,
extra_elems_len,
link_id, NULL,
link_present_elems);
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_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 */
assoc_data->ie_len + /* extra IEs */
(assoc_data->fils_kek_len ? 16 /* AES-SIV */ : 0) +
9; /* WMM */
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
const struct ieee80211_sband_iftype_data *iftd;
struct ieee80211_supported_band *sband;
if (!cbss)
continue;
sband = local->hw.wiphy->bands[cbss->channel->band];
n_links++;
/* add STA profile elements length */
size += assoc_data->link[link_id].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);
size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) +
sizeof(struct ieee80211_eht_mcs_nss_supp) +
IEEE80211_EHT_PPE_THRES_MAX_LEN;
/* non-inheritance element */
size += 2 + 2 + PRESENT_ELEMS_MAX;
/* should be the same across all BSSes */
if (cbss->capability & WLAN_CAPABILITY_PRIVACY)
capab |= WLAN_CAPABILITY_PRIVACY;
}
if (ieee80211_vif_is_mld(&sdata->vif)) {
/* consider the multi-link element with STA profile */
size += sizeof(struct ieee80211_multi_link_elem);
/* max common info field in basic multi-link element */
size += sizeof(struct ieee80211_mle_basic_common_info) +
2 + /* capa & op */
2; /* EML capa */
/*
* The capability elements were already considered above;
* note this over-estimates a bit because there's no
* STA profile for the assoc link.
*/
size += (n_links - 1) *
(1 + 1 + /* subelement ID/length */
2 + /* STA control */
1 + ETH_ALEN + 2 /* STA Info field */);
}
link = sdata_dereference(sdata->link[assoc_data->assoc_link_id], sdata);
if (WARN_ON(!link))
return -EINVAL;
if (WARN_ON(!assoc_data->link[assoc_data->assoc_link_id].bss))
return -EINVAL;
skb = alloc_skb(size, GFP_KERNEL);
if (!skb)
return -ENOMEM;
skb_reserve(skb, local->hw.extra_tx_headroom);
if (ifmgd->flags & IEEE80211_STA_ENABLE_RRM)
capab |= WLAN_CAPABILITY_RADIO_MEASURE;
/* Set MBSSID support for HE AP if needed */
if (ieee80211_hw_check(&local->hw, SUPPORTS_ONLY_HE_MULTI_BSSID) &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HE &&
ext_capa && ext_capa->datalen >= 3)
ext_capa->data[2] |= WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT;
mgmt = skb_put_zero(skb, 24);
memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN);
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN);
listen_int = cpu_to_le16(assoc_data->s1g ?
ieee80211_encode_usf(local->hw.conf.listen_interval) :
local->hw.conf.listen_interval);
if (!is_zero_ether_addr(assoc_data->prev_ap_addr)) {
skb_put(skb, 10);
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
IEEE80211_STYPE_REASSOC_REQ);
capab_pos = &mgmt->u.reassoc_req.capab_info;
mgmt->u.reassoc_req.listen_interval = listen_int;
memcpy(mgmt->u.reassoc_req.current_ap,
assoc_data->prev_ap_addr, ETH_ALEN);
info.subtype = IEEE80211_STYPE_REASSOC_REQ;
} else {
skb_put(skb, 4);
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
IEEE80211_STYPE_ASSOC_REQ);
capab_pos = &mgmt->u.assoc_req.capab_info;
mgmt->u.assoc_req.listen_interval = listen_int;
info.subtype = IEEE80211_STYPE_ASSOC_REQ;
}
/* SSID */
pos = skb_put(skb, 2 + assoc_data->ssid_len);
ie_start = pos;
*pos++ = WLAN_EID_SSID;
*pos++ = assoc_data->ssid_len;
memcpy(pos, assoc_data->ssid, assoc_data->ssid_len);
/*
* This bit is technically reserved, so it shouldn't matter for either
* the AP or us, but it also means we shouldn't set it. However, we've
* always set it in the past, and apparently some EHT APs check that
* we don't set it. To avoid interoperability issues with old APs that
* for some reason check it and want it to be set, set the bit for all
* pre-EHT connections as we used to do.
*/
if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT)
capab |= WLAN_CAPABILITY_ESS;
/* add the elements for the assoc (main) link */
link_capab = capab;
offset = ieee80211_assoc_link_elems(sdata, skb, &link_capab,
ext_capa,
assoc_data->ie,
assoc_data->ie_len,
assoc_data->assoc_link_id, link,
present_elems);
put_unaligned_le16(link_capab, capab_pos);
/* if present, add any custom non-vendor IEs */
if (assoc_data->ie_len) {
noffset = ieee80211_ie_split_vendor(assoc_data->ie,
assoc_data->ie_len,
offset);
skb_put_data(skb, assoc_data->ie + offset, noffset - offset);
offset = noffset;
}
if (assoc_data->wmm) {
if (assoc_data->uapsd) {
qos_info = ifmgd->uapsd_queues;
qos_info |= (ifmgd->uapsd_max_sp_len <<
IEEE80211_WMM_IE_STA_QOSINFO_SP_SHIFT);
} else {
qos_info = 0;
}
pos = ieee80211_add_wmm_info_ie(skb_put(skb, 9), qos_info);
}
/* add any remaining custom (i.e. vendor specific here) IEs */
if (assoc_data->ie_len) {
noffset = assoc_data->ie_len;
skb_put_data(skb, assoc_data->ie + offset, noffset - offset);
}
if (assoc_data->fils_kek_len) {
ret = fils_encrypt_assoc_req(skb, assoc_data);
if (ret < 0) {
dev_kfree_skb(skb);
return ret;
}
}
pos = skb_tail_pointer(skb);
kfree(ifmgd->assoc_req_ies);
ifmgd->assoc_req_ies = kmemdup(ie_start, pos - ie_start, GFP_ATOMIC);
if (!ifmgd->assoc_req_ies) {
dev_kfree_skb(skb);
return -ENOMEM;
}
ifmgd->assoc_req_ies_len = pos - ie_start;
info.link_id = assoc_data->assoc_link_id;
drv_mgd_prepare_tx(local, sdata, &info);
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS |
IEEE80211_TX_INTFL_MLME_CONN_TX;
ieee80211_tx_skb(sdata, skb);
return 0;
}
void ieee80211_send_pspoll(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_pspoll *pspoll;
struct sk_buff *skb;
skb = ieee80211_pspoll_get(&local->hw, &sdata->vif);
if (!skb)
return;
pspoll = (struct ieee80211_pspoll *) skb->data;
pspoll->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
ieee80211_tx_skb(sdata, skb);
}
void ieee80211_send_nullfunc(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
bool powersave)
{
struct sk_buff *skb;
struct ieee80211_hdr_3addr *nullfunc;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
skb = ieee80211_nullfunc_get(&local->hw, &sdata->vif, -1,
!ieee80211_hw_check(&local->hw,
DOESNT_SUPPORT_QOS_NDP));
if (!skb)
return;
nullfunc = (struct ieee80211_hdr_3addr *) skb->data;
if (powersave)
nullfunc->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL)
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_USE_MINRATE;
ieee80211_tx_skb(sdata, skb);
}
void ieee80211_send_4addr_nullfunc(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
struct sk_buff *skb;
struct ieee80211_hdr *nullfunc;
__le16 fc;
if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION))
return;
skb = dev_alloc_skb(local->hw.extra_tx_headroom + 30);
if (!skb)
return;
skb_reserve(skb, local->hw.extra_tx_headroom);
nullfunc = skb_put_zero(skb, 30);
fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC |
IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS);
nullfunc->frame_control = fc;
memcpy(nullfunc->addr1, sdata->deflink.u.mgd.bssid, ETH_ALEN);
memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN);
memcpy(nullfunc->addr3, sdata->deflink.u.mgd.bssid, ETH_ALEN);
memcpy(nullfunc->addr4, sdata->vif.addr, ETH_ALEN);
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_USE_MINRATE;
ieee80211_tx_skb(sdata, skb);
}
/* spectrum management related things */
static void ieee80211_chswitch_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
struct ieee80211_link_data *link =
container_of(work, struct ieee80211_link_data,
u.mgd.chswitch_work.work);
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
int ret;
if (!ieee80211_sdata_running(sdata))
return;
lockdep_assert_wiphy(local->hw.wiphy);
if (!ifmgd->associated)
return;
if (!link->conf->csa_active)
return;
/*
* using reservation isn't immediate as it may be deferred until later
* with multi-vif. once reservation is complete it will re-schedule the
* work with no reserved_chanctx so verify chandef to check if it
* completed successfully
*/
if (link->reserved_chanctx) {
/*
* with multi-vif csa driver may call ieee80211_csa_finish()
* many times while waiting for other interfaces to use their
* reservations
*/
if (link->reserved_ready)
return;
ret = ieee80211_link_use_reserved_context(link);
if (ret) {
sdata_info(sdata,
"failed to use reserved channel context, disconnecting (err=%d)\n",
ret);
wiphy_work_queue(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
}
return;
}
if (!ieee80211_chanreq_identical(&link->conf->chanreq,
&link->csa_chanreq)) {
sdata_info(sdata,
"failed to finalize channel switch, disconnecting\n");
wiphy_work_queue(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
return;
}
link->u.mgd.csa_waiting_bcn = true;
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
}
static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
int ret;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
WARN_ON(!link->conf->csa_active);
if (sdata->csa_blocked_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
sdata->csa_blocked_tx = false;
}
link->conf->csa_active = false;
link->u.mgd.csa_waiting_bcn = false;
ret = drv_post_channel_switch(link);
if (ret) {
sdata_info(sdata,
"driver post channel switch failed, disconnecting\n");
wiphy_work_queue(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
return;
}
cfg80211_ch_switch_notify(sdata->dev, &link->reserved.oper,
link->link_id);
}
void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success,
unsigned int link_id)
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
trace_api_chswitch_done(sdata, success, link_id);
rcu_read_lock();
if (!success) {
sdata_info(sdata,
"driver channel switch failed, disconnecting\n");
wiphy_work_queue(sdata->local->hw.wiphy,
&sdata->u.mgd.csa_connection_drop_work);
} else {
struct ieee80211_link_data *link =
rcu_dereference(sdata->link[link_id]);
if (WARN_ON(!link)) {
rcu_read_unlock();
return;
}
wiphy_delayed_work_queue(sdata->local->hw.wiphy,
&link->u.mgd.chswitch_work, 0);
}
rcu_read_unlock();
}
EXPORT_SYMBOL(ieee80211_chswitch_done);
static void
ieee80211_sta_abort_chanswitch(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
lockdep_assert_wiphy(local->hw.wiphy);
if (!local->ops->abort_channel_switch)
return;
ieee80211_link_unreserve_chanctx(link);
if (sdata->csa_blocked_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
sdata->csa_blocked_tx = false;
}
link->conf->csa_active = false;
drv_abort_channel_switch(link);
}
static void
ieee80211_sta_process_chanswitch(struct ieee80211_link_data *link,
u64 timestamp, u32 device_timestamp,
struct ieee802_11_elems *elems,
bool beacon)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct cfg80211_bss *cbss = link->conf->bss;
struct ieee80211_chanctx_conf *conf;
struct ieee80211_chanctx *chanctx;
enum nl80211_band current_band;
struct ieee80211_csa_ie csa_ie;
struct ieee80211_channel_switch ch_switch = {
.link_id = link->link_id,
};
struct ieee80211_bss *bss;
unsigned long timeout;
int res;
lockdep_assert_wiphy(local->hw.wiphy);
if (!cbss)
return;
current_band = cbss->channel->band;
bss = (void *)cbss->priv;
res = ieee80211_parse_ch_switch_ie(sdata, elems, current_band,
bss->vht_cap_info,
&link->u.mgd.conn,
link->u.mgd.bssid, &csa_ie);
if (!res) {
ch_switch.timestamp = timestamp;
ch_switch.device_timestamp = device_timestamp;
ch_switch.block_tx = csa_ie.mode;
ch_switch.chandef = csa_ie.chanreq.oper;
ch_switch.count = csa_ie.count;
ch_switch.delay = csa_ie.max_switch_time;
}
if (res < 0)
goto drop_connection;
if (link->conf->csa_active) {
/* already processing - disregard action frames */
if (!beacon)
return;
if (link->u.mgd.csa_waiting_bcn) {
ieee80211_chswitch_post_beacon(link);
/*
* If the CSA IE is still present in the beacon after
* the switch, we need to consider it as a new CSA
* (possibly to self) - this happens by not returning
* here so we'll get to the check below.
*/
} else if (res) {
ieee80211_sta_abort_chanswitch(link);
return;
} else {
drv_channel_switch_rx_beacon(sdata, &ch_switch);
return;
}
}
/* nothing to do at all - no active CSA nor a new one */
if (res)
return;
if (link->conf->chanreq.oper.chan->band !=
csa_ie.chanreq.oper.chan->band) {
sdata_info(sdata,
"AP %pM switches to different band (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n",
link->u.mgd.bssid,
csa_ie.chanreq.oper.chan->center_freq,
csa_ie.chanreq.oper.width,
csa_ie.chanreq.oper.center_freq1,
csa_ie.chanreq.oper.center_freq2);
goto drop_connection;
}
if (!cfg80211_chandef_usable(local->hw.wiphy, &csa_ie.chanreq.oper,
IEEE80211_CHAN_DISABLED)) {
sdata_info(sdata,
"AP %pM switches to unsupported channel "
"(%d.%03d MHz, width:%d, CF1/2: %d.%03d/%d MHz), "
"disconnecting\n",
link->u.mgd.bssid,
csa_ie.chanreq.oper.chan->center_freq,
csa_ie.chanreq.oper.chan->freq_offset,
csa_ie.chanreq.oper.width,
csa_ie.chanreq.oper.center_freq1,
csa_ie.chanreq.oper.freq1_offset,
csa_ie.chanreq.oper.center_freq2);
goto drop_connection;
}
if (cfg80211_chandef_identical(&csa_ie.chanreq.oper,
&link->conf->chanreq.oper) &&
(!csa_ie.mode || !beacon)) {
if (link->u.mgd.csa_ignored_same_chan)
return;
sdata_info(sdata,
"AP %pM tries to chanswitch to same channel, ignore\n",
link->u.mgd.bssid);
link->u.mgd.csa_ignored_same_chan = true;
return;
}
/*
* Drop all TDLS peers on the affected link - either we disconnect or
* move to a different channel from this point on. There's no telling
* what our peer will do.
* The TDLS WIDER_BW scenario is also problematic, as peers might now
* have an incompatible wider chandef.
*/
ieee80211_teardown_tdls_peers(link);
conf = rcu_dereference_protected(link->conf->chanctx_conf,
lockdep_is_held(&local->hw.wiphy->mtx));
if (!conf) {
sdata_info(sdata,
"no channel context assigned to vif?, disconnecting\n");
goto drop_connection;
}
chanctx = container_of(conf, struct ieee80211_chanctx, conf);
if (!ieee80211_hw_check(&local->hw, CHANCTX_STA_CSA)) {
sdata_info(sdata,
"driver doesn't support chan-switch with channel contexts\n");
goto drop_connection;
}
if (drv_pre_channel_switch(sdata, &ch_switch)) {
sdata_info(sdata,
"preparing for channel switch failed, disconnecting\n");
goto drop_connection;
}
res = ieee80211_link_reserve_chanctx(link, &csa_ie.chanreq,
chanctx->mode, false);
if (res) {
sdata_info(sdata,
"failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
res);
goto drop_connection;
}
link->conf->csa_active = true;
link->csa_chanreq = csa_ie.chanreq;
link->u.mgd.csa_ignored_same_chan = false;
link->u.mgd.beacon_crc_valid = false;
if (csa_ie.mode &&
!ieee80211_hw_check(&local->hw, HANDLES_QUIET_CSA)) {
ieee80211_stop_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
sdata->csa_blocked_tx = true;
}
cfg80211_ch_switch_started_notify(sdata->dev, &csa_ie.chanreq.oper,
link->link_id, csa_ie.count,
csa_ie.mode);
if (local->ops->channel_switch) {
/* use driver's channel switch callback */
drv_channel_switch(local, sdata, &ch_switch);
return;
}
/* channel switch handled in software */
timeout = TU_TO_JIFFIES((max_t(int, csa_ie.count, 1) - 1) *
cbss->beacon_interval);
wiphy_delayed_work_queue(local->hw.wiphy,
&link->u.mgd.chswitch_work,
timeout);
return;
drop_connection:
/*
* This is just so that the disconnect flow will know that
* we were trying to switch channel and failed. In case the
* mode is 1 (we are not allowed to Tx), we will know not to
* send a deauthentication frame. Those two fields will be
* reset when the disconnection worker runs.
*/
link->conf->csa_active = true;
sdata->csa_blocked_tx =
csa_ie.mode && !ieee80211_hw_check(&local->hw, HANDLES_QUIET_CSA);
wiphy_work_queue(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
}
static bool
ieee80211_find_80211h_pwr_constr(struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel *channel,
const u8 *country_ie, u8 country_ie_len,
const u8 *pwr_constr_elem,
int *chan_pwr, int *pwr_reduction)
{
struct ieee80211_country_ie_triplet *triplet;
int chan = ieee80211_frequency_to_channel(channel->center_freq);
int i, chan_increment;
bool have_chan_pwr = false;
/* Invalid IE */
if (country_ie_len % 2 || country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
return false;
triplet = (void *)(country_ie + 3);
country_ie_len -= 3;
switch (channel->band) {
default:
WARN_ON_ONCE(1);
fallthrough;
case NL80211_BAND_2GHZ:
case NL80211_BAND_60GHZ:
case NL80211_BAND_LC:
chan_increment = 1;
break;
case NL80211_BAND_5GHZ:
chan_increment = 4;
break;
case NL80211_BAND_6GHZ:
/*
* In the 6 GHz band, the "maximum transmit power level"
* field in the triplets is reserved, and thus will be
* zero and we shouldn't use it to control TX power.
* The actual TX power will be given in the transmit
* power envelope element instead.
*/
return false;
}
/* find channel */
while (country_ie_len >= 3) {
u8 first_channel = triplet->chans.first_channel;
if (first_channel >= IEEE80211_COUNTRY_EXTENSION_ID)
goto next;
for (i = 0; i < triplet->chans.num_channels; i++) {
if (first_channel + i * chan_increment == chan) {
have_chan_pwr = true;
*chan_pwr = triplet->chans.max_power;
break;
}
}
if (have_chan_pwr)
break;
next:
triplet++;
country_ie_len -= 3;
}
if (have_chan_pwr && pwr_constr_elem)
*pwr_reduction = *pwr_constr_elem;
else
*pwr_reduction = 0;
return have_chan_pwr;
}
static void ieee80211_find_cisco_dtpc(struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel *channel,
const u8 *cisco_dtpc_ie,
int *pwr_level)
{
/* From practical testing, the first data byte of the DTPC element
* seems to contain the requested dBm level, and the CLI on Cisco
* APs clearly state the range is -127 to 127 dBm, which indicates
* a signed byte, although it seemingly never actually goes negative.
* The other byte seems to always be zero.
*/
*pwr_level = (__s8)cisco_dtpc_ie[4];
}
static u64 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link,
struct ieee80211_channel *channel,
struct ieee80211_mgmt *mgmt,
const u8 *country_ie, u8 country_ie_len,
const u8 *pwr_constr_ie,
const u8 *cisco_dtpc_ie)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
bool has_80211h_pwr = false, has_cisco_pwr = false;
int chan_pwr = 0, pwr_reduction_80211h = 0;
int pwr_level_cisco, pwr_level_80211h;
int new_ap_level;
__le16 capab = mgmt->u.probe_resp.capab_info;
if (ieee80211_is_s1g_beacon(mgmt->frame_control))
return 0; /* TODO */
if (country_ie &&
(capab & cpu_to_le16(WLAN_CAPABILITY_SPECTRUM_MGMT) ||
capab & cpu_to_le16(WLAN_CAPABILITY_RADIO_MEASURE))) {
has_80211h_pwr = ieee80211_find_80211h_pwr_constr(
sdata, channel, country_ie, country_ie_len,
pwr_constr_ie, &chan_pwr, &pwr_reduction_80211h);
pwr_level_80211h =
max_t(int, 0, chan_pwr - pwr_reduction_80211h);
}
if (cisco_dtpc_ie) {
ieee80211_find_cisco_dtpc(
sdata, channel, cisco_dtpc_ie, &pwr_level_cisco);
has_cisco_pwr = true;
}
if (!has_80211h_pwr && !has_cisco_pwr)
return 0;
/* If we have both 802.11h and Cisco DTPC, apply both limits
* by picking the smallest of the two power levels advertised.
*/
if (has_80211h_pwr &&
(!has_cisco_pwr || pwr_level_80211h <= pwr_level_cisco)) {
new_ap_level = pwr_level_80211h;
if (link->ap_power_level == new_ap_level)
return 0;
sdata_dbg(sdata,
"Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
pwr_level_80211h, chan_pwr, pwr_reduction_80211h,
link->u.mgd.bssid);
} else { /* has_cisco_pwr is always true here. */
new_ap_level = pwr_level_cisco;
if (link->ap_power_level == new_ap_level)
return 0;
sdata_dbg(sdata,
"Limiting TX power to %d dBm as advertised by %pM\n",
pwr_level_cisco, link->u.mgd.bssid);
}
link->ap_power_level = new_ap_level;
if (__ieee80211_recalc_txpower(sdata))
return BSS_CHANGED_TXPOWER;
return 0;
}
/* powersave */
static void ieee80211_enable_ps(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_conf *conf = &local->hw.conf;
/*
* If we are scanning right now then the parameters will
* take effect when scan finishes.
*/
if (local->scanning)
return;
if (conf->dynamic_ps_timeout > 0 &&
!ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS)) {
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(conf->dynamic_ps_timeout));
} else {
if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK))
ieee80211_send_nullfunc(local, sdata, true);
if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) &&
ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
return;
conf->flags |= IEEE80211_CONF_PS;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
}
}
static void ieee80211_change_ps(struct ieee80211_local *local)
{
struct ieee80211_conf *conf = &local->hw.conf;
if (local->ps_sdata) {
ieee80211_enable_ps(local, local->ps_sdata);
} else if (conf->flags & IEEE80211_CONF_PS) {
conf->flags &= ~IEEE80211_CONF_PS;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
del_timer_sync(&local->dynamic_ps_timer);
wiphy_work_cancel(local->hw.wiphy,
&local->dynamic_ps_enable_work);
}
}
static bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *mgd = &sdata->u.mgd;
struct sta_info *sta = NULL;
bool authorized = false;
if (!mgd->powersave)
return false;
if (mgd->broken_ap)
return false;
if (!mgd->associated)
return false;
if (mgd->flags & IEEE80211_STA_CONNECTION_POLL)
return false;
if (!(local->hw.wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) &&
!sdata->deflink.u.mgd.have_beacon)
return false;
rcu_read_lock();
sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
if (sta)
authorized = test_sta_flag(sta, WLAN_STA_AUTHORIZED);
rcu_read_unlock();
return authorized;
}
/* need to hold RTNL or interface lock */
void ieee80211_recalc_ps(struct ieee80211_local *local)
{
struct ieee80211_sub_if_data *sdata, *found = NULL;
int count = 0;
int timeout;
if (!ieee80211_hw_check(&local->hw, SUPPORTS_PS) ||
ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS)) {
local->ps_sdata = NULL;
return;
}
list_for_each_entry(sdata, &local->interfaces, list) {
if (!ieee80211_sdata_running(sdata))
continue;
if (sdata->vif.type == NL80211_IFTYPE_AP) {
/* If an AP vif is found, then disable PS
* by setting the count to zero thereby setting
* ps_sdata to NULL.
*/
count = 0;
break;
}
if (sdata->vif.type != NL80211_IFTYPE_STATION)
continue;
found = sdata;
count++;
}
if (count == 1 && ieee80211_powersave_allowed(found)) {
u8 dtimper = found->deflink.u.mgd.dtim_period;
timeout = local->dynamic_ps_forced_timeout;
if (timeout < 0)
timeout = 100;
local->hw.conf.dynamic_ps_timeout = timeout;
/* If the TIM IE is invalid, pretend the value is 1 */
if (!dtimper)
dtimper = 1;
local->hw.conf.ps_dtim_period = dtimper;
local->ps_sdata = found;
} else {
local->ps_sdata = NULL;
}
ieee80211_change_ps(local);
}
void ieee80211_recalc_ps_vif(struct ieee80211_sub_if_data *sdata)
{
bool ps_allowed = ieee80211_powersave_allowed(sdata);
if (sdata->vif.cfg.ps != ps_allowed) {
sdata->vif.cfg.ps = ps_allowed;
ieee80211_vif_cfg_change_notify(sdata, BSS_CHANGED_PS);
}
}
void ieee80211_dynamic_ps_disable_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
struct ieee80211_local *local =
container_of(work, struct ieee80211_local,
dynamic_ps_disable_work);
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
local->hw.conf.flags &= ~IEEE80211_CONF_PS;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
}
ieee80211_wake_queues_by_reason(&local->hw,
IEEE80211_MAX_QUEUE_MAP,
IEEE80211_QUEUE_STOP_REASON_PS,
false);
}
void ieee80211_dynamic_ps_enable_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
struct ieee80211_local *local =
container_of(work, struct ieee80211_local,
dynamic_ps_enable_work);
struct ieee80211_sub_if_data *sdata = local->ps_sdata;
struct ieee80211_if_managed *ifmgd;
unsigned long flags;
int q;
/* can only happen when PS was just disabled anyway */
if (!sdata)
return;
ifmgd = &sdata->u.mgd;
if (local->hw.conf.flags & IEEE80211_CONF_PS)
return;
if (local->hw.conf.dynamic_ps_timeout > 0) {
/* don't enter PS if TX frames are pending */
if (drv_tx_frames_pending(local)) {
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(
local->hw.conf.dynamic_ps_timeout));
return;
}
/*
* transmission can be stopped by others which leads to
* dynamic_ps_timer expiry. Postpone the ps timer if it
* is not the actual idle state.
*/
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
for (q = 0; q < local->hw.queues; q++) {
if (local->queue_stop_reasons[q]) {
spin_unlock_irqrestore(&local->queue_stop_reason_lock,
flags);
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(
local->hw.conf.dynamic_ps_timeout));
return;
}
}
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
}
if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) &&
!(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
if (drv_tx_frames_pending(local)) {
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(
local->hw.conf.dynamic_ps_timeout));
} else {
ieee80211_send_nullfunc(local, sdata, true);
/* Flush to get the tx status of nullfunc frame */
ieee80211_flush_queues(local, sdata, false);
}
}
if (!(ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS) &&
ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK)) ||
(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
local->hw.conf.flags |= IEEE80211_CONF_PS;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
}
}
void ieee80211_dynamic_ps_timer(struct timer_list *t)
{
struct ieee80211_local *local = from_timer(local, t, dynamic_ps_timer);
wiphy_work_queue(local->hw.wiphy, &local->dynamic_ps_enable_work);
}
void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work)
{
struct ieee80211_link_data *link =
container_of(work, struct ieee80211_link_data,
dfs_cac_timer_work.work);
struct cfg80211_chan_def chandef = link->conf->chanreq.oper;
struct ieee80211_sub_if_data *sdata = link->sdata;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
if (sdata->wdev.cac_started) {
ieee80211_link_release_channel(link);
cfg80211_cac_event(sdata->dev, &chandef,
NL80211_RADAR_CAC_FINISHED,
GFP_KERNEL);
}
}
static bool
__ieee80211_sta_handle_tspec_ac_params(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
bool ret = false;
int ac;
if (local->hw.queues < IEEE80211_NUM_ACS)
return false;
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
struct ieee80211_sta_tx_tspec *tx_tspec = &ifmgd->tx_tspec[ac];
int non_acm_ac;
unsigned long now = jiffies;
if (tx_tspec->action == TX_TSPEC_ACTION_NONE &&
tx_tspec->admitted_time &&
time_after(now, tx_tspec->time_slice_start + HZ)) {
tx_tspec->consumed_tx_time = 0;
tx_tspec->time_slice_start = now;
if (tx_tspec->downgraded)
tx_tspec->action =
TX_TSPEC_ACTION_STOP_DOWNGRADE;
}
switch (tx_tspec->action) {
case TX_TSPEC_ACTION_STOP_DOWNGRADE:
/* take the original parameters */
if (drv_conf_tx(local, &sdata->deflink, ac,
&sdata->deflink.tx_conf[ac]))
link_err(&sdata->deflink,
"failed to set TX queue parameters for queue %d\n",
ac);
tx_tspec->action = TX_TSPEC_ACTION_NONE;
tx_tspec->downgraded = false;
ret = true;
break;
case TX_TSPEC_ACTION_DOWNGRADE:
if (time_after(now, tx_tspec->time_slice_start + HZ)) {
tx_tspec->action = TX_TSPEC_ACTION_NONE;
ret = true;
break;
}
/* downgrade next lower non-ACM AC */
for (non_acm_ac = ac + 1;
non_acm_ac < IEEE80211_NUM_ACS;
non_acm_ac++)
if (!(sdata->wmm_acm & BIT(7 - 2 * non_acm_ac)))
break;
/* Usually the loop will result in using BK even if it
* requires admission control, but such a configuration
* makes no sense and we have to transmit somehow - the
* AC selection does the same thing.
* If we started out trying to downgrade from BK, then
* the extra condition here might be needed.
*/
if (non_acm_ac >= IEEE80211_NUM_ACS)
non_acm_ac = IEEE80211_AC_BK;
if (drv_conf_tx(local, &sdata->deflink, ac,
&sdata->deflink.tx_conf[non_acm_ac]))
link_err(&sdata->deflink,
"failed to set TX queue parameters for queue %d\n",
ac);
tx_tspec->action = TX_TSPEC_ACTION_NONE;
ret = true;
wiphy_delayed_work_queue(local->hw.wiphy,
&ifmgd->tx_tspec_wk,
tx_tspec->time_slice_start +
HZ - now + 1);
break;
case TX_TSPEC_ACTION_NONE:
/* nothing now */
break;
}
}
return ret;
}
void ieee80211_sta_handle_tspec_ac_params(struct ieee80211_sub_if_data *sdata)
{
if (__ieee80211_sta_handle_tspec_ac_params(sdata))
ieee80211_link_info_change_notify(sdata, &sdata->deflink,
BSS_CHANGED_QOS);
}
static void ieee80211_sta_handle_tspec_ac_params_wk(struct wiphy *wiphy,
struct wiphy_work *work)
{
struct ieee80211_sub_if_data *sdata;
sdata = container_of(work, struct ieee80211_sub_if_data,
u.mgd.tx_tspec_wk.work);
ieee80211_sta_handle_tspec_ac_params(sdata);
}
void ieee80211_mgd_set_link_qos_params(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_tx_queue_params *params = link->tx_conf;
u8 ac;
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
mlme_dbg(sdata,
"WMM AC=%d acm=%d aifs=%d cWmin=%d cWmax=%d txop=%d uapsd=%d, downgraded=%d\n",
ac, params[ac].acm,
params[ac].aifs, params[ac].cw_min, params[ac].cw_max,
params[ac].txop, params[ac].uapsd,
ifmgd->tx_tspec[ac].downgraded);
if (!ifmgd->tx_tspec[ac].downgraded &&
drv_conf_tx(local, link, ac, &params[ac]))
link_err(link,
"failed to set TX queue parameters for AC %d\n",
ac);
}
}
/* MLME */
static bool
ieee80211_sta_wmm_params(struct ieee80211_local *local,
struct ieee80211_link_data *link,
const u8 *wmm_param, size_t wmm_param_len,
const struct ieee80211_mu_edca_param_set *mu_edca)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_tx_queue_params params[IEEE80211_NUM_ACS];
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
size_t left;
int count, mu_edca_count, ac;
const u8 *pos;
u8 uapsd_queues = 0;
if (!local->ops->conf_tx)
return false;
if (local->hw.queues < IEEE80211_NUM_ACS)
return false;
if (!wmm_param)
return false;
if (wmm_param_len < 8 || wmm_param[5] /* version */ != 1)
return false;
if (ifmgd->flags & IEEE80211_STA_UAPSD_ENABLED)
uapsd_queues = ifmgd->uapsd_queues;
count = wmm_param[6] & 0x0f;
/* -1 is the initial value of ifmgd->mu_edca_last_param_set.
* if mu_edca was preset before and now it disappeared tell
* the driver about it.
*/
mu_edca_count = mu_edca ? mu_edca->mu_qos_info & 0x0f : -1;
if (count == link->u.mgd.wmm_last_param_set &&
mu_edca_count == link->u.mgd.mu_edca_last_param_set)
return false;
link->u.mgd.wmm_last_param_set = count;
link->u.mgd.mu_edca_last_param_set = mu_edca_count;
pos = wmm_param + 8;
left = wmm_param_len - 8;
memset(&params, 0, sizeof(params));
sdata->wmm_acm = 0;
for (; left >= 4; left -= 4, pos += 4) {
int aci = (pos[0] >> 5) & 0x03;
int acm = (pos[0] >> 4) & 0x01;
bool uapsd = false;
switch (aci) {
case 1: /* AC_BK */
ac = IEEE80211_AC_BK;
if (acm)
sdata->wmm_acm |= BIT(1) | BIT(2); /* BK/- */
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
uapsd = true;
params[ac].mu_edca = !!mu_edca;
if (mu_edca)
params[ac].mu_edca_param_rec = mu_edca->ac_bk;
break;
case 2: /* AC_VI */
ac = IEEE80211_AC_VI;
if (acm)
sdata->wmm_acm |= BIT(4) | BIT(5); /* CL/VI */
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
uapsd = true;
params[ac].mu_edca = !!mu_edca;
if (mu_edca)
params[ac].mu_edca_param_rec = mu_edca->ac_vi;
break;
case 3: /* AC_VO */
ac = IEEE80211_AC_VO;
if (acm)
sdata->wmm_acm |= BIT(6) | BIT(7); /* VO/NC */
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
uapsd = true;
params[ac].mu_edca = !!mu_edca;
if (mu_edca)
params[ac].mu_edca_param_rec = mu_edca->ac_vo;
break;
case 0: /* AC_BE */
default:
ac = IEEE80211_AC_BE;
if (acm)
sdata->wmm_acm |= BIT(0) | BIT(3); /* BE/EE */
if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
uapsd = true;
params[ac].mu_edca = !!mu_edca;
if (mu_edca)
params[ac].mu_edca_param_rec = mu_edca->ac_be;
break;
}
params[ac].aifs = pos[0] & 0x0f;
if (params[ac].aifs < 2) {
link_info(link,
"AP has invalid WMM params (AIFSN=%d for ACI %d), will use 2\n",
params[ac].aifs, aci);
params[ac].aifs = 2;
}
params[ac].cw_max = ecw2cw((pos[1] & 0xf0) >> 4);
params[ac].cw_min = ecw2cw(pos[1] & 0x0f);
params[ac].txop = get_unaligned_le16(pos + 2);
params[ac].acm = acm;
params[ac].uapsd = uapsd;
if (params[ac].cw_min == 0 ||
params[ac].cw_min > params[ac].cw_max) {
link_info(link,
"AP has invalid WMM params (CWmin/max=%d/%d for ACI %d), using defaults\n",
params[ac].cw_min, params[ac].cw_max, aci);
return false;
}
ieee80211_regulatory_limit_wmm_params(sdata, &params[ac], ac);
}
/* WMM specification requires all 4 ACIs. */
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
if (params[ac].cw_min == 0) {
link_info(link,
"AP has invalid WMM params (missing AC %d), using defaults\n",
ac);
return false;
}
}
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
link->tx_conf[ac] = params[ac];
ieee80211_mgd_set_link_qos_params(link);
/* enable WMM or activate new settings */
link->conf->qos = true;
return true;
}
static void __ieee80211_stop_poll(struct ieee80211_sub_if_data *sdata)
{
lockdep_assert_wiphy(sdata->local->hw.wiphy);
sdata->u.mgd.flags &= ~IEEE80211_STA_CONNECTION_POLL;
ieee80211_run_deferred_scan(sdata->local);
}
static void ieee80211_stop_poll(struct ieee80211_sub_if_data *sdata)
{
lockdep_assert_wiphy(sdata->local->hw.wiphy);
__ieee80211_stop_poll(sdata);
}
static u64 ieee80211_handle_bss_capability(struct ieee80211_link_data *link,
u16 capab, bool erp_valid, u8 erp)
{
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_supported_band *sband;
u64 changed = 0;
bool use_protection;
bool use_short_preamble;
bool use_short_slot;
sband = ieee80211_get_link_sband(link);
if (!sband)
return changed;
if (erp_valid) {
use_protection = (erp & WLAN_ERP_USE_PROTECTION) != 0;
use_short_preamble = (erp & WLAN_ERP_BARKER_PREAMBLE) == 0;
} else {
use_protection = false;
use_short_preamble = !!(capab & WLAN_CAPABILITY_SHORT_PREAMBLE);
}
use_short_slot = !!(capab & WLAN_CAPABILITY_SHORT_SLOT_TIME);
if (sband->band == NL80211_BAND_5GHZ ||
sband->band == NL80211_BAND_6GHZ)
use_short_slot = true;
if (use_protection != bss_conf->use_cts_prot) {
bss_conf->use_cts_prot = use_protection;
changed |= BSS_CHANGED_ERP_CTS_PROT;
}
if (use_short_preamble != bss_conf->use_short_preamble) {
bss_conf->use_short_preamble = use_short_preamble;
changed |= BSS_CHANGED_ERP_PREAMBLE;
}
if (use_short_slot != bss_conf->use_short_slot) {
bss_conf->use_short_slot = use_short_slot;
changed |= BSS_CHANGED_ERP_SLOT;
}
return changed;
}
static u64 ieee80211_link_set_associated(struct ieee80211_link_data *link,
struct cfg80211_bss *cbss)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_bss *bss = (void *)cbss->priv;
u64 changed = BSS_CHANGED_QOS;
/* not really used in MLO */
sdata->u.mgd.beacon_timeout =
usecs_to_jiffies(ieee80211_tu_to_usec(beacon_loss_count *
bss_conf->beacon_int));
changed |= ieee80211_handle_bss_capability(link,
bss_conf->assoc_capability,
bss->has_erp_value,
bss->erp_value);
ieee80211_check_rate_mask(link);
link->conf->bss = cbss;
memcpy(link->u.mgd.bssid, cbss->bssid, ETH_ALEN);
if (sdata->vif.p2p ||
sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) {
const struct cfg80211_bss_ies *ies;
rcu_read_lock();
ies = rcu_dereference(cbss->ies);
if (ies) {
int ret;
ret = cfg80211_get_p2p_attr(
ies->data, ies->len,
IEEE80211_P2P_ATTR_ABSENCE_NOTICE,
(u8 *) &bss_conf->p2p_noa_attr,
sizeof(bss_conf->p2p_noa_attr));
if (ret >= 2) {
link->u.mgd.p2p_noa_index =
bss_conf->p2p_noa_attr.index;
changed |= BSS_CHANGED_P2P_PS;
}
}
rcu_read_unlock();
}
if (link->u.mgd.have_beacon) {
bss_conf->beacon_rate = bss->beacon_rate;
changed |= BSS_CHANGED_BEACON_INFO;
} else {
bss_conf->beacon_rate = NULL;
}
/* Tell the driver to monitor connection quality (if supported) */
if (sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI &&
bss_conf->cqm_rssi_thold)
changed |= BSS_CHANGED_CQM;
return changed;
}
static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgd_assoc_data *assoc_data,
u64 changed[IEEE80211_MLD_MAX_NUM_LINKS])
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_vif_cfg *vif_cfg = &sdata->vif.cfg;
u64 vif_changed = BSS_CHANGED_ASSOC;
unsigned int link_id;
lockdep_assert_wiphy(local->hw.wiphy);
sdata->u.mgd.associated = true;
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
struct ieee80211_link_data *link;
if (!cbss ||
assoc_data->link[link_id].status != WLAN_STATUS_SUCCESS)
continue;
if (ieee80211_vif_is_mld(&sdata->vif) &&
!(ieee80211_vif_usable_links(&sdata->vif) & BIT(link_id)))
continue;
link = sdata_dereference(sdata->link[link_id], sdata);
if (WARN_ON(!link))
return;
changed[link_id] |= ieee80211_link_set_associated(link, cbss);
}
/* just to be sure */
ieee80211_stop_poll(sdata);
ieee80211_led_assoc(local, 1);
vif_cfg->assoc = 1;
/* Enable ARP filtering */
if (vif_cfg->arp_addr_cnt)
vif_changed |= BSS_CHANGED_ARP_FILTER;
if (ieee80211_vif_is_mld(&sdata->vif)) {
for (link_id = 0;
link_id < IEEE80211_MLD_MAX_NUM_LINKS;
link_id++) {
struct ieee80211_link_data *link;
struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
if (!cbss ||
!(BIT(link_id) &
ieee80211_vif_usable_links(&sdata->vif)) ||