blob: 3d4ab711f0d1ebd5d4584d36e23909b3b894bdd1 [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 - 2022 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
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)
{
sdata_assert_lock(sdata);
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 ieee80211_conn_flags_t
ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
ieee80211_conn_flags_t conn_flags,
struct ieee80211_supported_band *sband,
struct ieee80211_channel *channel,
u32 vht_cap_info,
const struct ieee80211_ht_operation *ht_oper,
const struct ieee80211_vht_operation *vht_oper,
const struct ieee80211_he_operation *he_oper,
const struct ieee80211_eht_operation *eht_oper,
const struct ieee80211_s1g_oper_ie *s1g_oper,
struct cfg80211_chan_def *chandef, bool tracking)
{
struct cfg80211_chan_def vht_chandef;
struct ieee80211_sta_ht_cap sta_ht_cap;
ieee80211_conn_flags_t ret;
u32 ht_cfreq;
memset(chandef, 0, sizeof(struct cfg80211_chan_def));
chandef->chan = channel;
chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
chandef->center_freq1 = channel->center_freq;
chandef->freq1_offset = channel->freq_offset;
if (channel->band == NL80211_BAND_6GHZ) {
if (!ieee80211_chandef_he_6ghz_oper(sdata, he_oper, eht_oper,
chandef)) {
mlme_dbg(sdata,
"bad 6 GHz operation, disabling HT/VHT/HE/EHT\n");
ret = IEEE80211_CONN_DISABLE_HT |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
} else {
ret = 0;
}
vht_chandef = *chandef;
goto out;
} else if (sband->band == NL80211_BAND_S1GHZ) {
if (!ieee80211_chandef_s1g_oper(s1g_oper, chandef)) {
sdata_info(sdata,
"Missing S1G Operation Element? Trying operating == primary\n");
chandef->width = ieee80211_s1g_channel_width(channel);
}
ret = IEEE80211_CONN_DISABLE_HT | IEEE80211_CONN_DISABLE_40MHZ |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_80P80MHZ |
IEEE80211_CONN_DISABLE_160MHZ;
goto out;
}
memcpy(&sta_ht_cap, &sband->ht_cap, sizeof(sta_ht_cap));
ieee80211_apply_htcap_overrides(sdata, &sta_ht_cap);
if (!ht_oper || !sta_ht_cap.ht_supported) {
mlme_dbg(sdata, "HT operation missing / HT not supported\n");
ret = IEEE80211_CONN_DISABLE_HT |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
goto out;
}
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 (!tracking && 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);
ret = IEEE80211_CONN_DISABLE_HT |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
goto out;
}
/* check 40 MHz support, if we have it */
if (sta_ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
ieee80211_chandef_ht_oper(ht_oper, chandef);
} else {
mlme_dbg(sdata, "40 MHz not supported\n");
/* 40 MHz (and 80 MHz) must be supported for VHT */
ret = IEEE80211_CONN_DISABLE_VHT;
/* also mark 40 MHz disabled */
ret |= IEEE80211_CONN_DISABLE_40MHZ;
goto out;
}
if (!vht_oper || !sband->vht_cap.vht_supported) {
mlme_dbg(sdata, "VHT operation missing / VHT not supported\n");
ret = IEEE80211_CONN_DISABLE_VHT;
goto out;
}
vht_chandef = *chandef;
if (!(conn_flags & IEEE80211_CONN_DISABLE_HE) &&
he_oper &&
(le32_to_cpu(he_oper->he_oper_params) &
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)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_HE))
sdata_info(sdata,
"HE AP VHT information is invalid, disabling HE\n");
ret = IEEE80211_CONN_DISABLE_HE | IEEE80211_CONN_DISABLE_EHT;
goto out;
}
} else if (!ieee80211_chandef_vht_oper(&sdata->local->hw,
vht_cap_info,
vht_oper, ht_oper,
&vht_chandef)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_VHT))
sdata_info(sdata,
"AP VHT information is invalid, disabling VHT\n");
ret = IEEE80211_CONN_DISABLE_VHT;
goto out;
}
if (!cfg80211_chandef_valid(&vht_chandef)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_VHT))
sdata_info(sdata,
"AP VHT information is invalid, disabling VHT\n");
ret = IEEE80211_CONN_DISABLE_VHT;
goto out;
}
if (cfg80211_chandef_identical(chandef, &vht_chandef)) {
ret = 0;
goto out;
}
if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_VHT))
sdata_info(sdata,
"AP VHT information doesn't match HT, disabling VHT\n");
ret = IEEE80211_CONN_DISABLE_VHT;
goto out;
}
*chandef = vht_chandef;
/*
* 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 && (eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT)) {
struct cfg80211_chan_def eht_chandef = *chandef;
ieee80211_chandef_eht_oper(sdata, eht_oper,
eht_chandef.width ==
NL80211_CHAN_WIDTH_160,
false, &eht_chandef);
if (!cfg80211_chandef_valid(&eht_chandef)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_EHT))
sdata_info(sdata,
"AP EHT information is invalid, disabling EHT\n");
ret = IEEE80211_CONN_DISABLE_EHT;
goto out;
}
if (!cfg80211_chandef_compatible(chandef, &eht_chandef)) {
if (!(conn_flags & IEEE80211_CONN_DISABLE_EHT))
sdata_info(sdata,
"AP EHT information is incompatible, disabling EHT\n");
ret = IEEE80211_CONN_DISABLE_EHT;
goto out;
}
*chandef = eht_chandef;
}
ret = 0;
out:
/*
* When tracking the current AP, don't do any further checks if the
* new chandef is identical to the one we're currently using for the
* connection. 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)
* - the code below detects an unsupported channel, downgrades, and
* we disconnect from the AP in the caller
* - 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.
*/
if (tracking &&
cfg80211_chandef_identical(chandef, &link->conf->chandef))
return ret;
/* don't print the message below for VHT mismatch if VHT is disabled */
if (ret & IEEE80211_CONN_DISABLE_VHT)
vht_chandef = *chandef;
/*
* Ignore the DISABLED flag when we're already connected and only
* tracking the APs beacon for bandwidth changes - otherwise we
* might get disconnected here if we connect to an AP, update our
* regulatory information based on the AP's country IE and the
* information we have is wrong/outdated and disables the channel
* that we're actually using for the connection to the AP.
*/
while (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
tracking ? 0 :
IEEE80211_CHAN_DISABLED)) {
if (WARN_ON(chandef->width == NL80211_CHAN_WIDTH_20_NOHT)) {
ret = IEEE80211_CONN_DISABLE_HT |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
break;
}
ret |= ieee80211_chandef_downgrade(chandef);
}
if (!he_oper || !cfg80211_chandef_usable(sdata->wdev.wiphy, chandef,
IEEE80211_CHAN_NO_HE))
ret |= IEEE80211_CONN_DISABLE_HE | IEEE80211_CONN_DISABLE_EHT;
if (!eht_oper || !cfg80211_chandef_usable(sdata->wdev.wiphy, chandef,
IEEE80211_CHAN_NO_EHT))
ret |= IEEE80211_CONN_DISABLE_EHT;
if (chandef->width != vht_chandef.width && !tracking)
sdata_info(sdata,
"capabilities/regulatory prevented using AP HT/VHT configuration, downgraded\n");
WARN_ON_ONCE(!cfg80211_chandef_valid(chandef));
return ret;
}
static int ieee80211_config_bw(struct ieee80211_link_data *link,
const struct ieee80211_ht_cap *ht_cap,
const struct ieee80211_vht_cap *vht_cap,
const struct ieee80211_ht_operation *ht_oper,
const struct ieee80211_vht_operation *vht_oper,
const struct ieee80211_he_operation *he_oper,
const struct ieee80211_eht_operation *eht_oper,
const struct ieee80211_s1g_oper_ie *s1g_oper,
const u8 *bssid, u32 *changed)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_channel *chan = link->conf->chandef.chan;
struct ieee80211_supported_band *sband =
local->hw.wiphy->bands[chan->band];
struct cfg80211_chan_def chandef;
u16 ht_opmode;
ieee80211_conn_flags_t flags;
u32 vht_cap_info = 0;
int ret;
/* if HT was/is disabled, don't track any bandwidth changes */
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT || !ht_oper)
return 0;
/* don't check VHT if we associated as non-VHT station */
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)
vht_oper = NULL;
/* don't check HE if we associated as non-HE station */
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE ||
!ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif))) {
he_oper = NULL;
eht_oper = NULL;
}
/* don't check EHT if we associated as non-EHT station */
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_EHT ||
!ieee80211_get_eht_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif)))
eht_oper = NULL;
/*
* if bss configuration changed store the new one -
* this may be applicable even if channel is identical
*/
ht_opmode = le16_to_cpu(ht_oper->operation_mode);
if (link->conf->ht_operation_mode != ht_opmode) {
*changed |= BSS_CHANGED_HT;
link->conf->ht_operation_mode = ht_opmode;
}
if (vht_cap)
vht_cap_info = le32_to_cpu(vht_cap->vht_cap_info);
/* calculate new channel (type) based on HT/VHT/HE operation IEs */
flags = ieee80211_determine_chantype(sdata, link,
link->u.mgd.conn_flags,
sband, chan, vht_cap_info,
ht_oper, vht_oper,
he_oper, eht_oper,
s1g_oper, &chandef, true);
/*
* Downgrade the new channel if we associated with restricted
* 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.
*/
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_80P80MHZ &&
chandef.width == NL80211_CHAN_WIDTH_80P80)
flags |= ieee80211_chandef_downgrade(&chandef);
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_160MHZ &&
chandef.width == NL80211_CHAN_WIDTH_160)
flags |= ieee80211_chandef_downgrade(&chandef);
if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_40MHZ &&
chandef.width > NL80211_CHAN_WIDTH_20)
flags |= ieee80211_chandef_downgrade(&chandef);
if (cfg80211_chandef_identical(&chandef, &link->conf->chandef))
return 0;
link_info(link,
"AP %pM changed bandwidth, new config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
link->u.mgd.bssid, chandef.chan->center_freq,
chandef.chan->freq_offset, chandef.width,
chandef.center_freq1, chandef.freq1_offset,
chandef.center_freq2);
if (flags != (link->u.mgd.conn_flags &
(IEEE80211_CONN_DISABLE_HT |
IEEE80211_CONN_DISABLE_VHT |
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT |
IEEE80211_CONN_DISABLE_40MHZ |
IEEE80211_CONN_DISABLE_80P80MHZ |
IEEE80211_CONN_DISABLE_160MHZ |
IEEE80211_CONN_DISABLE_320MHZ)) ||
!cfg80211_chandef_valid(&chandef)) {
sdata_info(sdata,
"AP %pM changed caps/bw in a way we can't support (0x%x/0x%x) - disconnect\n",
link->u.mgd.bssid, flags, ifmgd->flags);
return -EINVAL;
}
ret = ieee80211_link_change_bandwidth(link, &chandef, changed);
if (ret) {
sdata_info(sdata,
"AP %pM changed bandwidth to incompatible one - disconnect\n",
link->u.mgd.bssid);
return ret;
}
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,
ieee80211_conn_flags_t conn_flags)
{
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_flags & IEEE80211_CONN_DISABLE_40MHZ) {
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,
ieee80211_conn_flags_t conn_flags)
{
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_flags & IEEE80211_CONN_DISABLE_80P80MHZ) {
u32 bw = cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
if (bw == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
bw == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
}
if (conn_flags & IEEE80211_CONN_DISABLE_160MHZ) {
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;
}
/* This function determines HE capability flags for the association
* and builds the IE.
*/
static void ieee80211_add_he_ie(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb,
struct ieee80211_supported_band *sband,
ieee80211_conn_flags_t conn_flags)
{
u8 *pos, *pre_he_pos;
const struct ieee80211_sta_he_cap *he_cap;
u8 he_cap_size;
he_cap = ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
if (WARN_ON(!he_cap))
return;
/* get a max size estimate */
he_cap_size =
2 + 1 + sizeof(he_cap->he_cap_elem) +
ieee80211_he_mcs_nss_size(&he_cap->he_cap_elem) +
ieee80211_he_ppe_size(he_cap->ppe_thres[0],
he_cap->he_cap_elem.phy_cap_info);
pos = skb_put(skb, he_cap_size);
pre_he_pos = pos;
pos = ieee80211_ie_build_he_cap(conn_flags,
pos, he_cap, pos + he_cap_size);
/* trim excess if any */
skb_trim(skb, skb->len - (pre_he_pos + he_cap_size - pos));
ieee80211_ie_build_he_6ghz_cap(sdata, skb);
}
static void ieee80211_add_eht_ie(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb,
struct ieee80211_supported_band *sband)
{
u8 *pos;
const struct ieee80211_sta_he_cap *he_cap;
const struct ieee80211_sta_eht_cap *eht_cap;
u8 eht_cap_size;
he_cap = ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
eht_cap = ieee80211_get_eht_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
/*
* EHT capabilities element is only added if the HE capabilities element
* was added so assume that 'he_cap' is valid and don't check it.
*/
if (WARN_ON(!he_cap || !eht_cap))
return;
eht_cap_size =
2 + 1 + sizeof(eht_cap->eht_cap_elem) +
ieee80211_eht_mcs_nss_size(&he_cap->he_cap_elem,
&eht_cap->eht_cap_elem) +
ieee80211_eht_ppe_size(eht_cap->eht_ppe_thres[0],
eht_cap->eht_cap_elem.phy_cap_info);
pos = skb_put(skb, eht_cap_size);
ieee80211_ie_build_eht_cap(pos, he_cap, eht_cap, pos + eht_cap_size);
}
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)
{
unsigned int shift = ieee80211_chanwidth_get_shift(width);
unsigned int rates_len, supp_rates_len;
u32 rates = 0;
int i, count;
u8 *pos;
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)...
*/
rates_len = 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_len = sband->n_bitrates;
for (i = 0; i < sband->n_bitrates; i++)
rates |= BIT(i);
}
supp_rates_len = rates_len;
if (supp_rates_len > 8)
supp_rates_len = 8;
pos = skb_put(skb, supp_rates_len + 2);
*pos++ = WLAN_EID_SUPP_RATES;
*pos++ = supp_rates_len;
count = 0;
for (i = 0; i < sband->n_bitrates; i++) {
if (BIT(i) & rates) {
int rate = DIV_ROUND_UP(sband->bitrates[i].bitrate,
5 * (1 << shift));
*pos++ = (u8)rate;
if (++count == 8)
break;
}
}
if (rates_len > count) {
pos = skb_put(skb, rates_len - count + 2);
*pos++ = WLAN_EID_EXT_SUPP_RATES;
*pos++ = rates_len - count;
for (i++; i < sband->n_bitrates; i++) {
if (BIT(i) & rates) {
int rate;
rate = DIV_ROUND_UP(sband->bitrates[i].bitrate,
5 * (1 << shift));
*pos++ = (u8)rate;
}
}
}
}
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_flags & IEEE80211_CONN_DISABLE_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_flags);
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_flags & IEEE80211_CONN_DISABLE_VHT)) {
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_flags);
if (link)
link->conf->mu_mimo_owner = mu_mimo_owner;
ADD_PRESENT_ELEM(WLAN_EID_VHT_CAPABILITY);
}
/*
* If AP doesn't support HT, mark HE and EHT as disabled.
* If on the 5GHz band, make sure it supports VHT.
*/
if (assoc_data->link[link_id].conn_flags & IEEE80211_CONN_DISABLE_HT ||
(sband->band == NL80211_BAND_5GHZ &&
assoc_data->link[link_id].conn_flags & IEEE80211_CONN_DISABLE_VHT))
assoc_data->link[link_id].conn_flags |=
IEEE80211_CONN_DISABLE_HE |
IEEE80211_CONN_DISABLE_EHT;
/* 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_flags & IEEE80211_CONN_DISABLE_HE)) {
ieee80211_add_he_ie(sdata, skb, sband,
assoc_data->link[link_id].conn_flags);
ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_HE_CAPABILITY);
}
/*
* 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_flags & IEEE80211_CONN_DISABLE_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_flags & IEEE80211_CONN_DISABLE_EHT))
ieee80211_add_eht_ie(sdata, skb, sband);
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);
if (link)
link->u.mgd.conn_flags = assoc_data->link[link_id].conn_flags;
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 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;
bool at_extension = 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);
}
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 (!sdata->vif.valid_links)
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_EML_CAPA |
IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP);
common = skb_put(skb, sizeof(*common));
common->len = sizeof(*common) +
2 + /* EML capabilities */
2; /* MLD capa/ops */
memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);
skb_put_data(skb, &eml_capa, sizeof(eml_capa));
/* need indication from userspace to support this */
mld_capa_ops &= ~cpu_to_le16(IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP);
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_fragment_element(skb, ml_elem_len);
}
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 = WLAN_CAPABILITY_ESS, 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);
sdata_assert_lock(sdata);
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 (sdata->vif.valid_links) {
/* 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_flags & IEEE80211_CONN_DISABLE_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);
/* 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;
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,
!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 work_struct *work)
{
struct ieee80211_link_data *link =
container_of(work, struct ieee80211_link_data, u.mgd.chswitch_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;
sdata_lock(sdata);
mutex_lock(&local->mtx);
mutex_lock(&local->chanctx_mtx);
if (!ifmgd->associated)
goto out;
if (!link->conf->csa_active)
goto out;
/*
* 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)
goto out;
ret = ieee80211_link_use_reserved_context(link);
if (ret) {
sdata_info(sdata,
"failed to use reserved channel context, disconnecting (err=%d)\n",
ret);
ieee80211_queue_work(&sdata->local->hw,
&ifmgd->csa_connection_drop_work);
goto out;
}
goto out;
}
if (!cfg80211_chandef_identical(&link->conf->chandef,
&link->csa_chandef)) {
sdata_info(sdata,
"failed to finalize channel switch, disconnecting\n");
ieee80211_queue_work(&sdata->local->hw,
&ifmgd->csa_connection_drop_work);
goto out;
}
link->u.mgd.csa_waiting_bcn = true;
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
out:
mutex_unlock(&local->chanctx_mtx);
mutex_unlock(&local->mtx);
sdata_unlock(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;
sdata_assert_lock(sdata);
WARN_ON(!link->conf->csa_active);
if (link->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
link->csa_block_tx = false;
}
link->conf->csa_active = false;
link->u.mgd.csa_waiting_bcn = false;
/*
* If the CSA IE is still present on the beacon after the switch,
* we need to consider it as a new CSA (possibly to self).
*/
link->u.mgd.beacon_crc_valid = false;
ret = drv_post_channel_switch(sdata);
if (ret) {
sdata_info(sdata,
"driver post channel switch failed, disconnecting\n");
ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work);
return;
}
cfg80211_ch_switch_notify(sdata->dev, &link->reserved_chandef, 0);
}
void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success)
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
if (WARN_ON(sdata->vif.valid_links))
success = false;
trace_api_chswitch_done(sdata, success);
if (!success) {
sdata_info(sdata,
"driver channel switch failed, disconnecting\n");
ieee80211_queue_work(&sdata->local->hw,
&ifmgd->csa_connection_drop_work);
} else {
ieee80211_queue_work(&sdata->local->hw,
&sdata->deflink.u.mgd.chswitch_work);
}
}
EXPORT_SYMBOL(ieee80211_chswitch_done);
static void ieee80211_chswitch_timer(struct timer_list *t)
{
struct ieee80211_link_data *link =
from_timer(link, t, u.mgd.chswitch_timer);
ieee80211_queue_work(&link->sdata->local->hw,
&link->u.mgd.chswitch_work);
}
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;
if (!local->ops->abort_channel_switch)
return;
mutex_lock(&local->mtx);
mutex_lock(&local->chanctx_mtx);
ieee80211_link_unreserve_chanctx(link);
mutex_unlock(&local->chanctx_mtx);
if (link->csa_block_tx)
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
link->csa_block_tx = false;
link->conf->csa_active = false;
mutex_unlock(&local->mtx);
drv_abort_channel_switch(sdata);
}
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->u.mgd.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;
struct ieee80211_bss *bss;
int res;
sdata_assert_lock(sdata);
if (!cbss)
return;
if (local->scanning)
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_flags,
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.chandef;
ch_switch.count = csa_ie.count;
ch_switch.delay = csa_ie.max_switch_time;
}
if (res < 0)
goto lock_and_drop_connection;
if (beacon && link->conf->csa_active &&
!link->u.mgd.csa_waiting_bcn) {
if (res)
ieee80211_sta_abort_chanswitch(link);
else
drv_channel_switch_rx_beacon(sdata, &ch_switch);
return;
} else if (link->conf->csa_active || res) {
/* disregard subsequent announcements if already processing */
return;
}
if (link->conf->chandef.chan->band !=
csa_ie.chandef.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.chandef.chan->center_freq,
csa_ie.chandef.width, csa_ie.chandef.center_freq1,
csa_ie.chandef.center_freq2);
goto lock_and_drop_connection;
}
if (!cfg80211_chandef_usable(local->hw.wiphy, &csa_ie.chandef,
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.chandef.chan->center_freq,
csa_ie.chandef.chan->freq_offset,
csa_ie.chandef.width, csa_ie.chandef.center_freq1,
csa_ie.chandef.freq1_offset,
csa_ie.chandef.center_freq2);
goto lock_and_drop_connection;
}
if (cfg80211_chandef_identical(&csa_ie.chandef,
&link->conf->chandef) &&
(!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 - 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(sdata);
mutex_lock(&local->mtx);
mutex_lock(&local->chanctx_mtx);
conf = rcu_dereference_protected(link->conf->chanctx_conf,
lockdep_is_held(&local->chanctx_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 (local->use_chanctx &&
!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.chandef,
chanctx->mode, false);
if (res) {
sdata_info(sdata,
"failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
res);
goto drop_connection;
}
mutex_unlock(&local->chanctx_mtx);
link->conf->csa_active = true;
link->csa_chandef = csa_ie.chandef;
link->csa_block_tx = csa_ie.mode;
link->u.mgd.csa_ignored_same_chan = false;
link->u.mgd.beacon_crc_valid = false;
if (link->csa_block_tx)
ieee80211_stop_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
mutex_unlock(&local->mtx);
cfg80211_ch_switch_started_notify(sdata->dev, &csa_ie.chandef,
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 */
if (csa_ie.count <= 1)
ieee80211_queue_work(&local->hw, &link->u.mgd.chswitch_work);
else
mod_timer(&link->u.mgd.chswitch_timer,
TU_TO_EXP_TIME((csa_ie.count - 1) *
cbss->beacon_interval));
return;
lock_and_drop_connection:
mutex_lock(&local->mtx);
mutex_lock(&local->chanctx_mtx);
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;
link->csa_block_tx = csa_ie.mode;
ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work);
mutex_unlock(&local->chanctx_mtx);
mutex_unlock(&local->mtx);
}
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 u32 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);
cancel_work_sync(&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 work_struct *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 work_struct *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);
ieee80211_queue_work(&local->hw, &local->dynamic_ps_enable_work);
}
void ieee80211_dfs_cac_timer_work(struct work_struct *work)
{
struct delayed_work *delayed_work = to_delayed_work(work);
struct ieee80211_link_data *link =
container_of(delayed_work, struct ieee80211_link_data,
dfs_cac_timer_work);
struct cfg80211_chan_def chandef = link->conf->chandef;
struct ieee80211_sub_if_data *sdata = link->sdata;
mutex_lock(&sdata->local->mtx);
if (sdata->wdev.cac_started) {
ieee80211_link_release_channel(link);
cfg80211_cac_event(sdata->dev, &chandef,
NL80211_RADAR_CAC_FINISHED,
GFP_KERNEL);
}
mutex_unlock(&sdata->local->mtx);
}
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;
schedule_delayed_work(&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 work_struct *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);
}
/* 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) {
sdata_info(sdata,
"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) {
sdata_info(sdata,
"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) {
sdata_info(sdata,
"AP has invalid WMM params (missing AC %d), using defaults\n",
ac);
return false;
}
}
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);
link->tx_conf[ac] = params[ac];
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);
}
/* 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_held(&sdata->local->mtx);
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)
{
mutex_lock(&sdata->local->mtx);
__ieee80211_stop_poll(sdata);
mutex_unlock(&sdata->local->mtx);
}
static u32 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;
u32 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 u32 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;
u32 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->u.mgd.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) {
/*
* If the AP is buggy we may get here with no DTIM period
* known, so assume it's 1 which is the only safe assumption
* in that case, although if the TIM IE is broken powersave
* probably just won't work at all.
*/
bss_conf->dtim_period = link->u.mgd.dtim_period ?: 1;
bss_conf->beacon_rate = bss->beacon_rate;
changed |= BSS_CHANGED_BEACON_INFO;
} else {
bss_conf->beacon_rate = NULL;
bss_conf->dtim_period = 0;
}
/* 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;
sdata->u.mgd.associated = true;
for (link_id = 0; link_id <