From: Shayne Chen 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 Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi --- .../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