Merge tag 's390-5.20-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[linux-2.6-microblaze.git] / net / mac80211 / sta_info.c
index e04a090..cb23da9 100644 (file)
  * freed before they are done using it.
  */
 
+struct sta_link_alloc {
+       struct link_sta_info info;
+       struct ieee80211_link_sta sta;
+       struct rcu_head rcu_head;
+};
+
 static const struct rhashtable_params sta_rht_params = {
        .nelem_hint = 3, /* start small */
        .automatic_shrinking = true,
@@ -73,6 +79,15 @@ static const struct rhashtable_params sta_rht_params = {
        .max_size = CONFIG_MAC80211_STA_HASH_MAX_SIZE,
 };
 
+static const struct rhashtable_params link_sta_rht_params = {
+       .nelem_hint = 3, /* start small */
+       .automatic_shrinking = true,
+       .head_offset = offsetof(struct link_sta_info, link_hash_node),
+       .key_offset = offsetof(struct link_sta_info, addr),
+       .key_len = ETH_ALEN,
+       .max_size = CONFIG_MAC80211_STA_HASH_MAX_SIZE,
+};
+
 /* Caller must hold local->sta_mtx */
 static int sta_info_hash_del(struct ieee80211_local *local,
                             struct sta_info *sta)
@@ -81,6 +96,24 @@ static int sta_info_hash_del(struct ieee80211_local *local,
                               sta_rht_params);
 }
 
+static int link_sta_info_hash_add(struct ieee80211_local *local,
+                                 struct link_sta_info *link_sta)
+{
+       lockdep_assert_held(&local->sta_mtx);
+       return rhltable_insert(&local->link_sta_hash,
+                              &link_sta->link_hash_node,
+                              link_sta_rht_params);
+}
+
+static int link_sta_info_hash_del(struct ieee80211_local *local,
+                                 struct link_sta_info *link_sta)
+{
+       lockdep_assert_held(&local->sta_mtx);
+       return rhltable_remove(&local->link_sta_hash,
+                              &link_sta->link_hash_node,
+                              link_sta_rht_params);
+}
+
 static void __cleanup_single_sta(struct sta_info *sta)
 {
        int ac, i;
@@ -210,6 +243,37 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
        return NULL;
 }
 
+struct rhlist_head *link_sta_info_hash_lookup(struct ieee80211_local *local,
+                                             const u8 *addr)
+{
+       return rhltable_lookup(&local->link_sta_hash, addr,
+                              link_sta_rht_params);
+}
+
+struct link_sta_info *
+link_sta_info_get_bss(struct ieee80211_sub_if_data *sdata, const u8 *addr)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct rhlist_head *tmp;
+       struct link_sta_info *link_sta;
+
+       rcu_read_lock();
+       for_each_link_sta_info(local, addr, link_sta, tmp) {
+               struct sta_info *sta = link_sta->sta;
+
+               if (sta->sdata == sdata ||
+                   (sta->sdata->bss && sta->sdata->bss == sdata->bss)) {
+                       rcu_read_unlock();
+                       /* this is safe as the caller must already hold
+                        * another rcu read section or the mutex
+                        */
+                       return link_sta;
+               }
+       }
+       rcu_read_unlock();
+       return NULL;
+}
+
 struct sta_info *sta_info_get_by_addrs(struct ieee80211_local *local,
                                       const u8 *sta_addr, const u8 *vif_addr)
 {
@@ -245,6 +309,38 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
        return NULL;
 }
 
+static void sta_info_free_link(struct link_sta_info *link_sta)
+{
+       free_percpu(link_sta->pcpu_rx_stats);
+}
+
+static void sta_remove_link(struct sta_info *sta, unsigned int link_id,
+                           bool unhash)
+{
+       struct sta_link_alloc *alloc = NULL;
+       struct link_sta_info *link_sta;
+
+       link_sta = rcu_dereference_protected(sta->link[link_id],
+                                            lockdep_is_held(&sta->local->sta_mtx));
+
+       if (WARN_ON(!link_sta))
+               return;
+
+       if (unhash)
+               link_sta_info_hash_del(sta->local, link_sta);
+
+       if (link_sta != &sta->deflink)
+               alloc = container_of(link_sta, typeof(*alloc), info);
+
+       sta->sta.valid_links &= ~BIT(link_id);
+       RCU_INIT_POINTER(sta->link[link_id], NULL);
+       RCU_INIT_POINTER(sta->sta.link[link_id], NULL);
+       if (alloc) {
+               sta_info_free_link(&alloc->info);
+               kfree_rcu(alloc, rcu_head);
+       }
+}
+
 /**
  * sta_info_free - free STA
  *
@@ -258,6 +354,15 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
  */
 void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
 {
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(sta->link); i++) {
+               if (!(sta->sta.valid_links & BIT(i)))
+                       continue;
+
+               sta_remove_link(sta, i, false);
+       }
+
        /*
         * If we had used sta_info_pre_move_state() then we might not
         * have gone through the state transitions down again, so do
@@ -287,7 +392,8 @@ void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
 #ifdef CONFIG_MAC80211_MESH
        kfree(sta->mesh);
 #endif
-       free_percpu(sta->deflink.pcpu_rx_stats);
+
+       sta_info_free_link(&sta->deflink);
        kfree(sta);
 }
 
@@ -333,8 +439,47 @@ static int sta_prepare_rate_control(struct ieee80211_local *local,
        return 0;
 }
 
-struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
-                               const u8 *addr, gfp_t gfp)
+static int sta_info_alloc_link(struct ieee80211_local *local,
+                              struct link_sta_info *link_info,
+                              gfp_t gfp)
+{
+       struct ieee80211_hw *hw = &local->hw;
+       int i;
+
+       if (ieee80211_hw_check(hw, USES_RSS)) {
+               link_info->pcpu_rx_stats =
+                       alloc_percpu_gfp(struct ieee80211_sta_rx_stats, gfp);
+               if (!link_info->pcpu_rx_stats)
+                       return -ENOMEM;
+       }
+
+       link_info->rx_stats.last_rx = jiffies;
+       u64_stats_init(&link_info->rx_stats.syncp);
+
+       ewma_signal_init(&link_info->rx_stats_avg.signal);
+       ewma_avg_signal_init(&link_info->status_stats.avg_ack_signal);
+       for (i = 0; i < ARRAY_SIZE(link_info->rx_stats_avg.chain_signal); i++)
+               ewma_signal_init(&link_info->rx_stats_avg.chain_signal[i]);
+
+       return 0;
+}
+
+static void sta_info_add_link(struct sta_info *sta,
+                             unsigned int link_id,
+                             struct link_sta_info *link_info,
+                             struct ieee80211_link_sta *link_sta)
+{
+       link_info->sta = sta;
+       link_info->link_id = link_id;
+       link_info->pub = link_sta;
+       rcu_assign_pointer(sta->link[link_id], link_info);
+       rcu_assign_pointer(sta->sta.link[link_id], link_sta);
+}
+
+static struct sta_info *
+__sta_info_alloc(struct ieee80211_sub_if_data *sdata,
+                const u8 *addr, int link_id, const u8 *link_addr,
+                gfp_t gfp)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_hw *hw = &local->hw;
@@ -345,11 +490,18 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        if (!sta)
                return NULL;
 
-       if (ieee80211_hw_check(hw, USES_RSS)) {
-               sta->deflink.pcpu_rx_stats =
-                       alloc_percpu_gfp(struct ieee80211_sta_rx_stats, gfp);
-               if (!sta->deflink.pcpu_rx_stats)
-                       goto free;
+       sta->local = local;
+       sta->sdata = sdata;
+
+       if (sta_info_alloc_link(local, &sta->deflink, gfp))
+               return NULL;
+
+       if (link_id >= 0) {
+               sta_info_add_link(sta, link_id, &sta->deflink,
+                                 &sta->sta.deflink);
+               sta->sta.valid_links = BIT(link_id);
+       } else {
+               sta_info_add_link(sta, 0, &sta->deflink, &sta->sta.deflink);
        }
 
        spin_lock_init(&sta->lock);
@@ -373,17 +525,13 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
 
        memcpy(sta->addr, addr, ETH_ALEN);
        memcpy(sta->sta.addr, addr, ETH_ALEN);
+       memcpy(sta->deflink.addr, link_addr, ETH_ALEN);
+       memcpy(sta->sta.deflink.addr, link_addr, ETH_ALEN);
        sta->sta.max_rx_aggregation_subframes =
                local->hw.max_rx_aggregation_subframes;
 
        /* TODO link specific alloc and assignments for MLO Link STA */
 
-       /* For non MLO STA, link info can be accessed either via deflink
-        * or link[0]
-        */
-       sta->link[0] = &sta->deflink;
-       sta->sta.link[0] = &sta->sta.deflink;
-
        /* Extended Key ID needs to install keys for keyid 0 and 1 Rx-only.
         * The Tx path starts to use a key as soon as the key slot ptk_idx
         * references to is not NULL. To not use the initial Rx-only key
@@ -393,11 +541,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        BUILD_BUG_ON(ARRAY_SIZE(sta->ptk) <= INVALID_PTK_KEYIDX);
        sta->ptk_idx = INVALID_PTK_KEYIDX;
 
-       sta->local = local;
-       sta->sdata = sdata;
-       sta->deflink.rx_stats.last_rx = jiffies;
-
-       u64_stats_init(&sta->deflink.rx_stats.syncp);
 
        ieee80211_init_frag_cache(&sta->frags);
 
@@ -407,10 +550,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        sta->reserved_tid = IEEE80211_TID_UNRESERVED;
 
        sta->last_connected = ktime_get_seconds();
-       ewma_signal_init(&sta->deflink.rx_stats_avg.signal);
-       ewma_avg_signal_init(&sta->deflink.status_stats.avg_ack_signal);
-       for (i = 0; i < ARRAY_SIZE(sta->deflink.rx_stats_avg.chain_signal); i++)
-               ewma_signal_init(&sta->deflink.rx_stats_avg.chain_signal[i]);
 
        if (local->ops->wake_tx_queue) {
                void *txq_data;
@@ -432,11 +571,15 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        if (sta_prepare_rate_control(local, sta, gfp))
                goto free_txq;
 
+       sta->airtime_weight = IEEE80211_DEFAULT_AIRTIME_WEIGHT;
 
        for (i = 0; i < IEEE80211_NUM_ACS; i++) {
                skb_queue_head_init(&sta->ps_tx_buf[i]);
                skb_queue_head_init(&sta->tx_filtered[i]);
-               init_airtime_info(&sta->airtime[i], &local->airtime[i]);
+               sta->airtime[i].deficit = sta->airtime_weight;
+               atomic_set(&sta->airtime[i].aql_tx_pending, 0);
+               sta->airtime[i].aql_limit_low = local->aql_txq_limit_low[i];
+               sta->airtime[i].aql_limit_high = local->aql_txq_limit_high[i];
        }
 
        for (i = 0; i < IEEE80211_NUM_TIDS; i++)
@@ -485,36 +628,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        }
 
        sta->sta.smps_mode = IEEE80211_SMPS_OFF;
-       if (sdata->vif.type == NL80211_IFTYPE_AP ||
-           sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
-               struct ieee80211_supported_band *sband;
-               u8 smps;
-
-               sband = ieee80211_get_sband(sdata);
-               if (!sband)
-                       goto free_txq;
-
-               smps = (sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >>
-                       IEEE80211_HT_CAP_SM_PS_SHIFT;
-               /*
-                * Assume that hostapd advertises our caps in the beacon and
-                * this is the known_smps_mode for a station that just assciated
-                */
-               switch (smps) {
-               case WLAN_HT_SMPS_CONTROL_DISABLED:
-                       sta->known_smps_mode = IEEE80211_SMPS_OFF;
-                       break;
-               case WLAN_HT_SMPS_CONTROL_STATIC:
-                       sta->known_smps_mode = IEEE80211_SMPS_STATIC;
-                       break;
-               case WLAN_HT_SMPS_CONTROL_DYNAMIC:
-                       sta->known_smps_mode = IEEE80211_SMPS_DYNAMIC;
-                       break;
-               default:
-                       WARN_ON(1);
-               }
-       }
-
        sta->sta.max_rc_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_BA;
 
        sta->cparams.ce_threshold = CODEL_DISABLED_THRESHOLD;
@@ -532,7 +645,7 @@ free_txq:
        if (sta->sta.txq[0])
                kfree(to_txq_info(sta->sta.txq[0]));
 free:
-       free_percpu(sta->deflink.pcpu_rx_stats);
+       sta_info_free_link(&sta->deflink);
 #ifdef CONFIG_MAC80211_MESH
        kfree(sta->mesh);
 #endif
@@ -540,6 +653,21 @@ free:
        return NULL;
 }
 
+struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
+                               const u8 *addr, gfp_t gfp)
+{
+       return __sta_info_alloc(sdata, addr, -1, addr, gfp);
+}
+
+struct sta_info *sta_info_alloc_with_link(struct ieee80211_sub_if_data *sdata,
+                                         const u8 *mld_addr,
+                                         unsigned int link_id,
+                                         const u8 *link_addr,
+                                         gfp_t gfp)
+{
+       return __sta_info_alloc(sdata, mld_addr, link_id, link_addr, gfp);
+}
+
 static int sta_info_insert_check(struct sta_info *sta)
 {
        struct ieee80211_sub_if_data *sdata = sta->sdata;
@@ -630,7 +758,8 @@ ieee80211_recalc_p2p_go_ps_allowed(struct ieee80211_sub_if_data *sdata)
 
        if (allow_p2p_go_ps != sdata->vif.bss_conf.allow_p2p_go_ps) {
                sdata->vif.bss_conf.allow_p2p_go_ps = allow_p2p_go_ps;
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_P2P_PS);
+               ieee80211_link_info_change_notify(sdata, &sdata->deflink,
+                                                 BSS_CHANGED_P2P_PS);
        }
 }
 
@@ -672,13 +801,21 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
        if (err)
                goto out_drop_sta;
 
+       if (sta->sta.valid_links) {
+               err = link_sta_info_hash_add(local, &sta->deflink);
+               if (err) {
+                       sta_info_hash_del(local, sta);
+                       goto out_drop_sta;
+               }
+       }
+
        list_add_tail_rcu(&sta->list, &local->sta_list);
 
        /* update channel context before notifying the driver about state
         * change, this enables driver using the updated channel context right away.
         */
        if (sta->sta_state >= IEEE80211_STA_ASSOC) {
-               ieee80211_recalc_min_chandef(sta->sdata);
+               ieee80211_recalc_min_chandef(sta->sdata, -1);
                if (!sta->sta.support_p2p_ps)
                        ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
        }
@@ -711,6 +848,8 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
 
        return 0;
  out_remove:
+       if (sta->sta.valid_links)
+               link_sta_info_hash_del(local, &sta->deflink);
        sta_info_hash_del(local, sta);
        list_del_rcu(&sta->list);
  out_drop_sta:
@@ -1005,7 +1144,7 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
 {
        struct ieee80211_local *local;
        struct ieee80211_sub_if_data *sdata;
-       int ret;
+       int ret, i;
 
        might_sleep();
 
@@ -1033,6 +1172,18 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
         */
        drv_sync_rx_queues(local, sta);
 
+       for (i = 0; i < ARRAY_SIZE(sta->link); i++) {
+               struct link_sta_info *link_sta;
+
+               if (!(sta->sta.valid_links & BIT(i)))
+                       continue;
+
+               link_sta = rcu_dereference_protected(sta->link[i],
+                                                    lockdep_is_held(&local->sta_mtx));
+
+               link_sta_info_hash_del(local, link_sta);
+       }
+
        ret = sta_info_hash_del(local, sta);
        if (WARN_ON(ret))
                return ret;
@@ -1189,6 +1340,12 @@ int sta_info_init(struct ieee80211_local *local)
        if (err)
                return err;
 
+       err = rhltable_init(&local->link_sta_hash, &link_sta_rht_params);
+       if (err) {
+               rhltable_destroy(&local->sta_hash);
+               return err;
+       }
+
        spin_lock_init(&local->tim_lock);
        mutex_init(&local->sta_mtx);
        INIT_LIST_HEAD(&local->sta_list);
@@ -1201,6 +1358,7 @@ void sta_info_stop(struct ieee80211_local *local)
 {
        del_timer_sync(&local->sta_cleanup);
        rhltable_destroy(&local->sta_hash);
+       rhltable_destroy(&local->link_sta_hash);
 }
 
 
@@ -1467,7 +1625,7 @@ static void ieee80211_send_null_response(struct sta_info *sta, int tid,
        skb->dev = sdata->dev;
 
        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)) {
                rcu_read_unlock();
                kfree_skb(skb);
@@ -1901,59 +2059,29 @@ void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
 }
 EXPORT_SYMBOL(ieee80211_sta_set_buffered);
 
-void ieee80211_register_airtime(struct ieee80211_txq *txq,
-                               u32 tx_airtime, u32 rx_airtime)
+void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+                                   u32 tx_airtime, u32 rx_airtime)
 {
-       struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->vif);
-       struct ieee80211_local *local = sdata->local;
-       u64 weight_sum, weight_sum_reciprocal;
-       struct airtime_sched_info *air_sched;
-       struct airtime_info *air_info;
+       struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+       struct ieee80211_local *local = sta->sdata->local;
+       u8 ac = ieee80211_ac_from_tid(tid);
        u32 airtime = 0;
+       u32 diff;
 
-       air_sched = &local->airtime[txq->ac];
-       air_info = to_airtime_info(txq);
-
-       if (local->airtime_flags & AIRTIME_USE_TX)
+       if (sta->local->airtime_flags & AIRTIME_USE_TX)
                airtime += tx_airtime;
-       if (local->airtime_flags & AIRTIME_USE_RX)
+       if (sta->local->airtime_flags & AIRTIME_USE_RX)
                airtime += rx_airtime;
 
-       /* Weights scale so the unit weight is 256 */
-       airtime <<= 8;
-
-       spin_lock_bh(&air_sched->lock);
-
-       air_info->tx_airtime += tx_airtime;
-       air_info->rx_airtime += rx_airtime;
-
-       if (air_sched->weight_sum) {
-               weight_sum = air_sched->weight_sum;
-               weight_sum_reciprocal = air_sched->weight_sum_reciprocal;
-       } else {
-               weight_sum = air_info->weight;
-               weight_sum_reciprocal = air_info->weight_reciprocal;
-       }
-
-       /* Round the calculation of global vt */
-       air_sched->v_t += (u64)((airtime + (weight_sum >> 1)) *
-                               weight_sum_reciprocal) >> IEEE80211_RECIPROCAL_SHIFT_64;
-       air_info->v_t += (u32)((airtime + (air_info->weight >> 1)) *
-                              air_info->weight_reciprocal) >> IEEE80211_RECIPROCAL_SHIFT_32;
-       ieee80211_resort_txq(&local->hw, txq);
+       spin_lock_bh(&local->active_txq_lock[ac]);
+       sta->airtime[ac].tx_airtime += tx_airtime;
+       sta->airtime[ac].rx_airtime += rx_airtime;
 
-       spin_unlock_bh(&air_sched->lock);
-}
-
-void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
-                                   u32 tx_airtime, u32 rx_airtime)
-{
-       struct ieee80211_txq *txq = pubsta->txq[tid];
-
-       if (!txq)
-               return;
+       diff = (u32)jiffies - sta->airtime[ac].last_active;
+       if (diff <= AIRTIME_ACTIVE_DURATION)
+               sta->airtime[ac].deficit -= airtime;
 
-       ieee80211_register_airtime(txq, tx_airtime, rx_airtime);
+       spin_unlock_bh(&local->active_txq_lock[ac]);
 }
 EXPORT_SYMBOL(ieee80211_sta_register_airtime);
 
@@ -1972,6 +2100,7 @@ void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
                                   &sta->airtime[ac].aql_tx_pending);
 
                atomic_add(tx_airtime, &local->aql_total_pending_airtime);
+               atomic_add(tx_airtime, &local->aql_ac_pending_airtime[ac]);
                return;
        }
 
@@ -1983,14 +2112,17 @@ void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
                                       tx_pending, 0);
        }
 
+       atomic_sub(tx_airtime, &local->aql_total_pending_airtime);
        tx_pending = atomic_sub_return(tx_airtime,
-                                      &local->aql_total_pending_airtime);
+                                      &local->aql_ac_pending_airtime[ac]);
        if (WARN_ONCE(tx_pending < 0,
                      "Device %s AC %d pending airtime underflow: %u, %u",
                      wiphy_name(local->hw.wiphy), ac, tx_pending,
-                     tx_airtime))
-               atomic_cmpxchg(&local->aql_total_pending_airtime,
+                     tx_airtime)) {
+               atomic_cmpxchg(&local->aql_ac_pending_airtime[ac],
                               tx_pending, 0);
+               atomic_sub(tx_pending, &local->aql_total_pending_airtime);
+       }
 }
 
 int sta_info_move_state(struct sta_info *sta,
@@ -2053,7 +2185,7 @@ int sta_info_move_state(struct sta_info *sta,
                        set_bit(WLAN_STA_AUTH, &sta->_flags);
                } else if (sta->sta_state == IEEE80211_STA_ASSOC) {
                        clear_bit(WLAN_STA_ASSOC, &sta->_flags);
-                       ieee80211_recalc_min_chandef(sta->sdata);
+                       ieee80211_recalc_min_chandef(sta->sdata, -1);
                        if (!sta->sta.support_p2p_ps)
                                ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
                }
@@ -2062,7 +2194,7 @@ int sta_info_move_state(struct sta_info *sta,
                if (sta->sta_state == IEEE80211_STA_AUTH) {
                        set_bit(WLAN_STA_ASSOC, &sta->_flags);
                        sta->assoc_at = ktime_get_boottime_ns();
-                       ieee80211_recalc_min_chandef(sta->sdata);
+                       ieee80211_recalc_min_chandef(sta->sdata, -1);
                        if (!sta->sta.support_p2p_ps)
                                ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
                } else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
@@ -2093,41 +2225,6 @@ int sta_info_move_state(struct sta_info *sta,
        return 0;
 }
 
-u8 sta_info_tx_streams(struct sta_info *sta)
-{
-       struct ieee80211_sta_ht_cap *ht_cap = &sta->sta.deflink.ht_cap;
-       u8 rx_streams;
-
-       if (!sta->sta.deflink.ht_cap.ht_supported)
-               return 1;
-
-       if (sta->sta.deflink.vht_cap.vht_supported) {
-               int i;
-               u16 tx_mcs_map =
-                       le16_to_cpu(sta->sta.deflink.vht_cap.vht_mcs.tx_mcs_map);
-
-               for (i = 7; i >= 0; i--)
-                       if ((tx_mcs_map & (0x3 << (i * 2))) !=
-                           IEEE80211_VHT_MCS_NOT_SUPPORTED)
-                               return i + 1;
-       }
-
-       if (ht_cap->mcs.rx_mask[3])
-               rx_streams = 4;
-       else if (ht_cap->mcs.rx_mask[2])
-               rx_streams = 3;
-       else if (ht_cap->mcs.rx_mask[1])
-               rx_streams = 2;
-       else
-               rx_streams = 1;
-
-       if (!(ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_RX_DIFF))
-               return rx_streams;
-
-       return ((ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK)
-                       >> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT) + 1;
-}
-
 static struct ieee80211_sta_rx_stats *
 sta_get_last_rx_stats(struct sta_info *sta)
 {
@@ -2312,7 +2409,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
         * (or just modify the value entirely, of course)
         */
        if (sdata->vif.type == NL80211_IFTYPE_STATION)
-               sinfo->rx_beacon = sdata->u.mgd.count_beacon_signal;
+               sinfo->rx_beacon = sdata->deflink.u.mgd.count_beacon_signal;
 
        drv_sta_statistics(local, sdata, &sta->sta, sinfo);
        sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME) |
@@ -2323,7 +2420,8 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
                         BIT_ULL(NL80211_STA_INFO_RX_DROP_MISC);
 
        if (sdata->vif.type == NL80211_IFTYPE_STATION) {
-               sinfo->beacon_loss_count = sdata->u.mgd.beacon_loss_count;
+               sinfo->beacon_loss_count =
+                       sdata->deflink.u.mgd.beacon_loss_count;
                sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_LOSS);
        }
 
@@ -2401,7 +2499,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
        }
 
        if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT))) {
-               sinfo->airtime_weight = sta->airtime[0].weight;
+               sinfo->airtime_weight = sta->airtime_weight;
                sinfo->filled |= BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT);
        }
 
@@ -2458,13 +2556,15 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
                }
        }
 
-       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) {
+       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE)) &&
+           !sta->sta.valid_links) {
                sta_set_rate_info_tx(sta, &sta->deflink.tx_stats.last_rate,
                                     &sinfo->txrate);
                sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
        }
 
-       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_BITRATE))) {
+       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_BITRATE)) &&
+           !sta->sta.valid_links) {
                if (sta_set_rate_info_rx(sta, &sinfo->rxrate) == 0)
                        sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
        }
@@ -2614,3 +2714,123 @@ void ieee80211_sta_set_expected_throughput(struct ieee80211_sta *pubsta,
 
        sta_update_codel_params(sta, thr);
 }
+
+int ieee80211_sta_allocate_link(struct sta_info *sta, unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       struct sta_link_alloc *alloc;
+       int ret;
+
+       lockdep_assert_held(&sdata->local->sta_mtx);
+
+       /* must represent an MLD from the start */
+       if (WARN_ON(!sta->sta.valid_links))
+               return -EINVAL;
+
+       if (WARN_ON(sta->sta.valid_links & BIT(link_id) ||
+                   sta->link[link_id]))
+               return -EBUSY;
+
+       alloc = kzalloc(sizeof(*alloc), GFP_KERNEL);
+       if (!alloc)
+               return -ENOMEM;
+
+       ret = sta_info_alloc_link(sdata->local, &alloc->info, GFP_KERNEL);
+       if (ret) {
+               kfree(alloc);
+               return ret;
+       }
+
+       sta_info_add_link(sta, link_id, &alloc->info, &alloc->sta);
+
+       return 0;
+}
+
+void ieee80211_sta_free_link(struct sta_info *sta, unsigned int link_id)
+{
+       lockdep_assert_held(&sta->sdata->local->sta_mtx);
+
+       sta_remove_link(sta, link_id, false);
+}
+
+int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       struct link_sta_info *link_sta;
+       u16 old_links = sta->sta.valid_links;
+       u16 new_links = old_links | BIT(link_id);
+       int ret;
+
+       link_sta = rcu_dereference_protected(sta->link[link_id],
+                                            lockdep_is_held(&sdata->local->sta_mtx));
+
+       if (WARN_ON(old_links == new_links || !link_sta))
+               return -EINVAL;
+
+       rcu_read_lock();
+       if (link_sta_info_hash_lookup(sdata->local, link_sta->addr)) {
+               rcu_read_unlock();
+               return -EALREADY;
+       }
+       /* we only modify under the mutex so this is fine */
+       rcu_read_unlock();
+
+       sta->sta.valid_links = new_links;
+
+       if (!test_sta_flag(sta, WLAN_STA_INSERTED)) {
+               ret = 0;
+               goto hash;
+       }
+
+       ret = drv_change_sta_links(sdata->local, sdata, &sta->sta,
+                                  old_links, new_links);
+       if (ret) {
+               sta->sta.valid_links = old_links;
+               sta_remove_link(sta, link_id, false);
+               return ret;
+       }
+
+hash:
+       ret = link_sta_info_hash_add(sdata->local, link_sta);
+       WARN_ON(ret);
+       return 0;
+}
+
+void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+
+       lockdep_assert_held(&sdata->local->sta_mtx);
+
+       sta->sta.valid_links &= ~BIT(link_id);
+
+       if (test_sta_flag(sta, WLAN_STA_INSERTED))
+               drv_change_sta_links(sdata->local, sdata, &sta->sta,
+                                    sta->sta.valid_links,
+                                    sta->sta.valid_links & ~BIT(link_id));
+
+       sta_remove_link(sta, link_id, true);
+}
+
+void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
+                                          const u8 *ext_capab,
+                                          unsigned int ext_capab_len)
+{
+       u8 val;
+
+       sta->sta.max_amsdu_subframes = 0;
+
+       if (ext_capab_len < 8)
+               return;
+
+       /* The sender might not have sent the last bit, consider it to be 0 */
+       val = u8_get_bits(ext_capab[7], WLAN_EXT_CAPA8_MAX_MSDU_IN_AMSDU_LSB);
+
+       /* we did get all the bits, take the MSB as well */
+       if (ext_capab_len >= 9)
+               val |= u8_get_bits(ext_capab[8],
+                                  WLAN_EXT_CAPA9_MAX_MSDU_IN_AMSDU_MSB) << 1;
+
+       if (val)
+               sta->sta.max_amsdu_subframes = 4 << val;
+}