Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mlme.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * BSS client mode implementation
 * Copyright 2003-2008, Jouni Malinen <j@w1.fi>
 * Copyright 2004, Instant802 Networks, Inc.
 * Copyright 2005, Devicescape Software, Inc.
 * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
 * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
 * Copyright 2013-2014  Intel Mobile Communications GmbH
 * Copyright (C) 2015 - 2017 Intel Deutschland GmbH
 * Copyright (C) 2018 - 2025 Intel Corporation
 */


#include <linux/delay.h>
#include <linux/fips.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/etherdevice.h>
#include <linux/moduleparam.h>
#include <linux/rtnetlink.h>
#include <linux/crc32.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <net/mac80211.h>
#include <linux/unaligned.h>

#include "ieee80211_i.h"
#include "driver-ops.h"
#include "rate.h"
#include "led.h"
#include "fils_aead.h"

#include <kunit/static_stub.h>

#define IEEE80211_AUTH_TIMEOUT  (HZ / 5)
#define IEEE80211_AUTH_TIMEOUT_LONG (HZ / 2)
#define IEEE80211_AUTH_TIMEOUT_SHORT (HZ / 10)
#define IEEE80211_AUTH_TIMEOUT_SAE (HZ * 2)
#define IEEE80211_AUTH_MAX_TRIES 3
#define IEEE80211_AUTH_WAIT_ASSOC (HZ * 5)
#define IEEE80211_AUTH_WAIT_SAE_RETRY (HZ * 2)
#define IEEE80211_ASSOC_TIMEOUT  (HZ / 5)
#define IEEE80211_ASSOC_TIMEOUT_LONG (HZ / 2)
#define IEEE80211_ASSOC_TIMEOUT_SHORT (HZ / 10)
#define IEEE80211_ASSOC_MAX_TRIES 3

#define IEEE80211_ADV_TTLM_SAFETY_BUFFER_MS (100 * USEC_PER_MSEC)
#define IEEE80211_ADV_TTLM_ST_UNDERFLOW 0xff00

#define IEEE80211_NEG_TTLM_REQ_TIMEOUT (HZ / 5)

static int max_nullfunc_tries = 2;
module_param(max_nullfunc_tries, int, 0644);
MODULE_PARM_DESC(max_nullfunc_tries,
   "Maximum nullfunc tx tries before disconnecting (reason 4).");

static int max_probe_tries = 5;
module_param(max_probe_tries, int, 0644);
MODULE_PARM_DESC(max_probe_tries,
   "Maximum probe tries before disconnecting (reason 4).");

/*
 * Beacon loss timeout is calculated as N frames times the
 * advertised beacon interval.  This may need to be somewhat
 * higher than what hardware might detect to account for
 * delays in the host processing frames. But since we also
 * probe on beacon miss before declaring the connection lost
 * default to what we want.
 */

static int beacon_loss_count = 7;
module_param(beacon_loss_count, int, 0644);
MODULE_PARM_DESC(beacon_loss_count,
   "Number of beacon intervals before we decide beacon was lost.");

/*
 * Time the connection can be idle before we probe
 * it to see if we can still talk to the AP.
 */

#define IEEE80211_CONNECTION_IDLE_TIME (30 * HZ)
/*
 * Time we wait for a probe response after sending
 * a probe request because of beacon loss or for
 * checking the connection still works.
 */

static int probe_wait_ms = 500;
module_param(probe_wait_ms, int, 0644);
MODULE_PARM_DESC(probe_wait_ms,
   "Maximum time(ms) to wait for probe response"
   " before disconnecting (reason 4).");

/*
 * How many Beacon frames need to have been used in average signal strength
 * before starting to indicate signal change events.
 */

#define IEEE80211_SIGNAL_AVE_MIN_COUNT 4

/*
 * We can have multiple work items (and connection probing)
 * scheduling this timer, but we need to take care to only
 * reschedule it when it should fire _earlier_ than it was
 * asked for before, or if it's not pending right now. This
 * function ensures that. Note that it then is required to
 * run this function for all timeouts after the first one
 * has happened -- the work that runs from this timer will
 * do that.
 */

static void run_again(struct ieee80211_sub_if_data *sdata,
        unsigned long timeout)
{
 lockdep_assert_wiphy(sdata->local->hw.wiphy);

 if (!timer_pending(&sdata->u.mgd.timer) ||
     time_before(timeout, sdata->u.mgd.timer.expires))
  mod_timer(&sdata->u.mgd.timer, timeout);
}

void ieee80211_sta_reset_beacon_monitor(struct ieee80211_sub_if_data *sdata)
{
 if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER)
  return;

 if (ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR))
  return;

 mod_timer(&sdata->u.mgd.bcn_mon_timer,
    round_jiffies_up(jiffies + sdata->u.mgd.beacon_timeout));
}

void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata)
{
 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;

 if (unlikely(!ifmgd->associated))
  return;

 if (ifmgd->probe_send_count)
  ifmgd->probe_send_count = 0;

 if (ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR))
  return;

 mod_timer(&ifmgd->conn_mon_timer,
    round_jiffies_up(jiffies + IEEE80211_CONNECTION_IDLE_TIME));
}

static int ecw2cw(int ecw)
{
 return (1 << ecw) - 1;
}

static enum ieee80211_conn_mode
ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
       struct ieee80211_channel *channel,
       u32 vht_cap_info,
       const struct ieee802_11_elems *elems,
       bool ignore_ht_channel_mismatch,
       const struct ieee80211_conn_settings *conn,
       struct cfg80211_chan_def *chandef)
{
 const struct ieee80211_ht_operation *ht_oper = elems->ht_operation;
 const struct ieee80211_vht_operation *vht_oper = elems->vht_operation;
 const struct ieee80211_he_operation *he_oper = elems->he_operation;
 const struct ieee80211_eht_operation *eht_oper = elems->eht_operation;
 struct ieee80211_supported_band *sband =
  sdata->local->hw.wiphy->bands[channel->band];
 struct cfg80211_chan_def vht_chandef;
 bool no_vht = false;
 u32 ht_cfreq;

 if (ieee80211_hw_check(&sdata->local->hw, STRICT))
  ignore_ht_channel_mismatch = false;

 *chandef = (struct cfg80211_chan_def) {
  .chan = channel,
  .width = NL80211_CHAN_WIDTH_20_NOHT,
  .center_freq1 = channel->center_freq,
  .freq1_offset = channel->freq_offset,
 };

 /* get special S1G case out of the way */
 if (sband->band == NL80211_BAND_S1GHZ) {
  if (!ieee80211_chandef_s1g_oper(elems->s1g_oper, chandef)) {
   sdata_info(sdata,
       "Missing S1G Operation Element? Trying operating == primary\n");
   chandef->width = ieee80211_s1g_channel_width(channel);
  }

  return IEEE80211_CONN_MODE_S1G;
 }

 /* get special 6 GHz case out of the way */
 if (sband->band == NL80211_BAND_6GHZ) {
  enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_EHT;

  /* this is an error */
  if (conn->mode < IEEE80211_CONN_MODE_HE)
   return IEEE80211_CONN_MODE_LEGACY;

  if (!elems->he_6ghz_capa || !elems->he_cap) {
   sdata_info(sdata,
       "HE 6 GHz AP is missing HE/HE 6 GHz band capability\n");
   return IEEE80211_CONN_MODE_LEGACY;
  }

  if (!eht_oper || !elems->eht_cap) {
   eht_oper = NULL;
   mode = IEEE80211_CONN_MODE_HE;
  }

  if (!ieee80211_chandef_he_6ghz_oper(sdata->local, he_oper,
          eht_oper, chandef)) {
   sdata_info(sdata, "bad HE/EHT 6 GHz operation\n");
   return IEEE80211_CONN_MODE_LEGACY;
  }

  return mode;
 }

 /* now we have the progression HT, VHT, ... */
 if (conn->mode < IEEE80211_CONN_MODE_HT)
  return IEEE80211_CONN_MODE_LEGACY;

 if (!ht_oper || !elems->ht_cap_elem)
  return IEEE80211_CONN_MODE_LEGACY;

 chandef->width = NL80211_CHAN_WIDTH_20;

 ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan,
        channel->band);
 /* check that channel matches the right operating channel */
 if (!ignore_ht_channel_mismatch && channel->center_freq != ht_cfreq) {
  /*
 * It's possible that some APs are confused here;
 * Netgear WNDR3700 sometimes reports 4 higher than
 * the actual channel in association responses, but
 * since we look at probe response/beacon data here
 * it should be OK.
 */

  sdata_info(sdata,
      "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n",
      channel->center_freq, ht_cfreq,
      ht_oper->primary_chan, channel->band);
  return IEEE80211_CONN_MODE_LEGACY;
 }

 ieee80211_chandef_ht_oper(ht_oper, chandef);

 if (conn->mode < IEEE80211_CONN_MODE_VHT)
  return IEEE80211_CONN_MODE_HT;

 vht_chandef = *chandef;

 /*
 * having he_cap/he_oper parsed out implies we're at
 * least operating as HE STA
 */

 if (elems->he_cap && he_oper &&
     he_oper->he_oper_params & cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO)) {
  struct ieee80211_vht_operation he_oper_vht_cap;

  /*
 * Set only first 3 bytes (other 2 aren't used in
 * ieee80211_chandef_vht_oper() anyway)
 */

  memcpy(&he_oper_vht_cap, he_oper->optional, 3);
  he_oper_vht_cap.basic_mcs_set = cpu_to_le16(0);

  if (!ieee80211_chandef_vht_oper(&sdata->local->hw, vht_cap_info,
      &he_oper_vht_cap, ht_oper,
      &vht_chandef)) {
   sdata_info(sdata,
       "HE AP VHT information is invalid, disabling HE\n");
   /* this will cause us to re-parse as VHT STA */
   return IEEE80211_CONN_MODE_VHT;
  }
 } else if (!vht_oper || !elems->vht_cap_elem) {
  if (sband->band == NL80211_BAND_5GHZ) {
   sdata_info(sdata,
       "VHT information is missing, disabling VHT\n");
   return IEEE80211_CONN_MODE_HT;
  }
  no_vht = true;
 } else if (sband->band == NL80211_BAND_2GHZ) {
  no_vht = true;
 } else if (!ieee80211_chandef_vht_oper(&sdata->local->hw,
            vht_cap_info,
            vht_oper, ht_oper,
            &vht_chandef)) {
  sdata_info(sdata,
      "AP VHT information is invalid, disabling VHT\n");
  return IEEE80211_CONN_MODE_HT;
 }

 if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
  sdata_info(sdata,
      "AP VHT information doesn't match HT, disabling VHT\n");
  return IEEE80211_CONN_MODE_HT;
 }

 *chandef = vht_chandef;

 /* stick to current max mode if we or the AP don't have HE */
 if (conn->mode < IEEE80211_CONN_MODE_HE ||
     !elems->he_operation || !elems->he_cap) {
  if (no_vht)
   return IEEE80211_CONN_MODE_HT;
  return IEEE80211_CONN_MODE_VHT;
 }

 /* stick to HE if we or the AP don't have EHT */
 if (conn->mode < IEEE80211_CONN_MODE_EHT ||
     !eht_oper || !elems->eht_cap)
  return IEEE80211_CONN_MODE_HE;

 /*
 * handle the case that the EHT operation indicates that it holds EHT
 * operation information (in case that the channel width differs from
 * the channel width reported in HT/VHT/HE).
 */

 if (eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT) {
  struct cfg80211_chan_def eht_chandef = *chandef;

  ieee80211_chandef_eht_oper((const void *)eht_oper->optional,
        &eht_chandef);

  eht_chandef.punctured =
   ieee80211_eht_oper_dis_subchan_bitmap(eht_oper);

  if (!cfg80211_chandef_valid(&eht_chandef)) {
   sdata_info(sdata,
       "AP EHT information is invalid, disabling EHT\n");
   return IEEE80211_CONN_MODE_HE;
  }

  if (!cfg80211_chandef_compatible(chandef, &eht_chandef)) {
   sdata_info(sdata,
       "AP EHT information doesn't match HT/VHT/HE, disabling EHT\n");
   return IEEE80211_CONN_MODE_HE;
  }

  *chandef = eht_chandef;
 }

 return IEEE80211_CONN_MODE_EHT;
}

static bool
ieee80211_verify_sta_ht_mcs_support(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_supported_band *sband,
        const struct ieee80211_ht_operation *ht_op)
{
 struct ieee80211_sta_ht_cap sta_ht_cap;
 int i;

 if (sband->band == NL80211_BAND_6GHZ)
  return true;

 if (!ht_op)
  return false;

 memcpy(&sta_ht_cap, &sband->ht_cap, sizeof(sta_ht_cap));
 ieee80211_apply_htcap_overrides(sdata, &sta_ht_cap);

 /*
 * P802.11REVme/D7.0 - 6.5.4.2.4
 * ...
 * If the MLME of an HT STA receives an MLME-JOIN.request primitive
 * with the SelectedBSS parameter containing a Basic HT-MCS Set field
 * in the HT Operation parameter that contains any unsupported MCSs,
 * the MLME response in the resulting MLME-JOIN.confirm primitive shall
 * contain a ResultCode parameter that is not set to the value SUCCESS.
 * ...
 */


 /* Simply check that all basic rates are in the STA RX mask */
 for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
  if ((ht_op->basic_set[i] & sta_ht_cap.mcs.rx_mask[i]) !=
      ht_op->basic_set[i])
   return false;
 }

 return true;
}

static bool
ieee80211_verify_sta_vht_mcs_support(struct ieee80211_sub_if_data *sdata,
         int link_id,
         struct ieee80211_supported_band *sband,
         const struct ieee80211_vht_operation *vht_op)
{
 struct ieee80211_sta_vht_cap sta_vht_cap;
 u16 ap_min_req_set, sta_rx_mcs_map, sta_tx_mcs_map;
 int nss;

 if (sband->band != NL80211_BAND_5GHZ)
  return true;

 if (!vht_op)
  return false;

 memcpy(&sta_vht_cap, &sband->vht_cap, sizeof(sta_vht_cap));
 ieee80211_apply_vhtcap_overrides(sdata, &sta_vht_cap);

 ap_min_req_set = le16_to_cpu(vht_op->basic_mcs_set);
 sta_rx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.rx_mcs_map);
 sta_tx_mcs_map = le16_to_cpu(sta_vht_cap.vht_mcs.tx_mcs_map);

 /*
 * Many APs are incorrectly advertising an all-zero value here,
 * which really means MCS 0-7 are required for 1-8 streams, but
 * they don't really mean it that way.
 * Some other APs are incorrectly advertising 3 spatial streams
 * with MCS 0-7 are required, but don't really mean it that way
 * and we'll connect only with HT, rather than even HE.
 * As a result, unfortunately the VHT basic MCS/NSS set cannot
 * be used at all, so check it only in strict mode.
 */

 if (!ieee80211_hw_check(&sdata->local->hw, STRICT))
  return true;

 /*
 * P802.11REVme/D7.0 - 6.5.4.2.4
 * ...
 * If the MLME of a VHT STA receives an MLME-JOIN.request primitive
 * with a SelectedBSS parameter containing a Basic VHT-MCS And NSS Set
 * field in the VHT Operation parameter that contains any unsupported
 * <VHT-MCS, NSS> tuple, the MLME response in the resulting
 * MLME-JOIN.confirm primitive shall contain a ResultCode parameter
 * that is not set to the value SUCCESS.
 * ...
 */

 for (nss = 8; nss > 0; nss--) {
  u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
  u8 sta_rx_val;
  u8 sta_tx_val;

  if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
   continue;

  sta_rx_val = (sta_rx_mcs_map >> (2 * (nss - 1))) & 3;
  sta_tx_val = (sta_tx_mcs_map >> (2 * (nss - 1))) & 3;

  if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
      sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
      sta_rx_val < ap_op_val || sta_tx_val < ap_op_val) {
   link_id_info(sdata, link_id,
         "Missing mandatory rates for %d Nss, rx %d, tx %d oper %d, disable VHT\n",
         nss, sta_rx_val, sta_tx_val, ap_op_val);
   return false;
  }
 }

 return true;
}

static bool
ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata,
         int link_id,
         const struct ieee80211_he_cap_elem *he_cap,
         const struct ieee80211_he_operation *he_op)
{
 struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp;
 u16 mcs_80_map_tx, mcs_80_map_rx;
 u16 ap_min_req_set;
 int nss;

 if (!he_cap)
  return false;

 /* mcs_nss is right after he_cap info */
 he_mcs_nss_supp = (void *)(he_cap + 1);

 mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80);
 mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80);

 /* P802.11-REVme/D0.3
 * 27.1.1 Introduction to the HE PHY
 * ...
 * An HE STA shall support the following features:
 * ...
 * Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all
 * supported channel widths for HE SU PPDUs
 */

 if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED ||
     (mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) {
  link_id_info(sdata, link_id,
        "Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n",
        mcs_80_map_tx, mcs_80_map_rx);
  return false;
 }

 if (!he_op)
  return true;

 ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);

 /*
 * Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
 * zeroes, which is nonsense, and completely inconsistent with itself
 * (it doesn't have 8 streams). Accept the settings in this case anyway.
 */

 if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
  return true;

 /* make sure the AP is consistent with itself
 *
 * P802.11-REVme/D0.3
 * 26.17.1 Basic HE BSS operation
 *
 * A STA that is operating in an HE BSS shall be able to receive and
 * transmit at each of the <HE-MCS, NSS> tuple values indicated by the
 * Basic HE-MCS And NSS Set field of the HE Operation parameter of the
 * MLME-START.request primitive and shall be able to receive at each of
 * the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and
 * NSS Set field in the HE Capabilities parameter of the MLMESTART.request
 * primitive
 */

 for (nss = 8; nss > 0; nss--) {
  u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;
  u8 ap_rx_val;
  u8 ap_tx_val;

  if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
   continue;

  ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3;
  ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3;

  if (ap_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
      ap_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
      ap_rx_val < ap_op_val || ap_tx_val < ap_op_val) {
   link_id_info(sdata, link_id,
         "Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n",
         nss, ap_rx_val, ap_tx_val, ap_op_val);
   return false;
  }
 }

 return true;
}

static bool
ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_supported_band *sband,
        const struct ieee80211_he_operation *he_op)
{
 const struct ieee80211_sta_he_cap *sta_he_cap =
  ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
 u16 ap_min_req_set;
 int i;

 if (!sta_he_cap || !he_op)
  return false;

 ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set);

 /*
 * Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all
 * zeroes, which is nonsense, and completely inconsistent with itself
 * (it doesn't have 8 streams). Accept the settings in this case anyway.
 */

 if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set)
  return true;

 /* Need to go over for 80MHz, 160MHz and for 80+80 */
 for (i = 0; i < 3; i++) {
  const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp =
   &sta_he_cap->he_mcs_nss_supp;
  u16 sta_mcs_map_rx =
   le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]);
  u16 sta_mcs_map_tx =
   le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]);
  u8 nss;
  bool verified = true;

  /*
 * For each band there is a maximum of 8 spatial streams
 * possible. Each of the sta_mcs_map_* is a 16-bit struct built
 * of 2 bits per NSS (1-8), with the values defined in enum
 * ieee80211_he_mcs_support. Need to make sure STA TX and RX
 * capabilities aren't less than the AP's minimum requirements
 * for this HE BSS per SS.
 * It is enough to find one such band that meets the reqs.
 */

  for (nss = 8; nss > 0; nss--) {
   u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3;
   u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3;
   u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3;

   if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED)
    continue;

   /*
 * Make sure the HE AP doesn't require MCSs that aren't
 * supported by the client as required by spec
 *
 * P802.11-REVme/D0.3
 * 26.17.1 Basic HE BSS operation
 *
 * An HE STA shall not attempt to join * (MLME-JOIN.request primitive)
 * a BSS, unless it supports (i.e., is able to both transmit and
 * receive using) all of the <HE-MCS, NSS> tuples in the basic
 * HE-MCS and NSS set.
 */

   if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
       sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED ||
       (ap_val > sta_rx_val) || (ap_val > sta_tx_val)) {
    verified = false;
    break;
   }
  }

  if (verified)
   return true;
 }

 /* If here, STA doesn't meet AP's HE min requirements */
 return false;
}

static u8
ieee80211_get_eht_cap_mcs_nss(const struct ieee80211_sta_he_cap *sta_he_cap,
         const struct ieee80211_sta_eht_cap *sta_eht_cap,
         unsigned int idx, int bw)
{
 u8 he_phy_cap0 = sta_he_cap->he_cap_elem.phy_cap_info[0];
 u8 eht_phy_cap0 = sta_eht_cap->eht_cap_elem.phy_cap_info[0];

 /* handle us being a 20 MHz-only EHT STA - with four values
 * for MCS 0-7, 8-9, 10-11, 12-13.
 */

 if (!(he_phy_cap0 & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL))
  return sta_eht_cap->eht_mcs_nss_supp.only_20mhz.rx_tx_max_nss[idx];

 /* the others have MCS 0-9 together, rather than separately from 0-7 */
 if (idx > 0)
  idx--;

 switch (bw) {
 case 0:
  return sta_eht_cap->eht_mcs_nss_supp.bw._80.rx_tx_max_nss[idx];
 case 1:
  if (!(he_phy_cap0 &
        (IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
         IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)))
   return 0xff; /* pass check */
  return sta_eht_cap->eht_mcs_nss_supp.bw._160.rx_tx_max_nss[idx];
 case 2:
  if (!(eht_phy_cap0 & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ))
   return 0xff; /* pass check */
  return sta_eht_cap->eht_mcs_nss_supp.bw._320.rx_tx_max_nss[idx];
 }

 WARN_ON(1);
 return 0;
}

static bool
ieee80211_verify_sta_eht_mcs_support(struct ieee80211_sub_if_data *sdata,
         struct ieee80211_supported_band *sband,
         const struct ieee80211_eht_operation *eht_op)
{
 const struct ieee80211_sta_he_cap *sta_he_cap =
  ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
 const struct ieee80211_sta_eht_cap *sta_eht_cap =
  ieee80211_get_eht_iftype_cap_vif(sband, &sdata->vif);
 const struct ieee80211_eht_mcs_nss_supp_20mhz_only *req;
 unsigned int i;

 if (!sta_he_cap || !sta_eht_cap || !eht_op)
  return false;

 req = &eht_op->basic_mcs_nss;

 for (i = 0; i < ARRAY_SIZE(req->rx_tx_max_nss); i++) {
  u8 req_rx_nss, req_tx_nss;
  unsigned int bw;

  req_rx_nss = u8_get_bits(req->rx_tx_max_nss[i],
      IEEE80211_EHT_MCS_NSS_RX);
  req_tx_nss = u8_get_bits(req->rx_tx_max_nss[i],
      IEEE80211_EHT_MCS_NSS_TX);

  for (bw = 0; bw < 3; bw++) {
   u8 have, have_rx_nss, have_tx_nss;

   have = ieee80211_get_eht_cap_mcs_nss(sta_he_cap,
            sta_eht_cap,
            i, bw);
   have_rx_nss = u8_get_bits(have,
        IEEE80211_EHT_MCS_NSS_RX);
   have_tx_nss = u8_get_bits(have,
        IEEE80211_EHT_MCS_NSS_TX);

   if (req_rx_nss > have_rx_nss ||
       req_tx_nss > have_tx_nss)
    return false;
  }
 }

 return true;
}

static void ieee80211_get_rates(struct ieee80211_supported_band *sband,
    const u8 *supp_rates,
    unsigned int supp_rates_len,
    const u8 *ext_supp_rates,
    unsigned int ext_supp_rates_len,
    u32 *rates, u32 *basic_rates,
    unsigned long *unknown_rates_selectors,
    bool *have_higher_than_11mbit,
    int *min_rate, int *min_rate_index)
{
 int i, j;

 for (i = 0; i < supp_rates_len + ext_supp_rates_len; i++) {
  u8 supp_rate = i < supp_rates_len ?
    supp_rates[i] :
    ext_supp_rates[i - supp_rates_len];
  int rate = supp_rate & 0x7f;
  bool is_basic = !!(supp_rate & 0x80);

  if ((rate * 5) > 110 && have_higher_than_11mbit)
   *have_higher_than_11mbit = true;

  /*
 * Skip membership selectors since they're not rates.
 *
 * Note: Even though the membership selector and the basic
 *  rate flag share the same bit, they are not exactly
 *  the same.
 */

  if (is_basic && rate >= BSS_MEMBERSHIP_SELECTOR_MIN) {
   if (unknown_rates_selectors)
    set_bit(rate, unknown_rates_selectors);
   continue;
  }

  for (j = 0; j < sband->n_bitrates; j++) {
   struct ieee80211_rate *br;
   int brate;

   br = &sband->bitrates[j];

   brate = DIV_ROUND_UP(br->bitrate, 5);
   if (brate == rate) {
    if (rates)
     *rates |= BIT(j);
    if (is_basic && basic_rates)
     *basic_rates |= BIT(j);
    if (min_rate && (rate * 5) < *min_rate) {
     *min_rate = rate * 5;
     if (min_rate_index)
      *min_rate_index = j;
    }
    break;
   }
  }

  /* Handle an unknown entry as if it is an unknown selector */
  if (is_basic && unknown_rates_selectors && j == sband->n_bitrates)
   set_bit(rate, unknown_rates_selectors);
 }
}

static bool ieee80211_chandef_usable(struct ieee80211_sub_if_data *sdata,
         const struct cfg80211_chan_def *chandef,
         u32 prohibited_flags)
{
 if (!cfg80211_chandef_usable(sdata->local->hw.wiphy,
         chandef, prohibited_flags))
  return false;

 if (chandef->punctured &&
     ieee80211_hw_check(&sdata->local->hw, DISALLOW_PUNCTURING))
  return false;

 return true;
}

static int ieee80211_chandef_num_subchans(const struct cfg80211_chan_def *c)
{
 if (c->width == NL80211_CHAN_WIDTH_80P80)
  return 4 + 4;

 return cfg80211_chandef_get_width(c) / 20;
}

static int ieee80211_chandef_num_widths(const struct cfg80211_chan_def *c)
{
 switch (c->width) {
 case NL80211_CHAN_WIDTH_20:
 case NL80211_CHAN_WIDTH_20_NOHT:
  return 1;
 case NL80211_CHAN_WIDTH_40:
  return 2;
 case NL80211_CHAN_WIDTH_80P80:
 case NL80211_CHAN_WIDTH_80:
  return 3;
 case NL80211_CHAN_WIDTH_160:
  return 4;
 case NL80211_CHAN_WIDTH_320:
  return 5;
 default:
  WARN_ON(1);
  return 0;
 }
}

VISIBLE_IF_MAC80211_KUNIT int
ieee80211_calc_chandef_subchan_offset(const struct cfg80211_chan_def *ap,
          u8 n_partial_subchans)
{
 int n = ieee80211_chandef_num_subchans(ap);
 struct cfg80211_chan_def tmp = *ap;
 int offset = 0;

 /*
 * Given a chandef (in this context, it's the AP's) and a number
 * of subchannels that we want to look at ('n_partial_subchans'),
 * calculate the offset in number of subchannels between the full
 * and the subset with the desired width.
 */


 /* same number of subchannels means no offset, obviously */
 if (n == n_partial_subchans)
  return 0;

 /* don't WARN - misconfigured APs could cause this if their N > width */
 if (n < n_partial_subchans)
  return 0;

 while (ieee80211_chandef_num_subchans(&tmp) > n_partial_subchans) {
  u32 prev = tmp.center_freq1;

  ieee80211_chandef_downgrade(&tmp, NULL);

  /*
 * if center_freq moved up, half the original channels
 * are gone now but were below, so increase offset
 */

  if (prev < tmp.center_freq1)
   offset += ieee80211_chandef_num_subchans(&tmp);
 }

 /*
 * 80+80 with secondary 80 below primary - four subchannels for it
 * (we cannot downgrade *to* 80+80, so no need to consider 'tmp')
 */

 if (ap->width == NL80211_CHAN_WIDTH_80P80 &&
     ap->center_freq2 < ap->center_freq1)
  offset += 4;

 return offset;
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_calc_chandef_subchan_offset);

VISIBLE_IF_MAC80211_KUNIT void
ieee80211_rearrange_tpe_psd(struct ieee80211_parsed_tpe_psd *psd,
       const struct cfg80211_chan_def *ap,
       const struct cfg80211_chan_def *used)
{
 u8 needed = ieee80211_chandef_num_subchans(used);
 u8 have = ieee80211_chandef_num_subchans(ap);
 u8 tmp[IEEE80211_TPE_PSD_ENTRIES_320MHZ];
 u8 offset;

 if (!psd->valid)
  return;

 /* if N is zero, all defaults were used, no point in rearranging */
 if (!psd->n)
  goto out;

 BUILD_BUG_ON(sizeof(tmp) != sizeof(psd->power));

 /*
 * This assumes that 'N' is consistent with the HE channel, as
 * it should be (otherwise the AP is broken).
 *
 * In psd->power we have values in the order 0..N, 0..K, where
 * N+K should cover the entire channel per 'ap', but even if it
 * doesn't then we've pre-filled 'unlimited' as defaults.
 *
 * But this is all the wrong order, we want to have them in the
 * order of the 'used' channel.
 *
 * So for example, we could have a 320 MHz EHT AP, which has the
 * HE channel as 80 MHz (e.g. due to puncturing, which doesn't
 * seem to be considered for the TPE), as follows:
 *
 * EHT  320:   |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
 * HE    80:                           |  |  |  |  |
 * used 160:                           |  |  |  |  |  |  |  |  |
 *
 * N entries:                          |--|--|--|--|
 * K entries:  |--|--|--|--|--|--|--|--|           |--|--|--|--|
 * power idx:   4  5  6  7  8  9  10 11 0  1  2  3  12 13 14 15
 * full chan:   0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
 * used chan:                           0  1  2  3  4  5  6  7
 *
 * The idx in the power array ('power idx') is like this since it
 * comes directly from the element's N and K entries in their
 * element order, and those are this way for HE compatibility.
 *
 * Rearrange them as desired here, first by putting them into the
 * 'full chan' order, and then selecting the necessary subset for
 * the 'used chan'.
 */


 /* first reorder according to AP channel */
 offset = ieee80211_calc_chandef_subchan_offset(ap, psd->n);
 for (int i = 0; i < have; i++) {
  if (i < offset)
   tmp[i] = psd->power[i + psd->n];
  else if (i < offset + psd->n)
   tmp[i] = psd->power[i - offset];
  else
   tmp[i] = psd->power[i];
 }

 /*
 * and then select the subset for the used channel
 * (set everything to defaults first in case a driver is confused)
 */

 memset(psd->power, IEEE80211_TPE_PSD_NO_LIMIT, sizeof(psd->power));
 offset = ieee80211_calc_chandef_subchan_offset(ap, needed);
 for (int i = 0; i < needed; i++)
  psd->power[i] = tmp[offset + i];

out:
 /* limit, but don't lie if there are defaults in the data */
 if (needed < psd->count)
  psd->count = needed;
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_rearrange_tpe_psd);

static void ieee80211_rearrange_tpe(struct ieee80211_parsed_tpe *tpe,
        const struct cfg80211_chan_def *ap,
        const struct cfg80211_chan_def *used)
{
 /* ignore this completely for narrow/invalid channels */
 if (!ieee80211_chandef_num_subchans(ap) ||
     !ieee80211_chandef_num_subchans(used)) {
  ieee80211_clear_tpe(tpe);
  return;
 }

 for (int i = 0; i < 2; i++) {
  int needed_pwr_count;

  ieee80211_rearrange_tpe_psd(&tpe->psd_local[i], ap, used);
  ieee80211_rearrange_tpe_psd(&tpe->psd_reg_client[i], ap, used);

  /* limit this to the widths we actually need */
  needed_pwr_count = ieee80211_chandef_num_widths(used);
  if (needed_pwr_count < tpe->max_local[i].count)
   tpe->max_local[i].count = needed_pwr_count;
  if (needed_pwr_count < tpe->max_reg_client[i].count)
   tpe->max_reg_client[i].count = needed_pwr_count;
 }
}

/*
 * The AP part of the channel request is used to distinguish settings
 * to the device used for wider bandwidth OFDMA. This is used in the
 * channel context code to assign two channel contexts even if they're
 * both for the same channel, if the AP bandwidths are incompatible.
 * If not EHT (or driver override) then ap.chan == NULL indicates that
 * there's no wider BW OFDMA used.
 */

static void ieee80211_set_chanreq_ap(struct ieee80211_sub_if_data *sdata,
         struct ieee80211_chan_req *chanreq,
         struct ieee80211_conn_settings *conn,
         struct cfg80211_chan_def *ap_chandef)
{
 chanreq->ap.chan = NULL;

 if (conn->mode < IEEE80211_CONN_MODE_EHT)
  return;
 if (sdata->vif.driver_flags & IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW)
  return;

 chanreq->ap = *ap_chandef;
}

VISIBLE_IF_MAC80211_KUNIT struct ieee802_11_elems *
ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
         struct ieee80211_conn_settings *conn,
         struct cfg80211_bss *cbss, int link_id,
         struct ieee80211_chan_req *chanreq,
         struct cfg80211_chan_def *ap_chandef,
         unsigned long *userspace_selectors)
{
 const struct cfg80211_bss_ies *ies = rcu_dereference(cbss->ies);
 struct ieee80211_bss *bss = (void *)cbss->priv;
 struct ieee80211_channel *channel = cbss->channel;
 struct ieee80211_elems_parse_params parse_params = {
  .link_id = -1,
  .from_ap = true,
  .start = ies->data,
  .len = ies->len,
 };
 struct ieee802_11_elems *elems;
 struct ieee80211_supported_band *sband;
 enum ieee80211_conn_mode ap_mode;
 unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
 unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
 int ret;

again:
 parse_params.mode = conn->mode;
 elems = ieee802_11_parse_elems_full(&parse_params);
 if (!elems)
  return ERR_PTR(-ENOMEM);

 ap_mode = ieee80211_determine_ap_chan(sdata, channel, bss->vht_cap_info,
           elems, false, conn, ap_chandef);

 /* this should be impossible since parsing depends on our mode */
 if (WARN_ON(ap_mode > conn->mode)) {
  ret = -EINVAL;
  goto free;
 }

 if (conn->mode != ap_mode) {
  conn->mode = ap_mode;
  kfree(elems);
  goto again;
 }

 mlme_link_id_dbg(sdata, link_id, "determined AP %pM to be %s\n",
    cbss->bssid, ieee80211_conn_mode_str(ap_mode));

 sband = sdata->local->hw.wiphy->bands[channel->band];

 ieee80211_get_rates(sband, elems->supp_rates, elems->supp_rates_len,
       elems->ext_supp_rates, elems->ext_supp_rates_len,
       NULL, NULL, unknown_rates_selectors, NULL, NULL,
       NULL);

 switch (channel->band) {
 case NL80211_BAND_S1GHZ:
  if (WARN_ON(ap_mode != IEEE80211_CONN_MODE_S1G)) {
   ret = -EINVAL;
   goto free;
  }
  return elems;
 case NL80211_BAND_6GHZ:
  if (ap_mode < IEEE80211_CONN_MODE_HE) {
   link_id_info(sdata, link_id,
         "Rejecting non-HE 6/7 GHz connection");
   ret = -EINVAL;
   goto free;
  }
  break;
 default:
  if (WARN_ON(ap_mode == IEEE80211_CONN_MODE_S1G)) {
   ret = -EINVAL;
   goto free;
  }
 }

 switch (ap_mode) {
 case IEEE80211_CONN_MODE_S1G:
  WARN_ON(1);
  ret = -EINVAL;
  goto free;
 case IEEE80211_CONN_MODE_LEGACY:
  conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
  break;
 case IEEE80211_CONN_MODE_HT:
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_40);
  break;
 case IEEE80211_CONN_MODE_VHT:
 case IEEE80211_CONN_MODE_HE:
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_160);
  break;
 case IEEE80211_CONN_MODE_EHT:
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_320);
  break;
 }

 chanreq->oper = *ap_chandef;

 bitmap_copy(sta_selectors, userspace_selectors, 128);
 if (conn->mode >= IEEE80211_CONN_MODE_HT)
  set_bit(BSS_MEMBERSHIP_SELECTOR_HT_PHY, sta_selectors);
 if (conn->mode >= IEEE80211_CONN_MODE_VHT)
  set_bit(BSS_MEMBERSHIP_SELECTOR_VHT_PHY, sta_selectors);
 if (conn->mode >= IEEE80211_CONN_MODE_HE)
  set_bit(BSS_MEMBERSHIP_SELECTOR_HE_PHY, sta_selectors);
 if (conn->mode >= IEEE80211_CONN_MODE_EHT)
  set_bit(BSS_MEMBERSHIP_SELECTOR_EHT_PHY, sta_selectors);

 /*
 * We do not support EPD or GLK so never add them.
 * SAE_H2E is handled through userspace_selectors.
 */


 /* Check if we support all required features */
 if (!bitmap_subset(unknown_rates_selectors, sta_selectors, 128)) {
  link_id_info(sdata, link_id,
        "required basic rate or BSS membership selectors not supported or disabled, rejecting connection\n");
  ret = -EINVAL;
  goto free;
 }

 ieee80211_set_chanreq_ap(sdata, chanreq, conn, ap_chandef);

 while (!ieee80211_chandef_usable(sdata, &chanreq->oper,
      IEEE80211_CHAN_DISABLED)) {
  if (WARN_ON(chanreq->oper.width == NL80211_CHAN_WIDTH_20_NOHT)) {
   ret = -EINVAL;
   goto free;
  }

  ieee80211_chanreq_downgrade(chanreq, conn);
 }

 if (conn->mode >= IEEE80211_CONN_MODE_HE &&
     !cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
         IEEE80211_CHAN_NO_HE)) {
  conn->mode = IEEE80211_CONN_MODE_VHT;
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_160);
 }

 if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
     !cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
         IEEE80211_CHAN_NO_EHT)) {
  conn->mode = IEEE80211_CONN_MODE_HE;
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_160);
 }

 if (chanreq->oper.width != ap_chandef->width || ap_mode != conn->mode)
  link_id_info(sdata, link_id,
        "regulatory prevented using AP config, downgraded\n");

 if (conn->mode >= IEEE80211_CONN_MODE_HT &&
     !ieee80211_verify_sta_ht_mcs_support(sdata, sband,
       elems->ht_operation)) {
  conn->mode = IEEE80211_CONN_MODE_LEGACY;
  conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
  link_id_info(sdata, link_id,
        "required MCSes not supported, disabling HT\n");
 }

 if (conn->mode >= IEEE80211_CONN_MODE_VHT &&
     !ieee80211_verify_sta_vht_mcs_support(sdata, link_id, sband,
        elems->vht_operation)) {
  conn->mode = IEEE80211_CONN_MODE_HT;
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_40);
  link_id_info(sdata, link_id,
        "required MCSes not supported, disabling VHT\n");
 }

 if (conn->mode >= IEEE80211_CONN_MODE_HE &&
     (!ieee80211_verify_peer_he_mcs_support(sdata, link_id,
         (void *)elems->he_cap,
         elems->he_operation) ||
      !ieee80211_verify_sta_he_mcs_support(sdata, sband,
        elems->he_operation))) {
  conn->mode = IEEE80211_CONN_MODE_VHT;
  link_id_info(sdata, link_id,
        "required MCSes not supported, disabling HE\n");
 }

 if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
     !ieee80211_verify_sta_eht_mcs_support(sdata, sband,
        elems->eht_operation)) {
  conn->mode = IEEE80211_CONN_MODE_HE;
  conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
           conn->bw_limit,
           IEEE80211_CONN_BW_LIMIT_160);
  link_id_info(sdata, link_id,
        "required MCSes not supported, disabling EHT\n");
 }

 if (conn->mode >= IEEE80211_CONN_MODE_EHT &&
     channel->band != NL80211_BAND_2GHZ &&
     conn->bw_limit == IEEE80211_CONN_BW_LIMIT_40) {
  conn->mode = IEEE80211_CONN_MODE_HE;
  link_id_info(sdata, link_id,
        "required bandwidth not supported, disabling EHT\n");
 }

 /* the mode can only decrease, so this must terminate */
 if (ap_mode != conn->mode) {
  kfree(elems);
  goto again;
 }

 mlme_link_id_dbg(sdata, link_id,
    "connecting with %s mode, max bandwidth %d MHz\n",
    ieee80211_conn_mode_str(conn->mode),
    20 * (1 << conn->bw_limit));

 if (WARN_ON_ONCE(!cfg80211_chandef_valid(&chanreq->oper))) {
  ret = -EINVAL;
  goto free;
 }

 return elems;
free:
 kfree(elems);
 return ERR_PTR(ret);
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode);

static int ieee80211_config_bw(struct ieee80211_link_data *link,
          struct ieee802_11_elems *elems,
          bool update, u64 *changed, u16 stype)
{
 struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
 struct ieee80211_sub_if_data *sdata = link->sdata;
 struct ieee80211_chan_req chanreq = {};
 struct cfg80211_chan_def ap_chandef;
 enum ieee80211_conn_mode ap_mode;
 const char *frame;
 u32 vht_cap_info = 0;
 u16 ht_opmode;
 int ret;

 switch (stype) {
 case IEEE80211_STYPE_BEACON:
  frame = "beacon";
  break;
 case IEEE80211_STYPE_ASSOC_RESP:
  frame = "assoc response";
  break;
 case IEEE80211_STYPE_REASSOC_RESP:
  frame = "reassoc response";
  break;
 case IEEE80211_STYPE_ACTION:
  /* the only action frame that gets here */
  frame = "ML reconf response";
  break;
 default:
  return -EINVAL;
 }

 /* don't track any bandwidth changes in legacy/S1G modes */
 if (link->u.mgd.conn.mode == IEEE80211_CONN_MODE_LEGACY ||
     link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G)
  return 0;

 if (elems->vht_cap_elem)
  vht_cap_info = le32_to_cpu(elems->vht_cap_elem->vht_cap_info);

 ap_mode = ieee80211_determine_ap_chan(sdata, channel, vht_cap_info,
           elems, true, &link->u.mgd.conn,
           &ap_chandef);

 if (ap_mode != link->u.mgd.conn.mode) {
  link_info(link,
     "AP %pM appears to change mode (expected %s, found %s) in %s, disconnect\n",
     link->u.mgd.bssid,
     ieee80211_conn_mode_str(link->u.mgd.conn.mode),
     ieee80211_conn_mode_str(ap_mode), frame);
  return -EINVAL;
 }

 chanreq.oper = ap_chandef;
 ieee80211_set_chanreq_ap(sdata, &chanreq, &link->u.mgd.conn,
     &ap_chandef);

 /*
 * if HT operation mode changed store the new one -
 * this may be applicable even if channel is identical
 */

 if (elems->ht_operation) {
  ht_opmode = le16_to_cpu(elems->ht_operation->operation_mode);
  if (link->conf->ht_operation_mode != ht_opmode) {
   *changed |= BSS_CHANGED_HT;
   link->conf->ht_operation_mode = ht_opmode;
  }
 }

 /*
 * Downgrade the new channel if we associated with restricted
 * bandwidth capabilities. For example, if we associated as a
 * 20 MHz STA to a 40 MHz AP (due to regulatory, capabilities
 * or config reasons) then switching to a 40 MHz channel now
 * won't do us any good -- we couldn't use it with the AP.
 */

 while (link->u.mgd.conn.bw_limit <
   ieee80211_min_bw_limit_from_chandef(&chanreq.oper))
  ieee80211_chandef_downgrade(&chanreq.oper, NULL);

 /* TPE element is not present in (re)assoc/ML reconfig response */
 if (stype == IEEE80211_STYPE_BEACON &&
     ap_chandef.chan->band == NL80211_BAND_6GHZ &&
     link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HE) {
  ieee80211_rearrange_tpe(&elems->tpe, &ap_chandef,
     &chanreq.oper);
  if (memcmp(&link->conf->tpe, &elems->tpe, sizeof(elems->tpe))) {
   link->conf->tpe = elems->tpe;
   *changed |= BSS_CHANGED_TPE;
  }
 }

 if (ieee80211_chanreq_identical(&chanreq, &link->conf->chanreq))
  return 0;

 link_info(link,
    "AP %pM changed bandwidth in %s, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n",
    link->u.mgd.bssid, frame, chanreq.oper.chan->center_freq,
    chanreq.oper.chan->freq_offset, chanreq.oper.width,
    chanreq.oper.center_freq1, chanreq.oper.freq1_offset,
    chanreq.oper.center_freq2);

 if (!cfg80211_chandef_valid(&chanreq.oper)) {
  sdata_info(sdata,
      "AP %pM changed caps/bw in %s in a way we can't support - disconnect\n",
      link->u.mgd.bssid, frame);
  return -EINVAL;
 }

 if (!update) {
  link->conf->chanreq = chanreq;
  return 0;
 }

 /*
 * We're tracking the current AP here, so don't do any further checks
 * here. This keeps us from playing ping-pong with regulatory, without
 * it the following can happen (for example):
 *  - connect to an AP with 80 MHz, world regdom allows 80 MHz
 *  - AP advertises regdom US
 *  - CRDA loads regdom US with 80 MHz prohibited (old database)
 *  - we detect an unsupported channel and disconnect
 *  - disconnect causes CRDA to reload world regdomain and the game
 *    starts anew.
 * (see https://bugzilla.kernel.org/show_bug.cgi?id=70881)
 *
 * It seems possible that there are still scenarios with CSA or real
 * bandwidth changes where a this could happen, but those cases are
 * less common and wouldn't completely prevent using the AP.
 */


 ret = ieee80211_link_change_chanreq(link, &chanreq, changed);
 if (ret) {
  sdata_info(sdata,
      "AP %pM changed bandwidth in %s to incompatible one - disconnect\n",
      link->u.mgd.bssid, frame);
  return ret;
 }

 cfg80211_schedule_channels_check(&sdata->wdev);
 return 0;
}

/* frame sending functions */

static void ieee80211_add_ht_ie(struct ieee80211_sub_if_data *sdata,
    struct sk_buff *skb, u8 ap_ht_param,
    struct ieee80211_supported_band *sband,
    struct ieee80211_channel *channel,
    enum ieee80211_smps_mode smps,
    const struct ieee80211_conn_settings *conn)
{
 u8 *pos;
 u32 flags = channel->flags;
 u16 cap;
 struct ieee80211_sta_ht_cap ht_cap;

 BUILD_BUG_ON(sizeof(ht_cap) != sizeof(sband->ht_cap));

 memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
 ieee80211_apply_htcap_overrides(sdata, &ht_cap);

 /* determine capability flags */
 cap = ht_cap.cap;

 switch (ap_ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
 case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
  if (flags & IEEE80211_CHAN_NO_HT40PLUS) {
   cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
   cap &= ~IEEE80211_HT_CAP_SGI_40;
  }
  break;
 case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
  if (flags & IEEE80211_CHAN_NO_HT40MINUS) {
   cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
   cap &= ~IEEE80211_HT_CAP_SGI_40;
  }
  break;
 }

 /*
 * If 40 MHz was disabled associate as though we weren't
 * capable of 40 MHz -- some broken APs will never fall
 * back to trying to transmit in 20 MHz.
 */

 if (conn->bw_limit <= IEEE80211_CONN_BW_LIMIT_20) {
  cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
  cap &= ~IEEE80211_HT_CAP_SGI_40;
 }

 /* set SM PS mode properly */
 cap &= ~IEEE80211_HT_CAP_SM_PS;
 switch (smps) {
 case IEEE80211_SMPS_AUTOMATIC:
 case IEEE80211_SMPS_NUM_MODES:
  WARN_ON(1);
  fallthrough;
 case IEEE80211_SMPS_OFF:
  cap |= WLAN_HT_CAP_SM_PS_DISABLED <<
   IEEE80211_HT_CAP_SM_PS_SHIFT;
  break;
 case IEEE80211_SMPS_STATIC:
  cap |= WLAN_HT_CAP_SM_PS_STATIC <<
   IEEE80211_HT_CAP_SM_PS_SHIFT;
  break;
 case IEEE80211_SMPS_DYNAMIC:
  cap |= WLAN_HT_CAP_SM_PS_DYNAMIC <<
   IEEE80211_HT_CAP_SM_PS_SHIFT;
  break;
 }

 /* reserve and fill IE */
 pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2);
 ieee80211_ie_build_ht_cap(pos, &ht_cap, cap);
}

/* This function determines vht capability flags for the association
 * and builds the IE.
 * Note - the function returns true to own the MU-MIMO capability
 */

static bool ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
     struct sk_buff *skb,
     struct ieee80211_supported_band *sband,
     struct ieee80211_vht_cap *ap_vht_cap,
     const struct ieee80211_conn_settings *conn)
{
 struct ieee80211_local *local = sdata->local;
 u8 *pos;
 u32 cap;
 struct ieee80211_sta_vht_cap vht_cap;
 u32 mask, ap_bf_sts, our_bf_sts;
 bool mu_mimo_owner = false;

 BUILD_BUG_ON(sizeof(vht_cap) != sizeof(sband->vht_cap));

 memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
 ieee80211_apply_vhtcap_overrides(sdata, &vht_cap);

 /* determine capability flags */
 cap = vht_cap.cap;

 if (conn->bw_limit <= IEEE80211_CONN_BW_LIMIT_80) {
  cap &= ~IEEE80211_VHT_CAP_SHORT_GI_160;
  cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
 }

 /*
 * Some APs apparently get confused if our capabilities are better
 * than theirs, so restrict what we advertise in the assoc request.
 */

 if (!ieee80211_hw_check(&local->hw, STRICT)) {
  if (!(ap_vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
   cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
     IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
  else if (!(ap_vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)))
   cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
 }

 /*
 * If some other vif is using the MU-MIMO capability we cannot associate
 * using MU-MIMO - this will lead to contradictions in the group-id
 * mechanism.
 * Ownership is defined since association request, in order to avoid
 * simultaneous associations with MU-MIMO.
 */

 if (cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) {
  bool disable_mu_mimo = false;
  struct ieee80211_sub_if_data *other;

  list_for_each_entry(other, &local->interfaces, list) {
   if (other->vif.bss_conf.mu_mimo_owner) {
    disable_mu_mimo = true;
    break;
   }
  }
  if (disable_mu_mimo)
   cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
  else
   mu_mimo_owner = true;
 }

 mask = IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK;

 ap_bf_sts = le32_to_cpu(ap_vht_cap->vht_cap_info) & mask;
 our_bf_sts = cap & mask;

 if (ap_bf_sts < our_bf_sts) {
  cap &= ~mask;
  cap |= ap_bf_sts;
 }

 /* reserve and fill IE */
 pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
 ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);

 return mu_mimo_owner;
}

static void ieee80211_assoc_add_rates(struct ieee80211_local *local,
          struct sk_buff *skb,
          enum nl80211_chan_width width,
          struct ieee80211_supported_band *sband,
          struct ieee80211_mgd_assoc_data *assoc_data)
{
 u32 rates;

 if (assoc_data->supp_rates_len &&
     !ieee80211_hw_check(&local->hw, STRICT)) {
  /*
 * Get all rates supported by the device and the AP as
 * some APs don't like getting a superset of their rates
 * in the association request (e.g. D-Link DAP 1353 in
 * b-only mode)...
 */

  ieee80211_parse_bitrates(width, sband,
      assoc_data->supp_rates,
      assoc_data->supp_rates_len,
      &rates);
 } else {
  /*
 * In case AP not provide any supported rates information
 * before association, we send information element(s) with
 * all rates that we support.
 */

  rates = ~0;
 }

 ieee80211_put_srates_elem(skb, sband, 0, ~rates,
      WLAN_EID_SUPP_RATES);
 ieee80211_put_srates_elem(skb, sband, 0, ~rates,
      WLAN_EID_EXT_SUPP_RATES);
}

static size_t ieee80211_add_before_ht_elems(struct sk_buff *skb,
         const u8 *elems,
         size_t elems_len,
         size_t offset)
{
 size_t noffset;

 static const u8 before_ht[] = {
  WLAN_EID_SSID,
  WLAN_EID_SUPP_RATES,
  WLAN_EID_EXT_SUPP_RATES,
  WLAN_EID_PWR_CAPABILITY,
  WLAN_EID_SUPPORTED_CHANNELS,
  WLAN_EID_RSN,
  WLAN_EID_QOS_CAPA,
  WLAN_EID_RRM_ENABLED_CAPABILITIES,
  WLAN_EID_MOBILITY_DOMAIN,
  WLAN_EID_FAST_BSS_TRANSITION, /* reassoc only */
  WLAN_EID_RIC_DATA,  /* reassoc only */
  WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
 };
 static const u8 after_ric[] = {
  WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
  WLAN_EID_HT_CAPABILITY,
  WLAN_EID_BSS_COEX_2040,
  /* luckily this is almost always there */
  WLAN_EID_EXT_CAPABILITY,
  WLAN_EID_QOS_TRAFFIC_CAPA,
  WLAN_EID_TIM_BCAST_REQ,
  WLAN_EID_INTERWORKING,
  /* 60 GHz (Multi-band, DMG, MMS) can't happen */
  WLAN_EID_VHT_CAPABILITY,
  WLAN_EID_OPMODE_NOTIF,
 };

 if (!elems_len)
  return offset;

 noffset = ieee80211_ie_split_ric(elems, elems_len,
      before_ht,
      ARRAY_SIZE(before_ht),
      after_ric,
      ARRAY_SIZE(after_ric),
      offset);
 skb_put_data(skb, elems + offset, noffset - offset);

 return noffset;
}

static size_t ieee80211_add_before_vht_elems(struct sk_buff *skb,
          const u8 *elems,
          size_t elems_len,
          size_t offset)
{
 static const u8 before_vht[] = {
  /*
 * no need to list the ones split off before HT
 * or generated here
 */

  WLAN_EID_BSS_COEX_2040,
  WLAN_EID_EXT_CAPABILITY,
  WLAN_EID_QOS_TRAFFIC_CAPA,
  WLAN_EID_TIM_BCAST_REQ,
  WLAN_EID_INTERWORKING,
  /* 60 GHz (Multi-band, DMG, MMS) can't happen */
 };
 size_t noffset;

 if (!elems_len)
  return offset;

 /* RIC already taken care of in ieee80211_add_before_ht_elems() */
 noffset = ieee80211_ie_split(elems, elems_len,
         before_vht, ARRAY_SIZE(before_vht),
         offset);
 skb_put_data(skb, elems + offset, noffset - offset);

 return noffset;
}

static size_t ieee80211_add_before_he_elems(struct sk_buff *skb,
         const u8 *elems,
         size_t elems_len,
         size_t offset)
{
 static const u8 before_he[] = {
  /*
 * no need to list the ones split off before VHT
 * or generated here
 */

  WLAN_EID_OPMODE_NOTIF,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FUTURE_CHAN_GUIDANCE,
  /* 11ai elements */
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_SESSION,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_PUBLIC_KEY,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_KEY_CONFIRM,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_HLP_CONTAINER,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN,
  /* TODO: add 11ah/11aj/11ak elements */
 };
 size_t noffset;

 if (!elems_len)
  return offset;

 /* RIC already taken care of in ieee80211_add_before_ht_elems() */
 noffset = ieee80211_ie_split(elems, elems_len,
         before_he, ARRAY_SIZE(before_he),
         offset);
 skb_put_data(skb, elems + offset, noffset - offset);

 return noffset;
}

static size_t ieee80211_add_before_reg_conn(struct sk_buff *skb,
         const u8 *elems, size_t elems_len,
         size_t offset)
{
 static const u8 before_reg_conn[] = {
  /*
 * no need to list the ones split off before HE
 * or generated here
 */

  WLAN_EID_EXTENSION, WLAN_EID_EXT_DH_PARAMETER,
  WLAN_EID_EXTENSION, WLAN_EID_EXT_KNOWN_STA_IDENTIFCATION,
 };
 size_t noffset;

 if (!elems_len)
  return offset;

 noffset = ieee80211_ie_split(elems, elems_len, before_reg_conn,
         ARRAY_SIZE(before_reg_conn), offset);
 skb_put_data(skb, elems + offset, noffset - offset);

 return noffset;
}

#define PRESENT_ELEMS_MAX 8
#define PRESENT_ELEM_EXT_OFFS 0x100

static void
ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
       struct sk_buff *skb, u16 capab,
       const struct element *ext_capa,
       const u16 *present_elems,
       struct ieee80211_mgd_assoc_data *assoc_data);

static size_t
ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
    struct sk_buff *skb, u16 *capab,
    const struct element *ext_capa,
    const u8 *extra_elems,
    size_t extra_elems_len,
    unsigned int link_id,
    struct ieee80211_link_data *link,
    u16 *present_elems,
    struct ieee80211_mgd_assoc_data *assoc_data)
{
 enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
 struct cfg80211_bss *cbss = assoc_data->link[link_id].bss;
 struct ieee80211_channel *chan = cbss->channel;
 const struct ieee80211_sband_iftype_data *iftd;
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_supported_band *sband;
 enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20;
 struct ieee80211_chanctx_conf *chanctx_conf;
 enum ieee80211_smps_mode smps_mode;
 u16 orig_capab = *capab;
 size_t offset = 0;
 int present_elems_len = 0;
 u8 *pos;
 int i;

#define ADD_PRESENT_ELEM(id) do {     \
 /* need a last for termination - we use 0 == SSID */ \
 if (!WARN_ON(present_elems_len >= PRESENT_ELEMS_MAX - 1)) \
  present_elems[present_elems_len++] = (id);  \
while (0)
#define ADD_PRESENT_EXT_ELEM(id) ADD_PRESENT_ELEM(PRESENT_ELEM_EXT_OFFS | (id))

 if (link)
  smps_mode = link->smps_mode;
 else if (sdata->u.mgd.powersave)
  smps_mode = IEEE80211_SMPS_DYNAMIC;
 else
  smps_mode = IEEE80211_SMPS_OFF;

 if (link) {
  /*
 * 5/10 MHz scenarios are only viable without MLO, in which
 * case this pointer should be used ... All of this is a bit
 * unclear though, not sure this even works at all.
 */

  rcu_read_lock();
  chanctx_conf = rcu_dereference(link->conf->chanctx_conf);
  if (chanctx_conf)
   width = chanctx_conf->def.width;
  rcu_read_unlock();
 }

 sband = local->hw.wiphy->bands[chan->band];
 iftd = ieee80211_get_sband_iftype_data(sband, iftype);

 if (sband->band == NL80211_BAND_2GHZ) {
  *capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
  *capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
 }

 if ((cbss->capability & WLAN_CAPABILITY_SPECTRUM_MGMT) &&
     ieee80211_hw_check(&local->hw, SPECTRUM_MGMT))
  *capab |= WLAN_CAPABILITY_SPECTRUM_MGMT;

 if (sband->band != NL80211_BAND_S1GHZ)
  ieee80211_assoc_add_rates(local, skb, width, sband, assoc_data);

 if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT ||
     *capab & WLAN_CAPABILITY_RADIO_MEASURE) {
  struct cfg80211_chan_def chandef = {
   .width = width,
   .chan = chan,
  };

  pos = skb_put(skb, 4);
  *pos++ = WLAN_EID_PWR_CAPABILITY;
  *pos++ = 2;
  *pos++ = 0; /* min tx power */
   /* max tx power */
  *pos++ = ieee80211_chandef_max_power(&chandef);
  ADD_PRESENT_ELEM(WLAN_EID_PWR_CAPABILITY);
 }

 /*
 * Per spec, we shouldn't include the list of channels if we advertise
 * support for extended channel switching, but we've always done that;
 * (for now?) apply this restriction only on the (new) 6 GHz band.
 */

 if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT &&
     (sband->band != NL80211_BAND_6GHZ ||
      !ext_capa || ext_capa->datalen < 1 ||
      !(ext_capa->data[0] & WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING))) {
  /* TODO: get this in reg domain format */
  pos = skb_put(skb, 2 * sband->n_channels + 2);
  *pos++ = WLAN_EID_SUPPORTED_CHANNELS;
  *pos++ = 2 * sband->n_channels;
  for (i = 0; i < sband->n_channels; i++) {
   int cf = sband->channels[i].center_freq;

   *pos++ = ieee80211_frequency_to_channel(cf);
   *pos++ = 1; /* one channel in the subband*/
  }
  ADD_PRESENT_ELEM(WLAN_EID_SUPPORTED_CHANNELS);
 }

 /* if present, add any custom IEs that go before HT */
 offset = ieee80211_add_before_ht_elems(skb, extra_elems,
            extra_elems_len,
            offset);

 if (sband->band != NL80211_BAND_6GHZ &&
     assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_HT) {
  ieee80211_add_ht_ie(sdata, skb,
        assoc_data->link[link_id].ap_ht_param,
        sband, chan, smps_mode,
        &assoc_data->link[link_id].conn);
  ADD_PRESENT_ELEM(WLAN_EID_HT_CAPABILITY);
 }

 /* if present, add any custom IEs that go before VHT */
 offset = ieee80211_add_before_vht_elems(skb, extra_elems,
      extra_elems_len,
      offset);

 if (sband->band != NL80211_BAND_6GHZ &&
     assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_VHT &&
     sband->vht_cap.vht_supported) {
  bool mu_mimo_owner =
   ieee80211_add_vht_ie(sdata, skb, sband,
          &assoc_data->link[link_id].ap_vht_cap,
          &assoc_data->link[link_id].conn);

  if (link)
   link->conf->mu_mimo_owner = mu_mimo_owner;
  ADD_PRESENT_ELEM(WLAN_EID_VHT_CAPABILITY);
 }

 /* if present, add any custom IEs that go before HE */
 offset = ieee80211_add_before_he_elems(skb, extra_elems,
            extra_elems_len,
            offset);

 if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_HE) {
  ieee80211_put_he_cap(skb, sdata, sband,
         &assoc_data->link[link_id].conn);
  ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_HE_CAPABILITY);
  if (sband->band == NL80211_BAND_6GHZ)
   ieee80211_put_he_6ghz_cap(skb, sdata, smps_mode);
 }

 /*
 * if present, add any custom IEs that go before regulatory
 * connectivity element
 */

 offset = ieee80211_add_before_reg_conn(skb, extra_elems,
            extra_elems_len, offset);

 if (sband->band == NL80211_BAND_6GHZ) {
  /*
 * as per Section E.2.7 of IEEE 802.11 REVme D7.0, non-AP STA
 * capable of operating on the 6 GHz band shall transmit
 * regulatory connectivity element.
 */

  ieee80211_put_reg_conn(skb, chan->flags);
 }

 /*
 * careful - need to know about all the present elems before
 * calling ieee80211_assoc_add_ml_elem(), so add this one if
 * we're going to put it after the ML element
 */

 if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
  ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_EHT_CAPABILITY);

 if (link_id == assoc_data->assoc_link_id)
  ieee80211_assoc_add_ml_elem(sdata, skb, orig_capab, ext_capa,
         present_elems, assoc_data);

 /* crash if somebody gets it wrong */
 present_elems = NULL;

 if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
  ieee80211_put_eht_cap(skb, sdata, sband,
          &assoc_data->link[link_id].conn);

 if (sband->band == NL80211_BAND_S1GHZ) {
  ieee80211_add_aid_request_ie(sdata, skb);
  ieee80211_add_s1g_capab_ie(sdata, &sband->s1g_cap, skb);
 }

 if (iftd && iftd->vendor_elems.data && iftd->vendor_elems.len)
  skb_put_data(skb, iftd->vendor_elems.data, iftd->vendor_elems.len);

 return offset;
}

static void ieee80211_add_non_inheritance_elem(struct sk_buff *skb,
            const u16 *outer,
            const u16 *inner)
{
 unsigned int skb_len = skb->len;
 bool at_extension = false;
 bool added = false;
 int i, j;
 u8 *len, *list_len = NULL;

 skb_put_u8(skb, WLAN_EID_EXTENSION);
 len = skb_put(skb, 1);
 skb_put_u8(skb, WLAN_EID_EXT_NON_INHERITANCE);

 for (i = 0; i < PRESENT_ELEMS_MAX && outer[i]; i++) {
  u16 elem = outer[i];
  bool have_inner = false;

  /* should at least be sorted in the sense of normal -> ext */
  WARN_ON(at_extension && elem < PRESENT_ELEM_EXT_OFFS);

  /* switch to extension list */
  if (!at_extension && elem >= PRESENT_ELEM_EXT_OFFS) {
   at_extension = true;
   if (!list_len)
    skb_put_u8(skb, 0);
   list_len = NULL;
  }

  for (j = 0; j < PRESENT_ELEMS_MAX && inner[j]; j++) {
   if (elem == inner[j]) {
    have_inner = true;
    break;
   }
  }

  if (have_inner)
   continue;

  if (!list_len) {
   list_len = skb_put(skb, 1);
   *list_len = 0;
  }
  *list_len += 1;
  skb_put_u8(skb, (u8)elem);
  added = true;
 }

 /* if we added a list but no extension list, make a zero-len one */
 if (added && (!at_extension || !list_len))
  skb_put_u8(skb, 0);

 /* if nothing added remove extension element completely */
 if (!added)
  skb_trim(skb, skb_len);
 else
  *len = skb->len - skb_len - 2;
}

static void
ieee80211_assoc_add_ml_elem(struct ieee80211_sub_if_data *sdata,
       struct sk_buff *skb, u16 capab,
       const struct element *ext_capa,
       const u16 *outer_present_elems,
       struct ieee80211_mgd_assoc_data *assoc_data)
{
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_multi_link_elem *ml_elem;
 struct ieee80211_mle_basic_common_info *common;
 const struct wiphy_iftype_ext_capab *ift_ext_capa;
 __le16 eml_capa = 0, mld_capa_ops = 0;
 unsigned int link_id;
 u8 *ml_elem_len;
 void *capab_pos;

 if (!ieee80211_vif_is_mld(&sdata->vif))
  return;

 ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy,
          ieee80211_vif_type_p2p(&sdata->vif));
 if (ift_ext_capa) {
  eml_capa = cpu_to_le16(ift_ext_capa->eml_capabilities);
  mld_capa_ops = cpu_to_le16(ift_ext_capa->mld_capa_and_ops);
 }

 skb_put_u8(skb, WLAN_EID_EXTENSION);
 ml_elem_len = skb_put(skb, 1);
 skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK);
 ml_elem = skb_put(skb, sizeof(*ml_elem));
 ml_elem->control =
  cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_BASIC |
       IEEE80211_MLC_BASIC_PRES_MLD_CAPA_OP);
 common = skb_put(skb, sizeof(*common));
 common->len = sizeof(*common) +
        2;  /* MLD capa/ops */
 memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);

 /* add EML_CAPA only if needed, see Draft P802.11be_D2.1, 35.3.17 */
 if (eml_capa &
     cpu_to_le16((IEEE80211_EML_CAP_EMLSR_SUPP |
    IEEE80211_EML_CAP_EMLMR_SUPPORT))) {
  common->len += 2; /* EML capabilities */
  ml_elem->control |=
   cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EML_CAPA);
  skb_put_data(skb, &eml_capa, sizeof(eml_capa));
 }
 skb_put_data(skb, &mld_capa_ops, sizeof(mld_capa_ops));

 if (assoc_data->ext_mld_capa_ops) {
  ml_elem->control |=
   cpu_to_le16(IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP);
  common->len += 2;
  skb_put_data(skb, &assoc_data->ext_mld_capa_ops,
        sizeof(assoc_data->ext_mld_capa_ops));
 }

 for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
  u16 link_present_elems[PRESENT_ELEMS_MAX] = {};
  const u8 *extra_elems;
  size_t extra_elems_len;
  size_t extra_used;
  u8 *subelem_len = NULL;
  __le16 ctrl;

  if (!assoc_data->link[link_id].bss ||
      link_id == assoc_data->assoc_link_id)
   continue;

  extra_elems = assoc_data->link[link_id].elems;
  extra_elems_len = assoc_data->link[link_id].elems_len;

  skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE);
  subelem_len = skb_put(skb, 1);

  ctrl = cpu_to_le16(link_id |
       IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE |
       IEEE80211_MLE_STA_CONTROL_STA_MAC_ADDR_PRESENT);
  skb_put_data(skb, &ctrl, sizeof(ctrl));
  skb_put_u8(skb, 1 + ETH_ALEN); /* STA Info Length */
  skb_put_data(skb, assoc_data->link[link_id].addr,
        ETH_ALEN);
  /*
 * Now add the contents of the (re)association request,
 * but the "listen interval" and "current AP address"
 * (if applicable) are skipped. So we only have
 * the capability field (remember the position and fill
 * later), followed by the elements added below by
 * calling ieee80211_add_link_elems().
 */

  capab_pos = skb_put(skb, 2);

  extra_used = ieee80211_add_link_elems(sdata, skb, &capab,
            ext_capa,
            extra_elems,
            extra_elems_len,
            link_id, NULL,
            link_present_elems,
            assoc_data);
  if (extra_elems)
   skb_put_data(skb, extra_elems + extra_used,
         extra_elems_len - extra_used);

  put_unaligned_le16(capab, capab_pos);

  ieee80211_add_non_inheritance_elem(skb, outer_present_elems,
         link_present_elems);

  ieee80211_fragment_element(skb, subelem_len,
        IEEE80211_MLE_SUBELEM_FRAGMENT);
 }

 ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
}

static int
ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata,
     enum nl80211_iftype iftype,
     struct cfg80211_bss *cbss,
     size_t elems_len)
{
 struct ieee80211_local *local = sdata->local;
 const struct ieee80211_sband_iftype_data *iftd;
 struct ieee80211_supported_band *sband;
 size_t size = 0;

 if (!cbss)
  return size;

 sband = local->hw.wiphy->bands[cbss->channel->band];

 /* add STA profile elements length */
 size += elems_len;

 /* and supported rates length */
 size += 4 + sband->n_bitrates;

 /* supported channels */
 size += 2 + 2 * sband->n_channels;

 iftd = ieee80211_get_sband_iftype_data(sband, iftype);
 if (iftd)
  size += iftd->vendor_elems.len;

 /* power capability */
 size += 4;

 /* HT, VHT, HE, EHT */
 size += 2 + sizeof(struct ieee80211_ht_cap);
 size += 2 + sizeof(struct ieee80211_vht_cap);
 size += 2 + 1 + sizeof(struct ieee80211_he_cap_elem) +
  sizeof(struct ieee80211_he_mcs_nss_supp) +
  IEEE80211_HE_PPE_THRES_MAX_LEN;

 if (sband->band == NL80211_BAND_6GHZ) {
  size += 2 + 1 + sizeof(struct ieee80211_he_6ghz_capa);
  /* reg connection */
  size += 4;
 }

 size += 2 + 1 + sizeof(struct ieee80211_eht_cap_elem) +
  sizeof(struct ieee80211_eht_mcs_nss_supp) +
  IEEE80211_EHT_PPE_THRES_MAX_LEN;

 return size;
}

static int ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
{
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data;
 struct ieee80211_link_data *link;
 struct sk_buff *skb;
 struct ieee80211_mgmt *mgmt;
 u8 *pos, qos_info, *ie_start;
 size_t offset, noffset;
 u16 capab = 0, link_capab;
 __le16 listen_int;
 struct element *ext_capa = NULL;
 enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
 struct ieee80211_prep_tx_info info = {};
 unsigned int link_id, n_links = 0;
 u16 present_elems[PRESENT_ELEMS_MAX] = {};
 void *capab_pos;
 size_t size;
 int ret;

 /* we know it's writable, cast away the const */
 if (assoc_data->ie_len)
  ext_capa = (void *)cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY,
            assoc_data->ie,
            assoc_data->ie_len);

 lockdep_assert_wiphy(sdata->local->hw.wiphy);

 size = local->hw.extra_tx_headroom +
        sizeof(*mgmt) + /* bit too much but doesn't matter */
        2 + assoc_data->ssid_len + /* SSID */
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=93 G=94

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge