| // 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, ¶ms[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(¶ms, 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, ¶ms[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)) || |