Merge tag 'ceph-for-5.20-rc1' of https://github.com/ceph/ceph-client
[linux-2.6-microblaze.git] / net / mac80211 / cfg.c
index 4ddf297..a4f6971 100644 (file)
@@ -5,7 +5,7 @@
  * 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-2021 Intel Corporation
+ * Copyright (C) 2018-2022 Intel Corporation
  */
 
 #include <linux/ieee80211.h>
@@ -39,7 +39,8 @@ static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata,
                memcpy(sdata->vif.bss_conf.mu_group.position,
                       params->vht_mumimo_groups + WLAN_MEMBERSHIP_LEN,
                       WLAN_USER_POSITION_LEN);
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_MU_GROUPS);
+               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);
@@ -53,7 +54,7 @@ static void ieee80211_set_mu_mimo_follow(struct ieee80211_sub_if_data *sdata,
                                params->vht_mumimo_follow_addr);
        }
 
-       sdata->vif.mu_mimo_owner = mu_mimo_groups || mu_mimo_follow;
+       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,
@@ -113,14 +114,15 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata,
 }
 
 static int ieee80211_set_ap_mbssid_options(struct ieee80211_sub_if_data *sdata,
-                                          struct cfg80211_mbssid_config params)
+                                          struct cfg80211_mbssid_config params,
+                                          struct ieee80211_bss_conf *link_conf)
 {
        struct ieee80211_sub_if_data *tx_sdata;
 
        sdata->vif.mbssid_tx_vif = NULL;
-       sdata->vif.bss_conf.bssid_index = 0;
-       sdata->vif.bss_conf.nontransmitted = false;
-       sdata->vif.bss_conf.ema_ap = false;
+       link_conf->bssid_index = 0;
+       link_conf->nontransmitted = false;
+       link_conf->ema_ap = false;
 
        if (sdata->vif.type != NL80211_IFTYPE_AP || !params.tx_wdev)
                return -EINVAL;
@@ -133,11 +135,11 @@ static int ieee80211_set_ap_mbssid_options(struct ieee80211_sub_if_data *sdata,
                sdata->vif.mbssid_tx_vif = &sdata->vif;
        } else {
                sdata->vif.mbssid_tx_vif = &tx_sdata->vif;
-               sdata->vif.bss_conf.nontransmitted = true;
-               sdata->vif.bss_conf.bssid_index = params.index;
+               link_conf->nontransmitted = true;
+               link_conf->bssid_index = params.index;
        }
        if (params.ema)
-               sdata->vif.bss_conf.ema_ap = true;
+               link_conf->ema_ap = true;
 
        return 0;
 }
@@ -205,7 +207,7 @@ static int ieee80211_change_iface(struct wiphy *wiphy,
                        return 0;
 
                mutex_lock(&local->sta_mtx);
-               sta = sta_info_get(sdata, ifmgd->bssid);
+               sta = sta_info_get(sdata, sdata->deflink.u.mgd.bssid);
                if (sta)
                        drv_sta_set_4addr(local, sdata, &sta->sta,
                                          params->use_4addr);
@@ -438,7 +440,6 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = sdata->local;
        struct sta_info *sta = NULL;
-       const struct ieee80211_cipher_scheme *cs = NULL;
        struct ieee80211_key *key;
        int err;
 
@@ -456,23 +457,12 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
                if (WARN_ON_ONCE(fips_enabled))
                        return -EINVAL;
                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:
-       case WLAN_CIPHER_SUITE_BIP_GMAC_128:
-       case WLAN_CIPHER_SUITE_BIP_GMAC_256:
-       case WLAN_CIPHER_SUITE_GCMP:
-       case WLAN_CIPHER_SUITE_GCMP_256:
-               break;
        default:
-               cs = ieee80211_cs_get(local, params->cipher, sdata->vif.type);
                break;
        }
 
        key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len,
-                                 params->key, params->seq_len, params->seq,
-                                 cs);
+                                 params->key, params->seq_len, params->seq);
        if (IS_ERR(key))
                return PTR_ERR(key);
 
@@ -537,9 +527,6 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
                break;
        }
 
-       if (sta)
-               sta->cipher_scheme = cs;
-
        err = ieee80211_key_link(key, sdata, sta);
 
  out_unlock:
@@ -548,33 +535,60 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
        return err;
 }
 
+static struct ieee80211_key *
+ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata,
+                    u8 key_idx, bool pairwise, const u8 *mac_addr)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_key *key;
+       struct sta_info *sta;
+
+       if (mac_addr) {
+               sta = sta_info_get_bss(sdata, mac_addr);
+               if (!sta)
+                       return NULL;
+
+               if (pairwise && key_idx < NUM_DEFAULT_KEYS)
+                       return rcu_dereference_check_key_mtx(local,
+                                                            sta->ptk[key_idx]);
+
+               if (!pairwise &&
+                   key_idx < NUM_DEFAULT_KEYS +
+                             NUM_DEFAULT_MGMT_KEYS +
+                             NUM_DEFAULT_BEACON_KEYS)
+                       return rcu_dereference_check_key_mtx(local,
+                                                            sta->deflink.gtk[key_idx]);
+
+               return NULL;
+       }
+
+       if (pairwise && key_idx < NUM_DEFAULT_KEYS)
+               return rcu_dereference_check_key_mtx(local,
+                                                    sdata->keys[key_idx]);
+
+       key = rcu_dereference_check_key_mtx(local, sdata->deflink.gtk[key_idx]);
+       if (key)
+               return key;
+
+       /* or maybe it was a WEP key */
+       if (key_idx < NUM_DEFAULT_KEYS)
+               return rcu_dereference_check_key_mtx(local, sdata->keys[key_idx]);
+
+       return NULL;
+}
+
 static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
                             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 sta_info *sta;
-       struct ieee80211_key *key = NULL;
+       struct ieee80211_key *key;
        int ret;
 
        mutex_lock(&local->sta_mtx);
        mutex_lock(&local->key_mtx);
 
-       if (mac_addr) {
-               ret = -ENOENT;
-
-               sta = sta_info_get_bss(sdata, mac_addr);
-               if (!sta)
-                       goto out_unlock;
-
-               if (pairwise)
-                       key = key_mtx_dereference(local, sta->ptk[key_idx]);
-               else
-                       key = key_mtx_dereference(local,
-                                                 sta->deflink.gtk[key_idx]);
-       } else
-               key = key_mtx_dereference(local, sdata->keys[key_idx]);
-
+       key = ieee80211_lookup_key(sdata, key_idx, pairwise, mac_addr);
        if (!key) {
                ret = -ENOENT;
                goto out_unlock;
@@ -597,10 +611,9 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev,
                                              struct key_params *params))
 {
        struct ieee80211_sub_if_data *sdata;
-       struct sta_info *sta = NULL;
        u8 seq[6] = {0};
        struct key_params params;
-       struct ieee80211_key *key = NULL;
+       struct ieee80211_key *key;
        u64 pn64;
        u32 iv32;
        u16 iv16;
@@ -611,20 +624,7 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev,
 
        rcu_read_lock();
 
-       if (mac_addr) {
-               sta = sta_info_get_bss(sdata, mac_addr);
-               if (!sta)
-                       goto out;
-
-               if (pairwise && key_idx < NUM_DEFAULT_KEYS)
-                       key = rcu_dereference(sta->ptk[key_idx]);
-               else if (!pairwise &&
-                        key_idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS +
-                        NUM_DEFAULT_BEACON_KEYS)
-                       key = rcu_dereference(sta->deflink.gtk[key_idx]);
-       } else
-               key = rcu_dereference(sdata->keys[key_idx]);
-
+       key = ieee80211_lookup_key(sdata, key_idx, pairwise, mac_addr);
        if (!key)
                goto out;
 
@@ -845,9 +845,10 @@ static int ieee80211_set_monitor_channel(struct wiphy *wiphy,
                sdata = wiphy_dereference(local->hw.wiphy,
                                          local->monitor_sdata);
                if (sdata) {
-                       ieee80211_vif_release_channel(sdata);
-                       ret = ieee80211_vif_use_channel(sdata, chandef,
-                                       IEEE80211_CHANCTX_EXCLUSIVE);
+                       ieee80211_link_release_channel(&sdata->deflink);
+                       ret = ieee80211_link_use_channel(&sdata->deflink,
+                                                        chandef,
+                                                        IEEE80211_CHANCTX_EXCLUSIVE);
                }
        } else if (local->open_count == local->monitors) {
                local->_oper_chandef = *chandef;
@@ -865,14 +866,15 @@ 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)
+                        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(sdata->u.ap.probe_resp, sdata);
+       old = sdata_dereference(link->u.ap.probe_resp, sdata);
 
        new = kzalloc(sizeof(struct probe_resp) + resp_len, GFP_KERNEL);
        if (!new)
@@ -888,7 +890,7 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
        else if (cca)
                new->cntdwn_counter_offsets[0] = cca->counter_offset_presp;
 
-       rcu_assign_pointer(sdata->u.ap.probe_resp, new);
+       rcu_assign_pointer(link->u.ap.probe_resp, new);
        if (old)
                kfree_rcu(old, rcu_head);
 
@@ -896,7 +898,9 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
 }
 
 static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata,
-                                       struct cfg80211_fils_discovery *params)
+                                       struct cfg80211_fils_discovery *params,
+                                       struct ieee80211_link_data *link,
+                                       struct ieee80211_bss_conf *link_conf)
 {
        struct fils_discovery_data *new, *old = NULL;
        struct ieee80211_fils_discovery *fd;
@@ -904,17 +908,17 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata,
        if (!params->tmpl || !params->tmpl_len)
                return -EINVAL;
 
-       fd = &sdata->vif.bss_conf.fils_discovery;
+       fd = &link_conf->fils_discovery;
        fd->min_interval = params->min_interval;
        fd->max_interval = params->max_interval;
 
-       old = sdata_dereference(sdata->u.ap.fils_discovery, sdata);
+       old = sdata_dereference(link->u.ap.fils_discovery, sdata);
        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(sdata->u.ap.fils_discovery, new);
+       rcu_assign_pointer(link->u.ap.fils_discovery, new);
 
        if (old)
                kfree_rcu(old, rcu_head);
@@ -924,26 +928,27 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata,
 
 static int
 ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata,
-                                    struct cfg80211_unsol_bcast_probe_resp *params)
+                                    struct cfg80211_unsol_bcast_probe_resp *params,
+                                    struct ieee80211_link_data *link,
+                                    struct ieee80211_bss_conf *link_conf)
 {
        struct unsol_bcast_probe_resp_data *new, *old = NULL;
 
        if (!params->tmpl || !params->tmpl_len)
                return -EINVAL;
 
-       old = sdata_dereference(sdata->u.ap.unsol_bcast_probe_resp, sdata);
+       old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata);
        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(sdata->u.ap.unsol_bcast_probe_resp, new);
+       rcu_assign_pointer(link->u.ap.unsol_bcast_probe_resp, new);
 
        if (old)
                kfree_rcu(old, rcu_head);
 
-       sdata->vif.bss_conf.unsol_bcast_probe_resp_interval =
-                                                       params->interval;
+       link_conf->unsol_bcast_probe_resp_interval = params->interval;
 
        return 0;
 }
@@ -951,18 +956,17 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata,
 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)
+                               const u8 *civicloc, size_t civicloc_len,
+                               struct ieee80211_bss_conf *link_conf)
 {
        struct ieee80211_ftm_responder_params *new, *old;
-       struct ieee80211_bss_conf *bss_conf;
        u8 *pos;
        int len;
 
        if (!lci_len && !civicloc_len)
                return 0;
 
-       bss_conf = &sdata->vif.bss_conf;
-       old = bss_conf->ftmr_params;
+       old = link_conf->ftmr_params;
        len = lci_len + civicloc_len;
 
        new = kzalloc(sizeof(*new) + len, GFP_KERNEL);
@@ -984,7 +988,7 @@ static int ieee80211_set_ftm_responder_params(
                pos += civicloc_len;
        }
 
-       bss_conf->ftmr_params = new;
+       link_conf->ftmr_params = new;
        kfree(old);
 
        return 0;
@@ -1008,6 +1012,7 @@ ieee80211_copy_mbssid_beacon(u8 *pos, struct cfg80211_mbssid_elems *dst,
 }
 
 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)
@@ -1017,9 +1022,9 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
        int new_head_len, new_tail_len;
        int size, err;
        u32 changed = BSS_CHANGED_BEACON;
+       struct ieee80211_bss_conf *link_conf = link->conf;
 
-       old = sdata_dereference(sdata->u.ap.beacon, sdata);
-
+       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)
@@ -1073,7 +1078,7 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
                pos += struct_size(new->mbssid_ies, elem, mbssid->cnt);
                ieee80211_copy_mbssid_beacon(pos, new->mbssid_ies, mbssid);
                /* update bssid_indicator */
-               sdata->vif.bss_conf.bssid_indicator =
+               link_conf->bssid_indicator =
                        ilog2(__roundup_pow_of_two(mbssid->cnt + 1));
        }
 
@@ -1101,7 +1106,7 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
                        memcpy(new->tail, old->tail, new_tail_len);
 
        err = ieee80211_set_probe_resp(sdata, params->probe_resp,
-                                      params->probe_resp_len, csa, cca);
+                                      params->probe_resp_len, csa, cca, link);
        if (err < 0) {
                kfree(new);
                return err;
@@ -1110,12 +1115,13 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
                changed |= BSS_CHANGED_AP_PROBE_RESP;
 
        if (params->ftm_responder != -1) {
-               sdata->vif.bss_conf.ftm_responder = params->ftm_responder;
+               link_conf->ftm_responder = params->ftm_responder;
                err = ieee80211_set_ftm_responder_params(sdata,
                                                         params->lci,
                                                         params->lci_len,
                                                         params->civicloc,
-                                                        params->civicloc_len);
+                                                        params->civicloc_len,
+                                                        link_conf);
 
                if (err < 0) {
                        kfree(new);
@@ -1125,7 +1131,8 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
                changed |= BSS_CHANGED_FTM_RESPONDER;
        }
 
-       rcu_assign_pointer(sdata->u.ap.beacon, new);
+       rcu_assign_pointer(link->u.ap.beacon, new);
+       sdata->u.ap.active = true;
 
        if (old)
                kfree_rcu(old, rcu_head);
@@ -1143,33 +1150,41 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
        u32 changed = BSS_CHANGED_BEACON_INT |
                      BSS_CHANGED_BEACON_ENABLED |
                      BSS_CHANGED_BEACON |
-                     BSS_CHANGED_SSID |
                      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;
+
+       link = sdata_dereference(sdata->link[link_id], sdata);
+       if (!link)
+               return -ENOLINK;
+
+       link_conf = link->conf;
 
-       old = sdata_dereference(sdata->u.ap.beacon, sdata);
+       old = sdata_dereference(link->u.ap.beacon, sdata);
        if (old)
                return -EALREADY;
 
        if (params->smps_mode != NL80211_SMPS_OFF)
                return -ENOTSUPP;
 
-       sdata->smps_mode = IEEE80211_SMPS_OFF;
+       link->smps_mode = IEEE80211_SMPS_OFF;
 
-       sdata->needed_rx_chains = sdata->local->rx_chains;
+       link->needed_rx_chains = sdata->local->rx_chains;
 
-       prev_beacon_int = sdata->vif.bss_conf.beacon_int;
-       sdata->vif.bss_conf.beacon_int = params->beacon_interval;
+       prev_beacon_int = link_conf->beacon_int;
+       link_conf->beacon_int = params->beacon_interval;
 
        if (params->he_cap && params->he_oper) {
-               sdata->vif.bss_conf.he_support = true;
-               sdata->vif.bss_conf.htc_trig_based_pkt_ext =
+               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);
-               sdata->vif.bss_conf.frame_time_rts_th =
+               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;
@@ -1181,19 +1196,20 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
        if (sdata->vif.type == NL80211_IFTYPE_AP &&
            params->mbssid_config.tx_wdev) {
                err = ieee80211_set_ap_mbssid_options(sdata,
-                                                     params->mbssid_config);
+                                                     params->mbssid_config,
+                                                     link_conf);
                if (err)
                        return err;
        }
 
        mutex_lock(&local->mtx);
-       err = ieee80211_vif_use_channel(sdata, &params->chandef,
-                                       IEEE80211_CHANCTX_SHARED);
+       err = ieee80211_link_use_channel(link, &params->chandef,
+                                        IEEE80211_CHANCTX_SHARED);
        if (!err)
-               ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+               ieee80211_link_copy_chanctx_to_vlans(link, false);
        mutex_unlock(&local->mtx);
        if (err) {
-               sdata->vif.bss_conf.beacon_int = prev_beacon_int;
+               link_conf->beacon_int = prev_beacon_int;
                return err;
        }
 
@@ -1207,9 +1223,6 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
                                params->crypto.control_port_over_nl80211;
        sdata->control_port_no_preauth =
                                params->crypto.control_port_no_preauth;
-       sdata->encrypt_headroom = ieee80211_cs_headroom(sdata->local,
-                                                       &params->crypto,
-                                                       sdata->vif.type);
 
        list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) {
                vlan->control_port_protocol =
@@ -1220,34 +1233,30 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
                        params->crypto.control_port_over_nl80211;
                vlan->control_port_no_preauth =
                        params->crypto.control_port_no_preauth;
-               vlan->encrypt_headroom =
-                       ieee80211_cs_headroom(sdata->local,
-                                             &params->crypto,
-                                             vlan->vif.type);
-       }
-
-       sdata->vif.bss_conf.dtim_period = params->dtim_period;
-       sdata->vif.bss_conf.enable_beacon = true;
-       sdata->vif.bss_conf.allow_p2p_go_ps = sdata->vif.p2p;
-       sdata->vif.bss_conf.twt_responder = params->twt_responder;
-       sdata->vif.bss_conf.he_obss_pd = params->he_obss_pd;
-       sdata->vif.bss_conf.he_bss_color = params->beacon.he_bss_color;
-       sdata->vif.bss_conf.s1g = params->chandef.chan->band ==
+       }
+
+       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;
+       sdata->vif.cfg.s1g = params->chandef.chan->band ==
                                  NL80211_BAND_S1GHZ;
 
-       sdata->vif.bss_conf.ssid_len = params->ssid_len;
+       sdata->vif.cfg.ssid_len = params->ssid_len;
        if (params->ssid_len)
-               memcpy(sdata->vif.bss_conf.ssid, params->ssid,
+               memcpy(sdata->vif.cfg.ssid, params->ssid,
                       params->ssid_len);
-       sdata->vif.bss_conf.hidden_ssid =
+       link_conf->hidden_ssid =
                (params->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE);
 
-       memset(&sdata->vif.bss_conf.p2p_noa_attr, 0,
-              sizeof(sdata->vif.bss_conf.p2p_noa_attr));
-       sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow =
+       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)
-               sdata->vif.bss_conf.p2p_noa_attr.oppps_ctwindow |=
+               link_conf->p2p_noa_attr.oppps_ctwindow |=
                                        IEEE80211_P2P_OPPPS_ENABLE_BIT;
 
        sdata->beacon_rate_set = false;
@@ -1262,16 +1271,17 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
        }
 
        if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL))
-               sdata->vif.bss_conf.beacon_tx_rate = params->beacon_rate;
+               link_conf->beacon_tx_rate = params->beacon_rate;
 
-       err = ieee80211_assign_beacon(sdata, &params->beacon, NULL, NULL);
+       err = ieee80211_assign_beacon(sdata, link, &params->beacon, NULL, NULL);
        if (err < 0)
                goto error;
        changed |= err;
 
        if (params->fils_discovery.max_interval) {
                err = ieee80211_set_fils_discovery(sdata,
-                                                  &params->fils_discovery);
+                                                  &params->fils_discovery,
+                                                  link, link_conf);
                if (err < 0)
                        goto error;
                changed |= BSS_CHANGED_FILS_DISCOVERY;
@@ -1279,24 +1289,27 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
 
        if (params->unsol_bcast_probe_resp.interval) {
                err = ieee80211_set_unsol_bcast_probe_resp(sdata,
-                                                          &params->unsol_bcast_probe_resp);
+                                                          &params->unsol_bcast_probe_resp,
+                                                          link, link_conf);
                if (err < 0)
                        goto error;
                changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP;
        }
 
-       err = drv_start_ap(sdata->local, sdata);
+       err = drv_start_ap(sdata->local, sdata, link_conf);
        if (err) {
-               old = sdata_dereference(sdata->u.ap.beacon, sdata);
+               old = sdata_dereference(link->u.ap.beacon, sdata);
 
                if (old)
                        kfree_rcu(old, rcu_head);
-               RCU_INIT_POINTER(sdata->u.ap.beacon, NULL);
+               RCU_INIT_POINTER(link->u.ap.beacon, NULL);
+               sdata->u.ap.active = false;
                goto error;
        }
 
        ieee80211_recalc_dtim(local, sdata);
-       ieee80211_bss_info_change_notify(sdata, changed);
+       ieee80211_vif_cfg_change_notify(sdata, BSS_CHANGED_SSID);
+       ieee80211_link_info_change_notify(sdata, link, changed);
 
        netif_carrier_on(dev);
        list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
@@ -1306,7 +1319,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
 
 error:
        mutex_lock(&local->mtx);
-       ieee80211_vif_release_channel(sdata);
+       ieee80211_link_release_channel(link);
        mutex_unlock(&local->mtx);
 
        return err;
@@ -1315,50 +1328,56 @@ error:
 static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
                                   struct cfg80211_beacon_data *params)
 {
-       struct ieee80211_sub_if_data *sdata;
-       struct ieee80211_bss_conf *bss_conf;
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_link_data *link;
        struct beacon_data *old;
        int err;
+       struct ieee80211_bss_conf *link_conf;
 
-       sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        sdata_assert_lock(sdata);
 
+       link = sdata_dereference(sdata->link[params->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 (sdata->vif.csa_active || sdata->vif.color_change_active)
+       if (link_conf->csa_active || link_conf->color_change_active)
                return -EBUSY;
 
-       old = sdata_dereference(sdata->u.ap.beacon, sdata);
+       old = sdata_dereference(link->u.ap.beacon, sdata);
        if (!old)
                return -ENOENT;
 
-       err = ieee80211_assign_beacon(sdata, params, NULL, NULL);
+       err = ieee80211_assign_beacon(sdata, link, params, NULL, NULL);
        if (err < 0)
                return err;
 
-       bss_conf = &sdata->vif.bss_conf;
        if (params->he_bss_color_valid &&
-           params->he_bss_color.enabled != bss_conf->he_bss_color.enabled) {
-               bss_conf->he_bss_color.enabled = params->he_bss_color.enabled;
+           params->he_bss_color.enabled != link_conf->he_bss_color.enabled) {
+               link_conf->he_bss_color.enabled = params->he_bss_color.enabled;
                err |= BSS_CHANGED_HE_BSS_COLOR;
        }
 
-       ieee80211_bss_info_change_notify(sdata, err);
+       ieee80211_link_info_change_notify(sdata, link, err);
        return 0;
 }
 
-static void ieee80211_free_next_beacon(struct ieee80211_sub_if_data *sdata)
+static void ieee80211_free_next_beacon(struct ieee80211_link_data *link)
 {
-       if (!sdata->u.ap.next_beacon)
+       if (!link->u.ap.next_beacon)
                return;
 
-       kfree(sdata->u.ap.next_beacon->mbssid_ies);
-       kfree(sdata->u.ap.next_beacon);
-       sdata->u.ap.next_beacon = NULL;
+       kfree(link->u.ap.next_beacon->mbssid_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)
+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;
@@ -1368,31 +1387,35 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
        struct fils_discovery_data *old_fils_discovery;
        struct unsol_bcast_probe_resp_data *old_unsol_bcast_probe_resp;
        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;
 
        sdata_assert_lock(sdata);
 
-       old_beacon = sdata_dereference(sdata->u.ap.beacon, sdata);
+       old_beacon = sdata_dereference(link->u.ap.beacon, sdata);
        if (!old_beacon)
                return -ENOENT;
-       old_probe_resp = sdata_dereference(sdata->u.ap.probe_resp, sdata);
-       old_fils_discovery = sdata_dereference(sdata->u.ap.fils_discovery,
+       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(sdata->u.ap.unsol_bcast_probe_resp,
+               sdata_dereference(link->u.ap.unsol_bcast_probe_resp,
                                  sdata);
 
        /* abort any running channel switch */
        mutex_lock(&local->mtx);
-       sdata->vif.csa_active = false;
-       if (sdata->csa_block_tx) {
+       link_conf->csa_active = false;
+       if (link->csa_block_tx) {
                ieee80211_wake_vif_queues(local, sdata,
                                          IEEE80211_QUEUE_STOP_REASON_CSA);
-               sdata->csa_block_tx = false;
+               link->csa_block_tx = false;
        }
 
        mutex_unlock(&local->mtx);
 
-       ieee80211_free_next_beacon(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)
@@ -1400,10 +1423,11 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
        netif_carrier_off(dev);
 
        /* remove beacon and probe response */
-       RCU_INIT_POINTER(sdata->u.ap.beacon, NULL);
-       RCU_INIT_POINTER(sdata->u.ap.probe_resp, NULL);
-       RCU_INIT_POINTER(sdata->u.ap.fils_discovery, NULL);
-       RCU_INIT_POINTER(sdata->u.ap.unsol_bcast_probe_resp, NULL);
+       sdata->u.ap.active = false;
+       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);
        kfree_rcu(old_beacon, rcu_head);
        if (old_probe_resp)
                kfree_rcu(old_probe_resp, rcu_head);
@@ -1412,35 +1436,36 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
        if (old_unsol_bcast_probe_resp)
                kfree_rcu(old_unsol_bcast_probe_resp, rcu_head);
 
-       kfree(sdata->vif.bss_conf.ftmr_params);
-       sdata->vif.bss_conf.ftmr_params = NULL;
+       kfree(link_conf->ftmr_params);
+       link_conf->ftmr_params = NULL;
 
        __sta_info_flush(sdata, true);
        ieee80211_free_keys(sdata, true);
 
-       sdata->vif.bss_conf.enable_beacon = false;
+       link_conf->enable_beacon = false;
        sdata->beacon_rate_set = false;
-       sdata->vif.bss_conf.ssid_len = 0;
+       sdata->vif.cfg.ssid_len = 0;
        clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, &sdata->state);
-       ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED);
+       ieee80211_link_info_change_notify(sdata, link,
+                                         BSS_CHANGED_BEACON_ENABLED);
 
        if (sdata->wdev.cac_started) {
-               chandef = sdata->vif.bss_conf.chandef;
-               cancel_delayed_work_sync(&sdata->dfs_cac_timer_work);
+               chandef = link_conf->chandef;
+               cancel_delayed_work_sync(&link->dfs_cac_timer_work);
                cfg80211_cac_event(sdata->dev, &chandef,
                                   NL80211_RADAR_CAC_ABORTED,
                                   GFP_KERNEL);
        }
 
-       drv_stop_ap(sdata->local, sdata);
+       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);
 
        mutex_lock(&local->mtx);
-       ieee80211_vif_copy_chanctx_to_vlans(sdata, true);
-       ieee80211_vif_release_channel(sdata);
+       ieee80211_link_copy_chanctx_to_vlans(link, true);
+       ieee80211_link_release_channel(link);
        mutex_unlock(&local->mtx);
 
        return 0;
@@ -1571,50 +1596,97 @@ static void sta_apply_mesh_params(struct ieee80211_local *local,
 #endif
 }
 
-static void sta_apply_airtime_params(struct ieee80211_local *local,
-                                    struct sta_info *sta,
-                                    struct station_parameters *params)
+static int sta_link_apply_parameters(struct ieee80211_local *local,
+                                    struct sta_info *sta, bool new_link,
+                                    struct link_station_parameters *params)
 {
-       u8 ac;
+       int ret = 0;
+       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->sta_mtx));
+
+       if (!link || !link_sta)
+               return -EINVAL;
 
-       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
-               struct airtime_sched_info *air_sched = &local->airtime[ac];
-               struct airtime_info *air_info = &sta->airtime[ac];
-               struct txq_info *txqi;
-               u8 tid;
-
-               spin_lock_bh(&air_sched->lock);
-               for (tid = 0; tid < IEEE80211_NUM_TIDS + 1; tid++) {
-                       if (air_info->weight == params->airtime_weight ||
-                           !sta->sta.txq[tid] ||
-                           ac != ieee80211_ac_from_tid(tid))
-                               continue;
+       sband = ieee80211_get_link_sband(link);
+       if (!sband)
+               return -EINVAL;
 
-                       airtime_weight_set(air_info, params->airtime_weight);
+       if (params->link_mac) {
+               if (new_link) {
+                       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;
+               }
+       }
 
-                       txqi = to_txq_info(sta->sta.txq[tid]);
-                       if (RB_EMPTY_NODE(&txqi->schedule_order))
-                               continue;
+       if (params->txpwr_set) {
+               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;
+       }
 
-                       ieee80211_update_airtime_weight(local, air_sched,
-                                                       0, true);
-               }
-               spin_unlock_bh(&air_sched->lock);
+       if (params->supported_rates &&
+           params->supported_rates_len) {
+               ieee80211_parse_bitrates(link->conf->chandef.width,
+                                        sband, params->supported_rates,
+                                        params->supported_rates_len,
+                                        &link_sta->pub->supp_rates[sband->band]);
+       }
+
+       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, 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->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->opmode_notif_used) {
+               /* 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 ret;
 }
 
 static int sta_apply_parameters(struct ieee80211_local *local,
                                struct sta_info *sta,
                                struct station_parameters *params)
 {
-       int ret = 0;
-       struct ieee80211_supported_band *sband;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        u32 mask, set;
-
-       sband = ieee80211_get_sband(sdata);
-       if (!sband)
-               return -EINVAL;
+       int ret = 0;
 
        mask = params->sta_flags_mask;
        set = params->sta_flags_set;
@@ -1680,7 +1752,7 @@ static int sta_apply_parameters(struct ieee80211_local *local,
 
        /* mark TDLS channel switch support, if the AP allows it */
        if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
-           !sdata->u.mgd.tdls_chan_switch_prohibited &&
+           !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);
@@ -1697,33 +1769,8 @@ static int sta_apply_parameters(struct ieee80211_local *local,
                sta->sta.max_sp = params->max_sp;
        }
 
-       /* The sender might not have sent the last bit, consider it to be 0 */
-       if (params->ext_capab_len >= 8) {
-               u8 val = (params->ext_capab[7] &
-                         WLAN_EXT_CAPA8_MAX_MSDU_IN_AMSDU_LSB) >> 7;
-
-               /* we did get all the bits, take the MSB as well */
-               if (params->ext_capab_len >= 9) {
-                       u8 val_msb = params->ext_capab[8] &
-                               WLAN_EXT_CAPA9_MAX_MSDU_IN_AMSDU_MSB;
-                       val_msb <<= 1;
-                       val |= val_msb;
-               }
-
-               switch (val) {
-               case 1:
-                       sta->sta.max_amsdu_subframes = 32;
-                       break;
-               case 2:
-                       sta->sta.max_amsdu_subframes = 16;
-                       break;
-               case 3:
-                       sta->sta.max_amsdu_subframes = 8;
-                       break;
-               default:
-                       sta->sta.max_amsdu_subframes = 0;
-               }
-       }
+       ieee80211_sta_set_max_amsdu_subframes(sta, params->ext_capab,
+                                             params->ext_capab_len);
 
        /*
         * cfg80211 validates this (1-2007) and allows setting the AID
@@ -1743,53 +1790,10 @@ static int sta_apply_parameters(struct ieee80211_local *local,
        if (params->listen_interval >= 0)
                sta->listen_interval = params->listen_interval;
 
-       if (params->sta_modify_mask & STATION_PARAM_APPLY_STA_TXPOWER) {
-               sta->sta.deflink.txpwr.type = params->txpwr.type;
-               if (params->txpwr.type == NL80211_TX_POWER_LIMITED)
-                       sta->sta.deflink.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(&sdata->vif.bss_conf.chandef,
-                                        sband, params->supported_rates,
-                                        params->supported_rates_len,
-                                        &sta->sta.deflink.supp_rates[sband->band]);
-       }
-
-       if (params->ht_capa)
-               ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
-                                                 params->ht_capa, 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, 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,
-                                                 sta);
-
-       if (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,
-                                                   sta);
-
-       if (params->opmode_notif_used) {
-               /* returned value is only needed for rc update, but the
-                * rc isn't initialized here yet, so ignore it
-                */
-               __ieee80211_vht_handle_opmode(sdata, sta, params->opmode_notif,
-                                             sband->band);
-       }
+       ret = sta_link_apply_parameters(local, sta, false,
+                                       &params->link_sta_params);
+       if (ret)
+               return ret;
 
        if (params->support_p2p_ps >= 0)
                sta->sta.support_p2p_ps = params->support_p2p_ps;
@@ -1798,8 +1802,7 @@ static int sta_apply_parameters(struct ieee80211_local *local,
                sta_apply_mesh_params(local, sta, params);
 
        if (params->airtime_weight)
-               sta_apply_airtime_params(local, sta, params);
-
+               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) ||
@@ -1809,6 +1812,10 @@ static int sta_apply_parameters(struct ieee80211_local *local,
                        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;
 }
 
@@ -1841,14 +1848,32 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
            !sdata->u.mgd.associated)
                return -EINVAL;
 
-       sta = sta_info_alloc(sdata, mac, GFP_KERNEL);
+       /*
+        * 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.
+        */
+       mutex_lock(&local->sta_mtx);
        err = sta_apply_parameters(local, sta, params);
+       mutex_unlock(&local->sta_mtx);
        if (err) {
                sta_info_free(local, sta);
                return err;
@@ -1968,7 +1993,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                }
        }
 
-       err = sta_apply_parameters(local, sta, params);
+       /* we use sta_info_get_bss() so this might be different */
+       if (sdata != sta->sdata) {
+               mutex_lock_nested(&sta->sdata->wdev.mtx, 1);
+               err = sta_apply_parameters(local, sta, params);
+               mutex_unlock(&sta->sdata->wdev.mtx);
+       } else {
+               err = sta_apply_parameters(local, sta, params);
+       }
        if (err)
                goto out_err;
 
@@ -2355,7 +2387,8 @@ static int ieee80211_update_mesh_config(struct wiphy *wiphy,
        if (_chg_mesh_attr(NL80211_MESHCONF_HT_OPMODE, mask)) {
                conf->ht_opmode = nconf->ht_opmode;
                sdata->vif.bss_conf.ht_operation_mode = nconf->ht_opmode;
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_HT);
+               ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                                 BSS_CHANGED_HT);
        }
        if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT, mask))
                conf->dot11MeshHWMPactivePathToRootTimeout =
@@ -2403,12 +2436,12 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev,
        sdata->control_port_over_nl80211 = setup->control_port_over_nl80211;
 
        /* can mesh use other SMPS modes? */
-       sdata->smps_mode = IEEE80211_SMPS_OFF;
-       sdata->needed_rx_chains = sdata->local->rx_chains;
+       sdata->deflink.smps_mode = IEEE80211_SMPS_OFF;
+       sdata->deflink.needed_rx_chains = sdata->local->rx_chains;
 
        mutex_lock(&sdata->local->mtx);
-       err = ieee80211_vif_use_channel(sdata, &setup->chandef,
-                                       IEEE80211_CHANCTX_SHARED);
+       err = ieee80211_link_use_channel(&sdata->deflink, &setup->chandef,
+                                        IEEE80211_CHANCTX_SHARED);
        mutex_unlock(&sdata->local->mtx);
        if (err)
                return err;
@@ -2422,7 +2455,7 @@ static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev)
 
        ieee80211_stop_mesh(sdata);
        mutex_lock(&sdata->local->mtx);
-       ieee80211_vif_release_channel(sdata);
+       ieee80211_link_release_channel(&sdata->deflink);
        kfree(sdata->u.mesh.ie);
        mutex_unlock(&sdata->local->mtx);
 
@@ -2438,7 +2471,7 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
        struct ieee80211_supported_band *sband;
        u32 changed = 0;
 
-       if (!sdata_dereference(sdata->u.ap.beacon, sdata))
+       if (!sdata_dereference(sdata->deflink.u.ap.beacon, sdata))
                return -ENOENT;
 
        sband = ieee80211_get_sband(sdata);
@@ -2469,13 +2502,13 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
        }
 
        if (params->basic_rates) {
-               ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef,
+               ieee80211_parse_bitrates(sdata->vif.bss_conf.chandef.width,
                                         wiphy->bands[sband->band],
                                         params->basic_rates,
                                         params->basic_rates_len,
                                         &sdata->vif.bss_conf.basic_rates);
                changed |= BSS_CHANGED_BASIC_RATES;
-               ieee80211_check_rate_mask(sdata);
+               ieee80211_check_rate_mask(&sdata->deflink);
        }
 
        if (params->ap_isolate >= 0) {
@@ -2510,7 +2543,7 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
                changed |= BSS_CHANGED_P2P_PS;
        }
 
-       ieee80211_bss_info_change_notify(sdata, changed);
+       ieee80211_link_info_change_notify(sdata, &sdata->deflink, changed);
 
        return 0;
 }
@@ -2521,6 +2554,7 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy,
 {
        struct ieee80211_local *local = wiphy_priv(wiphy);
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_link_data *link = &sdata->deflink;
        struct ieee80211_tx_queue_params p;
 
        if (!local->ops->conf_tx)
@@ -2543,15 +2577,16 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy,
 
        ieee80211_regulatory_limit_wmm_params(sdata, &p, params->ac);
 
-       sdata->tx_conf[params->ac] = p;
-       if (drv_conf_tx(local, sdata, params->ac, &p)) {
+       link->tx_conf[params->ac] = p;
+       if (drv_conf_tx(local, link, params->ac, &p)) {
                wiphy_debug(local->hw.wiphy,
                            "failed to set TX queue parameters for AC %d\n",
                            params->ac);
                return -EINVAL;
        }
 
-       ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_QOS);
+       ieee80211_link_info_change_notify(sdata, link,
+                                         BSS_CHANGED_QOS);
 
        return 0;
 }
@@ -2603,7 +2638,7 @@ static int ieee80211_scan(struct wiphy *wiphy,
                 * the  frames sent while scanning on other channel will be
                 * lost)
                 */
-               if (sdata->u.ap.beacon &&
+               if (sdata->deflink.u.ap.beacon &&
                    (!(wiphy->features & NL80211_FEATURE_AP_SCAN) ||
                     !(req->flags & NL80211_SCAN_FLAG_AP)))
                        return -EOPNOTSUPP;
@@ -2700,7 +2735,8 @@ static int ieee80211_set_mcast_rate(struct wiphy *wiphy, struct net_device *dev,
        memcpy(sdata->vif.bss_conf.mcast_rate, rate,
               sizeof(int) * NUM_NL80211_BANDS);
 
-       ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_MCAST_RATE);
+       ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                         BSS_CHANGED_MCAST_RATE);
 
        return 0;
 }
@@ -2784,14 +2820,15 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy,
 
                switch (type) {
                case NL80211_TX_POWER_AUTOMATIC:
-                       sdata->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
+                       sdata->deflink.user_power_level =
+                               IEEE80211_UNSET_POWER_LEVEL;
                        txp_type = NL80211_TX_POWER_LIMITED;
                        break;
                case NL80211_TX_POWER_LIMITED:
                case NL80211_TX_POWER_FIXED:
                        if (mbm < 0 || (mbm % 100))
                                return -EOPNOTSUPP;
-                       sdata->user_power_level = MBM_TO_DBM(mbm);
+                       sdata->deflink.user_power_level = MBM_TO_DBM(mbm);
                        break;
                }
 
@@ -2824,7 +2861,7 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy,
                        has_monitor = true;
                        continue;
                }
-               sdata->user_power_level = local->user_power_level;
+               sdata->deflink.user_power_level = local->user_power_level;
                if (txp_type != sdata->vif.bss_conf.txpower_type)
                        update_txp_type = true;
                sdata->vif.bss_conf.txpower_type = txp_type;
@@ -2840,7 +2877,7 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy,
                sdata = wiphy_dereference(local->hw.wiphy,
                                          local->monitor_sdata);
                if (sdata) {
-                       sdata->user_power_level = local->user_power_level;
+                       sdata->deflink.user_power_level = local->user_power_level;
                        if (txp_type != sdata->vif.bss_conf.txpower_type)
                                update_txp_type = true;
                        sdata->vif.bss_conf.txpower_type = txp_type;
@@ -2914,6 +2951,7 @@ static int ieee80211_testmode_dump(struct wiphy *wiphy,
 #endif
 
 int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata,
+                                struct ieee80211_link_data *link,
                                 enum ieee80211_smps_mode smps_mode)
 {
        const u8 *ap;
@@ -2927,8 +2965,8 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata,
        if (WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_STATION))
                return -EINVAL;
 
-       old_req = sdata->u.mgd.req_smps;
-       sdata->u.mgd.req_smps = smps_mode;
+       old_req = link->u.mgd.req_smps;
+       link->u.mgd.req_smps = smps_mode;
 
        if (old_req == smps_mode &&
            smps_mode != IEEE80211_SMPS_AUTOMATIC)
@@ -2940,10 +2978,10 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata,
         * the new value until we associate.
         */
        if (!sdata->u.mgd.associated ||
-           sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT)
+           link->conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT)
                return 0;
 
-       ap = sdata->u.mgd.bssid;
+       ap = link->u.mgd.bssid;
 
        rcu_read_lock();
        list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) {
@@ -2967,7 +3005,7 @@ int __ieee80211_request_smps_mgd(struct ieee80211_sub_if_data *sdata,
        err = ieee80211_send_smps_action(sdata, smps_mode,
                                         ap, ap);
        if (err)
-               sdata->u.mgd.req_smps = old_req;
+               link->u.mgd.req_smps = old_req;
        else if (smps_mode != IEEE80211_SMPS_OFF && tdls_peer_found)
                ieee80211_teardown_tdls_peers(sdata);
 
@@ -2979,6 +3017,7 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       unsigned int link_id;
 
        if (sdata->vif.type != NL80211_IFTYPE_STATION)
                return -EOPNOTSUPP;
@@ -2995,7 +3034,16 @@ static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
 
        /* no change, but if automatic follow powersave */
        sdata_lock(sdata);
-       __ieee80211_request_smps_mgd(sdata, sdata->u.mgd.req_smps);
+       for (link_id = 0; link_id < ARRAY_SIZE(sdata->link); link_id++) {
+               struct ieee80211_link_data *link;
+
+               link = sdata_dereference(sdata->link[link_id], sdata);
+
+               if (!link)
+                       continue;
+               __ieee80211_request_smps_mgd(sdata, link,
+                                            link->u.mgd.req_smps);
+       }
        sdata_unlock(sdata);
 
        if (ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS))
@@ -3028,12 +3076,13 @@ static int ieee80211_set_cqm_rssi_config(struct wiphy *wiphy,
        bss_conf->cqm_rssi_hyst = rssi_hyst;
        bss_conf->cqm_rssi_low = 0;
        bss_conf->cqm_rssi_high = 0;
-       sdata->u.mgd.last_cqm_event_signal = 0;
+       sdata->deflink.u.mgd.last_cqm_event_signal = 0;
 
        /* tell the driver upon association, unless already associated */
        if (sdata->u.mgd.associated &&
            sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_CQM);
+               ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                                 BSS_CHANGED_CQM);
 
        return 0;
 }
@@ -3053,18 +3102,20 @@ static int ieee80211_set_cqm_rssi_range_config(struct wiphy *wiphy,
        bss_conf->cqm_rssi_high = rssi_high;
        bss_conf->cqm_rssi_thold = 0;
        bss_conf->cqm_rssi_hyst = 0;
-       sdata->u.mgd.last_cqm_event_signal = 0;
+       sdata->deflink.u.mgd.last_cqm_event_signal = 0;
 
        /* tell the driver upon association, unless already associated */
        if (sdata->u.mgd.associated &&
            sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_CQM);
+               ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                                 BSS_CHANGED_CQM);
 
        return 0;
 }
 
 static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
                                      struct net_device *dev,
+                                     unsigned int link_id,
                                      const u8 *addr,
                                      const struct cfg80211_bitrate_mask *mask)
 {
@@ -3081,7 +3132,7 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
         * to send something, and if we're an AP we have to be able to do
         * so at a basic rate so that all clients can receive it.
         */
-       if (rcu_access_pointer(sdata->vif.chanctx_conf) &&
+       if (rcu_access_pointer(sdata->vif.bss_conf.chanctx_conf) &&
            sdata->vif.bss_conf.chandef.chan) {
                u32 basic_rates = sdata->vif.bss_conf.basic_rates;
                enum nl80211_band band = sdata->vif.bss_conf.chandef.chan->band;
@@ -3146,16 +3197,16 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
        }
 
        /* whatever, but channel contexts should not complain about that one */
-       sdata->smps_mode = IEEE80211_SMPS_OFF;
-       sdata->needed_rx_chains = local->rx_chains;
+       sdata->deflink.smps_mode = IEEE80211_SMPS_OFF;
+       sdata->deflink.needed_rx_chains = local->rx_chains;
 
-       err = ieee80211_vif_use_channel(sdata, chandef,
-                                       IEEE80211_CHANCTX_SHARED);
+       err = ieee80211_link_use_channel(&sdata->deflink, chandef,
+                                        IEEE80211_CHANCTX_SHARED);
        if (err)
                goto out_unlock;
 
        ieee80211_queue_delayed_work(&sdata->local->hw,
-                                    &sdata->dfs_cac_timer_work,
+                                    &sdata->deflink.dfs_cac_timer_work,
                                     msecs_to_jiffies(cac_time_ms));
 
  out_unlock:
@@ -3175,10 +3226,10 @@ static void ieee80211_end_cac(struct wiphy *wiphy,
                 * by the time it gets it, sdata->wdev.cac_started
                 * will no longer be true
                 */
-               cancel_delayed_work(&sdata->dfs_cac_timer_work);
+               cancel_delayed_work(&sdata->deflink.dfs_cac_timer_work);
 
                if (sdata->wdev.cac_started) {
-                       ieee80211_vif_release_channel(sdata);
+                       ieee80211_link_release_channel(&sdata->deflink);
                        sdata->wdev.cac_started = false;
                }
        }
@@ -3294,10 +3345,10 @@ void ieee80211_csa_finish(struct ieee80211_vif *vif)
                                continue;
 
                        ieee80211_queue_work(&iter->local->hw,
-                                            &iter->csa_finalize_work);
+                                            &iter->deflink.csa_finalize_work);
                }
        }
-       ieee80211_queue_work(&local->hw, &sdata->csa_finalize_work);
+       ieee80211_queue_work(&local->hw, &sdata->deflink.csa_finalize_work);
 
        rcu_read_unlock();
 }
@@ -3309,7 +3360,7 @@ void ieee80211_channel_switch_disconnect(struct ieee80211_vif *vif, bool block_t
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_local *local = sdata->local;
 
-       sdata->csa_block_tx = block_tx;
+       sdata->deflink.csa_block_tx = block_tx;
        sdata_info(sdata, "channel switch failed, disconnecting\n");
        ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work);
 }
@@ -3322,12 +3373,13 @@ static int ieee80211_set_after_csa_beacon(struct ieee80211_sub_if_data *sdata,
 
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_AP:
-               if (!sdata->u.ap.next_beacon)
+               if (!sdata->deflink.u.ap.next_beacon)
                        return -EINVAL;
 
-               err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon,
+               err = ieee80211_assign_beacon(sdata, &sdata->deflink,
+                                             sdata->deflink.u.ap.next_beacon,
                                              NULL, NULL);
-               ieee80211_free_next_beacon(sdata);
+               ieee80211_free_next_beacon(&sdata->deflink);
 
                if (err < 0)
                        return err;
@@ -3372,41 +3424,41 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
         * completed successfully
         */
 
-       if (sdata->reserved_chanctx) {
+       if (sdata->deflink.reserved_chanctx) {
                /*
                 * with multi-vif csa driver may call ieee80211_csa_finish()
                 * many times while waiting for other interfaces to use their
                 * reservations
                 */
-               if (sdata->reserved_ready)
+               if (sdata->deflink.reserved_ready)
                        return 0;
 
-               return ieee80211_vif_use_reserved_context(sdata);
+               return ieee80211_link_use_reserved_context(&sdata->deflink);
        }
 
        if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
-                                       &sdata->csa_chandef))
+                                       &sdata->deflink.csa_chandef))
                return -EINVAL;
 
-       sdata->vif.csa_active = false;
+       sdata->vif.bss_conf.csa_active = false;
 
        err = ieee80211_set_after_csa_beacon(sdata, &changed);
        if (err)
                return err;
 
-       ieee80211_bss_info_change_notify(sdata, changed);
+       ieee80211_link_info_change_notify(sdata, &sdata->deflink, changed);
 
-       if (sdata->csa_block_tx) {
+       if (sdata->deflink.csa_block_tx) {
                ieee80211_wake_vif_queues(local, sdata,
                                          IEEE80211_QUEUE_STOP_REASON_CSA);
-               sdata->csa_block_tx = false;
+               sdata->deflink.csa_block_tx = false;
        }
 
        err = drv_post_channel_switch(sdata);
        if (err)
                return err;
 
-       cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef);
+       cfg80211_ch_switch_notify(sdata->dev, &sdata->deflink.csa_chandef, 0);
 
        return 0;
 }
@@ -3424,7 +3476,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 {
        struct ieee80211_sub_if_data *sdata =
                container_of(work, struct ieee80211_sub_if_data,
-                            csa_finalize_work);
+                            deflink.csa_finalize_work);
        struct ieee80211_local *local = sdata->local;
 
        sdata_lock(sdata);
@@ -3432,7 +3484,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
        mutex_lock(&local->chanctx_mtx);
 
        /* AP might have been stopped while waiting for the lock. */
-       if (!sdata->vif.csa_active)
+       if (!sdata->vif.bss_conf.csa_active)
                goto unlock;
 
        if (!ieee80211_sdata_running(sdata))
@@ -3455,9 +3507,9 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
 
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_AP:
-               sdata->u.ap.next_beacon =
+               sdata->deflink.u.ap.next_beacon =
                        cfg80211_beacon_dup(&params->beacon_after);
-               if (!sdata->u.ap.next_beacon)
+               if (!sdata->deflink.u.ap.next_beacon)
                        return -ENOMEM;
 
                /*
@@ -3483,7 +3535,7 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
                     IEEE80211_MAX_CNTDWN_COUNTERS_NUM) ||
                    (params->n_counter_offsets_presp >
                     IEEE80211_MAX_CNTDWN_COUNTERS_NUM)) {
-                       ieee80211_free_next_beacon(sdata);
+                       ieee80211_free_next_beacon(&sdata->deflink);
                        return -EINVAL;
                }
 
@@ -3493,16 +3545,18 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
                csa.n_counter_offsets_presp = params->n_counter_offsets_presp;
                csa.count = params->count;
 
-               err = ieee80211_assign_beacon(sdata, &params->beacon_csa, &csa, NULL);
+               err = ieee80211_assign_beacon(sdata, &sdata->deflink,
+                                             &params->beacon_csa, &csa,
+                                             NULL);
                if (err < 0) {
-                       ieee80211_free_next_beacon(sdata);
+                       ieee80211_free_next_beacon(&sdata->deflink);
                        return err;
                }
                *changed |= err;
 
                break;
        case NL80211_IFTYPE_ADHOC:
-               if (!sdata->vif.bss_conf.ibss_joined)
+               if (!sdata->vif.cfg.ibss_joined)
                        return -EINVAL;
 
                if (params->chandef.width != sdata->u.ibss.chandef.width)
@@ -3584,9 +3638,9 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
 
 static void ieee80211_color_change_abort(struct ieee80211_sub_if_data  *sdata)
 {
-       sdata->vif.color_change_active = false;
+       sdata->vif.bss_conf.color_change_active = false;
 
-       ieee80211_free_next_beacon(sdata);
+       ieee80211_free_next_beacon(&sdata->deflink);
 
        cfg80211_color_change_aborted_notify(sdata->dev);
 }
@@ -3617,11 +3671,11 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
                return -EINVAL;
 
        /* don't allow another channel switch if one is already active. */
-       if (sdata->vif.csa_active)
+       if (sdata->vif.bss_conf.csa_active)
                return -EBUSY;
 
        mutex_lock(&local->chanctx_mtx);
-       conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+       conf = rcu_dereference_protected(sdata->vif.bss_conf.chanctx_conf,
                                         lockdep_is_held(&local->chanctx_mtx));
        if (!conf) {
                err = -EBUSY;
@@ -3646,42 +3700,44 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
        if (err)
                goto out;
 
-       err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
-                                           chanctx->mode,
-                                           params->radar_required);
+       err = ieee80211_link_reserve_chanctx(&sdata->deflink, &params->chandef,
+                                            chanctx->mode,
+                                            params->radar_required);
        if (err)
                goto out;
 
        /* if reservation is invalid then this will fail */
        err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0);
        if (err) {
-               ieee80211_vif_unreserve_chanctx(sdata);
+               ieee80211_link_unreserve_chanctx(&sdata->deflink);
                goto out;
        }
 
        /* if there is a color change in progress, abort it */
-       if (sdata->vif.color_change_active)
+       if (sdata->vif.bss_conf.color_change_active)
                ieee80211_color_change_abort(sdata);
 
        err = ieee80211_set_csa_beacon(sdata, params, &changed);
        if (err) {
-               ieee80211_vif_unreserve_chanctx(sdata);
+               ieee80211_link_unreserve_chanctx(&sdata->deflink);
                goto out;
        }
 
-       sdata->csa_chandef = params->chandef;
-       sdata->csa_block_tx = params->block_tx;
-       sdata->vif.csa_active = true;
+       sdata->deflink.csa_chandef = params->chandef;
+       sdata->deflink.csa_block_tx = params->block_tx;
+       sdata->vif.bss_conf.csa_active = true;
 
-       if (sdata->csa_block_tx)
+       if (sdata->deflink.csa_block_tx)
                ieee80211_stop_vif_queues(local, sdata,
                                          IEEE80211_QUEUE_STOP_REASON_CSA);
 
-       cfg80211_ch_switch_started_notify(sdata->dev, &sdata->csa_chandef,
+       cfg80211_ch_switch_started_notify(sdata->dev,
+                                         &sdata->deflink.csa_chandef,
                                          params->count, params->block_tx);
 
        if (changed) {
-               ieee80211_bss_info_change_notify(sdata, changed);
+               ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                                 changed);
                drv_channel_switch_beacon(sdata, &params->chandef);
        } else {
                /* if the beacon didn't change, we can finalize immediately */
@@ -3840,7 +3896,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
        mutex_lock(&local->mtx);
 
        rcu_read_lock();
-       chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+       chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
        if (WARN_ON(!chanctx_conf)) {
                ret = -EINVAL;
                goto unlock;
@@ -3914,17 +3970,25 @@ unlock:
 
 static int ieee80211_cfg_get_channel(struct wiphy *wiphy,
                                     struct wireless_dev *wdev,
+                                    unsigned int link_id,
                                     struct cfg80211_chan_def *chandef)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
        struct ieee80211_local *local = wiphy_priv(wiphy);
        struct ieee80211_chanctx_conf *chanctx_conf;
+       struct ieee80211_link_data *link;
        int ret = -ENODATA;
 
        rcu_read_lock();
-       chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+       link = rcu_dereference(sdata->link[link_id]);
+       if (!link) {
+               ret = -ENOLINK;
+               goto out;
+       }
+
+       chanctx_conf = rcu_dereference(link->conf->chanctx_conf);
        if (chanctx_conf) {
-               *chandef = sdata->vif.bss_conf.chandef;
+               *chandef = link->conf->chandef;
                ret = 0;
        } else if (local->open_count > 0 &&
                   local->open_count == local->monitors &&
@@ -3935,6 +3999,7 @@ static int ieee80211_cfg_get_channel(struct wiphy *wiphy,
                        *chandef = local->_oper_chandef;
                ret = 0;
        }
+out:
        rcu_read_unlock();
 
        return ret;
@@ -3974,15 +4039,19 @@ static int ieee80211_set_qos_map(struct wiphy *wiphy,
 
 static int ieee80211_set_ap_chanwidth(struct wiphy *wiphy,
                                      struct net_device *dev,
+                                     unsigned int link_id,
                                      struct cfg80211_chan_def *chandef)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_link_data *link;
        int ret;
        u32 changed = 0;
 
-       ret = ieee80211_vif_change_bandwidth(sdata, chandef, &changed);
+       link = sdata_dereference(sdata->link[link_id], sdata);
+
+       ret = ieee80211_link_change_bandwidth(link, chandef, &changed);
        if (ret == 0)
-               ieee80211_bss_info_change_notify(sdata, changed);
+               ieee80211_link_info_change_notify(sdata, link, changed);
 
        return ret;
 }
@@ -4322,12 +4391,13 @@ ieee80211_set_after_color_change_beacon(struct ieee80211_sub_if_data *sdata,
        case NL80211_IFTYPE_AP: {
                int ret;
 
-               if (!sdata->u.ap.next_beacon)
+               if (!sdata->deflink.u.ap.next_beacon)
                        return -EINVAL;
 
-               ret = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon,
+               ret = ieee80211_assign_beacon(sdata, &sdata->deflink,
+                                             sdata->deflink.u.ap.next_beacon,
                                              NULL, NULL);
-               ieee80211_free_next_beacon(sdata);
+               ieee80211_free_next_beacon(&sdata->deflink);
 
                if (ret < 0)
                        return ret;
@@ -4353,9 +4423,9 @@ ieee80211_set_color_change_beacon(struct ieee80211_sub_if_data *sdata,
 
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_AP:
-               sdata->u.ap.next_beacon =
+               sdata->deflink.u.ap.next_beacon =
                        cfg80211_beacon_dup(&params->beacon_next);
-               if (!sdata->u.ap.next_beacon)
+               if (!sdata->deflink.u.ap.next_beacon)
                        return -ENOMEM;
 
                if (params->count <= 1)
@@ -4367,10 +4437,11 @@ ieee80211_set_color_change_beacon(struct ieee80211_sub_if_data *sdata,
                        params->counter_offset_presp;
                color_change.count = params->count;
 
-               err = ieee80211_assign_beacon(sdata, &params->beacon_color_change,
+               err = ieee80211_assign_beacon(sdata, &sdata->deflink,
+                                             &params->beacon_color_change,
                                              NULL, &color_change);
                if (err < 0) {
-                       ieee80211_free_next_beacon(sdata);
+                       ieee80211_free_next_beacon(&sdata->deflink);
                        return err;
                }
                *changed |= err;
@@ -4390,7 +4461,7 @@ ieee80211_color_change_bss_config_notify(struct ieee80211_sub_if_data *sdata,
        sdata->vif.bss_conf.he_bss_color.enabled = enable;
        changed |= BSS_CHANGED_HE_BSS_COLOR;
 
-       ieee80211_bss_info_change_notify(sdata, changed);
+       ieee80211_link_info_change_notify(sdata, &sdata->deflink, changed);
 
        if (!sdata->vif.bss_conf.nontransmitted && sdata->vif.mbssid_tx_vif) {
                struct ieee80211_sub_if_data *child;
@@ -4400,8 +4471,9 @@ ieee80211_color_change_bss_config_notify(struct ieee80211_sub_if_data *sdata,
                        if (child != sdata && child->vif.mbssid_tx_vif == &sdata->vif) {
                                child->vif.bss_conf.he_bss_color.color = color;
                                child->vif.bss_conf.he_bss_color.enabled = enable;
-                               ieee80211_bss_info_change_notify(child,
-                                                                BSS_CHANGED_HE_BSS_COLOR);
+                               ieee80211_link_info_change_notify(child,
+                                                                 &child->deflink,
+                                                                 BSS_CHANGED_HE_BSS_COLOR);
                        }
                }
                mutex_unlock(&sdata->local->iflist_mtx);
@@ -4417,7 +4489,7 @@ static int ieee80211_color_change_finalize(struct ieee80211_sub_if_data *sdata)
        sdata_assert_lock(sdata);
        lockdep_assert_held(&local->mtx);
 
-       sdata->vif.color_change_active = false;
+       sdata->vif.bss_conf.color_change_active = false;
 
        err = ieee80211_set_after_color_change_beacon(sdata, &changed);
        if (err) {
@@ -4426,7 +4498,7 @@ static int ieee80211_color_change_finalize(struct ieee80211_sub_if_data *sdata)
        }
 
        ieee80211_color_change_bss_config_notify(sdata,
-                                                sdata->vif.color_change_color,
+                                                sdata->vif.bss_conf.color_change_color,
                                                 1, changed);
        cfg80211_color_change_notify(sdata->dev);
 
@@ -4437,14 +4509,14 @@ void ieee80211_color_change_finalize_work(struct work_struct *work)
 {
        struct ieee80211_sub_if_data *sdata =
                container_of(work, struct ieee80211_sub_if_data,
-                            color_change_finalize_work);
+                            deflink.color_change_finalize_work);
        struct ieee80211_local *local = sdata->local;
 
        sdata_lock(sdata);
        mutex_lock(&local->mtx);
 
        /* AP might have been stopped while waiting for the lock. */
-       if (!sdata->vif.color_change_active)
+       if (!sdata->vif.bss_conf.color_change_active)
                goto unlock;
 
        if (!ieee80211_sdata_running(sdata))
@@ -4462,7 +4534,7 @@ void ieee80211_color_change_finish(struct ieee80211_vif *vif)
        struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 
        ieee80211_queue_work(&sdata->local->hw,
-                            &sdata->color_change_finalize_work);
+                            &sdata->deflink.color_change_finalize_work);
 }
 EXPORT_SYMBOL_GPL(ieee80211_color_change_finish);
 
@@ -4472,7 +4544,7 @@ ieeee80211_obss_color_collision_notify(struct ieee80211_vif *vif,
 {
        struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 
-       if (sdata->vif.color_change_active || sdata->vif.csa_active)
+       if (sdata->vif.bss_conf.color_change_active || sdata->vif.bss_conf.csa_active)
                return;
 
        cfg80211_obss_color_collision_notify(sdata->dev, color_bitmap, gfp);
@@ -4498,7 +4570,7 @@ ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev,
        /* don't allow another color change if one is already active or if csa
         * is active
         */
-       if (sdata->vif.color_change_active || sdata->vif.csa_active) {
+       if (sdata->vif.bss_conf.color_change_active || sdata->vif.bss_conf.csa_active) {
                err = -EBUSY;
                goto out;
        }
@@ -4507,8 +4579,8 @@ ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev,
        if (err)
                goto out;
 
-       sdata->vif.color_change_active = true;
-       sdata->vif.color_change_color = params->color;
+       sdata->vif.bss_conf.color_change_active = true;
+       sdata->vif.bss_conf.color_change_color = params->color;
 
        cfg80211_color_change_started_notify(sdata->dev, params->count);
 
@@ -4536,6 +4608,136 @@ ieee80211_set_radar_background(struct wiphy *wiphy,
        return local->ops->set_radar_background(&local->hw, chandef);
 }
 
+static int ieee80211_add_intf_link(struct wiphy *wiphy,
+                                  struct wireless_dev *wdev,
+                                  unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+       return ieee80211_vif_set_links(sdata, wdev->valid_links);
+}
+
+static void ieee80211_del_intf_link(struct wiphy *wiphy,
+                                   struct wireless_dev *wdev,
+                                   unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+       ieee80211_vif_set_links(sdata, wdev->valid_links);
+}
+
+static int sta_add_link_station(struct ieee80211_local *local,
+                               struct ieee80211_sub_if_data *sdata,
+                               struct link_station_parameters *params)
+{
+       struct sta_info *sta;
+       int ret;
+
+       sta = sta_info_get_bss(sdata, params->mld_mac);
+       if (!sta)
+               return -ENOENT;
+
+       if (!sta->sta.valid_links)
+               return -EINVAL;
+
+       if (sta->sta.valid_links & BIT(params->link_id))
+               return -EALREADY;
+
+       ret = ieee80211_sta_allocate_link(sta, params->link_id);
+       if (ret)
+               return ret;
+
+       ret = sta_link_apply_parameters(local, sta, true, params);
+       if (ret) {
+               ieee80211_sta_free_link(sta, params->link_id);
+               return ret;
+       }
+
+       /* ieee80211_sta_activate_link frees the link upon failure */
+       return ieee80211_sta_activate_link(sta, params->link_id);
+}
+
+static int
+ieee80211_add_link_station(struct wiphy *wiphy, struct net_device *dev,
+                          struct link_station_parameters *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = wiphy_priv(wiphy);
+       int ret;
+
+       mutex_lock(&sdata->local->sta_mtx);
+       ret = sta_add_link_station(local, sdata, params);
+       mutex_unlock(&sdata->local->sta_mtx);
+
+       return ret;
+}
+
+static int sta_mod_link_station(struct ieee80211_local *local,
+                               struct ieee80211_sub_if_data *sdata,
+                               struct link_station_parameters *params)
+{
+       struct sta_info *sta;
+
+       sta = sta_info_get_bss(sdata, params->mld_mac);
+       if (!sta)
+               return -ENOENT;
+
+       if (!(sta->sta.valid_links & BIT(params->link_id)))
+               return -EINVAL;
+
+       return sta_link_apply_parameters(local, sta, false, params);
+}
+
+static int
+ieee80211_mod_link_station(struct wiphy *wiphy, struct net_device *dev,
+                          struct link_station_parameters *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = wiphy_priv(wiphy);
+       int ret;
+
+       mutex_lock(&sdata->local->sta_mtx);
+       ret = sta_mod_link_station(local, sdata, params);
+       mutex_unlock(&sdata->local->sta_mtx);
+
+       return ret;
+}
+
+static int sta_del_link_station(struct ieee80211_sub_if_data *sdata,
+                               struct link_station_del_parameters *params)
+{
+       struct sta_info *sta;
+
+       sta = sta_info_get_bss(sdata, params->mld_mac);
+       if (!sta)
+               return -ENOENT;
+
+       if (!(sta->sta.valid_links & BIT(params->link_id)))
+               return -EINVAL;
+
+       /* must not create a STA without links */
+       if (sta->sta.valid_links == BIT(params->link_id))
+               return -EINVAL;
+
+       ieee80211_sta_remove_link(sta, params->link_id);
+
+       return 0;
+}
+
+static int
+ieee80211_del_link_station(struct wiphy *wiphy, struct net_device *dev,
+                          struct link_station_del_parameters *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       int ret;
+
+       mutex_lock(&sdata->local->sta_mtx);
+       ret = sta_del_link_station(sdata, params);
+       mutex_unlock(&sdata->local->sta_mtx);
+
+       return ret;
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -4641,4 +4843,9 @@ const struct cfg80211_ops mac80211_config_ops = {
        .set_sar_specs = ieee80211_set_sar_specs,
        .color_change = ieee80211_color_change,
        .set_radar_background = ieee80211_set_radar_background,
+       .add_intf_link = ieee80211_add_intf_link,
+       .del_intf_link = ieee80211_del_intf_link,
+       .add_link_station = ieee80211_add_link_station,
+       .mod_link_station = ieee80211_mod_link_station,
+       .del_link_station = ieee80211_del_link_station,
 };