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


Quelle  cfg.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * mac80211 configuration hooks for cfg80211
 *
 * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
 * Copyright 2013-2015  Intel Mobile Communications GmbH
 * Copyright (C) 2015-2017 Intel Deutschland GmbH
 * Copyright (C) 2018-2025 Intel Corporation
 */


#include <linux/ieee80211.h>
#include <linux/nl80211.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <net/net_namespace.h>
#include <linux/rcupdate.h>
#include <linux/fips.h>
#include <linux/if_ether.h>
#include <net/cfg80211.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "rate.h"
#include "mesh.h"
#include "wme.h"

static struct ieee80211_link_data *
ieee80211_link_or_deflink(struct ieee80211_sub_if_data *sdata, int link_id,
     bool require_valid)
{
 struct ieee80211_link_data *link;

 if (link_id < 0) {
  /*
 * For keys, if sdata is not an MLD, we might not use
 * the return value at all (if it's not a pairwise key),
 * so in that case (require_valid==false) don't error.
 */

  if (require_valid && ieee80211_vif_is_mld(&sdata->vif))
   return ERR_PTR(-EINVAL);

  return &sdata->deflink;
 }

 link = sdata_dereference(sdata->link[link_id], sdata);
 if (!link)
  return ERR_PTR(-ENOLINK);
 return link;
}

static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata,
      struct vif_params *params)
{
 bool mu_mimo_groups = false;
 bool mu_mimo_follow = false;

 if (params->vht_mumimo_groups) {
  u64 membership;

  BUILD_BUG_ON(sizeof(membership) != WLAN_MEMBERSHIP_LEN);

  memcpy(sdata->vif.bss_conf.mu_group.membership,
         params->vht_mumimo_groups, WLAN_MEMBERSHIP_LEN);
  memcpy(sdata->vif.bss_conf.mu_group.position,
         params->vht_mumimo_groups + WLAN_MEMBERSHIP_LEN,
         WLAN_USER_POSITION_LEN);
  ieee80211_link_info_change_notify(sdata, &sdata->deflink,
        BSS_CHANGED_MU_GROUPS);
  /* don't care about endianness - just check for 0 */
  memcpy(&membership, params->vht_mumimo_groups,
         WLAN_MEMBERSHIP_LEN);
  mu_mimo_groups = membership != 0;
 }

 if (params->vht_mumimo_follow_addr) {
  mu_mimo_follow =
   is_valid_ether_addr(params->vht_mumimo_follow_addr);
  ether_addr_copy(sdata->u.mntr.mu_follow_addr,
    params->vht_mumimo_follow_addr);
 }

 sdata->vif.bss_conf.mu_mimo_owner = mu_mimo_groups || mu_mimo_follow;
}

static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata,
         struct vif_params *params)
{
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_sub_if_data *monitor_sdata;

 /* check flags first */
 if (params->flags && ieee80211_sdata_running(sdata)) {
  u32 mask = MONITOR_FLAG_ACTIVE;

  /*
 * Prohibit MONITOR_FLAG_ACTIVE to be changed
 * while the interface is up.
 * Else we would need to add a lot of cruft
 * to update everything:
 * monitor and all fif_* counters
 * reconfigure hardware
 */

  if ((params->flags & mask) != (sdata->u.mntr.flags & mask))
   return -EBUSY;
 }

 /* also validate MU-MIMO change */
 if (ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
  monitor_sdata = sdata;
 else
  monitor_sdata = wiphy_dereference(local->hw.wiphy,
        local->monitor_sdata);

 if (!monitor_sdata &&
     (params->vht_mumimo_groups || params->vht_mumimo_follow_addr))
  return -EOPNOTSUPP;

 /* apply all changes now - no failures allowed */

 if (monitor_sdata &&
  (ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) ||
   ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)))
  ieee80211_set_mu_mimo_follow(monitor_sdata, params);

 if (params->flags) {
  if (ieee80211_sdata_running(sdata)) {
   ieee80211_adjust_monitor_flags(sdata, -1);
   sdata->u.mntr.flags = params->flags;
   ieee80211_adjust_monitor_flags(sdata, 1);

   ieee80211_configure_filter(local);
  } else {
   /*
 * Because the interface is down, ieee80211_do_stop
 * and ieee80211_do_open take care of "everything"
 * mentioned in the comment above.
 */

   sdata->u.mntr.flags = params->flags;
  }
 }

 return 0;
}

static int ieee80211_set_ap_mbssid_options(struct ieee80211_sub_if_data *sdata,
        struct cfg80211_mbssid_config *params,
        struct ieee80211_bss_conf *link_conf)
{
 struct ieee80211_sub_if_data *tx_sdata;
 struct ieee80211_bss_conf *old;

 link_conf->bssid_index = 0;
 link_conf->nontransmitted = false;
 link_conf->ema_ap = false;
 link_conf->bssid_indicator = 0;

 if (sdata->vif.type != NL80211_IFTYPE_AP || !params->tx_wdev)
  return -EINVAL;

 old = sdata_dereference(link_conf->tx_bss_conf, sdata);
 if (old)
  return -EALREADY;

 tx_sdata = IEEE80211_WDEV_TO_SUB_IF(params->tx_wdev);
 if (!tx_sdata)
  return -EINVAL;

 if (tx_sdata == sdata) {
  rcu_assign_pointer(link_conf->tx_bss_conf, link_conf);
 } else {
  struct ieee80211_bss_conf *tx_bss_conf;

  tx_bss_conf = sdata_dereference(tx_sdata->vif.link_conf[params->tx_link_id],
      sdata);
  if (rcu_access_pointer(tx_bss_conf->tx_bss_conf) != tx_bss_conf)
   return -EINVAL;

  rcu_assign_pointer(link_conf->tx_bss_conf, tx_bss_conf);

  link_conf->nontransmitted = true;
  link_conf->bssid_index = params->index;
  link_conf->bssid_indicator = tx_bss_conf->bssid_indicator;
 }
 if (params->ema)
  link_conf->ema_ap = true;

 return 0;
}

static struct wireless_dev *ieee80211_add_iface(struct wiphy *wiphy,
      const char *name,
      unsigned char name_assign_type,
      enum nl80211_iftype type,
      struct vif_params *params)
{
 struct ieee80211_local *local = wiphy_priv(wiphy);
 struct wireless_dev *wdev;
 struct ieee80211_sub_if_data *sdata;
 int err;

 err = ieee80211_if_add(local, name, name_assign_type, &wdev, type, params);
 if (err)
  return ERR_PTR(err);

 sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);

 if (type == NL80211_IFTYPE_MONITOR) {
  err = ieee80211_set_mon_options(sdata, params);
  if (err) {
   ieee80211_if_remove(sdata);
   return NULL;
  }
 }

 /* Let the driver know that an interface is going to be added.
 * Indicate so only for interface types that will be added to the
 * driver.
 */

 switch (type) {
 case NL80211_IFTYPE_AP_VLAN:
  break;
 case NL80211_IFTYPE_MONITOR:
  if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) ||
      !(params->flags & MONITOR_FLAG_ACTIVE))
   break;
  fallthrough;
 default:
  drv_prep_add_interface(local,
           ieee80211_vif_type_p2p(&sdata->vif));
  break;
 }

 return wdev;
}

static int ieee80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
{
 ieee80211_if_remove(IEEE80211_WDEV_TO_SUB_IF(wdev));

 return 0;
}

static int ieee80211_change_iface(struct wiphy *wiphy,
      struct net_device *dev,
      enum nl80211_iftype type,
      struct vif_params *params)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = sdata->local;
 struct sta_info *sta;
 int ret;

 lockdep_assert_wiphy(local->hw.wiphy);

 ret = ieee80211_if_change_type(sdata, type);
 if (ret)
  return ret;

 if (type == NL80211_IFTYPE_AP_VLAN && params->use_4addr == 0) {
  RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
  ieee80211_check_fast_rx_iface(sdata);
 } else if (type == NL80211_IFTYPE_STATION && params->use_4addr >= 0) {
  struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;

  if (params->use_4addr == ifmgd->use_4addr)
   return 0;

  /* FIXME: no support for 4-addr MLO yet */
  if (ieee80211_vif_is_mld(&sdata->vif))
   return -EOPNOTSUPP;

  sdata->u.mgd.use_4addr = params->use_4addr;
  if (!ifmgd->associated)
   return 0;

  sta = sta_info_get(sdata, sdata->deflink.u.mgd.bssid);
  if (sta)
   drv_sta_set_4addr(local, sdata, &sta->sta,
       params->use_4addr);

  if (params->use_4addr)
   ieee80211_send_4addr_nullfunc(local, sdata);
 }

 if (sdata->vif.type == NL80211_IFTYPE_MONITOR) {
  ret = ieee80211_set_mon_options(sdata, params);
  if (ret)
   return ret;
 }

 return 0;
}

static int ieee80211_start_p2p_device(struct wiphy *wiphy,
          struct wireless_dev *wdev)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 int ret;

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

 ret = ieee80211_check_combinations(sdata, NULL, 0, 0, -1);
 if (ret < 0)
  return ret;

 return ieee80211_do_open(wdev, true);
}

static void ieee80211_stop_p2p_device(struct wiphy *wiphy,
          struct wireless_dev *wdev)
{
 ieee80211_sdata_stop(IEEE80211_WDEV_TO_SUB_IF(wdev));
}

static int ieee80211_start_nan(struct wiphy *wiphy,
          struct wireless_dev *wdev,
          struct cfg80211_nan_conf *conf)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 int ret;

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

 if (sdata->u.nan.started)
  return -EALREADY;

 ret = ieee80211_check_combinations(sdata, NULL, 0, 0, -1);
 if (ret < 0)
  return ret;

 ret = ieee80211_do_open(wdev, true);
 if (ret)
  return ret;

 ret = drv_start_nan(sdata->local, sdata, conf);
 if (ret) {
  ieee80211_sdata_stop(sdata);
  return ret;
 }

 sdata->u.nan.started = true;
 ieee80211_recalc_idle(sdata->local);

 sdata->u.nan.conf.master_pref = conf->master_pref;
 sdata->u.nan.conf.bands = conf->bands;

 return 0;
}

static void ieee80211_stop_nan(struct wiphy *wiphy,
          struct wireless_dev *wdev)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);

 if (!sdata->u.nan.started)
  return;

 drv_stop_nan(sdata->local, sdata);
 sdata->u.nan.started = false;
 ieee80211_sdata_stop(sdata);
 ieee80211_recalc_idle(sdata->local);
}

static int ieee80211_nan_change_conf(struct wiphy *wiphy,
         struct wireless_dev *wdev,
         struct cfg80211_nan_conf *conf,
         u32 changes)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 struct cfg80211_nan_conf new_conf;
 int ret = 0;

 if (sdata->vif.type != NL80211_IFTYPE_NAN)
  return -EOPNOTSUPP;

 if (!ieee80211_sdata_running(sdata))
  return -ENETDOWN;

 new_conf = sdata->u.nan.conf;

 if (changes & CFG80211_NAN_CONF_CHANGED_PREF)
  new_conf.master_pref = conf->master_pref;

 if (changes & CFG80211_NAN_CONF_CHANGED_BANDS)
  new_conf.bands = conf->bands;

 ret = drv_nan_change_conf(sdata->local, sdata, &new_conf, changes);
 if (!ret)
  sdata->u.nan.conf = new_conf;

 return ret;
}

static int ieee80211_add_nan_func(struct wiphy *wiphy,
      struct wireless_dev *wdev,
      struct cfg80211_nan_func *nan_func)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 int ret;

 if (sdata->vif.type != NL80211_IFTYPE_NAN)
  return -EOPNOTSUPP;

 if (!ieee80211_sdata_running(sdata))
  return -ENETDOWN;

 spin_lock_bh(&sdata->u.nan.func_lock);

 ret = idr_alloc(&sdata->u.nan.function_inst_ids,
   nan_func, 1, sdata->local->hw.max_nan_de_entries + 1,
   GFP_ATOMIC);
 spin_unlock_bh(&sdata->u.nan.func_lock);

 if (ret < 0)
  return ret;

 nan_func->instance_id = ret;

 WARN_ON(nan_func->instance_id == 0);

 ret = drv_add_nan_func(sdata->local, sdata, nan_func);
 if (ret) {
  spin_lock_bh(&sdata->u.nan.func_lock);
  idr_remove(&sdata->u.nan.function_inst_ids,
      nan_func->instance_id);
  spin_unlock_bh(&sdata->u.nan.func_lock);
 }

 return ret;
}

static struct cfg80211_nan_func *
ieee80211_find_nan_func_by_cookie(struct ieee80211_sub_if_data *sdata,
      u64 cookie)
{
 struct cfg80211_nan_func *func;
 int id;

 lockdep_assert_held(&sdata->u.nan.func_lock);

 idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, id) {
  if (func->cookie == cookie)
   return func;
 }

 return NULL;
}

static void ieee80211_del_nan_func(struct wiphy *wiphy,
      struct wireless_dev *wdev, u64 cookie)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 struct cfg80211_nan_func *func;
 u8 instance_id = 0;

 if (sdata->vif.type != NL80211_IFTYPE_NAN ||
     !ieee80211_sdata_running(sdata))
  return;

 spin_lock_bh(&sdata->u.nan.func_lock);

 func = ieee80211_find_nan_func_by_cookie(sdata, cookie);
 if (func)
  instance_id = func->instance_id;

 spin_unlock_bh(&sdata->u.nan.func_lock);

 if (instance_id)
  drv_del_nan_func(sdata->local, sdata, instance_id);
}

static int ieee80211_set_noack_map(struct wiphy *wiphy,
      struct net_device *dev,
      u16 noack_map)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);

 sdata->noack_map = noack_map;

 ieee80211_check_fast_xmit_iface(sdata);

 return 0;
}

static int ieee80211_set_tx(struct ieee80211_sub_if_data *sdata,
       const u8 *mac_addr, u8 key_idx)
{
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_key *key;
 struct sta_info *sta;
 int ret = -EINVAL;

 if (!wiphy_ext_feature_isset(local->hw.wiphy,
         NL80211_EXT_FEATURE_EXT_KEY_ID))
  return -EINVAL;

 sta = sta_info_get_bss(sdata, mac_addr);

 if (!sta)
  return -EINVAL;

 if (sta->ptk_idx == key_idx)
  return 0;

 key = wiphy_dereference(local->hw.wiphy, sta->ptk[key_idx]);

 if (key && key->conf.flags & IEEE80211_KEY_FLAG_NO_AUTO_TX)
  ret = ieee80211_set_tx_key(key);

 return ret;
}

static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
        int link_id, u8 key_idx, bool pairwise,
        const u8 *mac_addr, struct key_params *params)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_link_data *link =
  ieee80211_link_or_deflink(sdata, link_id, false);
 struct ieee80211_local *local = sdata->local;
 struct sta_info *sta = NULL;
 struct ieee80211_key *key;
 int err;

 lockdep_assert_wiphy(local->hw.wiphy);

 if (!ieee80211_sdata_running(sdata))
  return -ENETDOWN;

 if (IS_ERR(link))
  return PTR_ERR(link);

 if (WARN_ON(pairwise && link_id >= 0))
  return -EINVAL;

 if (pairwise && params->mode == NL80211_KEY_SET_TX)
  return ieee80211_set_tx(sdata, mac_addr, key_idx);

 /* reject WEP and TKIP keys if WEP failed to initialize */
 switch (params->cipher) {
 case WLAN_CIPHER_SUITE_WEP40:
 case WLAN_CIPHER_SUITE_TKIP:
 case WLAN_CIPHER_SUITE_WEP104:
  if (link_id >= 0)
   return -EINVAL;
  if (WARN_ON_ONCE(fips_enabled))
   return -EINVAL;
  break;
 default:
  break;
 }

 key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len,
      params->key, params->seq_len, params->seq);
 if (IS_ERR(key))
  return PTR_ERR(key);

 if (pairwise) {
  key->conf.flags |= IEEE80211_KEY_FLAG_PAIRWISE;
  key->conf.link_id = -1;
 } else {
  key->conf.link_id = link->link_id;
 }

 if (params->mode == NL80211_KEY_NO_TX)
  key->conf.flags |= IEEE80211_KEY_FLAG_NO_AUTO_TX;

 if (mac_addr) {
  sta = sta_info_get_bss(sdata, mac_addr);
  /*
 * The ASSOC test makes sure the driver is ready to
 * receive the key. When wpa_supplicant has roamed
 * using FT, it attempts to set the key before
 * association has completed, this rejects that attempt
 * so it will set the key again after association.
 *
 * TODO: accept the key if we have a station entry and
 *       add it to the device after the station.
 */

  if (!sta || !test_sta_flag(sta, WLAN_STA_ASSOC)) {
   ieee80211_key_free_unused(key);
   return -ENOENT;
  }
 }

 switch (sdata->vif.type) {
 case NL80211_IFTYPE_STATION:
  if (sdata->u.mgd.mfp != IEEE80211_MFP_DISABLED)
   key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
  break;
 case NL80211_IFTYPE_AP:
 case NL80211_IFTYPE_AP_VLAN:
  /* Keys without a station are used for TX only */
  if (sta && test_sta_flag(sta, WLAN_STA_MFP))
   key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
  break;
 case NL80211_IFTYPE_ADHOC:
  /* no MFP (yet) */
  break;
 case NL80211_IFTYPE_MESH_POINT:
#ifdef CONFIG_MAC80211_MESH
  if (sdata->u.mesh.security != IEEE80211_MESH_SEC_NONE)
   key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
  break;
#endif
 case NL80211_IFTYPE_WDS:
 case NL80211_IFTYPE_MONITOR:
 case NL80211_IFTYPE_P2P_DEVICE:
 case NL80211_IFTYPE_NAN:
 case NL80211_IFTYPE_UNSPECIFIED:
 case NUM_NL80211_IFTYPES:
 case NL80211_IFTYPE_P2P_CLIENT:
 case NL80211_IFTYPE_P2P_GO:
 case NL80211_IFTYPE_OCB:
  /* shouldn't happen */
  WARN_ON_ONCE(1);
  break;
 }

 err = ieee80211_key_link(key, link, sta);
 /* KRACK protection, shouldn't happen but just silently accept key */
 if (err == -EALREADY)
  err = 0;

 return err;
}

static struct ieee80211_key *
ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id,
       u8 key_idx, bool pairwise, const u8 *mac_addr)
{
 struct ieee80211_local *local __maybe_unused = sdata->local;
 struct ieee80211_link_data *link = &sdata->deflink;
 struct ieee80211_key *key;

 if (link_id >= 0) {
  link = sdata_dereference(sdata->link[link_id], sdata);
  if (!link)
   return NULL;
 }

 if (mac_addr) {
  struct sta_info *sta;
  struct link_sta_info *link_sta;

  sta = sta_info_get_bss(sdata, mac_addr);
  if (!sta)
   return NULL;

  if (link_id >= 0) {
   link_sta = rcu_dereference_check(sta->link[link_id],
        lockdep_is_held(&local->hw.wiphy->mtx));
   if (!link_sta)
    return NULL;
  } else {
   link_sta = &sta->deflink;
  }

  if (pairwise && key_idx < NUM_DEFAULT_KEYS)
   return wiphy_dereference(local->hw.wiphy,
       sta->ptk[key_idx]);

  if (!pairwise &&
      key_idx < NUM_DEFAULT_KEYS +
         NUM_DEFAULT_MGMT_KEYS +
         NUM_DEFAULT_BEACON_KEYS)
   return wiphy_dereference(local->hw.wiphy,
       link_sta->gtk[key_idx]);

  return NULL;
 }

 if (pairwise && key_idx < NUM_DEFAULT_KEYS)
  return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]);

 key = wiphy_dereference(local->hw.wiphy, link->gtk[key_idx]);
 if (key)
  return key;

 /* or maybe it was a WEP key */
 if (key_idx < NUM_DEFAULT_KEYS)
  return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]);

 return NULL;
}

static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
        int link_id, u8 key_idx, bool pairwise,
        const u8 *mac_addr)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = sdata->local;
 struct ieee80211_key *key;

 lockdep_assert_wiphy(local->hw.wiphy);

 key = ieee80211_lookup_key(sdata, link_id, key_idx, pairwise, mac_addr);
 if (!key)
  return -ENOENT;

 ieee80211_key_free(key, sdata->vif.type == NL80211_IFTYPE_STATION);

 return 0;
}

static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev,
        int link_id, u8 key_idx, bool pairwise,
        const u8 *mac_addr, void *cookie,
        void (*callback)(void *cookie,
           struct key_params *params))
{
 struct ieee80211_sub_if_data *sdata;
 u8 seq[6] = {0};
 struct key_params params;
 struct ieee80211_key *key;
 u64 pn64;
 u32 iv32;
 u16 iv16;
 int err = -ENOENT;
 struct ieee80211_key_seq kseq = {};

 sdata = IEEE80211_DEV_TO_SUB_IF(dev);

 rcu_read_lock();

 key = ieee80211_lookup_key(sdata, link_id, key_idx, pairwise, mac_addr);
 if (!key)
  goto out;

 memset(¶ms, 0, sizeof(params));

 params.cipher = key->conf.cipher;

 switch (key->conf.cipher) {
 case WLAN_CIPHER_SUITE_TKIP:
  pn64 = atomic64_read(&key->conf.tx_pn);
  iv32 = TKIP_PN_TO_IV32(pn64);
  iv16 = TKIP_PN_TO_IV16(pn64);

  if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE &&
      !(key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV)) {
   drv_get_key_seq(sdata->local, key, &kseq);
   iv32 = kseq.tkip.iv32;
   iv16 = kseq.tkip.iv16;
  }

  seq[0] = iv16 & 0xff;
  seq[1] = (iv16 >> 8) & 0xff;
  seq[2] = iv32 & 0xff;
  seq[3] = (iv32 >> 8) & 0xff;
  seq[4] = (iv32 >> 16) & 0xff;
  seq[5] = (iv32 >> 24) & 0xff;
  params.seq = seq;
  params.seq_len = 6;
  break;
 case WLAN_CIPHER_SUITE_CCMP:
 case WLAN_CIPHER_SUITE_CCMP_256:
 case WLAN_CIPHER_SUITE_AES_CMAC:
 case WLAN_CIPHER_SUITE_BIP_CMAC_256:
  BUILD_BUG_ON(offsetof(typeof(kseq), ccmp) !=
        offsetof(typeof(kseq), aes_cmac));
  fallthrough;
 case WLAN_CIPHER_SUITE_BIP_GMAC_128:
 case WLAN_CIPHER_SUITE_BIP_GMAC_256:
  BUILD_BUG_ON(offsetof(typeof(kseq), ccmp) !=
        offsetof(typeof(kseq), aes_gmac));
  fallthrough;
 case WLAN_CIPHER_SUITE_GCMP:
 case WLAN_CIPHER_SUITE_GCMP_256:
  BUILD_BUG_ON(offsetof(typeof(kseq), ccmp) !=
        offsetof(typeof(kseq), gcmp));

  if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE &&
      !(key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV)) {
   drv_get_key_seq(sdata->local, key, &kseq);
   memcpy(seq, kseq.ccmp.pn, 6);
  } else {
   pn64 = atomic64_read(&key->conf.tx_pn);
   seq[0] = pn64;
   seq[1] = pn64 >> 8;
   seq[2] = pn64 >> 16;
   seq[3] = pn64 >> 24;
   seq[4] = pn64 >> 32;
   seq[5] = pn64 >> 40;
  }
  params.seq = seq;
  params.seq_len = 6;
  break;
 default:
  if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
   break;
  if (WARN_ON(key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV))
   break;
  drv_get_key_seq(sdata->local, key, &kseq);
  params.seq = kseq.hw.seq;
  params.seq_len = kseq.hw.seq_len;
  break;
 }

 callback(cookie, ¶ms);
 err = 0;

 out:
 rcu_read_unlock();
 return err;
}

static int ieee80211_config_default_key(struct wiphy *wiphy,
     struct net_device *dev,
     int link_id, u8 key_idx, bool uni,
     bool multi)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_link_data *link =
  ieee80211_link_or_deflink(sdata, link_id, false);

 if (IS_ERR(link))
  return PTR_ERR(link);

 ieee80211_set_default_key(link, key_idx, uni, multi);

 return 0;
}

static int ieee80211_config_default_mgmt_key(struct wiphy *wiphy,
          struct net_device *dev,
          int link_id, u8 key_idx)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_link_data *link =
  ieee80211_link_or_deflink(sdata, link_id, true);

 if (IS_ERR(link))
  return PTR_ERR(link);

 ieee80211_set_default_mgmt_key(link, key_idx);

 return 0;
}

static int ieee80211_config_default_beacon_key(struct wiphy *wiphy,
            struct net_device *dev,
            int link_id, u8 key_idx)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_link_data *link =
  ieee80211_link_or_deflink(sdata, link_id, true);

 if (IS_ERR(link))
  return PTR_ERR(link);

 ieee80211_set_default_beacon_key(link, key_idx);

 return 0;
}

void sta_set_rate_info_tx(struct sta_info *sta,
     const struct ieee80211_tx_rate *rate,
     struct rate_info *rinfo)
{
 rinfo->flags = 0;
 if (rate->flags & IEEE80211_TX_RC_MCS) {
  rinfo->flags |= RATE_INFO_FLAGS_MCS;
  rinfo->mcs = rate->idx;
 } else if (rate->flags & IEEE80211_TX_RC_VHT_MCS) {
  rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS;
  rinfo->mcs = ieee80211_rate_get_vht_mcs(rate);
  rinfo->nss = ieee80211_rate_get_vht_nss(rate);
 } else {
  struct ieee80211_supported_band *sband;

  sband = ieee80211_get_sband(sta->sdata);
  WARN_ON_ONCE(sband && !sband->bitrates);
  if (sband && sband->bitrates)
   rinfo->legacy = sband->bitrates[rate->idx].bitrate;
 }
 if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
  rinfo->bw = RATE_INFO_BW_40;
 else if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
  rinfo->bw = RATE_INFO_BW_80;
 else if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH)
  rinfo->bw = RATE_INFO_BW_160;
 else
  rinfo->bw = RATE_INFO_BW_20;
 if (rate->flags & IEEE80211_TX_RC_SHORT_GI)
  rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
}

static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev,
      int idx, u8 *mac, struct station_info *sinfo)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = sdata->local;
 struct sta_info *sta;
 int ret = -ENOENT;

 lockdep_assert_wiphy(local->hw.wiphy);

 sta = sta_info_get_by_idx(sdata, idx);
 if (sta) {
  ret = 0;
  memcpy(mac, sta->sta.addr, ETH_ALEN);
  sta_set_sinfo(sta, sinfo, true);

  /* Add accumulated removed link data to sinfo data for
 * consistency for MLO
 */

  if (sinfo->valid_links)
   sta_set_accumulated_removed_links_sinfo(sta, sinfo);

 }

 return ret;
}

static int ieee80211_dump_survey(struct wiphy *wiphy, struct net_device *dev,
     int idx, struct survey_info *survey)
{
 struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);

 return drv_get_survey(local, idx, survey);
}

static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
     const u8 *mac, struct station_info *sinfo)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = sdata->local;
 struct sta_info *sta;
 int ret = -ENOENT;

 lockdep_assert_wiphy(local->hw.wiphy);

 sta = sta_info_get_bss(sdata, mac);
 if (sta) {
  ret = 0;
  sta_set_sinfo(sta, sinfo, true);

  /* Add accumulated removed link data to sinfo data for
 * consistency for MLO
 */

  if (sinfo->valid_links)
   sta_set_accumulated_removed_links_sinfo(sta, sinfo);
 }

 return ret;
}

static int ieee80211_set_monitor_channel(struct wiphy *wiphy,
      struct net_device *dev,
      struct cfg80211_chan_def *chandef)
{
 struct ieee80211_local *local = wiphy_priv(wiphy);
 struct ieee80211_sub_if_data *sdata;
 struct ieee80211_chan_req chanreq = { .oper = *chandef };
 int ret;

 lockdep_assert_wiphy(local->hw.wiphy);

 sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 if (!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
  if (cfg80211_chandef_identical(&local->monitor_chanreq.oper,
            &chanreq.oper))
   return 0;

  sdata = wiphy_dereference(wiphy, local->monitor_sdata);
  if (!sdata)
   goto done;
 }

 if (rcu_access_pointer(sdata->deflink.conf->chanctx_conf) &&
     cfg80211_chandef_identical(&sdata->vif.bss_conf.chanreq.oper,
           &chanreq.oper))
  return 0;

 ieee80211_link_release_channel(&sdata->deflink);
 ret = ieee80211_link_use_channel(&sdata->deflink, &chanreq,
      IEEE80211_CHANCTX_SHARED);
 if (ret)
  return ret;
done:
 local->monitor_chanreq = chanreq;
 return 0;
}

static int
ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
    const u8 *resp, size_t resp_len,
    const struct ieee80211_csa_settings *csa,
    const struct ieee80211_color_change_settings *cca,
    struct ieee80211_link_data *link)
{
 struct probe_resp *new, *old;

 if (!resp || !resp_len)
  return 1;

 old = sdata_dereference(link->u.ap.probe_resp, sdata);

 new = kzalloc(sizeof(struct probe_resp) + resp_len, GFP_KERNEL);
 if (!new)
  return -ENOMEM;

 new->len = resp_len;
 memcpy(new->data, resp, resp_len);

 if (csa)
  memcpy(new->cntdwn_counter_offsets, csa->counter_offsets_presp,
         csa->n_counter_offsets_presp *
         sizeof(new->cntdwn_counter_offsets[0]));
 else if (cca)
  new->cntdwn_counter_offsets[0] = cca->counter_offset_presp;

 rcu_assign_pointer(link->u.ap.probe_resp, new);
 if (old)
  kfree_rcu(old, rcu_head);

 return 0;
}

static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata,
     struct cfg80211_fils_discovery *params,
     struct ieee80211_link_data *link,
     struct ieee80211_bss_conf *link_conf,
     u64 *changed)
{
 struct fils_discovery_data *new, *old = NULL;
 struct ieee80211_fils_discovery *fd;

 if (!params->update)
  return 0;

 fd = &link_conf->fils_discovery;
 fd->min_interval = params->min_interval;
 fd->max_interval = params->max_interval;

 old = sdata_dereference(link->u.ap.fils_discovery, sdata);
 if (old)
  kfree_rcu(old, rcu_head);

 if (params->tmpl && params->tmpl_len) {
  new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL);
  if (!new)
   return -ENOMEM;
  new->len = params->tmpl_len;
  memcpy(new->data, params->tmpl, params->tmpl_len);
  rcu_assign_pointer(link->u.ap.fils_discovery, new);
 } else {
  RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL);
 }

 *changed |= BSS_CHANGED_FILS_DISCOVERY;
 return 0;
}

static int
ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata,
         struct cfg80211_unsol_bcast_probe_resp *params,
         struct ieee80211_link_data *link,
         struct ieee80211_bss_conf *link_conf,
         u64 *changed)
{
 struct unsol_bcast_probe_resp_data *new, *old = NULL;

 if (!params->update)
  return 0;

 link_conf->unsol_bcast_probe_resp_interval = params->interval;

 old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata);
 if (old)
  kfree_rcu(old, rcu_head);

 if (params->tmpl && params->tmpl_len) {
  new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL);
  if (!new)
   return -ENOMEM;
  new->len = params->tmpl_len;
  memcpy(new->data, params->tmpl, params->tmpl_len);
  rcu_assign_pointer(link->u.ap.unsol_bcast_probe_resp, new);
 } else {
  RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL);
 }

 *changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP;
 return 0;
}

static int
ieee80211_set_s1g_short_beacon(struct ieee80211_sub_if_data *sdata,
          struct ieee80211_link_data *link,
          struct cfg80211_s1g_short_beacon *params)
{
 struct s1g_short_beacon_data *new;
 struct s1g_short_beacon_data *old =
  sdata_dereference(link->u.ap.s1g_short_beacon, sdata);
 size_t new_len =
  sizeof(*new) + params->short_head_len + params->short_tail_len;

 if (!params->update)
  return 0;

 if (!params->short_head)
  return -EINVAL;

 new = kzalloc(new_len, GFP_KERNEL);
 if (!new)
  return -ENOMEM;

 /* Memory layout: | struct | head | tail | */
 new->short_head = (u8 *)new + sizeof(*new);
 new->short_head_len = params->short_head_len;
 memcpy(new->short_head, params->short_head, params->short_head_len);

 if (params->short_tail) {
  new->short_tail = new->short_head + params->short_head_len;
  new->short_tail_len = params->short_tail_len;
  memcpy(new->short_tail, params->short_tail,
         params->short_tail_len);
 }

 rcu_assign_pointer(link->u.ap.s1g_short_beacon, new);

 if (old)
  kfree_rcu(old, rcu_head);

 return 0;
}

static int ieee80211_set_ftm_responder_params(
    struct ieee80211_sub_if_data *sdata,
    const u8 *lci, size_t lci_len,
    const u8 *civicloc, size_t civicloc_len,
    struct ieee80211_bss_conf *link_conf)
{
 struct ieee80211_ftm_responder_params *new, *old;
 u8 *pos;
 int len;

 if (!lci_len && !civicloc_len)
  return 0;

 old = link_conf->ftmr_params;
 len = lci_len + civicloc_len;

 new = kzalloc(sizeof(*new) + len, GFP_KERNEL);
 if (!new)
  return -ENOMEM;

 pos = (u8 *)(new + 1);
 if (lci_len) {
  new->lci_len = lci_len;
  new->lci = pos;
  memcpy(pos, lci, lci_len);
  pos += lci_len;
 }

 if (civicloc_len) {
  new->civicloc_len = civicloc_len;
  new->civicloc = pos;
  memcpy(pos, civicloc, civicloc_len);
  pos += civicloc_len;
 }

 link_conf->ftmr_params = new;
 kfree(old);

 return 0;
}

static int
ieee80211_copy_mbssid_beacon(u8 *pos, struct cfg80211_mbssid_elems *dst,
        struct cfg80211_mbssid_elems *src)
{
 int i, offset = 0;

 dst->cnt = src->cnt;
 for (i = 0; i < src->cnt; i++) {
  memcpy(pos + offset, src->elem[i].data, src->elem[i].len);
  dst->elem[i].len = src->elem[i].len;
  dst->elem[i].data = pos + offset;
  offset += dst->elem[i].len;
 }

 return offset;
}

static int
ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst,
     struct cfg80211_rnr_elems *src)
{
 int i, offset = 0;

 dst->cnt = src->cnt;
 for (i = 0; i < src->cnt; i++) {
  memcpy(pos + offset, src->elem[i].data, src->elem[i].len);
  dst->elem[i].len = src->elem[i].len;
  dst->elem[i].data = pos + offset;
  offset += dst->elem[i].len;
 }

 return offset;
}

static int
ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
   struct ieee80211_link_data *link,
   struct cfg80211_beacon_data *params,
   const struct ieee80211_csa_settings *csa,
   const struct ieee80211_color_change_settings *cca,
   u64 *changed)
{
 struct cfg80211_mbssid_elems *mbssid = NULL;
 struct cfg80211_rnr_elems *rnr = NULL;
 struct beacon_data *new, *old;
 int new_head_len, new_tail_len;
 int size, err;
 u64 _changed = BSS_CHANGED_BEACON;
 struct ieee80211_bss_conf *link_conf = link->conf;

 old = sdata_dereference(link->u.ap.beacon, sdata);

 /* Need to have a beacon head if we don't have one yet */
 if (!params->head && !old)
  return -EINVAL;

 /* new or old head? */
 if (params->head)
  new_head_len = params->head_len;
 else
  new_head_len = old->head_len;

 /* new or old tail? */
 if (params->tail || !old)
  /* params->tail_len will be zero for !params->tail */
  new_tail_len = params->tail_len;
 else
  new_tail_len = old->tail_len;

 size = sizeof(*new) + new_head_len + new_tail_len;

 /* new or old multiple BSSID elements? */
 if (params->mbssid_ies) {
  mbssid = params->mbssid_ies;
  size += struct_size(new->mbssid_ies, elem, mbssid->cnt);
  if (params->rnr_ies) {
   rnr = params->rnr_ies;
   size += struct_size(new->rnr_ies, elem, rnr->cnt);
  }
  size += ieee80211_get_mbssid_beacon_len(mbssid, rnr,
       mbssid->cnt);
 } else if (old && old->mbssid_ies) {
  mbssid = old->mbssid_ies;
  size += struct_size(new->mbssid_ies, elem, mbssid->cnt);
  if (old && old->rnr_ies) {
   rnr = old->rnr_ies;
   size += struct_size(new->rnr_ies, elem, rnr->cnt);
  }
  size += ieee80211_get_mbssid_beacon_len(mbssid, rnr,
       mbssid->cnt);
 }

 new = kzalloc(size, GFP_KERNEL);
 if (!new)
  return -ENOMEM;

 /* start filling the new info now */

 /*
 * pointers go into the block we allocated,
 * memory is | beacon_data | head | tail | mbssid_ies | rnr_ies
 */

 new->head = ((u8 *) new) + sizeof(*new);
 new->tail = new->head + new_head_len;
 new->head_len = new_head_len;
 new->tail_len = new_tail_len;
 /* copy in optional mbssid_ies */
 if (mbssid) {
  u8 *pos = new->tail + new->tail_len;

  new->mbssid_ies = (void *)pos;
  pos += struct_size(new->mbssid_ies, elem, mbssid->cnt);
  pos += ieee80211_copy_mbssid_beacon(pos, new->mbssid_ies,
          mbssid);
  if (rnr) {
   new->rnr_ies = (void *)pos;
   pos += struct_size(new->rnr_ies, elem, rnr->cnt);
   ieee80211_copy_rnr_beacon(pos, new->rnr_ies, rnr);
  }
  /* update bssid_indicator */
  if (new->mbssid_ies->cnt && new->mbssid_ies->elem[0].len > 2)
   link_conf->bssid_indicator =
     *(new->mbssid_ies->elem[0].data + 2);
  else
   link_conf->bssid_indicator = 0;
 }

 if (csa) {
  new->cntdwn_current_counter = csa->count;
  memcpy(new->cntdwn_counter_offsets, csa->counter_offsets_beacon,
         csa->n_counter_offsets_beacon *
         sizeof(new->cntdwn_counter_offsets[0]));
 } else if (cca) {
  new->cntdwn_current_counter = cca->count;
  new->cntdwn_counter_offsets[0] = cca->counter_offset_beacon;
 }

 /* copy in head */
 if (params->head)
  memcpy(new->head, params->head, new_head_len);
 else
  memcpy(new->head, old->head, new_head_len);

 /* copy in optional tail */
 if (params->tail)
  memcpy(new->tail, params->tail, new_tail_len);
 else
  if (old)
   memcpy(new->tail, old->tail, new_tail_len);

 err = ieee80211_set_probe_resp(sdata, params->probe_resp,
           params->probe_resp_len, csa, cca, link);
 if (err < 0) {
  kfree(new);
  return err;
 }
 if (err == 0)
  _changed |= BSS_CHANGED_AP_PROBE_RESP;

 if (params->ftm_responder != -1) {
  link_conf->ftm_responder = params->ftm_responder;
  err = ieee80211_set_ftm_responder_params(sdata,
        params->lci,
        params->lci_len,
        params->civicloc,
        params->civicloc_len,
        link_conf);

  if (err < 0) {
   kfree(new);
   return err;
  }

  _changed |= BSS_CHANGED_FTM_RESPONDER;
 }

 rcu_assign_pointer(link->u.ap.beacon, new);
 sdata->u.ap.active = true;

 if (old)
  kfree_rcu(old, rcu_head);

 *changed |= _changed;
 return 0;
}

static u8 ieee80211_num_beaconing_links(struct ieee80211_sub_if_data *sdata)
{
 struct ieee80211_link_data *link;
 u8 link_id, num = 0;

 if (sdata->vif.type != NL80211_IFTYPE_AP &&
     sdata->vif.type != NL80211_IFTYPE_P2P_GO)
  return num;

 /* non-MLO mode of operation also uses link_id 0 in sdata so it is
 * safe to directly proceed with the below loop
 */

 for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
  link = sdata_dereference(sdata->link[link_id], sdata);
  if (!link)
   continue;

  if (sdata_dereference(link->u.ap.beacon, sdata))
   num++;
 }

 return num;
}

static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
         struct cfg80211_ap_settings *params)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = sdata->local;
 struct beacon_data *old;
 struct ieee80211_sub_if_data *vlan;
 u64 changed = BSS_CHANGED_BEACON_INT |
        BSS_CHANGED_BEACON_ENABLED |
        BSS_CHANGED_BEACON |
        BSS_CHANGED_P2P_PS |
        BSS_CHANGED_TXPOWER |
        BSS_CHANGED_TWT;
 int i, err;
 int prev_beacon_int;
 unsigned int link_id = params->beacon.link_id;
 struct ieee80211_link_data *link;
 struct ieee80211_bss_conf *link_conf;
 struct ieee80211_chan_req chanreq = { .oper = params->chandef };
 u64 tsf;

 lockdep_assert_wiphy(local->hw.wiphy);

 link = sdata_dereference(sdata->link[link_id], sdata);
 if (!link)
  return -ENOLINK;

 link_conf = link->conf;

 old = sdata_dereference(link->u.ap.beacon, sdata);
 if (old)
  return -EALREADY;

 link->smps_mode = IEEE80211_SMPS_OFF;

 link->needed_rx_chains = sdata->local->rx_chains;

 prev_beacon_int = link_conf->beacon_int;
 link_conf->beacon_int = params->beacon_interval;

 if (params->ht_cap)
  link_conf->ht_ldpc =
   params->ht_cap->cap_info &
    cpu_to_le16(IEEE80211_HT_CAP_LDPC_CODING);

 if (params->vht_cap) {
  link_conf->vht_ldpc =
   params->vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_RXLDPC);
  link_conf->vht_su_beamformer =
   params->vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE);
  link_conf->vht_su_beamformee =
   params->vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE);
  link_conf->vht_mu_beamformer =
   params->vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE);
  link_conf->vht_mu_beamformee =
   params->vht_cap->vht_cap_info &
    cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
 }

 if (params->he_cap && params->he_oper) {
  link_conf->he_support = true;
  link_conf->htc_trig_based_pkt_ext =
   le32_get_bits(params->he_oper->he_oper_params,
         IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK);
  link_conf->frame_time_rts_th =
   le32_get_bits(params->he_oper->he_oper_params,
         IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK);
  changed |= BSS_CHANGED_HE_OBSS_PD;

  if (params->beacon.he_bss_color.enabled)
   changed |= BSS_CHANGED_HE_BSS_COLOR;
 }

 if (params->he_cap) {
  link_conf->he_ldpc =
   params->he_cap->phy_cap_info[1] &
    IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
  link_conf->he_su_beamformer =
   params->he_cap->phy_cap_info[3] &
    IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER;
  link_conf->he_su_beamformee =
   params->he_cap->phy_cap_info[4] &
    IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE;
  link_conf->he_mu_beamformer =
   params->he_cap->phy_cap_info[4] &
    IEEE80211_HE_PHY_CAP4_MU_BEAMFORMER;
  link_conf->he_full_ul_mumimo =
   params->he_cap->phy_cap_info[2] &
    IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO;
 }

 if (params->eht_cap) {
  if (!link_conf->he_support)
   return -EOPNOTSUPP;

  link_conf->eht_support = true;

  link_conf->eht_su_beamformer =
   params->eht_cap->fixed.phy_cap_info[0] &
    IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER;
  link_conf->eht_su_beamformee =
   params->eht_cap->fixed.phy_cap_info[0] &
    IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE;
  link_conf->eht_mu_beamformer =
   params->eht_cap->fixed.phy_cap_info[7] &
    (IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_80MHZ |
     IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_160MHZ |
     IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ);
  link_conf->eht_80mhz_full_bw_ul_mumimo =
   params->eht_cap->fixed.phy_cap_info[7] &
    (IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_80MHZ |
     IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_160MHZ |
     IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ);
  link_conf->eht_disable_mcs15 =
   u8_get_bits(params->eht_oper->params,
        IEEE80211_EHT_OPER_MCS15_DISABLE);
 } else {
  link_conf->eht_su_beamformer = false;
  link_conf->eht_su_beamformee = false;
  link_conf->eht_mu_beamformer = false;
 }

 if (sdata->vif.type == NL80211_IFTYPE_AP &&
     params->mbssid_config.tx_wdev) {
  err = ieee80211_set_ap_mbssid_options(sdata,
            ¶ms->mbssid_config,
            link_conf);
  if (err)
   return err;
 }

 err = ieee80211_link_use_channel(link, &chanreq,
      IEEE80211_CHANCTX_SHARED);
 if (!err)
  ieee80211_link_copy_chanctx_to_vlans(link, false);
 if (err) {
  link_conf->beacon_int = prev_beacon_int;
  return err;
 }

 /*
 * Apply control port protocol, this allows us to
 * not encrypt dynamic WEP control frames.
 */

 sdata->control_port_protocol = params->crypto.control_port_ethertype;
 sdata->control_port_no_encrypt = params->crypto.control_port_no_encrypt;
 sdata->control_port_over_nl80211 =
    params->crypto.control_port_over_nl80211;
 sdata->control_port_no_preauth =
    params->crypto.control_port_no_preauth;

 list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) {
  vlan->control_port_protocol =
   params->crypto.control_port_ethertype;
  vlan->control_port_no_encrypt =
   params->crypto.control_port_no_encrypt;
  vlan->control_port_over_nl80211 =
   params->crypto.control_port_over_nl80211;
  vlan->control_port_no_preauth =
   params->crypto.control_port_no_preauth;
 }

 link_conf->dtim_period = params->dtim_period;
 link_conf->enable_beacon = true;
 link_conf->allow_p2p_go_ps = sdata->vif.p2p;
 link_conf->twt_responder = params->twt_responder;
 link_conf->he_obss_pd = params->he_obss_pd;
 link_conf->he_bss_color = params->beacon.he_bss_color;
 link_conf->s1g_long_beacon_period = params->s1g_long_beacon_period;
 sdata->vif.cfg.s1g = params->chandef.chan->band == NL80211_BAND_S1GHZ;

 sdata->vif.cfg.ssid_len = params->ssid_len;
 if (params->ssid_len)
  memcpy(sdata->vif.cfg.ssid, params->ssid,
         params->ssid_len);
 link_conf->hidden_ssid =
  (params->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE);

 memset(&link_conf->p2p_noa_attr, 0,
        sizeof(link_conf->p2p_noa_attr));
 link_conf->p2p_noa_attr.oppps_ctwindow =
  params->p2p_ctwindow & IEEE80211_P2P_OPPPS_CTWINDOW_MASK;
 if (params->p2p_opp_ps)
  link_conf->p2p_noa_attr.oppps_ctwindow |=
     IEEE80211_P2P_OPPPS_ENABLE_BIT;

 sdata->beacon_rate_set = false;
 if (wiphy_ext_feature_isset(local->hw.wiphy,
        NL80211_EXT_FEATURE_BEACON_RATE_LEGACY)) {
  for (i = 0; i < NUM_NL80211_BANDS; i++) {
   sdata->beacon_rateidx_mask[i] =
    params->beacon_rate.control[i].legacy;
   if (sdata->beacon_rateidx_mask[i])
    sdata->beacon_rate_set = true;
  }
 }

 if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL))
  link_conf->beacon_tx_rate = params->beacon_rate;

 err = ieee80211_assign_beacon(sdata, link, ¶ms->beacon, NULL, NULL,
          &changed);
 if (err < 0)
  goto error;

 err = ieee80211_set_fils_discovery(sdata, ¶ms->fils_discovery,
        link, link_conf, &changed);
 if (err < 0)
  goto error;

 err = ieee80211_set_unsol_bcast_probe_resp(sdata,
         ¶ms->unsol_bcast_probe_resp,
         link, link_conf, &changed);
 if (err < 0)
  goto error;

 if (sdata->vif.cfg.s1g) {
  err = ieee80211_set_s1g_short_beacon(sdata, link,
           ¶ms->s1g_short_beacon);
  if (err < 0)
   goto error;
 }

 err = drv_start_ap(sdata->local, sdata, link_conf);
 if (err) {
  old = sdata_dereference(link->u.ap.beacon, sdata);

  if (old)
   kfree_rcu(old, rcu_head);
  RCU_INIT_POINTER(link->u.ap.beacon, NULL);

  if (ieee80211_num_beaconing_links(sdata) == 0)
   sdata->u.ap.active = false;

  goto error;
 }

 tsf = drv_get_tsf(local, sdata);
 ieee80211_recalc_dtim(sdata, tsf);

 if (link->u.ap.s1g_short_beacon)
  ieee80211_recalc_sb_count(sdata, tsf);

 ieee80211_vif_cfg_change_notify(sdata, BSS_CHANGED_SSID);
 ieee80211_link_info_change_notify(sdata, link, changed);

 if (ieee80211_num_beaconing_links(sdata) <= 1)
  netif_carrier_on(dev);

 list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
  netif_carrier_on(vlan->dev);

 return 0;

error:
 ieee80211_link_release_channel(link);

 return err;
}

static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
       struct cfg80211_ap_update *params)

{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_link_data *link;
 struct cfg80211_beacon_data *beacon = ¶ms->beacon;
 struct beacon_data *old;
 int err;
 struct ieee80211_bss_conf *link_conf;
 u64 changed = 0;

 lockdep_assert_wiphy(wiphy);

 link = sdata_dereference(sdata->link[beacon->link_id], sdata);
 if (!link)
  return -ENOLINK;

 link_conf = link->conf;

 /* don't allow changing the beacon while a countdown is in place - offset
 * of channel switch counter may change
 */

 if (link_conf->csa_active || link_conf->color_change_active)
  return -EBUSY;

 old = sdata_dereference(link->u.ap.beacon, sdata);
 if (!old)
  return -ENOENT;

 err = ieee80211_assign_beacon(sdata, link, beacon, NULL, NULL,
          &changed);
 if (err < 0)
  return err;

 err = ieee80211_set_fils_discovery(sdata, ¶ms->fils_discovery,
        link, link_conf, &changed);
 if (err < 0)
  return err;

 err = ieee80211_set_unsol_bcast_probe_resp(sdata,
         ¶ms->unsol_bcast_probe_resp,
         link, link_conf, &changed);
 if (err < 0)
  return err;

 if (link->u.ap.s1g_short_beacon) {
  err = ieee80211_set_s1g_short_beacon(sdata, link,
           ¶ms->s1g_short_beacon);
  if (err < 0)
   return err;
 }

 if (beacon->he_bss_color_valid &&
     beacon->he_bss_color.enabled != link_conf->he_bss_color.enabled) {
  link_conf->he_bss_color.enabled = beacon->he_bss_color.enabled;
  changed |= BSS_CHANGED_HE_BSS_COLOR;
 }

 ieee80211_link_info_change_notify(sdata, link, changed);
 return 0;
}

static void ieee80211_free_next_beacon(struct ieee80211_link_data *link)
{
 if (!link->u.ap.next_beacon)
  return;

 kfree(link->u.ap.next_beacon->mbssid_ies);
 kfree(link->u.ap.next_beacon->rnr_ies);
 kfree(link->u.ap.next_beacon);
 link->u.ap.next_beacon = NULL;
}

static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev,
        unsigned int link_id)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_sub_if_data *vlan;
 struct ieee80211_local *local = sdata->local;
 struct beacon_data *old_beacon;
 struct probe_resp *old_probe_resp;
 struct fils_discovery_data *old_fils_discovery;
 struct unsol_bcast_probe_resp_data *old_unsol_bcast_probe_resp;
 struct s1g_short_beacon_data *old_s1g_short_beacon;
 struct cfg80211_chan_def chandef;
 struct ieee80211_link_data *link =
  sdata_dereference(sdata->link[link_id], sdata);
 struct ieee80211_bss_conf *link_conf = link->conf;
 LIST_HEAD(keys);

 lockdep_assert_wiphy(local->hw.wiphy);

 old_beacon = sdata_dereference(link->u.ap.beacon, sdata);
 if (!old_beacon)
  return -ENOENT;
 old_probe_resp = sdata_dereference(link->u.ap.probe_resp,
        sdata);
 old_fils_discovery = sdata_dereference(link->u.ap.fils_discovery,
            sdata);
 old_unsol_bcast_probe_resp =
  sdata_dereference(link->u.ap.unsol_bcast_probe_resp,
      sdata);
 old_s1g_short_beacon =
  sdata_dereference(link->u.ap.s1g_short_beacon, sdata);

 /* abort any running channel switch or color change */
 link_conf->csa_active = false;
 link_conf->color_change_active = false;
 ieee80211_vif_unblock_queues_csa(sdata);

 ieee80211_free_next_beacon(link);

 /* turn off carrier for this interface and dependent VLANs */
 list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
  netif_carrier_off(vlan->dev);

 if (ieee80211_num_beaconing_links(sdata) <= 1) {
  netif_carrier_off(dev);
  sdata->u.ap.active = false;
 }

 /* remove beacon and probe response */
 RCU_INIT_POINTER(link->u.ap.beacon, NULL);
 RCU_INIT_POINTER(link->u.ap.probe_resp, NULL);
 RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL);
 RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL);
 RCU_INIT_POINTER(link->u.ap.s1g_short_beacon, NULL);
 kfree_rcu(old_beacon, rcu_head);
 if (old_probe_resp)
  kfree_rcu(old_probe_resp, rcu_head);
 if (old_fils_discovery)
  kfree_rcu(old_fils_discovery, rcu_head);
 if (old_unsol_bcast_probe_resp)
  kfree_rcu(old_unsol_bcast_probe_resp, rcu_head);
 if (old_s1g_short_beacon)
  kfree_rcu(old_s1g_short_beacon, rcu_head);

 kfree(link_conf->ftmr_params);
 link_conf->ftmr_params = NULL;

 link_conf->bssid_index = 0;
 link_conf->nontransmitted = false;
 link_conf->ema_ap = false;
 link_conf->bssid_indicator = 0;
 link_conf->fils_discovery.min_interval = 0;
 link_conf->fils_discovery.max_interval = 0;
 link_conf->unsol_bcast_probe_resp_interval = 0;

 __sta_info_flush(sdata, true, link_id, NULL);

 ieee80211_remove_link_keys(link, &keys);
 if (!list_empty(&keys)) {
  synchronize_net();
  ieee80211_free_key_list(local, &keys);
 }

 ieee80211_stop_mbssid(sdata);
 RCU_INIT_POINTER(link_conf->tx_bss_conf, NULL);

 link_conf->enable_beacon = false;
 sdata->beacon_rate_set = false;
 sdata->vif.cfg.ssid_len = 0;
 sdata->vif.cfg.s1g = false;
 clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, &sdata->state);
 ieee80211_link_info_change_notify(sdata, link,
       BSS_CHANGED_BEACON_ENABLED);

 if (sdata->wdev.links[link_id].cac_started) {
  chandef = link_conf->chanreq.oper;
  wiphy_delayed_work_cancel(wiphy, &link->dfs_cac_timer_work);
  cfg80211_cac_event(sdata->dev, &chandef,
       NL80211_RADAR_CAC_ABORTED,
       GFP_KERNEL, link_id);
 }

 drv_stop_ap(sdata->local, sdata, link_conf);

 /* free all potentially still buffered bcast frames */
 local->total_ps_buffered -= skb_queue_len(&sdata->u.ap.ps.bc_buf);
 ieee80211_purge_tx_queue(&local->hw, &sdata->u.ap.ps.bc_buf);

 ieee80211_link_copy_chanctx_to_vlans(link, true);
 ieee80211_link_release_channel(link);

 return 0;
}

static int sta_apply_auth_flags(struct ieee80211_local *local,
    struct sta_info *sta,
    u32 mask, u32 set)
{
 int ret;

 if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
     set & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
     !test_sta_flag(sta, WLAN_STA_AUTH)) {
  ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
  if (ret)
   return ret;
 }

 if (mask & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
     set & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
     !test_sta_flag(sta, WLAN_STA_ASSOC)) {
  /*
 * When peer becomes associated, init rate control as
 * well. Some drivers require rate control initialized
 * before drv_sta_state() is called.
 */

  if (!test_sta_flag(sta, WLAN_STA_RATE_CONTROL))
   rate_control_rate_init_all_links(sta);

  ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
  if (ret)
   return ret;
 }

 if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
  if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
   ret = sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
  else if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
   ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
  else
   ret = 0;
  if (ret)
   return ret;
 }

 if (mask & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
     !(set & BIT(NL80211_STA_FLAG_ASSOCIATED)) &&
     test_sta_flag(sta, WLAN_STA_ASSOC)) {
  ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
  if (ret)
   return ret;
 }

 if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
     !(set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) &&
     test_sta_flag(sta, WLAN_STA_AUTH)) {
  ret = sta_info_move_state(sta, IEEE80211_STA_NONE);
  if (ret)
   return ret;
 }

 return 0;
}

static void sta_apply_mesh_params(struct ieee80211_local *local,
      struct sta_info *sta,
      struct station_parameters *params)
{
#ifdef CONFIG_MAC80211_MESH
 struct ieee80211_sub_if_data *sdata = sta->sdata;
 u64 changed = 0;

 if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) {
  switch (params->plink_state) {
  case NL80211_PLINK_ESTAB:
   if (sta->mesh->plink_state != NL80211_PLINK_ESTAB)
    changed = mesh_plink_inc_estab_count(sdata);
   sta->mesh->plink_state = params->plink_state;
   sta->mesh->aid = params->peer_aid;

   ieee80211_mps_sta_status_update(sta);
   changed |= ieee80211_mps_set_sta_local_pm(sta,
          sdata->u.mesh.mshcfg.power_mode);

   ewma_mesh_tx_rate_avg_init(&sta->mesh->tx_rate_avg);
   /* init at low value */
   ewma_mesh_tx_rate_avg_add(&sta->mesh->tx_rate_avg, 10);

   break;
  case NL80211_PLINK_LISTEN:
  case NL80211_PLINK_BLOCKED:
  case NL80211_PLINK_OPN_SNT:
  case NL80211_PLINK_OPN_RCVD:
  case NL80211_PLINK_CNF_RCVD:
  case NL80211_PLINK_HOLDING:
   if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
    changed = mesh_plink_dec_estab_count(sdata);
   sta->mesh->plink_state = params->plink_state;

   ieee80211_mps_sta_status_update(sta);
   changed |= ieee80211_mps_set_sta_local_pm(sta,
     NL80211_MESH_POWER_UNKNOWN);
   break;
  default:
   /*  nothing  */
   break;
  }
 }

 switch (params->plink_action) {
 case NL80211_PLINK_ACTION_NO_ACTION:
  /* nothing */
  break;
 case NL80211_PLINK_ACTION_OPEN:
  changed |= mesh_plink_open(sta);
  break;
 case NL80211_PLINK_ACTION_BLOCK:
  changed |= mesh_plink_block(sta);
  break;
 }

 if (params->local_pm)
  changed |= ieee80211_mps_set_sta_local_pm(sta,
         params->local_pm);

 ieee80211_mbss_info_change_notify(sdata, changed);
#endif
}

enum sta_link_apply_mode {
 STA_LINK_MODE_NEW,
 STA_LINK_MODE_STA_MODIFY,
 STA_LINK_MODE_LINK_MODIFY,
};

static int sta_link_apply_parameters(struct ieee80211_local *local,
         struct sta_info *sta,
         enum sta_link_apply_mode mode,
         struct link_station_parameters *params)
{
 struct ieee80211_supported_band *sband;
 struct ieee80211_sub_if_data *sdata = sta->sdata;
 u32 link_id = params->link_id < 0 ? 0 : params->link_id;
 struct ieee80211_link_data *link =
  sdata_dereference(sdata->link[link_id], sdata);
 struct link_sta_info *link_sta =
  rcu_dereference_protected(sta->link[link_id],
       lockdep_is_held(&local->hw.wiphy->mtx));
 bool changes = params->link_mac ||
         params->txpwr_set ||
         params->supported_rates_len ||
         params->ht_capa ||
         params->vht_capa ||
         params->he_capa ||
         params->eht_capa ||
         params->s1g_capa ||
         params->opmode_notif_used;

 switch (mode) {
 case STA_LINK_MODE_NEW:
  if (!params->link_mac)
   return -EINVAL;
  break;
 case STA_LINK_MODE_LINK_MODIFY:
  break;
 case STA_LINK_MODE_STA_MODIFY:
  if (params->link_id >= 0)
   break;
  if (!changes)
   return 0;
  break;
 }

 if (!link || !link_sta)
  return -EINVAL;

 sband = ieee80211_get_link_sband(link);
 if (!sband)
  return -EINVAL;

 if (params->link_mac) {
  if (mode == STA_LINK_MODE_NEW) {
   memcpy(link_sta->addr, params->link_mac, ETH_ALEN);
   memcpy(link_sta->pub->addr, params->link_mac, ETH_ALEN);
  } else if (!ether_addr_equal(link_sta->addr,
          params->link_mac)) {
   return -EINVAL;
  }
 }

 if (params->txpwr_set) {
  int ret;

  link_sta->pub->txpwr.type = params->txpwr.type;
  if (params->txpwr.type == NL80211_TX_POWER_LIMITED)
   link_sta->pub->txpwr.power = params->txpwr.power;
  ret = drv_sta_set_txpwr(local, sdata, sta);
  if (ret)
   return ret;
 }

 if (params->supported_rates &&
     params->supported_rates_len &&
     !ieee80211_parse_bitrates(link->conf->chanreq.oper.width,
          sband, params->supported_rates,
          params->supported_rates_len,
          &link_sta->pub->supp_rates[sband->band]))
  return -EINVAL;

 if (params->ht_capa)
  ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
        params->ht_capa, link_sta);

 /* VHT can override some HT caps such as the A-MSDU max length */
 if (params->vht_capa)
  ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
          params->vht_capa, NULL,
          link_sta);

 if (params->he_capa)
  ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
        (void *)params->he_capa,
        params->he_capa_len,
        (void *)params->he_6ghz_capa,
        link_sta);

 if (params->he_capa && params->eht_capa)
  ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
          (u8 *)params->he_capa,
          params->he_capa_len,
          params->eht_capa,
          params->eht_capa_len,
          link_sta);

 if (params->s1g_capa)
  ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
       link_sta);

 ieee80211_sta_init_nss(link_sta);

 if (params->opmode_notif_used) {
  enum nl80211_chan_width width = link->conf->chanreq.oper.width;

  switch (width) {
  case NL80211_CHAN_WIDTH_20:
  case NL80211_CHAN_WIDTH_40:
  case NL80211_CHAN_WIDTH_80:
  case NL80211_CHAN_WIDTH_160:
  case NL80211_CHAN_WIDTH_80P80:
  case NL80211_CHAN_WIDTH_320: /* not VHT, allowed for HE/EHT */
   break;
  default:
   return -EINVAL;
  }

  /* returned value is only needed for rc update, but the
 * rc isn't initialized here yet, so ignore it
 */

  __ieee80211_vht_handle_opmode(sdata, link_sta,
           params->opmode_notif,
           sband->band);
 }

 return 0;
}

static int sta_apply_parameters(struct ieee80211_local *local,
    struct sta_info *sta,
    struct station_parameters *params)
{
 struct ieee80211_sub_if_data *sdata = sta->sdata;
 u32 mask, set;
 int ret = 0;

 mask = params->sta_flags_mask;
 set = params->sta_flags_set;

 if (ieee80211_vif_is_mesh(&sdata->vif)) {
  /*
 * In mesh mode, ASSOCIATED isn't part of the nl80211
 * API but must follow AUTHENTICATED for driver state.
 */

  if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED))
   mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
  if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
   set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
 } else if (test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
  /*
 * TDLS -- everything follows authorized, but
 * only becoming authorized is possible, not
 * going back
 */

  if (set & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
   set |= BIT(NL80211_STA_FLAG_AUTHENTICATED) |
          BIT(NL80211_STA_FLAG_ASSOCIATED);
   mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED) |
    BIT(NL80211_STA_FLAG_ASSOCIATED);
  }
 }

 if (mask & BIT(NL80211_STA_FLAG_WME) &&
     local->hw.queues >= IEEE80211_NUM_ACS)
  sta->sta.wme = set & BIT(NL80211_STA_FLAG_WME);

 /* auth flags will be set later for TDLS,
 * and for unassociated stations that move to associated */

 if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
     !((mask & BIT(NL80211_STA_FLAG_ASSOCIATED)) &&
       (set & BIT(NL80211_STA_FLAG_ASSOCIATED)))) {
  ret = sta_apply_auth_flags(local, sta, mask, set);
  if (ret)
   return ret;
 }

 if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
  if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
   set_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
  else
   clear_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
 }

 if (mask & BIT(NL80211_STA_FLAG_MFP)) {
  sta->sta.mfp = !!(set & BIT(NL80211_STA_FLAG_MFP));
  if (set & BIT(NL80211_STA_FLAG_MFP))
   set_sta_flag(sta, WLAN_STA_MFP);
  else
   clear_sta_flag(sta, WLAN_STA_MFP);
 }

 if (mask & BIT(NL80211_STA_FLAG_TDLS_PEER)) {
  if (set & BIT(NL80211_STA_FLAG_TDLS_PEER))
   set_sta_flag(sta, WLAN_STA_TDLS_PEER);
  else
   clear_sta_flag(sta, WLAN_STA_TDLS_PEER);
 }

 if (mask & BIT(NL80211_STA_FLAG_SPP_AMSDU))
  sta->sta.spp_amsdu = set & BIT(NL80211_STA_FLAG_SPP_AMSDU);

 /* mark TDLS channel switch support, if the AP allows it */
 if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
     !sdata->deflink.u.mgd.tdls_chan_switch_prohibited &&
     params->ext_capab_len >= 4 &&
     params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH)
  set_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH);

 if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
     !sdata->u.mgd.tdls_wider_bw_prohibited &&
     ieee80211_hw_check(&local->hw, TDLS_WIDER_BW) &&
     params->ext_capab_len >= 8 &&
     params->ext_capab[7] & WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED)
  set_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW);

 if (params->sta_modify_mask & STATION_PARAM_APPLY_UAPSD) {
  sta->sta.uapsd_queues = params->uapsd_queues;
  sta->sta.max_sp = params->max_sp;
 }

 ieee80211_sta_set_max_amsdu_subframes(sta, params->ext_capab,
           params->ext_capab_len);

 /*
 * cfg80211 validates this (1-2007) and allows setting the AID
 * only when creating a new station entry
 */

 if (params->aid)
  sta->sta.aid = params->aid;

 /*
 * Some of the following updates would be racy if called on an
 * existing station, via ieee80211_change_station(). However,
 * all such changes are rejected by cfg80211 except for updates
 * changing the supported rates on an existing but not yet used
 * TDLS peer.
 */


 if (params->listen_interval >= 0)
  sta->listen_interval = params->listen_interval;

 if (params->eml_cap_present)
  sta->sta.eml_cap = params->eml_cap;

 ret = sta_link_apply_parameters(local, sta, STA_LINK_MODE_STA_MODIFY,
     ¶ms->link_sta_params);
 if (ret)
  return ret;

 if (params->support_p2p_ps >= 0)
  sta->sta.support_p2p_ps = params->support_p2p_ps;

 if (ieee80211_vif_is_mesh(&sdata->vif))
  sta_apply_mesh_params(local, sta, params);

 if (params->airtime_weight)
  sta->airtime_weight = params->airtime_weight;

 /* set the STA state after all sta info from usermode has been set */
 if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
     set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
  ret = sta_apply_auth_flags(local, sta, mask, set);
  if (ret)
   return ret;
 }

 /* Mark the STA as MLO if MLD MAC address is available */
 if (params->link_sta_params.mld_mac)
  sta->sta.mlo = true;

 return 0;
}

static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
     const u8 *mac,
     struct station_parameters *params)
{
 struct ieee80211_local *local = wiphy_priv(wiphy);
 struct sta_info *sta;
 struct ieee80211_sub_if_data *sdata;
 int err;

 lockdep_assert_wiphy(local->hw.wiphy);

 if (params->vlan) {
  sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);

  if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
      sdata->vif.type != NL80211_IFTYPE_AP)
   return -EINVAL;
 } else
  sdata = IEEE80211_DEV_TO_SUB_IF(dev);

 if (ether_addr_equal(mac, sdata->vif.addr))
  return -EINVAL;

 if (!is_valid_ether_addr(mac))
  return -EINVAL;

 if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER) &&
     sdata->vif.type == NL80211_IFTYPE_STATION &&
     !sdata->u.mgd.associated)
  return -EINVAL;

 /*
 * If we have a link ID, it can be a non-MLO station on an AP MLD,
 * but we need to have a link_mac in that case as well, so use the
 * STA's MAC address in that case.
 */

 if (params->link_sta_params.link_id >= 0)
  sta = sta_info_alloc_with_link(sdata, mac,
            params->link_sta_params.link_id,
            params->link_sta_params.link_mac ?: mac,
            GFP_KERNEL);
 else
  sta = sta_info_alloc(sdata, mac, GFP_KERNEL);

 if (!sta)
  return -ENOMEM;

 if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
  sta->sta.tdls = true;

 /* Though the mutex is not needed here (since the station is not
 * visible yet), sta_apply_parameters (and inner functions) require
 * the mutex due to other paths.
 */

 err = sta_apply_parameters(local, sta, params);
 if (err) {
  sta_info_free(local, sta);
  return err;
 }

 /*
 * for TDLS and for unassociated station, rate control should be
 * initialized only when rates are known and station is marked
 * authorized/associated
 */

 if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
     test_sta_flag(sta, WLAN_STA_ASSOC))
  rate_control_rate_init_all_links(sta);

 return sta_info_insert(sta);
}

static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
     struct station_del_parameters *params)
{
 struct ieee80211_sub_if_data *sdata;

 sdata = IEEE80211_DEV_TO_SUB_IF(dev);

 if (params->mac)
  return sta_info_destroy_addr_bss(sdata, params->mac);

 sta_info_flush(sdata, params->link_id);
 return 0;
}

static int ieee80211_change_station(struct wiphy *wiphy,
        struct net_device *dev, const u8 *mac,
        struct station_parameters *params)
{
 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 struct ieee80211_local *local = wiphy_priv(wiphy);
 struct sta_info *sta;
 struct ieee80211_sub_if_data *vlansdata;
 enum cfg80211_station_type statype;
 int err;

 lockdep_assert_wiphy(local->hw.wiphy);

 sta = sta_info_get_bss(sdata, mac);
 if (!sta)
  return -ENOENT;

 switch (sdata->vif.type) {
 case NL80211_IFTYPE_MESH_POINT:
  if (sdata->u.mesh.user_mpm)
   statype = CFG80211_STA_MESH_PEER_USER;
  else
   statype = CFG80211_STA_MESH_PEER_KERNEL;
  break;
 case NL80211_IFTYPE_ADHOC:
  statype = CFG80211_STA_IBSS;
  break;
 case NL80211_IFTYPE_STATION:
  if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
   statype = CFG80211_STA_AP_STA;
   break;
  }
  if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
   statype = CFG80211_STA_TDLS_PEER_ACTIVE;
  else
   statype = CFG80211_STA_TDLS_PEER_SETUP;
  break;
 case NL80211_IFTYPE_AP:
 case NL80211_IFTYPE_AP_VLAN:
  if (test_sta_flag(sta, WLAN_STA_ASSOC))
   statype = CFG80211_STA_AP_CLIENT;
  else
   statype = CFG80211_STA_AP_CLIENT_UNASSOC;
  break;
 default:
  return -EOPNOTSUPP;
 }

 err = cfg80211_check_station_change(wiphy, params, statype);
 if (err)
  return err;

 if (params->vlan && params->vlan != sta->sdata->dev) {
  vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);

  if (params->vlan->ieee80211_ptr->use_4addr) {
   if (vlansdata->u.vlan.sta)
    return -EBUSY;

   rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
   __ieee80211_check_fast_rx_iface(vlansdata);
   drv_sta_set_4addr(local, sta->sdata, &sta->sta, true);
  }

  if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
      sta->sdata->u.vlan.sta)
   RCU_INIT_POINTER(sta->sdata->u.vlan.sta, NULL);

  if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
   ieee80211_vif_dec_num_mcast(sta->sdata);

  sta->sdata = vlansdata;
  ieee80211_check_fast_rx(sta);
  ieee80211_check_fast_xmit(sta);

  if (test_sta_flag(sta, WLAN_STA_AUTHORIZED)) {
   ieee80211_vif_inc_num_mcast(sta->sdata);
   cfg80211_send_layer2_update(sta->sdata->dev,
          sta->sta.addr);
  }
 }

 err = sta_apply_parameters(local, sta, params);
 if (err)
  return err;

 if (sdata->vif.type == NL80211_IFTYPE_STATION &&
     params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
  ieee80211_recalc_ps(local);
--> --------------------

--> maximum size reached

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

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet)  ¤

*© 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