mt7996_tx_prepare_skb() takes the link of every frame other than the QoS data of an MLO station from IEEE80211_TX_CTRL_MLO_LINK. mac80211 does not set that field for 802.3 frames, as include/net/mac80211.h documents, so it reads 0. Since commit 729c83a3330c ("wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()"), mt7996_mac_write_txwi() uses that link instead of the link of the wcid. When the host writes the TXD of an 802.3 frame to a non-MLO station on a link other than 0, the TXD thus names the BSS and band of link 0, and the frame is lost. Treat an 802.3 frame like one with IEEE80211_LINK_UNSPECIFIED, so that it takes the link of its wcid, as before that commit. Fixes: 729c83a3330c ("wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add()") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f36da545045b..edff4e5a5dff 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1062,6 +1062,9 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, u8 tid = tx_info->skb->priority & IEEE80211_QOS_CTL_TID_MASK; link_id = (tid % 2) ? msta->seclink_id : msta->deflink_id; + } else if (is_8023) { + /* mac80211 does not set the MLO link of an 802.3 frame */ + link_id = IEEE80211_LINK_UNSPECIFIED; } else { link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); -- 2.47.3 Each upload of the CSA beacon makes the firmware restart the countdown from the template, but the driver uploads it more than once. At the start of a CSA, mt7996_link_info_changed() and mt7996_channel_switch_beacon() both upload it. mt7996_set_channel() uploads it once more for the channel switch that __ieee80211_csa_finalize() performs while csa_active is still set. Upload the CSA beacon of a link only while the firmware does not hold it: at the start of the CSA, after offchannel operation and after a hardware restart. Do this, and the beacon reset for a CSA during CAC, in mt7996_link_info_changed(), which knows the link of the CSA. Drop mt7996_channel_switch_beacon(), which selects the link by the band of the new channel; that band can belong to another radio. In mt7996_set_channel(), restore only the beacons that were disabled for offchannel operation. Other beacon updates during CSA are dropped as well, since mac80211 rejects beacon changes from user space during CSA and sets the post-CSA beacon right after the switch. A beacon disable during CSA still goes to the firmware. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Fixes: f30906c55a40 ("wifi: mt76: mt7996: disable beacons when going offchannel") Co-developed-by: StanleyYP Wang Signed-off-by: StanleyYP Wang Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 1 + .../net/wireless/mediatek/mt76/mt7996/main.c | 68 ++++++++----------- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 13 +++- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 + 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index edff4e5a5dff..2e1b67ac2d11 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2251,6 +2251,7 @@ void mt7996_mac_update_beacons(struct mt7996_phy *phy) ieee80211_iterate_active_interfaces(phy->mt76->hw, IEEE80211_IFACE_ITER_RESUME_ALL, mt7996_update_vif_beacon, phy); + phy->offchan_bcn_stopped = phy->mt76->offchannel; } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 9f1927cad5c4..a2655784a13d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -349,6 +349,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return -ENOSPC; link->mld_idx = mld_idx; + link->csa_bcn_uploaded = false; eth_zero_addr(link->bmc_bssid); mlink->omac_idx = idx; mlink->band_idx = band_idx; @@ -703,7 +704,7 @@ int mt7996_set_channel(struct mt76_phy *mphy) mt7996_mac_reset_counters(phy); phy->noise = 0; - if (!mphy->offchannel) + if (!mphy->offchannel && phy->offchan_bcn_stopped) mt7996_mac_update_beacons(phy); out: @@ -995,6 +996,30 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); } +static void +mt7996_link_beacon_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link, struct mt7996_phy *phy) +{ + if (!link_conf->enable_beacon || !link_conf->csa_active) { + mt7996_mcu_add_beacon(hw, vif, link_conf, + link_conf->enable_beacon); + return; + } + + /* each upload of the CSA beacon restarts the firmware countdown */ + if (link->csa_bcn_uploaded) + return; + + /* Reset beacon when channel switch triggered during CAC to let + * FW correctly perform CSA countdown + */ + if (!cfg80211_reg_can_beacon(hw->wiphy, &phy->mt76->chandef, vif->type)) + mt7996_mcu_add_beacon(hw, vif, link_conf, false); + + mt7996_mcu_add_beacon(hw, vif, link_conf, true); +} + static void mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *info, u64 changed) @@ -1067,7 +1092,7 @@ mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, link->mt76.beacon_rates_idx = mt7996_get_rates_table(phy, info, true, false); - mt7996_mcu_add_beacon(hw, vif, info, info->enable_beacon); + mt7996_link_beacon_update(hw, vif, info, link, phy); } if (changed & (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP | @@ -1087,44 +1112,6 @@ mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); } -static void -mt7996_channel_switch_beacon(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct cfg80211_chan_def *chandef) -{ - struct mt7996_dev *dev = mt7996_hw_dev(hw); - struct mt7996_phy *phy = mt7996_band_phy(dev, chandef->chan->band); - struct ieee80211_bss_conf *link_conf; - unsigned int link_id; - - mutex_lock(&dev->mt76.mutex); - - for_each_vif_active_link(vif, link_conf, link_id) { - struct mt7996_vif_link *link; - struct mt7996_phy *link_phy; - - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - - link_phy = mt7996_vif_link_phy(link); - if (link_phy != phy) - continue; - - /* Reset beacon when channel switch triggered during CAC to let - * FW correctly perform CSA countdown - */ - if (!cfg80211_reg_can_beacon(hw->wiphy, &phy->mt76->chandef, - vif->type)) - mt7996_mcu_add_beacon(hw, vif, link_conf, false); - - mt7996_mcu_add_beacon(hw, vif, link_conf, true); - break; - } - - mutex_unlock(&dev->mt76.mutex); -} - static int mt7996_post_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf) @@ -3093,7 +3080,6 @@ const struct ieee80211_ops mt7996_ops = { .cancel_remain_on_channel = mt76_cancel_remain_on_channel, .release_buffered_frames = mt76_release_buffered_frames, .get_txpower = mt7996_get_txpower, - .channel_switch_beacon = mt7996_channel_switch_beacon, .post_channel_switch = mt7996_post_channel_switch, .get_stats = mt7996_get_stats, .get_et_sset_count = mt7996_get_et_sset_count, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index e3420ac523ab..7e72174bd6b1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3254,7 +3254,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct sk_buff *skb, *rskb; struct tlv *tlv; struct bss_bcn_content_tlv *bcn; - int len, extra_len = 0; + int len, extra_len = 0, ret; if (link_conf->nontransmitted) return 0; @@ -3262,6 +3262,10 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!mlink) return -EINVAL; + /* a later CSA needs a new template, also after a failed upload */ + if (!link_conf->csa_active) + link->csa_bcn_uploaded = false; + if (mphy && mphy->offchannel) enabled = false; @@ -3303,8 +3307,11 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mt7996_mcu_beacon_cntdwn(rskb, skb, &offs, link_conf->csa_active); out: dev_kfree_skb(skb); - return mt76_mcu_skb_send_msg(&dev->mt76, rskb, - MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); + ret = mt76_mcu_skb_send_msg(&dev->mt76, rskb, + MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); + link->csa_bcn_uploaded = !ret && enabled && link_conf->csa_active; + + return ret; } int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index da9958647b54..84edb905fe06 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -295,6 +295,7 @@ struct mt7996_vif_link { u8 mld_idx; u8 bmc_bssid[ETH_ALEN]; + bool csa_bcn_uploaded; }; struct mt7996_vif_link_info { @@ -416,6 +417,7 @@ struct mt7996_phy { bool has_aux_rx; bool counter_reset; bool rdd_tx_paused; + bool offchan_bcn_stopped; }; struct mt7996_dev { -- 2.47.3 mt7996_vif_link_add() defers the BMC STA_REC of a station interface to BSS_CHANGED_BSSID or BSS_CHANGED_ASSOC. A link that mt76_get_vif_phy_link() creates for a scan or ROC receives neither, because it is not part of the vif links. mt7996_vif_link_destroy() still sends a STA_REC removal for it at the end of each scan, and the firmware rejects it: staRecFreeStaRec-1606 Err: u2ClientSupportCnt is already 0. (wcid 1087) staRecUniCmdUpdate, u2Tag 0x0 ret = 0xc0000001 not success Record per link whether the firmware accepted its BMC STA_REC, and only send the removal for such a link. This also covers a station link that is torn down before its first BSSID or association update. Fixes: 51b795d68cad ("wifi: mt76: connac: use peer address for station BMC entry") Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 18 +++++++++++------- .../net/wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index a2655784a13d..e8faa826e037 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -349,6 +349,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return -ENOSPC; link->mld_idx = mld_idx; + link->bmc_sta_added = false; link->csa_bcn_uploaded = false; eth_zero_addr(link->bmc_bssid); mlink->omac_idx = idx; @@ -392,9 +393,10 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, /* defer the first STA_REC of BMC entry to BSS_CHANGED_BSSID for STA * interface, since firmware only records BSSID when the entry is new */ - if (vif->type != NL80211_IFTYPE_STATION) - mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, - CONN_STATE_PORT_SECURE, true); + if (vif->type != NL80211_IFTYPE_STATION && + !mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, + CONN_STATE_PORT_SECURE, true)) + link->bmc_sta_added = true; rcu_assign_pointer(dev->mt76.wcid[idx], &msta_link->wcid); if (!mlink->wcid->offchannel) @@ -441,8 +443,9 @@ static void mt7996_vif_link_destroy(struct mt7996_phy *phy, if (!link_conf) link_conf = &vif->bss_conf; - mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, - CONN_STATE_DISCONNECT, false); + if (link->bmc_sta_added) + mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, + CONN_STATE_DISCONNECT, false); mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false); @@ -939,8 +942,9 @@ mt7996_vif_link_bss_add(struct mt7996_phy *phy, struct ieee80211_vif *vif, mt7996_mcu_add_bss_info(phy, vif, link_conf, &link->mt76, &link->msta_link, true); - mt7996_mcu_add_sta(phy->dev, link_conf, NULL, link, NULL, - CONN_STATE_PORT_SECURE, newly); + if (!mt7996_mcu_add_sta(phy->dev, link_conf, NULL, link, NULL, + CONN_STATE_PORT_SECURE, newly)) + link->bmc_sta_added = true; } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 84edb905fe06..d30b4d689bac 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -295,6 +295,7 @@ struct mt7996_vif_link { u8 mld_idx; u8 bmc_bssid[ETH_ALEN]; + bool bmc_sta_added; bool csa_bcn_uploaded; }; -- 2.47.3 mt7996_set_channel() programs the channel and the width of a chanctx, but not chandef->punctured, so the firmware does not know which subchannels of the channel are punctured. Send the bitmap of the chandef with the PP_EN_CTRL command on every channel set, in user mode with the firmware's own puncturing detection off. The firmware holds one bitmap per band, so stations and APs on the band use the same command. Do not wait for a response, like the MediaTek vendor driver, which sends this command with the same firmware. Skip 2.4 GHz, where an EHT channel is at most 40 MHz wide and cannot be punctured. Co-developed-by: Allen Ye Signed-off-by: Allen Ye Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7996/main.c | 4 +++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 32 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 4 +++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 5 files changed, 42 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 88281733a3df..e9a7542c07dc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1349,6 +1349,7 @@ enum { MCU_UNI_CMD_CHANNEL_SWITCH = 0x34, MCU_UNI_CMD_THERMAL = 0x35, MCU_UNI_CMD_VOW = 0x37, + MCU_UNI_CMD_PP = 0x38, MCU_UNI_CMD_FIXED_RATE_TABLE = 0x40, MCU_UNI_CMD_RSSI_MONITOR = 0x41, MCU_UNI_CMD_TESTMODE_CTRL = 0x46, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e8faa826e037..3a3441951254 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -690,6 +690,10 @@ int mt7996_set_channel(struct mt76_phy *mphy) if (mphy->offchannel) mt7996_mac_update_beacons(phy); + ret = mt7996_mcu_set_punct(phy); + if (ret) + goto out; + ret = mt7996_mcu_set_chan_info(phy, UNI_CHANNEL_SWITCH); if (ret) goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 7e72174bd6b1..f35250430383 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -4255,6 +4255,38 @@ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag) &req, sizeof(req), true); } +int mt7996_mcu_set_punct(struct mt7996_phy *phy) +{ + struct cfg80211_chan_def *chandef = &phy->mt76->chandef; + struct mt7996_dev *dev = phy->dev; + struct { + u8 __rsv[4]; + + __le16 tag; + __le16 len; + u8 mgmt_mode; + u8 band_idx; + u8 force_bitmap_ctrl; + u8 auto_mode; + __le16 bitmap; + u8 csa_enable; + u8 __rsv2; + } __packed req = { + .tag = cpu_to_le16(UNI_CMD_PP_EN_CTRL), + .len = cpu_to_le16(sizeof(req) - 4), + .mgmt_mode = 1, + .band_idx = phy->mt76->band_idx, + .force_bitmap_ctrl = 2, + .bitmap = cpu_to_le16(chandef->punctured), + }; + + if (chandef->chan->band == NL80211_BAND_2GHZ) + return 0; + + return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(PP), + &req, sizeof(req), false); +} + static int mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 6078f8811b1e..70ed57d9bf9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -1033,6 +1033,10 @@ enum{ UNI_CMD_SR_SET_SIGA = 0xd0, }; +enum { + UNI_CMD_PP_EN_CTRL, +}; + enum { UNI_CMD_THERMAL_PROTECT_ENABLE = 0x6, UNI_CMD_THERMAL_PROTECT_DISABLE, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index d30b4d689bac..1f1dff9f6610 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -786,6 +786,7 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct mt7996_sta *msta, bool changed); int mt7996_set_channel(struct mt76_phy *mphy); int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag); +int mt7996_mcu_set_punct(struct mt7996_phy *phy); int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev, -- 2.47.3 mt76_change_chanctx() sets the channel of the chanctx again while a scan can still run on the phy. During the dwell on an offchannel scan channel, the phy thus returns to the operating channel without the mt76_offchannel_notify() that ends the power save period towards the AP. Abort the scan before the channel is set again, like mt76_add_chanctx() and mt76_remove_chanctx() do. Fixes: 82334623af0c ("wifi: mt76: add chanctx functions for multi-channel phy support") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 192276cf19fd..115117a19b5c 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -88,6 +88,9 @@ void mt76_change_chanctx(struct ieee80211_hw *hw, IEEE80211_CHANCTX_CHANGE_RADAR))) return; + if (mt76_phy_scanning(phy)) + mt76_abort_scan(dev); + if (phy->roc_vif) mt76_abort_roc(phy); -- 2.47.3 mt76_change_chanctx() ignores a change that only sets IEEE80211_CHANCTX_CHANGE_PUNCTURING, so the driver keeps the old puncturing bitmap. mac80211 sends such a change from ieee80211_link_change_chanreq(), for example when a station follows a new Disabled Subchannel Bitmap of its AP without a channel switch. Set the channel again on a puncturing change, as for a width change, so that the driver programs the new bitmap. Only mt7996 uses mt76_change_chanctx(), and mt7996_set_channel() sends the bitmap to the firmware. Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 115117a19b5c..39a5dca4ca7d 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -85,7 +85,8 @@ void mt76_change_chanctx(struct ieee80211_hw *hw, struct mt76_dev *dev = phy->dev; if (!(changed & (IEEE80211_CHANCTX_CHANGE_WIDTH | - IEEE80211_CHANCTX_CHANGE_RADAR))) + IEEE80211_CHANCTX_CHANGE_RADAR | + IEEE80211_CHANCTX_CHANGE_PUNCTURING))) return; if (mt76_phy_scanning(phy)) -- 2.47.3 nl80211 refuses a non-zero NL80211_ATTR_PUNCT_BITMAP for a driver without NL80211_EXT_FEATURE_PUNCT, so an mt7996 AP cannot start or switch to a punctured channel. Advertise the feature. mt7996_set_channel() sends the puncturing bitmap of every chanctx to the firmware, including after a puncturing-only chanctx change. Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index cc94eade9950..f44a3a8588d3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -526,6 +526,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_PUNCT); if (mt7996_eeprom_has_background_radar(dev) && (!mdev->dev->of_node || -- 2.47.3 When an MLD vif scans or does ROC on a radio where it has no link, mt76_get_vif_phy_link() adds an offchannel link with &vif->bss_conf. mt7996_mcu_bss_mld_tlv() registers that link in the firmware as a member of the vif's MLD group, with the group and remap index, the MLD address and link_id 0. The group then holds link 0 twice if the MLD has a real link 0, and once more for each other radio the scan visits. The removal of the offchannel link sends a BSS_INFO without an MLD TLV, so the firmware gets no command that takes it out of the group again. Register an offchannel link like the link of a non-MLD vif. It only carries scan and ROC management frames, which need no MLD address translation. Fixes: ed01c310eca9 ("wifi: mt76: mt7996: Fix mt7996_mcu_bss_mld_tlv routine") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index f35250430383..ce37b6b65918 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1172,7 +1172,7 @@ mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, mld->own_mld_id = link->mld_idx; mld->link_id = link_conf->link_id; - if (ieee80211_vif_is_mld(vif)) { + if (ieee80211_vif_is_mld(vif) && !link->mt76.offchannel) { mld->group_mld_id = mvif->mld_group_idx; mld->remap_idx = mvif->mld_remap_idx; memcpy(mld->mac_addr, vif->addr, ETH_ALEN); -- 2.47.3 Move the loop that destroys the links of a vif out of mt7996_remove_interface() into a helper that takes the links to destroy and, optionally, their link confs. A following change destroys station links when the vif leaves MLO, where mac80211 still passes the old link confs. No functional change. Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 3a3441951254..cc8ce83f8b2d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -471,6 +471,33 @@ static void mt7996_vif_link_destroy(struct mt7996_phy *phy, } } +static void +mt7996_vif_links_destroy(struct mt7996_dev *dev, struct ieee80211_vif *vif, + unsigned long links, + struct ieee80211_bss_conf **link_confs) +{ + unsigned int link_id; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct ieee80211_bss_conf *link_conf = NULL; + struct mt7996_vif_link *link; + struct mt7996_phy *phy; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + phy = __mt7996_phy(dev, link->msta_link.wcid.phy_idx); + if (!phy) + continue; + + if (link_confs) + link_conf = link_confs[link_id]; + + mt7996_vif_link_destroy(phy, link, vif, link_conf); + } +} + void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, struct mt76_vif_link *mlink) @@ -640,30 +667,12 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - unsigned long rem_links = mvif->mt76.valid_links; struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_radio_data rdata = {}; - unsigned int link_id; int i; mutex_lock(&dev->mt76.mutex); - - /* Remove all active links */ - for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) { - struct mt7996_vif_link *link; - struct mt7996_phy *phy; - - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - - phy = __mt7996_phy(dev, link->msta_link.wcid.phy_idx); - if (!phy) - continue; - - mt7996_vif_link_destroy(phy, link, vif, NULL); - } - + mt7996_vif_links_destroy(dev, vif, mvif->mt76.valid_links, NULL); mutex_unlock(&dev->mt76.mutex); ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter, -- 2.47.3 Since the link teardown moved to mt7996_remove_interface(), a station link stays in the firmware after its chanctx is unassigned. When the links of the vif go to 0, mt7996_change_vif_links() frees the MLD group and remap index in the driver, while the firmware BSS of each link still names them. The firmware has no MLD link removal for a station link, so the next vif that takes the same index shares it with a stale BSS until the station vif is removed. That removal then uses &vif->bss_conf instead of the link conf. Destroy the station links when the vif leaves MLO, with the link confs that mac80211 passes, and before the indexes are freed. mac80211 releases the chanctx of each removed link before it calls the driver, so all links of the vif are unassigned at this point. AP links are not affected: they leave their group at unassign through the MLD link operation. First abort a remain-on-channel that holds one of the destroyed links, since mt76_unassign_vif_chanctx() aborts only a scan and mac80211 ends a remain-on-channel only when the interface stops. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index cc8ce83f8b2d..1ca4d9e099fe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2995,6 +2995,26 @@ mt7996_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } #endif +static void +mt7996_vif_link_roc_abort(struct mt7996_dev *dev, struct ieee80211_vif *vif) +{ + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct mt76_phy *phy = mvif->mt76.roc_phy; + struct mt76_vif_link *mlink; + bool abort; + + if (!phy) + return; + + mutex_lock(&dev->mt76.mutex); + mlink = phy->roc_link; + abort = phy->roc_vif == vif && mlink && !mlink->offchannel; + mutex_unlock(&dev->mt76.mutex); + + if (abort) + mt76_abort_roc(phy); +} + static int mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 old_links, u16 new_links, @@ -3002,8 +3022,15 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + bool sta_links_destroy; int ret = 0; + sta_links_destroy = !vif->valid_links && + vif->type == NL80211_IFTYPE_STATION; + /* a ROC can hold a station link that is destroyed below */ + if (sta_links_destroy) + mt7996_vif_link_roc_abort(dev, vif); + mutex_lock(&dev->mt76.mutex); if (!old_links) { @@ -3030,6 +3057,13 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (new_links) goto out; + /* the firmware supports no MLD link removal for a station link, so + * remove its BSS before the group and remap index can be reused + */ + if (sta_links_destroy) + mt7996_vif_links_destroy(dev, vif, mvif->mt76.valid_links, + old); + dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); dev->mld_remap_idx_mask &= ~BIT_ULL(mvif->mld_remap_idx); -- 2.47.3 mt7996_vif_link_remove() sends the MLD reconfiguration stop and the MLD link removal for every AP link, and mt7996_vif_link_add() sends the MLD link add for every AP link it holds already. A non-MLD AP has no MLD group in the firmware: its BSS carries group_mld_id 0xff. The firmware refuses both removal commands at each teardown of a non-MLD AP: mt7996e 0000:01:00.0: Message 00120082 (seq 1) cid 0082 failed: firmware status 0xc0000001 mt7996e 0000:01:00.0: Message 001a0002 (seq 2) cid 0002 failed: firmware status 0xc0000001 The add sends the group_mld_id and remap_idx of the vif, which a vif that never was an MLD holds at 0, the first index that an AP MLD gets. Send the MLD link operations only for an AP MLD, matching the MLD tag of mt7996_mcu_add_bss_info(). A non-MLD AP link that is added again programs its BSS as a station link does. Fixes: e7ec71d9f8fa ("wifi: mt76: mt7996: Add mcu APIs to enable/disable vif links.") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 1ca4d9e099fe..d22abbd543b7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -328,7 +328,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (ret) return ret; - if (vif->type == NL80211_IFTYPE_AP) + if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif)) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); @@ -514,7 +514,7 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (mlink->wcid->offchannel) { mt7996_vif_link_destroy(phy, link, vif, link_conf); } else { - if (vif->type == NL80211_IFTYPE_AP) { + if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif)) { mt7996_mcu_mld_reconf_stop_link(phy->dev, vif, BIT(link_id)); mt7996_mcu_mld_link_oper(phy->dev, link_conf, link, -- 2.47.3 Since the link teardown moved to mt7996_remove_interface(), a link stays in mvif->mt76.valid_links after its chanctx is unassigned. When the next chanctx assignment puts the link on another radio, mt7996_vif_link_add() takes the reuse branch and programs DEV_INFO and BSS_INFO on the new radio with the OMAC index and the band of the old radio. The firmware then holds the BSS of the link on the wrong band, and the STA_REC of a station on that link is created under it. After the station disconnects, the firmware keeps that record, so every later station that gets the same WTBL index is refused: mt7996e 0000:01:00.0: Message 001a0003 (seq 1) cid 0003 failed: firmware status 0x10003 hostapd then fails with "Could not set STA to kernel driver" for each new station on that index until the firmware restarts. Examples are a non-AP interface that used link 0 on 6 GHz and associates as an MLD with link 0 on 2.4 GHz, and an AP that is stopped and started on another band, both without an interface down. Tear such a link down on its old radio and add it again on the new one. If the add on the new radio fails, free the link, since it is no longer a valid link and mt7996_remove_interface() destroys only valid links. Keep the reuse branch for a link that stays on its band, and for mt76_switch_vif_chanctx(), which moves a link to another radio during a channel switch with its stations in place. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 145 ++++++++++++------ 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d22abbd543b7..3384e46e7b05 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -305,6 +305,76 @@ mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, sta, it->link_id, key)); } +static void mt7996_vif_link_teardown(struct mt7996_phy *phy, + struct mt7996_vif_link *link, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct mt7996_sta_link *msta_link = &link->msta_link; + unsigned int link_id = msta_link->wcid.link_id; + struct mt76_vif_link *mlink = &link->mt76; + struct mt7996_dev *dev = phy->dev; + int idx = msta_link->wcid.idx; + + if (!link_conf) + link_conf = &vif->bss_conf; + + if (link->bmc_sta_added) + mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, + CONN_STATE_DISCONNECT, false); + mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); + mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false); + + rcu_assign_pointer(dev->mt76.wcid[idx], NULL); + + dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); + dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx); + phy->omac_mask &= ~BIT_ULL(mlink->omac_idx); + if (!mlink->wcid->offchannel) + mvif->mt76.valid_links &= ~BIT(link_id); + + spin_lock_bh(&dev->mt76.sta_poll_lock); + if (!list_empty(&msta_link->wcid.poll_list)) + list_del_init(&msta_link->wcid.poll_list); + spin_unlock_bh(&dev->mt76.sta_poll_lock); + + mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); +} + +static void mt7996_vif_link_free(struct mt7996_vif_link *link, + struct ieee80211_vif *vif) +{ + unsigned int link_id = link->msta_link.wcid.link_id; + struct mt76_vif_link *mlink = &link->mt76; + + if (mlink == (struct mt76_vif_link *)vif->drv_priv || + mlink->wcid->offchannel) + return; + + rcu_assign_pointer(mlink->mvif->link[link_id], NULL); + kfree_rcu(mlink, rcu_head); +} + +static int mt7996_vif_link_reuse(struct mt7996_phy *phy, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link) +{ + struct mt76_vif_link *mlink = &link->mt76; + int ret; + + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif)) + return mt7996_mcu_mld_link_oper(phy->dev, link_conf, link, true); + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + &link->msta_link, true); +} + int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, struct mt76_vif_link *mlink) @@ -320,33 +390,38 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, .link_id = link_conf->link_id, }; int mld_idx, idx, ret; + bool moved = false; if ((mvif->mt76.valid_links & BIT(link_conf->link_id)) && !mlink->offchannel) { - /* update the link address */ - ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); - if (ret) - return ret; + struct mt7996_phy *old_phy; - if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif)) - return mt7996_mcu_mld_link_oper(dev, link_conf, link, - true); + /* mt76_switch_vif_chanctx() sets the new chanctx only after + * the link moved + */ + if (mlink->band_idx == band_idx || + mt76_vif_link_phy(mlink) != mphy) + return mt7996_vif_link_reuse(phy, vif, link_conf, link); - return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, - msta_link, true); + old_phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); + if (old_phy) { + mt7996_vif_link_teardown(old_phy, link, vif, link_conf); + moved = true; + } } + ret = -ENOSPC; mlink->idx = __ffs64(~dev->mt76.vif_mask); if (mlink->idx >= mt7996_max_interface_num(dev)) - return -ENOSPC; + goto error; idx = get_omac_idx(vif->type, phy->omac_mask); if (idx < 0) - return -ENOSPC; + goto error; mld_idx = get_own_mld_idx(dev->mld_idx_mask, false); if (mld_idx < 0) - return -ENOSPC; + goto error; link->mld_idx = mld_idx; link->bmc_sta_added = false; @@ -360,7 +435,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); if (ret) - return ret; + goto error; dev->mt76.vif_mask |= BIT_ULL(mlink->idx); dev->mld_idx_mask |= BIT_ULL(link->mld_idx); @@ -426,6 +501,12 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, } return 0; + +error: + if (moved) + mt7996_vif_link_free(link, vif); + + return ret; } static void mt7996_vif_link_destroy(struct mt7996_phy *phy, @@ -433,42 +514,8 @@ static void mt7996_vif_link_destroy(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf) { - struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - struct mt7996_sta_link *msta_link = &link->msta_link; - unsigned int link_id = msta_link->wcid.link_id; - struct mt76_vif_link *mlink = &link->mt76; - struct mt7996_dev *dev = phy->dev; - int idx = msta_link->wcid.idx; - - if (!link_conf) - link_conf = &vif->bss_conf; - - if (link->bmc_sta_added) - mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, - CONN_STATE_DISCONNECT, false); - mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); - mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false); - - rcu_assign_pointer(dev->mt76.wcid[idx], NULL); - - dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); - dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx); - phy->omac_mask &= ~BIT_ULL(mlink->omac_idx); - if (!mlink->wcid->offchannel) - mvif->mt76.valid_links &= ~BIT(link_id); - - spin_lock_bh(&dev->mt76.sta_poll_lock); - if (!list_empty(&msta_link->wcid.poll_list)) - list_del_init(&msta_link->wcid.poll_list); - spin_unlock_bh(&dev->mt76.sta_poll_lock); - - mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); - - if (mlink != (struct mt76_vif_link *)vif->drv_priv && - !mlink->wcid->offchannel) { - rcu_assign_pointer(mlink->mvif->link[link_id], NULL); - kfree_rcu(mlink, rcu_head); - } + mt7996_vif_link_teardown(phy, link, vif, link_conf); + mt7996_vif_link_free(link, vif); } static void -- 2.47.3 The firmware learns about an MLD peer only from the MLD_SETUP TLV of STA_REC_UPDATE. The vendor driver for the same firmware describes the peer with the PEER_MLD command instead: a PEER_MLD_INFO TLV with the links of the peer, its primary, secondary and setup WCID, and a PEER_MLD_CAP TLV with the A-MSDU limits. It flags every MLD_SETUP TLV with EXTRA_INFO_MLD_SKIP, so that the firmware takes the MLD setup from PEER_MLD. Send PEER_MLD at the association of an MLO peer, before the STA_REC of its first link. The firmware MLD index of the peer is the WCID of its primary link. The teardown stays with STA_REC_MLD_OFF. Flag an MLD_SETUP TLV only while its links are the links that PEER_MLD described. mac80211 activates the partner links of a station after the association, so PEER_MLD of a station covers only the assoc link. The firmware refuses a flagged MLD_SETUP for the full link set, and change_sta_links then fails with a warning in _ieee80211_set_active_links(). An unflagged MLD_SETUP sets up the links that join or leave later, as before. While the PEER_MLD description is valid, keep its MLD index as the setup and primary WCID of each MLD_SETUP, also after mt7996 makes another link the primary link when the first one goes away. With the WCID of the new primary link, the next association of the peer gets no MLD record from its PEER_MLD, and the firmware refuses the MLD_SETUP of that association: NULL MLDREC of WCID 1 A link keeps its WCID until the station is removed, also after its link station goes. TX still moves to another link; only the MLD description in the firmware keeps the primary of the association. Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7996/main.c | 8 ++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 110 +++++++++++++++++- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 59 +++++++++- .../wireless/mediatek/mt76/mt7996/mt7996.h | 4 + 5 files changed, 180 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 e9a7542c07dc..bb98a6eacac4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1363,6 +1363,7 @@ enum { MCU_UNI_CMD_FACT_CAL = 0x7c, MCU_UNI_CMD_RADIO_STATUS = 0x80, MCU_UNI_CMD_MLD = 0x82, + MCU_UNI_CMD_PEER_MLD = 0x83, MCU_UNI_CMD_SDO = 0x88, MCU_UNI_CMD_AXIDMA = 0xa2, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 3384e46e7b05..e97af1828444 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1531,6 +1531,7 @@ mt7996_mac_sta_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; msta->seclink_id = IEEE80211_LINK_UNSPECIFIED; msta->conn_state = CONN_STATE_DISCONNECT; + msta->peer_mld_links = 0; msta->ba_tx_mask = 0; msta->ba_rx_mask = 0; msta->vif = mvif; @@ -1649,6 +1650,12 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, mutex_lock(&dev->mt76.mutex); + if (ev == MT76_STA_EVENT_ASSOC && sta->mlo) { + err = mt7996_mcu_set_peer_mld(dev, vif, sta); + if (err) + goto out; + } + err = mt7996_mac_sta_links_event(dev, vif, sta, mt7996_mac_sta_links(dev, vif, sta), ev); @@ -1664,6 +1671,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, break; case MT76_STA_EVENT_DISASSOC: msta->conn_state = CONN_STATE_DISCONNECT; + msta->peer_mld_links = 0; mt7996_mac_sta_clear_connected(dev, msta); break; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index ce37b6b65918..34ac25c22b01 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2803,13 +2803,19 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, mld_setup = (struct sta_rec_mld_setup *)tlv; memcpy(mld_setup->mld_addr, sta->addr, ETH_ALEN); mld_setup->setup_wcid = cpu_to_le16(msta_link->wcid.idx); - mld_setup->primary_id = cpu_to_le16(msta_link->wcid.idx); + /* the firmware keeps the MLD record of PEER_MLD under fw_mld_idx */ + if (msta->peer_mld_links) + mld_setup->setup_wcid = cpu_to_le16(msta->fw_mld_idx); + mld_setup->primary_id = mld_setup->setup_wcid; if (links & BIT(msta->seclink_id)) seclink_id = msta->seclink_id; msta_link = mt7996_sta_link_protected(dev, msta, seclink_id); mld_setup->seconed_id = cpu_to_le16(msta_link->wcid.idx); mld_setup->link_num = nlinks; + /* PEER_MLD_INFO holds only the links of the association */ + if (links == msta->peer_mld_links) + mld_setup->extra_info = EXTRA_INFO_MLD_SKIP; mld_setup_link = (struct mld_setup_link *)mld_setup->link_info; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { @@ -2824,6 +2830,108 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, } } +static u32 mt7996_mcu_peer_mld_amsdu_len(u16 max_amsdu_len) +{ + switch (max_amsdu_len) { + case IEEE80211_MAX_MPDU_LEN_VHT_11454: + return IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + case IEEE80211_MAX_MPDU_LEN_HT_7935: + case IEEE80211_MAX_MPDU_LEN_VHT_7991: + return IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991; + default: + return IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + } +} + +int mt7996_mcu_set_peer_mld(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + unsigned long links = mt7996_mac_sta_links(dev, vif, sta); + unsigned int nlinks = hweight16(links); + struct peer_mld_cap_entry *cap_entry; + struct peer_mld_link_info *link_info; + struct mt7996_sta_link *msta_link; + u8 seclink_id = msta->deflink_id; + struct peer_mld_req_hdr *hdr; + u16 max_amsdu_len = U16_MAX; + struct peer_mld_info *info; + struct peer_mld_cap *cap; + unsigned int link_id; + struct sk_buff *skb; + struct tlv *tlv; + u16 fw_mld_idx; + int len, ret; + + if (!(links & BIT(msta->deflink_id))) + return 0; + + len = sizeof(*hdr) + sizeof(*info) + nlinks * sizeof(*link_info) + + sizeof(*cap) + 2 * sizeof(*cap_entry); + skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, len); + if (!skb) + return -ENOMEM; + + msta_link = mt7996_sta_link_protected(dev, msta, msta->deflink_id); + + fw_mld_idx = msta_link->wcid.idx; + hdr = skb_put_zero(skb, sizeof(*hdr)); + hdr->fw_mld_idx = cpu_to_le16(fw_mld_idx); + memcpy(hdr->peer_mld_addr, sta->addr, ETH_ALEN); + + tlv = mt7996_mcu_add_uni_tlv(skb, UNI_CMD_PEER_MLD_INFO, + sizeof(*info) + + nlinks * sizeof(*link_info)); + info = (struct peer_mld_info *)tlv; + info->link_num = nlinks; + info->primary_id = cpu_to_le16(msta_link->wcid.idx); + info->setup_wcid = cpu_to_le16(msta_link->wcid.idx); + + if (links & BIT(msta->seclink_id)) + seclink_id = msta->seclink_id; + msta_link = mt7996_sta_link_protected(dev, msta, seclink_id); + info->second_id = cpu_to_le16(msta_link->wcid.idx); + + link_info = info->link_info; + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct ieee80211_link_sta *link_sta; + struct mt7996_vif_link *link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + link = mt7996_vif_link(dev, vif, link_id); + link_sta = link_sta_dereference_protected(sta, link_id); + + max_amsdu_len = min(max_amsdu_len, link_sta->agg.max_amsdu_len); + + link_info->fw_link_id = cpu_to_le16(msta_link->wcid.idx); + link_info->link_id = link_id; + link_info->bss_idx = link->mt76.idx; + link_info->wlan_idx = cpu_to_le16(msta_link->wcid.idx); + link_info->band_idx = link->mt76.band_idx; + link_info++; + } + + tlv = mt7996_mcu_add_uni_tlv(skb, UNI_CMD_PEER_MLD_CAP, + sizeof(*cap) + 2 * sizeof(*cap_entry)); + cap = (struct peer_mld_cap *)tlv; + + cap_entry = cap->entry; + cap_entry->type = cpu_to_le32(UNI_CMD_PEER_MLD_CAP_TYPE_AMSDU_MAX_LEN); + cap_entry->val = cpu_to_le32(mt7996_mcu_peer_mld_amsdu_len(max_amsdu_len)); + cap_entry++; + cap_entry->type = cpu_to_le32(UNI_CMD_PEER_MLD_CAP_TYPE_AMSDU_MAX_CNT); + cap_entry->val = cpu_to_le32(31); + + ret = mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(PEER_MLD), true); + if (!ret) { + msta->peer_mld_links = links; + msta->fw_mld_idx = fw_mld_idx; + } + + return ret; +} + static void mt7996_mcu_sta_eht_mld_tlv(struct mt7996_dev *dev, struct sk_buff *skb, struct ieee80211_sta *sta) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 70ed57d9bf9a..2fdf82394355 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -730,7 +730,8 @@ struct sta_rec_mld_setup { __le16 seconed_id; __le16 setup_wcid; u8 link_num; - u8 info; +#define EXTRA_INFO_MLD_SKIP BIT(2) + u8 extra_info; u8 __rsv[2]; u8 link_info[]; } __packed; @@ -773,6 +774,62 @@ enum { UNI_CMD_MLD_RECONF_STOP_LINK = 0x04, }; +struct peer_mld_req_hdr { + u8 ver; + u8 peer_mld_addr[ETH_ALEN]; + u8 rsv; + __le16 fw_mld_idx; + __le16 flag; + u8 buf[]; +} __packed; + +struct peer_mld_link_info { + __le16 fw_link_id; + u8 link_id; + u8 bss_idx; + __le16 wlan_idx; + u8 band_idx; + u8 rsv; +} __packed; + +struct peer_mld_info { + __le16 tag; + __le16 len; + u8 ver; + u8 link_num; + u8 rsv[2]; + __le16 primary_id; + __le16 second_id; + __le16 setup_wcid; + u8 rsv2[2]; + struct peer_mld_link_info link_info[]; +} __packed; + +struct peer_mld_cap_entry { + __le32 type; + __le32 val; +} __packed; + +struct peer_mld_cap { + __le16 tag; + __le16 len; + u8 ver; + u8 rsv; + __le16 bitmap; + u8 rsv2[8]; + struct peer_mld_cap_entry entry[]; +} __packed; + +enum { + UNI_CMD_PEER_MLD_INFO = 0x01, + UNI_CMD_PEER_MLD_CAP = 0x02, +}; + +enum { + UNI_CMD_PEER_MLD_CAP_TYPE_AMSDU_MAX_LEN = 0x01, + UNI_CMD_PEER_MLD_CAP_TYPE_AMSDU_MAX_CNT = 0x02, +}; + struct hdr_trans_en { __le16 tag; __le16 len; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 1f1dff9f6610..0c6a3dba2e03 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -276,6 +276,8 @@ struct mt7996_sta { u8 deflink_id; u8 seclink_id; u8 conn_state; + u16 peer_mld_links; + u16 fw_mld_idx; unsigned long wcid_flags; @@ -753,6 +755,8 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link, int conn_state, bool newly); +int mt7996_mcu_set_peer_mld(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); -- 2.47.3 mt7915_mcu_get_eeprom() ignores the valid flag of the EFUSE_ACCESS response, and copies undefined data for an unwritten efuse block. Return -ENODATA for an invalid block. mt7915_eeprom_load() skips the block and leaves its eeprom range untouched. mt7915_eeprom_has_background_radar() then reports no background radar on MT7906. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c index 145d14a1012d..0ab02580423f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c @@ -154,7 +154,7 @@ static int mt7915_eeprom_load(struct mt7915_dev *dev) block_num = DIV_ROUND_UP(eeprom_size, eeprom_blk_size); for (i = 0; i < block_num; i++) { ret = mt7915_mcu_get_eeprom(dev, i * eeprom_blk_size, NULL); - if (ret < 0) + if (ret < 0 && ret != -ENODATA) return ret; } } diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 67b470f4bd9b..e66968b819da 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -2952,6 +2952,11 @@ int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset, u8 *read_buf) } res = (struct mt7915_mcu_eeprom_info *)skb->data; + if (!le32_to_cpu(res->valid)) { + dev_kfree_skb(skb); + return -ENODATA; + } + if (!buf) { u32 addr = le32_to_cpu(res->addr); -- 2.47.3 mt7996_tx() translates the MLD addresses of a frame into link addresses for an MLD interface. It casts skb->data to struct ieee80211_hdr, but a frame with IEEE80211_TX_CTL_HW_80211_ENCAP set carries an Ethernet header, so the writes to addr1 and addr2 corrupt the Ethernet header and the start of the payload. Skip the translation for such a frame. This is currently not reachable, because mac80211 queues encap offload frames to a TXQ and mt7996_tx() receives them only when they bypass the TXQ. Found by inspection. Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e97af1828444..f715817243a6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2089,7 +2089,8 @@ static void mt7996_tx(struct ieee80211_hw *hw, if (link_id == IEEE80211_LINK_UNSPECIFIED) link_id = deflink_id; - if (vif && ieee80211_vif_is_mld(vif)) { + if (vif && ieee80211_vif_is_mld(vif) && + !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)) { struct ieee80211_bss_conf *link_conf; if (msta) { -- 2.47.3 mt7921_mcu_read_eeprom() ignores the valid flag of the EFUSE_ACCESS response. For an unwritten efuse block, mt7921_load_clc() thus selects the CLC records by an undefined hardware type. Return -ENODATA for an invalid block, like mt7915_mcu_get_eeprom(). mt7921_load_clc() then keeps the hardware type 0, which it also uses on USB and SDIO, where it does not read the field. Fixes: 23bdc5d8cadf ("wifi: mt76: mt7921: introduce Country Location Control support") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index a0caa77339ee..242a4415539e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -111,6 +111,11 @@ static int mt7921_mcu_read_eeprom(struct mt792x_dev *dev, u32 offset, u8 *val) } res = (struct mt7921_mcu_eeprom_info *)skb->data; + if (!le32_to_cpu(res->valid)) { + dev_kfree_skb(skb); + return -ENODATA; + } + *val = res->data[offset % MT7921_EEPROM_BLOCK_SIZE]; dev_kfree_skb(skb); @@ -453,7 +458,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) if (mt76_is_mmio(&dev->mt76)) { ret = mt7921_mcu_read_eeprom(dev, MT_EE_HW_TYPE, &hw_encap); - if (ret) + if (ret && ret != -ENODATA) return ret; hw_encap = u8_get_bits(hw_encap, MT_EE_HW_TYPE_ENCAP); } -- 2.47.3 At the start of a CSA, __ieee80211_channel_switch() reports BSS_CHANGED_BEACON with csa_active set and then calls .channel_switch_beacon. mt7915_bss_info_changed() and mt7915_channel_switch_beacon() thus both upload the same CSA beacon, and each upload passes the initial count of the template to the firmware. Drop mt7915_channel_switch_beacon(). mac80211 calls it only after a BSS_CHANGED_BEACON report, so mt7915_bss_info_changed() already uploads the CSA beacon, like mt7996 does. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 556f8948abac..8cf7fde1a197 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -737,18 +737,6 @@ mt7915_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); } -static void -mt7915_channel_switch_beacon(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct cfg80211_chan_def *chandef) -{ - struct mt7915_dev *dev = mt7915_hw_dev(hw); - - mutex_lock(&dev->mt76.mutex); - mt7915_mcu_add_beacon(hw, vif, true, BSS_CHANGED_BEACON); - mutex_unlock(&dev->mt76.mutex); -} - int mt7915_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct ieee80211_sta *sta) { @@ -1803,7 +1791,6 @@ const struct ieee80211_ops mt7915_ops = { .release_buffered_frames = mt76_release_buffered_frames, .get_txpower = mt76_get_txpower, .set_sar_specs = mt7915_set_sar_specs, - .channel_switch_beacon = mt7915_channel_switch_beacon, .get_stats = mt7915_get_stats, .get_et_sset_count = mt7915_get_et_sset_count, .get_et_stats = mt7915_get_et_stats, -- 2.47.3 mt76_vif_link_phy() and mt76_vif_phy() test mlink->ctx and then load it again to reach the mt76_chanctx. mt7996_tx(), mt7996_mac_sta_poll() from the TX free path, mt76_rx_beacon(), the beacon monitor and mt76_offchannel_notify() call mt76_vif_link_phy() without dev->mutex, while mt76_unassign_vif_chanctx() can set mlink->ctx to NULL in between. The second load can then return NULL. Load mlink->ctx once with acquire semantics, and publish it with release semantics in the chanctx functions, so that a reader that sees a chanctx also sees the link state written before it. Let mt76_vif_phy() use mt76_vif_link_phy() instead of its own copy. Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 6 +++--- drivers/net/wireless/mediatek/mt76/mac80211.c | 7 +------ drivers/net/wireless/mediatek/mt76/mt76.h | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 39a5dca4ca7d..a44773b9a49e 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -137,7 +137,7 @@ int mt76_assign_vif_chanctx(struct ieee80211_hw *hw, mlink_alloc = true; } - mlink->ctx = conf; + mt76_vif_link_ctx_set(mlink, conf); ret = dev->drv->vif_link_add(phy, vif, link_conf, mlink); if (ret) { if (mlink_alloc) @@ -179,7 +179,7 @@ void mt76_unassign_vif_chanctx(struct ieee80211_hw *hw, goto out; dev->drv->vif_link_remove(phy, vif, link_conf, mlink); - mlink->ctx = NULL; + mt76_vif_link_ctx_set(mlink, NULL); out: mutex_unlock(&dev->mutex); } @@ -257,7 +257,7 @@ int mt76_switch_vif_chanctx(struct ieee80211_hw *hw, goto out; } - mlink->ctx = v->new_ctx; + mt76_vif_link_ctx_set(mlink, v->new_ctx); if (mlink->beacon_mon_interval) WRITE_ONCE(mlink->beacon_mon_last, jiffies); } diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 89f289ac57a9..b2702c9079d9 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -876,16 +876,11 @@ struct mt76_phy *mt76_vif_phy(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv; - struct mt76_chanctx *ctx; if (!hw->wiphy->n_radio) return hw->priv; - if (!mlink->ctx) - return NULL; - - ctx = (struct mt76_chanctx *)mlink->ctx->drv_priv; - return ctx->phy; + return mt76_vif_link_phy(mlink); } EXPORT_SYMBOL_GPL(mt76_vif_phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 9e851dea71d9..42a1f84c5ca9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -2167,15 +2167,26 @@ mt76_vif_conf_link(struct mt76_dev *dev, struct ieee80211_vif *vif, return mt76_dereference(mvif->link[link_conf->link_id], dev); } +static inline void +mt76_vif_link_ctx_set(struct mt76_vif_link *mlink, + struct ieee80211_chanctx_conf *conf) +{ + /* pairs with smp_load_acquire() in mt76_vif_link_phy() */ + smp_store_release(&mlink->ctx, conf); +} + static inline struct mt76_phy * mt76_vif_link_phy(struct mt76_vif_link *mlink) { + struct ieee80211_chanctx_conf *conf; struct mt76_chanctx *ctx; - if (!mlink->ctx) + /* pairs with smp_store_release() in mt76_vif_link_ctx_set() */ + conf = smp_load_acquire(&mlink->ctx); + if (!conf) return NULL; - ctx = (struct mt76_chanctx *)mlink->ctx->drv_priv; + ctx = (struct mt76_chanctx *)conf->drv_priv; return ctx->phy; } -- 2.47.3 mt7996_vif_link_remove() keeps a station link when its chanctx is unassigned, for example when mac80211 deactivates the link. The link keeps its beacon_mon_interval and the beacon_mon_last of its last beacon. When the link is activated again, mt7996_vif_link_add() takes the reuse branch, which does not touch the beacon monitor. Unless a beacon on that link arrives first, the next mt76_beacon_mon_check() reports a beacon loss for the stale timestamp, and the station disconnects: sta-mld0: CTRL-EVENT-BEACON-LOSS sta-mld0: CTRL-EVENT-DISCONNECTED bssid=a0:ad:9f:70:fb:4a reason=4 locally_generated=1 Reset beacon_mon_last whenever a link gets a chanctx, which mt76_switch_vif_chanctx() already did after a channel switch. Do it before the link points to the chanctx, since mt76_beacon_mon_check() runs without dev->mutex and can see the link while vif_link_add() waits for the firmware. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 2 -- drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index a44773b9a49e..da111d37c4f5 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -258,8 +258,6 @@ int mt76_switch_vif_chanctx(struct ieee80211_hw *hw, } mt76_vif_link_ctx_set(mlink, v->new_ctx); - if (mlink->beacon_mon_interval) - WRITE_ONCE(mlink->beacon_mon_last, jiffies); } out: diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 42a1f84c5ca9..fb1b99f56e9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -2171,6 +2171,9 @@ static inline void mt76_vif_link_ctx_set(struct mt76_vif_link *mlink, struct ieee80211_chanctx_conf *conf) { + if (conf && mlink->beacon_mon_interval) + WRITE_ONCE(mlink->beacon_mon_last, jiffies); + /* pairs with smp_load_acquire() in mt76_vif_link_phy() */ smp_store_release(&mlink->ctx, conf); } -- 2.47.3 mt7996_vif_link_add() and mt7996_vif_cfg_changed() start the beacon monitor of a station link with a plain store of beacon_mon_interval, followed by the store of beacon_mon_last. mt76_beacon_mon_iter() runs without dev->mutex and reads the interval first. It can thus see the new interval with the old timestamp, for example 0 for a new link, and report a beacon loss at once. Add helpers that start and stop the monitor: the timestamp is written first, and the interval is published with release semantics, which mt76_beacon_mon_iter() pairs with an acquire load. Read the interval with READ_ONCE() in the other lockless users. This also removes the duplicated interval computation in mt7996. Fixes: 4df75606ece5 ("wifi: mt76: add per-link beacon monitoring for MLO") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 19 +++++++++++++----- drivers/net/wireless/mediatek/mt76/mt76.h | 16 +++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/main.c | 20 +++++++------------ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index b2702c9079d9..76908bd835f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -2243,7 +2243,7 @@ mt76_offchannel_notify_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) if (!ieee80211_vif_is_mld(vif)) { if (mt76_vif_link_phy(mlink) == data->phy) { - if (!data->offchannel && mlink->beacon_mon_interval) + if (!data->offchannel && READ_ONCE(mlink->beacon_mon_interval)) WRITE_ONCE(mlink->beacon_mon_last, jiffies); mt76_offchannel_send_nullfunc(data, vif, -1); } @@ -2260,7 +2260,7 @@ mt76_offchannel_notify_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) if (mt76_vif_link_phy(mlink) != data->phy) continue; - if (!data->offchannel && mlink->beacon_mon_interval) + if (!data->offchannel && READ_ONCE(mlink->beacon_mon_interval)) WRITE_ONCE(mlink->beacon_mon_last, jiffies); mt76_offchannel_send_nullfunc(data, vif, link_id); @@ -2308,7 +2308,7 @@ static void mt76_rx_beacon_iter(void *_data, u8 *mac, mlink = (struct mt76_vif_link *)vif->drv_priv; else mlink = rcu_dereference(mvif->link[link_id]); - if (!mlink || !mlink->beacon_mon_interval) + if (!mlink || !READ_ONCE(mlink->beacon_mon_interval)) continue; if (mt76_vif_link_phy(mlink) != data->phy) @@ -2363,11 +2363,20 @@ static void mt76_beacon_mon_iter(void *data, u8 *mac, return; for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + u16 interval; + if (link_id == mvif->deflink_id) mlink = (struct mt76_vif_link *)vif->drv_priv; else mlink = rcu_dereference(mvif->link[link_id]); - if (!mlink || !mlink->beacon_mon_interval) + if (!mlink) + continue; + + /* pairs with smp_store_release() in + * mt76_vif_link_beacon_mon_start() + */ + interval = smp_load_acquire(&mlink->beacon_mon_interval); + if (!interval) continue; if (mt76_vif_link_phy(mlink) != phy) @@ -2375,7 +2384,7 @@ static void mt76_beacon_mon_iter(void *data, u8 *mac, if (time_after(jiffies, READ_ONCE(mlink->beacon_mon_last) + - MT76_BEACON_MON_MAX_MISS * mlink->beacon_mon_interval)) + MT76_BEACON_MON_MAX_MISS * interval)) ieee80211_beacon_loss(vif); } } diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index fb1b99f56e9c..fce7bf2f0333 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -2178,6 +2178,22 @@ mt76_vif_link_ctx_set(struct mt76_vif_link *mlink, smp_store_release(&mlink->ctx, conf); } +static inline void +mt76_vif_link_beacon_mon_start(struct mt76_vif_link *mlink, u16 beacon_int) +{ + u16 interval = msecs_to_jiffies(ieee80211_tu_to_usec(beacon_int) / 1000); + + WRITE_ONCE(mlink->beacon_mon_last, jiffies); + /* pairs with smp_load_acquire() in mt76_beacon_mon_iter() */ + smp_store_release(&mlink->beacon_mon_interval, interval); +} + +static inline void +mt76_vif_link_beacon_mon_stop(struct mt76_vif_link *mlink) +{ + WRITE_ONCE(mlink->beacon_mon_interval, 0); +} + static inline struct mt76_phy * mt76_vif_link_phy(struct mt76_vif_link *mlink) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f715817243a6..6815f5192116 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -492,12 +492,9 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_STATION) { vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; - if (vif->cfg.assoc && link_conf->beacon_int) { - mlink->beacon_mon_interval = - msecs_to_jiffies(ieee80211_tu_to_usec( - link_conf->beacon_int) / 1000); - WRITE_ONCE(mlink->beacon_mon_last, jiffies); - } + if (vif->cfg.assoc && link_conf->beacon_int) + mt76_vif_link_beacon_mon_start(mlink, + link_conf->beacon_int); } return 0; @@ -1027,12 +1024,9 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!link) continue; - if (vif->type == NL80211_IFTYPE_STATION) { - link->mt76.beacon_mon_interval = - msecs_to_jiffies(ieee80211_tu_to_usec( - link_conf->beacon_int) / 1000); - WRITE_ONCE(link->mt76.beacon_mon_last, jiffies); - } + if (vif->type == NL80211_IFTYPE_STATION) + mt76_vif_link_beacon_mon_start(&link->mt76, + link_conf->beacon_int); phy = mt7996_vif_link_phy(link); if (!phy) @@ -1053,7 +1047,7 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, link = mt7996_vif_link(dev, vif, link_id); if (link) - link->mt76.beacon_mon_interval = 0; + mt76_vif_link_beacon_mon_stop(&link->mt76); } } -- 2.47.3 MT_MDP_DCR0_RX_HDR_TRANS_EN is one bit for the whole chip, but mt7996_set_monitor() writes it from the state of one radio. When a monitor interface goes away that covers a radio no other monitor covers, mt7996_remove_interface() turns sniffer mode off on that radio, and header translation comes back on while a monitor of another radio still runs. Frames translated to 802.3 do not reach monitor interfaces, so that monitor stops receiving the data frames addressed to the device. Turn header translation off while any radio of the device is in monitor mode, like mt7915_config() does with dev->monitor_mask. Fixes: 947d63d8cd3b ("wifi: mt76: mt7996: Disable Rx hdr_trans in monitor mode") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 6815f5192116..ec7c5e8f3fda 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -612,6 +612,17 @@ static void mt7996_phy_set_rxfilter(struct mt7996_phy *phy) mt76_clear(dev, MT_WF_RFCR1(phy->mt76->band_idx), ctl_flags); } +static bool mt7996_monitor_active(struct mt7996_dev *dev) +{ + struct mt7996_phy *phy; + + mt7996_for_each_phy(dev, phy) + if (!(phy->rxfilter & MT_WF_RFCR_DROP_OTHER_UC)) + return true; + + return false; +} + static void mt7996_set_monitor(struct mt7996_phy *phy, bool enabled) { struct mt7996_dev *dev; @@ -632,7 +643,7 @@ static void mt7996_set_monitor(struct mt7996_phy *phy, bool enabled) mt76_rmw_field(dev, MT_DMA_DCR0(phy->mt76->band_idx), MT_DMA_DCR0_RXD_G5_EN, enabled); mt76_rmw_field(dev, MT_MDP_DCR0, - MT_MDP_DCR0_RX_HDR_TRANS_EN, !enabled); + MT_MDP_DCR0_RX_HDR_TRANS_EN, !mt7996_monitor_active(dev)); mt7996_phy_set_rxfilter(phy); mt7996_mcu_set_sniffer_mode(phy, enabled); } -- 2.47.3 mt7996_mac_full_reset() loads the firmware again in mt7996_mac_restart() and then lets ieee80211_restart_hw() add every interface back. The reset also clears the RXD group 5 bit in MT_DMA_DCR0 and sets the header translation bit in MT_MDP_DCR0. phy->rxfilter still has MT_WF_RFCR_DROP_OTHER_UC cleared on the radios of a monitor interface, so mt7996_set_monitor() returns early on the re-add. The new firmware never gets the sniffer mode command, and the monitor runs on with header translation on and without RXD group 5. Mark every radio as out of monitor mode before the interfaces are added again, so that the re-add programs the monitor state from scratch. Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 2e1b67ac2d11..a92536180dbe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2491,8 +2491,10 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) if (i == 10) dev_err(dev->mt76.dev, "chip full reset failed\n"); - mt7996_for_each_phy(dev, phy) + mt7996_for_each_phy(dev, phy) { phy->omac_mask = 0; + phy->rxfilter |= MT_WF_RFCR_DROP_OTHER_UC; + } dev->mld_idx_mask = 0; dev->mld_remap_idx_mask = 0; -- 2.47.3 For a frame that the hardware decrypted, mt76_check_ccmp_pn() compares the PN with the last PN of status->wcid. For an MLD peer, mt7996 reports each frame on the wcid of the link it arrived on, so every link keeps its own replay counter, although all links of the MLD share the PN space of the key. With the IV stripped, the function also sets RX_FLAG_PN_VALIDATED, so mac80211 skips its own check per key. A frame received on one link and replayed on another one thus passes the replay check whenever the second link has seen lower PNs. Keep the replay counters of a peer on its default wcid (def_wcid), like the reorder state in mt76_rx_aggr_reorder(). The key check still uses the wcid of the link. This also applies to mt7925, which sets def_wcid for the links of an MLD station. A key install resets these counters to the RX sequence of the key in mac80211, which frames marked RX_FLAG_PN_VALIDATED never advance. In mt7996 and mt7925, the key of a station is set up link by link, and in mt7996 a link that is added later gets the installed keys again. Each of these setups would reset the counters of the whole peer, so that a frame of the new key received on one link could be replayed after the setup of the next one. Seed the counters on the default wcid once per key install, before the key reaches any link of the station, and only enable the check on each link. Fixes: dd82a9e02c05 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 18 ++++++++---------- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ .../net/wireless/mediatek/mt76/mt7925/main.c | 13 +++++++++++-- .../net/wireless/mediatek/mt76/mt7996/main.c | 11 ++++++++++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 76908bd835f2..08ed3fc6d386 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1209,16 +1209,10 @@ void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_seq seq; int i; - wcid->rx_check_pn = false; - - if (!key) + mt76_wcid_key_link_setup(wcid, key); + if (!wcid->rx_check_pn) return; - if (key->cipher != WLAN_CIPHER_SUITE_CCMP) - return; - - wcid->rx_check_pn = true; - /* data frame */ for (i = 0; i < IEEE80211_NUM_TIDS; i++) { ieee80211_get_key_rx_seq(key, i, &seq); @@ -1327,6 +1321,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb) struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; struct mt76_wcid *wcid = status->wcid; struct ieee80211_hdr *hdr; + struct mt76_wcid *pn_wcid; int security_idx; int ret; @@ -1339,6 +1334,9 @@ mt76_check_ccmp_pn(struct sk_buff *skb) if (!wcid || !wcid->rx_check_pn) return; + /* the links of an MLD peer share one PN space per key */ + pn_wcid = mt76_wcid_primary(wcid); + security_idx = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; if (status->flag & RX_FLAG_8023) goto skip_hdr_check; @@ -1366,14 +1364,14 @@ mt76_check_ccmp_pn(struct sk_buff *skb) skip_hdr_check: BUILD_BUG_ON(sizeof(status->iv) != sizeof(wcid->rx_key_pn[0])); - ret = memcmp(status->iv, wcid->rx_key_pn[security_idx], + ret = memcmp(status->iv, pn_wcid->rx_key_pn[security_idx], sizeof(status->iv)); if (ret <= 0) { status->flag |= RX_FLAG_ONLY_MONITOR; return; } - memcpy(wcid->rx_key_pn[security_idx], status->iv, sizeof(status->iv)); + memcpy(pn_wcid->rx_key_pn[security_idx], status->iv, sizeof(status->iv)); if (status->flag & RX_FLAG_IV_STRIPPED) status->flag |= RX_FLAG_PN_VALIDATED; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index fce7bf2f0333..477d33a0022d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1574,6 +1574,13 @@ int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid, u16 ssn, u16 size); void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid); +static inline void +mt76_wcid_key_link_setup(struct mt76_wcid *wcid, + struct ieee80211_key_conf *key) +{ + wcid->rx_check_pn = key && key->cipher == WLAN_CIPHER_SUITE_CCMP; +} + void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_conf *key); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 22cc5b49da0a..ffbc6b954632 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -707,8 +707,11 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, else goto out; - mt76_wcid_key_setup(&dev->mt76, wcid, - cmd == SET_KEY ? key : NULL); + if (sta) + mt76_wcid_key_link_setup(wcid, cmd == SET_KEY ? key : NULL); + else + mt76_wcid_key_setup(&dev->mt76, wcid, + cmd == SET_KEY ? key : NULL); err = mt7925_mcu_add_key(&dev->mt76, vif, &mlink->bip, key, MCU_UNI_CMD(STA_REC_UPDATE), @@ -749,6 +752,12 @@ static int mt7925_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, mt792x_mutex_acquire(dev); + /* the links of a station share one RX PN state, seeded before the + * key reaches any of them + */ + if (sta && cmd == SET_KEY) + mt76_wcid_key_setup(&dev->mt76, &msta->deflink.wcid, key); + if (ieee80211_vif_is_mld(vif)) { unsigned int link_id; unsigned long add; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index ec7c5e8f3fda..82143ade1cc0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -268,7 +268,7 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, if (cmd != SET_KEY && !is_bigtk) return 0; - mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); + mt76_wcid_key_link_setup(&msta_link->wcid, key); err = mt7996_mcu_add_key(&dev->mt76, link, key, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), @@ -837,6 +837,15 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, else links = BIT(0); + /* the links of a station share one RX PN state, seeded before the + * key reaches any of them + */ + if (sta && cmd == SET_KEY) { + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + + mt76_wcid_key_setup(&dev->mt76, &msta->deflink.wcid, key); + } + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { err = mt7996_set_hw_key(hw, cmd, vif, sta, link_id, key); if (err) -- 2.47.3 Since links added after association are brought up in mt7996_mac_sta_links_sync(), the remaining links of an MLO station also get an MLD_SETUP when links are only removed, with a link list that no longer holds the removed links. After that MLD_SETUP, the remaining links pass no traffic in either direction, although TX completes without failures. Traffic only resumes once a link is added again. Send the MLD_SETUP to the other links only when a link joins. The MediaTek driver also sends no firmware command when a link station goes, and tears down all links with STA_REC_MLD_OFF at disassociation. This also applies to the restore after a failed link add. Fixes: 1aef01d3cbbe ("wifi: mt76: mt7996: bring up station links added after association") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 82143ade1cc0..5df5354beb50 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1864,7 +1864,8 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, mt7996_mac_sta_link_ps_leave(dev, link, msta_link); } - updated = sta->mlo ? links & ~updated : 0; + /* a link list without a removed link stalls the remaining links */ + updated = sta->mlo && joined ? links & ~updated : 0; for_each_set_bit(link_id, &updated, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; -- 2.47.3 mt7996_mac_write_txwi_80211() sets MT_TXD5_FL for an MLD interface only on multicast, EAPOL and 4-address null frames. A unicast management frame to an MLD peer goes out on a link that the hardware picks among the links of the peer, not on the link of its TXD. ML reconfiguration depends on that link: mac80211 removes a link before it sends the Link Reconfiguration Request, so that the request leaves on a remaining link, and the AP MLD sends the Response on the link where the request was received. With hardware link selection, the request leaves on the removed link, or the AP sends the Response there. The station then drops the Response, and the link removal ends with a disconnection. Set MT_TXD5_FL for unicast management frames to a station of an MLD interface, so that they keep the link that mac80211 or the driver selected. Fixes: f0b0b239b8f3 ("wifi: mt76: mt7996: rework mt7996_mac_write_txwi() for MLO support") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index a92536180dbe..03670293a78f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -851,7 +851,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, } if (vif && ieee80211_vif_is_mld(vif) && - (multicast || unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE)))) + (multicast || unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE)) || + (ieee80211_is_mgmt(fc) && wcid->sta))) txwi[5] |= cpu_to_le32(MT_TXD5_FL); if (ieee80211_is_nullfunc(fc) && ieee80211_has_a4(fc) && -- 2.47.3