[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
3a2eef0b [MAC80211][Release][Update release note for Filogic 880/860 MLO Beta release]
cfbd2411 [MAC80211][Release][Filogic 880/860 MLO Beta release]
6c180e3f [MAC80211][WiFi7][misc][Add Eagle BE14000 efem default bin]
a55f34db [MAC80211][Release][Prepare for Filogic 880/860 release]
5b45ebca [MAC80211][WiFi7][hostapd][Add puncture bitmap to ucode]
95bbea73 [MAC80211][WiFi6][mt76][Add PID to only report data-frame TX rate]
b15ced26 [MAC80211][WiFi6][hostapd][Fix DFS channel selection issue]
d59133cb [MAC80211][WiFi6][mt76][Fix pse info not correct information]
3921b4b2 [MAC80211][WiFi6][mt76][Fix incomplete QoS-map setting to FW]
4e7690c7 [MAC80211][WiFi6/7][app][Change ATECHANNEL mapping cmd]
eb37af90 [MAC80211][WiFi7][app][Add support for per-packet bw & primary selection]
0ea82adf [MAC80211][WiFi6][core][Fix DFS CAC issue after CSA]

[Release-log]

Change-Id: I9bec97ec1b2e1c49ed43a812a07a5b21fcbb70a6
diff --git a/recipes-wifi/linux-mt76/files/patches-3.x/0182-mtk-mt76-mt7996-add-AP-affiliated-link-removal-suppo.patch b/recipes-wifi/linux-mt76/files/patches-3.x/0182-mtk-mt76-mt7996-add-AP-affiliated-link-removal-suppo.patch
new file mode 100644
index 0000000..b35da7b
--- /dev/null
+++ b/recipes-wifi/linux-mt76/files/patches-3.x/0182-mtk-mt76-mt7996-add-AP-affiliated-link-removal-suppo.patch
@@ -0,0 +1,564 @@
+From e61df790cc5ef66271a12e14af49f48ad097f7a6 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Fri, 31 May 2024 18:14:59 +0800
+Subject: [PATCH 182/199] mtk: mt76: mt7996: add AP affiliated link removal
+ support
+
+Add support for ap link removal of MLD reconfiguration.
+
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt76_connac_mcu.h |   3 +
+ mt7996/main.c     |  68 ++++++++++++---
+ mt7996/mcu.c      | 217 ++++++++++++++++++++++++++++++++++++++++++++++
+ mt7996/mcu.h      |  89 ++++++++++++++++++-
+ mt7996/mt7996.h   |   2 +
+ 5 files changed, 367 insertions(+), 12 deletions(-)
+
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 545de4ae..fd1bf1d1 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1074,6 +1074,7 @@ enum {
+ 	MCU_UNI_EVENT_PP = 0x5a,
+ 	MCU_UNI_EVENT_PER_STA_INFO = 0x6d,
+ 	MCU_UNI_EVENT_ALL_STA_INFO = 0x6e,
++	MCU_UNI_EVENT_MLD = 0x81,
+ };
+ 
+ #define MCU_UNI_CMD_EVENT			BIT(1)
+@@ -1319,6 +1320,7 @@ enum {
+ 	MCU_UNI_CMD_ALL_STA_INFO = 0x6e,
+ 	MCU_UNI_CMD_ASSERT_DUMP = 0x6f,
+ 	MCU_UNI_CMD_PTA_3WIRE_CTRL = 0x78,
++	MCU_UNI_CMD_MLD = 0x82,
+ };
+ 
+ enum {
+@@ -1393,6 +1395,7 @@ enum {
+ 	UNI_BSS_INFO_PM_DISABLE = 27,
+ 	UNI_BSS_INFO_BCN_CRIT_UPDATE = 32,
+ 	UNI_BSS_INFO_BCN_STA_PROF_CSA = 37,
++	UNI_BSS_INFO_BCN_ML_RECONF = 38,
+ };
+ 
+ enum {
+diff --git a/mt7996/main.c b/mt7996/main.c
+index ff0b9c0e..fc6f4ef9 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -319,6 +319,21 @@ static void mt7996_remove_bss_conf(struct ieee80211_vif *vif,
+ 		list_del_init(&mlink->wcid.poll_list);
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
++	/* reassign a new bss wcid if the previous one was removed */
++	if (vif->txq && ieee80211_vif_is_mld(vif) &&
++	    hweight16(vif->valid_links) > 1) {
++		struct mt76_txq *mtxq = (struct mt76_txq *)vif->txq->drv_priv;
++
++		if (mtxq->wcid == mlink->wcid.idx) {
++			u8 new_link = __ffs(vif->valid_links & ~BIT(link_id));
++			struct mt7996_link_sta *new_mlink =
++				mlink_dereference_protected(&mvif->sta, new_link);
++
++			if (new_mlink)
++				mtxq->wcid = new_mlink->wcid.idx;
++		}
++	}
++
+ 	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
+ 
+ 	if (mlink != &mvif->sta.deflink)
+@@ -425,7 +440,7 @@ static int mt7996_add_bss_conf(struct mt7996_phy *phy,
+ 	mt7996_mac_wtbl_update(dev, idx,
+ 			       MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ 
+-	if (vif->txq) {
++	if (vif->txq && hweight16(vif->valid_links) <= 1) {
+ 		mtxq = (struct mt76_txq *)vif->txq->drv_priv;
+ 		mtxq->wcid = idx;
+ 	}
+@@ -1128,6 +1143,8 @@ static void mt7996_remove_link_sta(struct mt7996_dev *dev,
+ {
+ 	struct ieee80211_sta *sta = link_sta->sta;
+ 	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	u16 valid_links = sta->valid_links;
++	bool pri_changed;
+ 	int i;
+ 
+ 	if (!mlink)
+@@ -1148,6 +1165,7 @@ static void mt7996_remove_link_sta(struct mt7996_dev *dev,
+ 		mt7996_mac_twt_teardown_flow(dev, mlink, i);
+ 
+ 	rcu_assign_pointer(mlink->sta->link[mlink->wcid.link_id], NULL);
++	rcu_assign_pointer(dev->mt76.wcid[mlink->wcid.idx], NULL);
+ 
+ 	spin_lock_bh(&dev->mt76.sta_poll_lock);
+ 	if (!list_empty(&mlink->wcid.poll_list))
+@@ -1156,17 +1174,41 @@ static void mt7996_remove_link_sta(struct mt7996_dev *dev,
+ 		list_del_init(&mlink->rc_list);
+ 	spin_unlock_bh(&dev->mt76.sta_poll_lock);
+ 
+-	/* TODO: update primary link */
+-	if (sta->valid_links) {
+-		if (mlink->wcid.link_id == msta->pri_link)
+-			msta->pri_link = msta->sec_link;
++	valid_links &= ~BIT(mlink->wcid.link_id);
++	if (!valid_links)
++		goto done;
+ 
+-		if (sta->valid_links & ~(BIT(msta->pri_link)))
+-			msta->sec_link = __ffs(sta->valid_links & ~(BIT(msta->pri_link)));
+-		else
+-			msta->sec_link = msta->pri_link;
++	/* update primary and secondary link */
++	pri_changed = mlink->wcid.link_id == msta->pri_link;
++	if (pri_changed)
++		msta->pri_link = msta->sec_link;
++
++	if (valid_links & ~(BIT(msta->pri_link)))
++		msta->sec_link = __ffs(valid_links & ~(BIT(msta->pri_link)));
++	else
++		msta->sec_link = msta->pri_link;
++
++	if (pri_changed) {
++		struct mt7996_link_sta *mlink_new =
++			mlink_dereference_protected(msta, msta->pri_link);
++
++		if (!mlink_new)
++			goto done;
++
++		mlink_new->wcid.ampdu_state = mlink->wcid.ampdu_state;
++		for (i = 0; i < ARRAY_SIZE(mlink->wcid.aggr); i++)
++			rcu_assign_pointer(mlink_new->wcid.aggr[i], mlink->wcid.aggr[i]);
++		for (i = 0; i < ARRAY_SIZE(sta->txq); i++) {
++			struct mt76_txq *mtxq;
++
++			if (!sta->txq[i])
++				continue;
++			mtxq = (struct mt76_txq *)sta->txq[i]->drv_priv;
++			mtxq->wcid = mlink_new->wcid.idx;
++		}
+ 	}
+ 
++done:
+ 	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
+ 	mt76_wcid_mask_clear(dev->mt76.wcid_mask, mlink->wcid.idx);
+ 	mt76_wcid_mask_clear(dev->mt76.wcid_phy_mask, mlink->wcid.idx);
+@@ -3104,9 +3146,15 @@ mt7996_change_sta_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	mt76_vif_dbg(vif, "STA %pM old=0x%x, new=0x%x\n", sta->addr, old_links, new_links);
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	if (rem)
++	if (rem) {
+ 		mt7996_mac_sta_remove_links(dev, vif, sta, rem);
+ 
++		/* Todo: update hw info of MLD STA */
++		/* ret = mt7996_mcu_add_mld_sta(dev, vif, sta, new_links); */
++		/* if (ret) */
++		/* 	goto remove; */
++	}
++
+ 	ret = mt7996_mac_sta_add_links(dev, vif, sta, add, false);
+ 	if (ret)
+ 		goto remove;
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 4310d35b..662e4f02 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1350,6 +1350,48 @@ mt7996_mcu_pp_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	}
+ }
+ 
++static void
++mt7996_mcu_mld_reconf_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
++{
++	struct mt7996_mld_event_data *data = priv;
++	struct mt7996_mcu_mld_ap_reconf_event *reconf = (void *)data->data;
++
++	if (!ether_addr_equal(vif->addr, data->mld_addr))
++		return;
++
++	ieee80211_links_removed(vif, le16_to_cpu(reconf->link_bitmap));
++}
++
++static void
++mt7996_mcu_mld_event(struct mt7996_dev *dev, struct sk_buff *skb)
++{
++	struct mt7996_mcu_mld_event *event = (void *)skb->data;
++	struct mt7996_mld_event_data data = {};
++	struct tlv *tlv;
++	int len;
++
++	memcpy(data.mld_addr, event->mld_addr, ETH_ALEN);
++	skb_pull(skb, sizeof(*event));
++	tlv = (struct tlv *)skb->data;
++	len = skb->len;
++
++	while (len > 0 && le16_to_cpu(tlv->len) <= len) {
++		switch (le16_to_cpu(tlv->tag)) {
++		case UNI_EVENT_MLD_RECONF_AP_REM_TIMER:
++			data.data = (u8 *)tlv;
++			ieee80211_iterate_active_interfaces_atomic(dev->mt76.hw,
++					IEEE80211_IFACE_ITER_RESUME_ALL,
++					mt7996_mcu_mld_reconf_finish, &data);
++			break;
++		default:
++			break;
++		}
++
++		len -= le16_to_cpu(tlv->len);
++		tlv = (struct tlv *)((u8 *)(tlv) + le16_to_cpu(tlv->len));
++	}
++}
++
+ static void
+ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ {
+@@ -1378,6 +1420,9 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 	case MCU_UNI_EVENT_PP:
+ 		mt7996_mcu_pp_event(dev, skb);
+ 		break;
++	case MCU_UNI_EVENT_MLD:
++		mt7996_mcu_mld_event(dev, skb);
++		break;
+ #ifdef CONFIG_MTK_DEBUG
+ 	case MCU_UNI_EVENT_SR:
+ 		mt7996_mcu_rx_sr_event(dev, skb);
+@@ -3169,6 +3214,44 @@ out:
+ 				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+ }
+ 
++int mt7996_mcu_add_mld_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			   struct ieee80211_sta *sta, unsigned long add)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
++	u8 link_id;
++
++	if (!sta->mlo)
++		return 0;
++
++	for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		struct mt7996_link_sta *mlink =
++			mlink_dereference_protected(msta, link_id);
++		struct sk_buff *skb;
++		int ret;
++
++		if (!mconf || !mlink)
++			continue;
++
++		skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
++						      &mlink->wcid,
++						      MT7996_STA_UPDATE_MAX_SIZE);
++		if (IS_ERR(skb))
++			return PTR_ERR(skb);
++		/* starec mld setup */
++		mt7996_mcu_sta_mld_setup_tlv(dev, skb, sta, add);
++		/* starec eht mld */
++		mt7996_mcu_sta_eht_mld_tlv(dev, skb, sta);
++		ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
++					    MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
++		if (ret)
++			return ret;
++	}
++	return 0;
++}
++
+ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev,
+ 				struct mt7996_bss_conf *mconf,
+ 				struct mt7996_link_sta *mlink)
+@@ -3381,6 +3464,51 @@ int mt7996_mcu_add_dev_info(struct mt7996_phy *phy,
+ 				 &data, sizeof(data), true);
+ }
+ 
++static int
++mt7996_mcu_mld_reconf(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++		      u16 removed_links, u16 *removal_count)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mld_req_hdr hdr = { .mld_idx = 0xff };
++	struct mld_reconf_timer *rt;
++	struct sk_buff *skb;
++	struct tlv *tlv;
++	int len = sizeof(hdr) + sizeof(*rt);
++	unsigned long rem = removed_links;
++	u8 link_id;
++
++	memcpy(hdr.mld_addr, vif->addr, ETH_ALEN);
++
++	skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, len);
++	if (!skb)
++		return -ENOMEM;
++
++	skb_put_data(skb, &hdr, sizeof(hdr));
++
++	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_CMD_MLD_RECONF_AP_REM_TIMER, sizeof(*rt));
++	rt = (struct mld_reconf_timer *)tlv;
++	rt->link_bitmap = cpu_to_le16(removed_links);
++
++	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct ieee80211_bss_conf *conf =
++			link_conf_dereference_protected(vif, link_id);
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++		u8 band_idx;
++		u16 to_sec;
++
++		if (!conf || !mconf)
++			continue;
++
++		band_idx = mconf->phy->mt76->band_idx;
++		to_sec = conf->beacon_int * removal_count[link_id] / 1000;
++		rt->to_sec[band_idx] = cpu_to_le16(to_sec);
++		rt->bss_idx[band_idx] = mconf->mt76.idx;
++	}
++
++	return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WM_UNI_CMD(MLD), true);
++}
++
+ static void
+ mt7996_mcu_beacon_cntdwn(struct ieee80211_bss_conf *conf, struct sk_buff *rskb,
+ 			 struct sk_buff *skb,
+@@ -3575,6 +3703,94 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_bss_conf *conf,
+ 		mt7996_packet_log_to_host(dev, skb->data, skb->len, PKT_BIN_DEBUG_TX, 0);
+ }
+ 
++static void
++mt7996_mcu_beacon_ml_reconf(struct mt7996_dev *dev,
++			    struct ieee80211_bss_conf *conf,
++			    struct sk_buff *rskb, struct sk_buff *skb,
++			    struct ieee80211_mutable_offsets *offs)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)conf->vif->drv_priv;
++	struct bss_bcn_ml_reconf_tlv *reconf;
++	struct bss_bcn_ml_reconf_offset *reconf_offs;
++	const struct element *elem, *sub;
++	struct tlv *tlv;
++	u16 removal_offs[IEEE80211_MLD_MAX_NUM_LINKS] = {};
++	u16 removal_count[IEEE80211_MLD_MAX_NUM_LINKS] = {};
++	u16 tail_offset = offs->tim_offset + offs->tim_length;
++	unsigned long removed_links = 0;
++	bool has_reconf = false;
++	u8 link_id, *beacon_tail = skb->data + tail_offset;
++
++	if (!ieee80211_vif_is_mld(conf->vif))
++		return;
++
++	/* TODO: currently manually parse reconf info directly from the IE, it
++	 * is expected to be passed from upper layer in the future.
++	 */
++	for_each_element_extid(elem, WLAN_EID_EXT_EHT_MULTI_LINK,
++			       beacon_tail, skb->len - tail_offset) {
++		if (ieee80211_mle_type_ok(elem->data + 1,
++					  IEEE80211_ML_CONTROL_TYPE_RECONF,
++					  elem->datalen - 1)) {
++			has_reconf = true;
++			break;
++		}
++	}
++
++	if (!has_reconf)
++		return;
++
++	for_each_mle_subelement(sub, elem->data + 1, elem->datalen - 1) {
++		struct ieee80211_mle_per_sta_profile *prof = (void *)sub->data;
++		u8 *pos = prof->variable;
++		u16 control;
++
++		if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
++			continue;
++
++		if (!ieee80211_mle_reconf_sta_prof_size_ok(sub->data,
++							   sub->datalen))
++			return;
++
++		control = le16_to_cpu(prof->control);
++		link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID;
++
++		removed_links |= BIT(link_id);
++
++		if (control & IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT)
++			pos += 6;
++
++		if (control & IEEE80211_MLE_STA_RECONF_CONTROL_AP_REM_TIMER_PRESENT) {
++			removal_offs[link_id] = pos - skb->data;
++			removal_count[link_id] = le16_to_cpu(*(__le16 *)pos);
++		}
++	}
++
++	if (!removed_links)
++		return;
++
++	/* the first link to be removed */
++	if (conf->link_id == ffs(removed_links) - 1)
++		mt7996_mcu_mld_reconf(dev, conf->vif, removed_links, removal_count);
++
++	tlv = mt7996_mcu_add_uni_tlv(rskb, UNI_BSS_INFO_BCN_ML_RECONF,
++				     sizeof(*reconf) +
++				     sizeof(*reconf_offs) * hweight16(removed_links));
++	reconf = (struct bss_bcn_ml_reconf_tlv *)tlv;
++	reconf->reconf_count = hweight16(removed_links);
++
++	reconf_offs = (struct bss_bcn_ml_reconf_offset *)reconf->offset;
++	for_each_set_bit(link_id, &removed_links, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++
++		reconf_offs->ap_removal_timer_offs =
++			cpu_to_le16(removal_offs[link_id]);
++		reconf_offs->bss_idx = mconf->mt76.idx;
++		reconf_offs++;
++	}
++}
++
+ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 			  struct ieee80211_bss_conf *conf,
+ 			  struct mt7996_bss_conf *mconf, int en)
+@@ -3628,6 +3844,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw,
+ 	mt7996_mcu_beacon_cntdwn(conf, rskb, skb, &offs);
+ 	mt7996_mcu_beacon_sta_prof_csa(rskb, conf, &offs);
+ 	mt7996_mcu_beacon_crit_update(rskb, skb, conf, mconf, &offs);
++	mt7996_mcu_beacon_ml_reconf(dev, conf, rskb, skb, &offs);
+ out:
+ 	dev_kfree_skb(skb);
+ 	return mt76_mcu_skb_send_msg(&phy->dev->mt76, rskb,
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index 739e357c..7c559d2b 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -546,6 +546,20 @@ struct bss_bcn_sta_prof_cntdwn_tlv {
+ 	u8 pkt_content[9];
+ } __packed;
+ 
++struct bss_bcn_ml_reconf_tlv {
++	__le16 tag;
++	__le16 len;
++	u8 reconf_count;
++	u8 rsv[3];
++	u8 offset[];
++} __packed;
++
++struct bss_bcn_ml_reconf_offset {
++	__le16 ap_removal_timer_offs;
++	u8 bss_idx;
++	u8 rsv;
++} __packed;
++
+ struct bss_txcmd_tlv {
+ 	__le16 tag;
+ 	__le16 len;
+@@ -594,6 +608,17 @@ struct bss_mld_tlv {
+ 	u8 __rsv[3];
+ } __packed;
+ 
++struct bss_mld_link_op_tlv {
++	__le16 tag;
++	__le16 len;
++	u8 group_mld_id;
++	u8 own_mld_id;
++	u8 mac_addr[ETH_ALEN];
++	u8 remap_idx;
++	u8 link_operation;
++	u8 rsv[2];
++} __packed;
++
+ struct sta_rec_ht_uni {
+ 	__le16 tag;
+ 	__le16 len;
+@@ -1003,8 +1028,9 @@ enum {
+ 					 sizeof(struct bss_bcn_cntdwn_tlv) +	\
+ 					 sizeof(struct bss_bcn_mbss_tlv) +	\
+ 					 sizeof(struct bss_bcn_crit_update_tlv) +	\
+-					 sizeof(struct bss_bcn_sta_prof_cntdwn_tlv))	\
+-
++					 sizeof(struct bss_bcn_sta_prof_cntdwn_tlv) +	\
++					 sizeof(struct bss_bcn_ml_reconf_tlv) +	\
++					 3 * sizeof(struct bss_bcn_ml_reconf_offset))
+ #define MT7996_MAX_BSS_OFFLOAD_SIZE	(MT7996_MAX_BEACON_SIZE +		\
+ 					 MT7996_BEACON_UPDATE_SIZE)
+ 
+@@ -1105,6 +1131,65 @@ enum {
+ 	UNI_CMD_THERMAL_PROTECT_DUTY_CONFIG,
+ };
+ 
++struct mld_req_hdr {
++	u8 ver;
++	u8 mld_addr[ETH_ALEN];
++	u8 mld_idx;
++	u8 flag;
++	u8 rsv[3];
++	u8 buf[];
++} __packed;
++
++struct mld_reconf_timer {
++	__le16 tag;
++	__le16 len;
++	__le16 link_bitmap;
++	__le16 to_sec[__MT_MAX_BAND]; /* timeout of reconf (second) */
++	u8 bss_idx[__MT_MAX_BAND];
++	u8 rsv;
++} __packed;
++
++struct mld_reconf_stop_link {
++	__le16 tag;
++	__le16 len;
++	__le16 link_bitmap;
++	u8 rsv[2];
++	u8 bss_idx[16];
++} __packed;
++
++enum {
++	UNI_CMD_MLD_RECONF_AP_REM_TIMER = 0x03,
++};
++
++struct mt7996_mcu_mld_event {
++	struct mt7996_mcu_rxd rxd;
++
++	/* fixed field */
++	u8 ver;
++	u8 mld_addr[ETH_ALEN];
++	u8 mld_idx;
++	u8 rsv[4];
++	/* tlv */
++	u8 buf[];
++} __packed;
++
++struct mt7996_mld_event_data {
++	u8 mld_addr[ETH_ALEN];
++	u8 *data;
++};
++
++struct mt7996_mcu_mld_ap_reconf_event {
++	__le16 tag;
++	__le16 len;
++	__le16 link_bitmap;
++	u8 bss_idx[3];
++	u8 rsv[3];
++} __packed;
++
++enum {
++	UNI_EVENT_MLD_RECONF_AP_REM_TIMER = 0x04,
++};
++
+ struct tx_power_ctrl {
+ 	u8 _rsv[4];
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 8b00b05b..6f9f82b7 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -1083,6 +1083,8 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev,
+ 			     struct mt7996_bss_conf *mconf,
+ 			     struct ieee80211_link_sta *link_sta,
+ 			     struct mt7996_link_sta *mlink, bool changed);
++int mt7996_mcu_add_mld_sta(struct mt7996_dev *dev, struct ieee80211_vif *vif,
++			   struct ieee80211_sta *sta, unsigned long add);
+ int mt7996_set_channel(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef);
+ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag, bool sta);
+ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct mt7996_bss_conf *mconf);
+-- 
+2.18.0
+