[PATCH RFC 2/2] wifi: mt76: mt7996: Add beacon_reconf IE parsing support
Lorenzo Bianconi
lorenzo at kernel.org
Wed Feb 11 15:38:12 PST 2026
From: Shayne Chen <shayne.chen at mediatek.com>
Implement the capability to offload the countdown for MLO link
reconfiguration IE sent by the AP MLD if the specified link is going
to be removed.
Signed-off-by: Shayne Chen <shayne.chen at mediatek.com>
Co-developed-by: Lorenzo Bianconi <lorenzo at kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo at kernel.org>
---
.../net/wireless/mediatek/mt76/mt76_connac_mcu.h | 5 +
drivers/net/wireless/mediatek/mt76/mt7996/init.c | 3 +-
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 185 +++++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 69 +++++++-
4 files changed, 260 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index f44977f9093da76a9f5e2b4d7ce147de28c5b18e..669f20d57d591a092afbb33bc58f4b63e82ae9b6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1067,6 +1067,7 @@ enum {
MCU_UNI_EVENT_WED_RRO = 0x57,
MCU_UNI_EVENT_PER_STA_INFO = 0x6d,
MCU_UNI_EVENT_ALL_STA_INFO = 0x6e,
+ MCU_UNI_EVENT_MLD = 0x81,
MCU_UNI_EVENT_SDO = 0x83,
};
@@ -1309,6 +1310,8 @@ enum {
MCU_UNI_CMD_ALL_STA_INFO = 0x6e,
MCU_UNI_CMD_ASSERT_DUMP = 0x6f,
MCU_UNI_CMD_RADIO_STATUS = 0x80,
+ MCU_UNI_CMD_MLD = 0x82,
+ MCU_UNI_CMD_PEER_MLD = 0x83,
MCU_UNI_CMD_SDO = 0x88,
};
@@ -1384,6 +1387,8 @@ enum {
UNI_BSS_INFO_MLD = 26,
UNI_BSS_INFO_PM_DISABLE = 27,
UNI_BSS_INFO_EHT = 30,
+ UNI_BSS_INFO_MLD_LINK_OP = 36,
+ UNI_BSS_INFO_BCN_ML_RECONF = 38,
};
enum {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
index 2937e89ad0c9c489274cfa4f6aeb694fb4456bd1..bf084cd434304e1afd3c3f8bbe726740f4d19f52 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
@@ -507,7 +507,8 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
wiphy->reg_notifier = mt7996_regd_notifier;
wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH |
- WIPHY_FLAG_SUPPORTS_MLO;
+ WIPHY_FLAG_SUPPORTS_MLO |
+ WIPHY_FLAG_MLO_RECONF_ADV_OFFLOAD;
wiphy->mbssid_max_interfaces = 16;
wiphy->iftype_ext_capab = iftypes_ext_capa;
wiphy->num_iftype_ext_capab = ARRAY_SIZE(iftypes_ext_capa);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 8e1c8e1d6a99f093a2b7d7dc3a0c56f3a4bc220b..310a580028a8e3532923fa82d65739b008dc58af 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -816,6 +816,50 @@ mt7996_mcu_wed_rro_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_mlo_reconf_complete_notify(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 *e = (void *)skb->data;
+ struct mt7996_mld_event_data data = {};
+ struct tlv *tlv;
+ int len;
+
+ memcpy(data.mld_addr, e->mld_addr, ETH_ALEN);
+ skb_pull(skb, sizeof(*e));
+ tlv = (struct tlv *)skb->data;
+ len = skb->len;
+
+ while (len > 0 && le16_to_cpu(tlv->len) <= len) {
+ data.data = (u8 *)tlv;
+
+ switch (le16_to_cpu(tlv->tag)) {
+ case UNI_EVENT_MLD_RECONF_AP_REM_TIMER:
+ 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)
{
@@ -837,6 +881,9 @@ mt7996_mcu_uni_rx_unsolicited_event(struct mt7996_dev *dev, struct sk_buff *skb)
case MCU_UNI_EVENT_WED_RRO:
mt7996_mcu_wed_rro_event(dev, skb);
break;
+ case MCU_UNI_EVENT_MLD:
+ mt7996_mcu_mld_event(dev, skb);
+ break;
default:
break;
}
@@ -2863,6 +2910,142 @@ mt7996_mcu_beacon_cntdwn(struct sk_buff *rskb, struct sk_buff *skb,
}
}
+static int
+mt7996_mcu_mld_reconf(struct mt7996_dev *dev, struct ieee80211_vif *vif,
+ u16 removed_links, u16 *removal_count)
+{
+ struct mld_req_hdr hdr = { .mld_idx = 0xff };
+ unsigned long rem = removed_links;
+ struct mld_reconf_timer *rt;
+ struct sk_buff *skb;
+ struct tlv *tlv;
+ u8 link_id;
+
+ skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, sizeof(hdr) + sizeof(*rt));
+ if (!skb)
+ return -ENOMEM;
+
+ memcpy(hdr.mld_addr, vif->addr, ETH_ALEN);
+ 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 *link_conf;
+ struct mt7996_vif_link *link;
+ u8 band_idx;
+ u16 to_sec;
+
+ link_conf = link_conf_dereference_protected(vif, link_id);
+ if (!link_conf)
+ continue;
+
+ link = mt7996_vif_link(dev, vif, link_id);
+ if (!link)
+ continue;
+
+ to_sec = link_conf->beacon_int * removal_count[link_id] / 1000;
+ band_idx = link->phy->mt76->band_idx;
+ rt->to_sec[band_idx] = cpu_to_le16(to_sec);
+ rt->bss_idx[band_idx] = link->mt76.idx;
+ }
+
+ return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WM_UNI_CMD(MLD), true);
+}
+
+static void
+mt7996_mcu_beacon_ml_reconf(struct mt7996_dev *dev,
+ struct ieee80211_bss_conf *link_conf,
+ struct sk_buff *rskb, struct sk_buff *skb,
+ struct ieee80211_mutable_offsets *offs)
+{
+ u16 tail_offset = offs->tim_offset + offs->tim_length;
+ u16 removal_count[IEEE80211_MLD_MAX_NUM_LINKS] = {};
+ u16 removal_offs[IEEE80211_MLD_MAX_NUM_LINKS] = {};
+ u8 link_id, *beacon_tail = skb->data + tail_offset;
+ struct bss_bcn_ml_reconf_offset *reconf_offs;
+ struct bss_bcn_ml_reconf_tlv *reconf;
+ const struct element *elem, *sub;
+ unsigned long removed_links = 0;
+ bool has_reconf = false;
+ struct tlv *tlv;
+
+ /* 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 (link_conf->link_id == __ffs(removed_links))
+ mt7996_mcu_mld_reconf(dev, link_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_vif_link *link;
+
+ link = mt7996_vif_link(dev, link_conf->vif, link_id);
+ if (!link)
+ continue;
+
+ reconf_offs->ap_removal_timer_offs =
+ cpu_to_le16(removal_offs[link_id]);
+ reconf_offs->bss_idx = link->mt76.idx;
+ reconf_offs++;
+ }
+}
+
static void
mt7996_mcu_beacon_mbss(struct sk_buff *rskb, struct sk_buff *skb,
struct bss_bcn_content_tlv *bcn,
@@ -3006,6 +3189,8 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
if (link_conf->bssid_indicator)
mt7996_mcu_beacon_mbss(rskb, skb, bcn, &offs);
mt7996_mcu_beacon_cntdwn(rskb, skb, &offs, link_conf->csa_active);
+ if (ieee80211_vif_is_mld(link_conf->vif))
+ mt7996_mcu_beacon_ml_reconf(dev, link_conf, rskb, skb, &offs);
out:
dev_kfree_skb(skb);
return mt76_mcu_skb_send_msg(&dev->mt76, rskb,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
index d9fb49f7b01b6cca2fd7a0e760c1d2b1b74eab5b..bb98194b98879ed7039791b3990019cbd43f120b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
@@ -487,6 +487,20 @@ struct bss_prot_tlv {
__le32 prot_mode;
} __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 sta_rec_ht_uni {
__le16 tag;
__le16 len;
@@ -660,6 +674,57 @@ struct mld_setup_link {
u8 __rsv;
} __packed;
+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;
+
+enum {
+ UNI_CMD_MLD_RECONF_AP_REM_TIMER = 0x03,
+ UNI_CMD_MLD_RECONF_STOP_LINK = 0x04,
+};
+
+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 hdr_trans_en {
__le16 tag;
__le16 len;
@@ -847,7 +912,9 @@ enum {
sizeof(struct bss_bcn_content_tlv) + \
4 + MT_TXD_SIZE + \
sizeof(struct bss_bcn_cntdwn_tlv) + \
- sizeof(struct bss_bcn_mbss_tlv))
+ sizeof(struct bss_bcn_mbss_tlv) + \
+ sizeof(struct bss_bcn_ml_reconf_tlv) + \
+ 3 * sizeof(struct bss_bcn_ml_reconf_offset))
#define MT7996_MAX_BSS_OFFLOAD_SIZE 2048
#define MT7996_MAX_BEACON_SIZE (MT7996_MAX_BSS_OFFLOAD_SIZE - \
MT7996_BEACON_UPDATE_SIZE)
--
2.53.0
More information about the linux-arm-kernel
mailing list