* @max_tid_amsdu_len: Maximum A-MSDU size in bytes for this TID
  * @txq: per-TID data TX queues (if driver uses the TXQ abstraction); note that
  *     the last entry (%IEEE80211_NUM_TIDS) is used for non-data frames
- * @multi_link_sta: Identifies if this sta is a MLD STA
  * @deflink: This holds the default link STA information, for non MLO STA all link
  *     specific STA information is accessed through @deflink or through
  *     link[0] which points to address of @deflink. For MLO Link STA
  *     @deflink address and remaining would be allocated and the address
  *     would be assigned to link[link_id] where link_id is the id assigned
  *     by the AP.
+ * @valid_links: bitmap of valid links, or 0 for non-MLO
  */
 struct ieee80211_sta {
        u8 addr[ETH_ALEN];
 
        struct ieee80211_txq *txq[IEEE80211_NUM_TIDS + 1];
 
-       bool multi_link_sta;
+       u16 valid_links;
        struct ieee80211_link_sta deflink;
        struct ieee80211_link_sta *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
  *     The @old[] array contains pointers to the old bss_conf structures
  *     that were already removed, in case they're needed.
  *     This callback can sleep.
+ * @change_sta_links: Change the valid links of a station, similar to
+ *     @change_vif_links. This callback can sleep.
+ *     Note that a sta can also be inserted or removed with valid links,
+ *     i.e. passed to @sta_add/@sta_state with sta->valid_links not zero.
+ *     In fact, cannot change from having valid_links and not having them.
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw,
                                struct ieee80211_vif *vif,
                                u16 old_links, u16 new_links,
                                struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS]);
+       int (*change_sta_links)(struct ieee80211_hw *hw,
+                               struct ieee80211_vif *vif,
+                               struct ieee80211_sta *sta,
+                               u16 old_links, u16 new_links);
 };
 
 /**
 
  * freed before they are done using it.
  */
 
+struct sta_link_alloc {
+       struct link_sta_info info;
+       struct ieee80211_link_sta sta;
+};
+
 static const struct rhashtable_params sta_rht_params = {
        .nelem_hint = 3, /* start small */
        .automatic_shrinking = true,
        return NULL;
 }
 
-static void sta_info_free_links(struct sta_info *sta)
+static void sta_info_free_link(struct link_sta_info *link_sta)
 {
-       unsigned int link_id;
+       free_percpu(link_sta->pcpu_rx_stats);
+}
 
-       for (link_id = 0; link_id < ARRAY_SIZE(sta->link); link_id++) {
-               if (!sta->link[link_id])
-                       continue;
-               free_percpu(sta->link[link_id]->pcpu_rx_stats);
+static void sta_remove_link(struct sta_info *sta, unsigned int link_id)
+{
+       struct sta_link_alloc *alloc = NULL;
 
-               if (sta->link[link_id] != &sta->deflink)
-                       kfree(sta->link[link_id]);
+       if (WARN_ON(!sta->link[link_id]))
+               return;
+
+       if (sta->link[link_id] != &sta->deflink)
+               alloc = container_of(sta->link[link_id], typeof(*alloc), info);
+
+       sta->sta.valid_links &= ~BIT(link_id);
+       sta->link[link_id] = NULL;
+       sta->sta.link[link_id] = NULL;
+       if (alloc) {
+               sta_info_free_link(&alloc->info);
+               kfree(alloc);
        }
 }
 
  */
 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);
+       }
+
        /*
         * If we had used sta_info_pre_move_state() then we might not
         * have gone through the state transitions down again, so do
        kfree(sta->mesh);
 #endif
 
-       sta_info_free_links(sta);
+       sta_info_free_link(&sta->deflink);
        kfree(sta);
 }
 
        return 0;
 }
 
-static int sta_info_init_link(struct sta_info *sta,
-                             unsigned int link_id,
-                             struct link_sta_info *link_info,
-                             struct ieee80211_link_sta *link_sta,
-                             gfp_t gfp)
+static int sta_info_alloc_link(struct ieee80211_local *local,
+                              struct link_sta_info *link_info,
+                              gfp_t gfp)
 {
-       struct ieee80211_local *local = sta->local;
        struct ieee80211_hw *hw = &local->hw;
        int i;
 
-       link_info->sta = sta;
-       link_info->link_id = link_id;
-
        if (ieee80211_hw_check(hw, USES_RSS)) {
                link_info->pcpu_rx_stats =
                        alloc_percpu_gfp(struct ieee80211_sta_rx_stats, gfp);
                        return -ENOMEM;
        }
 
-       sta->link[link_id] = link_info;
-       sta->sta.link[link_id] = link_sta;
-
        link_info->rx_stats.last_rx = jiffies;
        u64_stats_init(&link_info->rx_stats.syncp);
 
        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;
+       sta->link[link_id] = link_info;
+       sta->sta.link[link_id] = link_sta;
+}
+
 struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
-                               const u8 *addr, gfp_t gfp)
+                               const u8 *addr, int link_id, gfp_t gfp)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_hw *hw = &local->hw;
        sta->local = local;
        sta->sdata = sdata;
 
-       if (sta_info_init_link(sta, 0, &sta->deflink, &sta->sta.deflink, gfp))
+       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);
        spin_lock_init(&sta->ps_lock);
        INIT_WORK(&sta->drv_deliver_wk, sta_deliver_ps_frames);
        if (sta->sta.txq[0])
                kfree(to_txq_info(sta->sta.txq[0]));
 free:
-       sta_info_free_links(sta);
+       sta_info_free_link(&sta->deflink);
 #ifdef CONFIG_MAC80211_MESH
        kfree(sta->mesh);
 #endif
 
        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;
+}
+
+int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id)
+{
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       u16 old_links = sta->sta.valid_links;
+       u16 new_links = old_links | BIT(link_id);
+       int ret;
+
+       lockdep_assert_held(&sdata->local->sta_mtx);
+
+       if (WARN_ON(old_links == new_links || !sta->link[link_id]))
+               return -EINVAL;
+
+       sta->sta.valid_links = new_links;
+
+       if (!test_sta_flag(sta, WLAN_STA_INSERTED))
+               return 0;
+
+       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);
+       }
+
+       return ret;
+}
+
+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);
+}
 
  * @tdls_chandef: a TDLS peer can have a wider chandef that is compatible to
  *     the BSS one.
  * @frags: fragment cache
- * @multi_link_sta: Identifies if this sta is a MLD STA or regular STA
  * @deflink: This is the default link STA information, for non MLO STA all link
  *     specific STA information is accessed through @deflink or through
  *     link[0] which points to address of @deflink. For MLO Link STA
 
        struct ieee80211_fragment_cache frags;
 
-       bool multi_link_sta;
        struct link_sta_info deflink;
        struct link_sta_info *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
  * until sta_info_insert().
  */
 struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
-                               const u8 *addr, gfp_t gfp);
+                               const u8 *addr, int link_id, gfp_t gfp);
 
 void sta_info_free(struct ieee80211_local *local, struct sta_info *sta);
 
 void ieee80211_sta_expire(struct ieee80211_sub_if_data *sdata,
                          unsigned long exp_time);
 
+int ieee80211_sta_allocate_link(struct sta_info *sta, unsigned int link_id);
+int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id);
+void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id);
+
 void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta);
 void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta);
 void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);