mt76_assign_vif_chanctx() and mt76_unassign_vif_chanctx() abort the scan whenever the scanning vif gains or loses a link, on any radio. An AP MLD that scans on one radio for ACS while its link on another radio comes up therefore loses the whole scan, and hostapd gets an aborted scan. mac80211 does not continue an aborted scan with the next band. The scan uses the link of the vif that mt76_get_vif_phy_link() returns for the scanning radio, so it needs to stop only for a channel context on that radio: the new link would duplicate the offchannel link, or the removed link is the one the scan uses. Abort only in that case, as mt76_add_chanctx() does. Fixes: e411b8190fe7 ("wifi: mt76: add multi-radio support to scanning code") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 903f3a420e4c..8d4daa76be54 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -114,7 +114,7 @@ int mt76_assign_vif_chanctx(struct ieee80211_hw *hw, bool mlink_alloc = false; int ret = 0; - if (dev->scan.vif == vif) + if (dev->scan.vif == vif && dev->scan.phy == phy) mt76_abort_scan(dev); mutex_lock(&dev->mutex); @@ -161,7 +161,7 @@ void mt76_unassign_vif_chanctx(struct ieee80211_hw *hw, struct mt76_phy *phy = ctx->phy; struct mt76_dev *dev = phy->dev; - if (dev->scan.vif == vif) + if (dev->scan.vif == vif && dev->scan.phy == phy) mt76_abort_scan(dev); mutex_lock(&dev->mutex); -- 2.47.3 mt7996_mcu_set_txpower_sku() uploads the per-rate power limit table, but the firmware applies it only after the driver enables the SKU power limit. mt7915 does that in mt7915_mcu_set_sku_en(); mt7996 never does, so the radio keeps the EEPROM target power and the configured tx power has no effect. Add the power control command and enable the table per band in mt7996_run(), as the MediaTek vendor driver does. The backoff table stays disabled. This driver does not build the per-path limits; it pads that part of the message with zeros. Fixes: f75e4779d215 ("wifi: mt76: mt7996: add txpower setting support") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 5 +++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 34 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 23 +++++++++++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 4 files changed, 63 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d17d1dbb2038..c04e980f6ee7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -34,6 +34,11 @@ int mt7996_run(struct mt7996_phy *phy) if (ret) return ret; + ret = mt7996_mcu_set_tx_power_ctrl(phy, UNI_TXPOWER_SKU_POWER_LIMIT_CTRL, + true); + if (ret) + return ret; + set_bit(MT76_STATE_RUNNING, &phy->mt76->state); ieee80211_queue_delayed_work(dev->mphy.hw, &phy->mt76->mac_work, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index a534d07ea1e8..310ce18ec12c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -5488,6 +5488,40 @@ int mt7996_mcu_set_sniffer_mode(struct mt7996_phy *phy, bool enabled) sizeof(req), true); } +int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data) +{ + struct mt7996_dev *dev = phy->dev; + struct tx_power_ctrl req = { + .tag = cpu_to_le16(power_ctrl_id), + .len = cpu_to_le16(sizeof(req) - 4), + .power_ctrl_id = power_ctrl_id, + .band_idx = phy->mt76->band_idx, + }; + + switch (power_ctrl_id) { + case UNI_TXPOWER_SKU_POWER_LIMIT_CTRL: + req.sku_enable = !!data; + break; + case UNI_TXPOWER_PERCENTAGE_CTRL: + req.percentage_ctrl_enable = !!data; + break; + case UNI_TXPOWER_PERCENTAGE_DROP_CTRL: + req.power_drop_level = data; + break; + case UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL: + req.bf_backoff_enable = !!data; + break; + case UNI_TXPOWER_ATE_MODE_CTRL: + req.ate_mode_enable = !!data; + break; + default: + return -EINVAL; + } + + return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(TXPOWER), + &req, sizeof(req), false); +} + int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy) { #define TX_POWER_LIMIT_TABLE_RATE 0 diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3d..87573688171b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -1036,8 +1036,31 @@ enum { UNI_CMD_THERMAL_PROTECT_DUTY_CONFIG, }; +struct tx_power_ctrl { + u8 _rsv[4]; + + __le16 tag; + __le16 len; + + u8 power_ctrl_id; + union { + bool sku_enable; + bool ate_mode_enable; + bool percentage_ctrl_enable; + bool bf_backoff_enable; + u8 power_drop_level; + }; + u8 band_idx; + u8 rsv[1]; +} __packed; + enum { + UNI_TXPOWER_SKU_POWER_LIMIT_CTRL = 0, + UNI_TXPOWER_PERCENTAGE_CTRL = 1, + UNI_TXPOWER_PERCENTAGE_DROP_CTRL = 2, + UNI_TXPOWER_BACKOFF_POWER_LIMIT_CTRL = 3, UNI_TXPOWER_POWER_LIMIT_TABLE_CTRL = 4, + UNI_TXPOWER_ATE_MODE_CTRL = 6, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 2a0cdb56f822..dcfdcd5975c3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -778,6 +778,7 @@ int mt7996_mcu_get_temperature(struct mt7996_phy *phy); int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state); int mt7996_mcu_set_thermal_protect(struct mt7996_phy *phy, bool enable); int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy); +int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data); int mt7996_mcu_rdd_resume_tx(struct mt7996_phy *phy); int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 rdd_idx, u8 val); int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy, -- 2.47.3 mt7996_add_interface() skips a radio that has MT76_STATE_RUNNING set, and the skip covers both the start of the radio and the call to mt7996_set_monitor(). mt7996_init_wiphy_band() sets MT_WF_RFCR_DROP_OTHER_UC in phy->rxfilter on every band, and mt7996_configure_filter() does not clear it, so only mt7996_set_monitor() does. A monitor interface that is added while an AP runs on the same radio thus receives no unicast frame addressed to another device. Start only an idle radio, but put every radio of a monitor interface into sniffer mode. mt7996_remove_interface() already clears sniffer mode whether the radio runs or not. Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index c04e980f6ee7..d013a472c28d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -570,13 +570,14 @@ static int mt7996_add_interface(struct ieee80211_hw *hw, for (i = 0; i < MT7996_MAX_RADIOS; i++) { struct mt7996_phy *phy = dev->radio_phy[i]; - if (!phy || !(wdev->radio_mask & BIT(i)) || - test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) + if (!phy || !(wdev->radio_mask & BIT(i))) continue; - err = mt7996_run(phy); - if (err) - goto out; + if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) { + err = mt7996_run(phy); + if (err) + goto out; + } if (vif->type == NL80211_IFTYPE_MONITOR) mt7996_set_monitor(phy, true); -- 2.47.3 mt7996_vif_link_remove() only stops an AP link and keeps its BSS, so a link that is added again takes the early branch of mt7996_vif_link_add(). For other interface types that branch sends DEV_INFO with the new link address. For an AP it only restarts the link, and the firmware keeps the own MAC address the link had when it was first created. The hardware takes A2 of an encapsulation offload frame from that address. A reload that gives an AP MLD link a new address therefore makes every data frame on that link carry the old address as TA. The receiver acknowledges the frame by its RA but cannot find a key for the TA and drops it, so the AP counts no failure. The BSS_INFO update on BSS_CHANGED_BEACON_ENABLED restores the BSSID, but nothing sends DEV_INFO again. Send DEV_INFO for an AP link as well, before the link is restarted. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d013a472c28d..621e1337dec2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -312,15 +312,15 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if ((mvif->mt76.valid_links & BIT(link_conf->link_id)) && !mlink->offchannel) { - if (vif->type == NL80211_IFTYPE_AP) - return mt7996_mcu_mld_link_oper(dev, link_conf, link, - true); - /* update the link address */ ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); if (ret) return ret; + if (vif->type == NL80211_IFTYPE_AP) + return mt7996_mcu_mld_link_oper(dev, link_conf, link, + true); + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, true); } -- 2.47.3 mt7996_mcu_sta_mld_setup_tlv() sets link_num and the TLV length from hweight16(sta->valid_links), but fills an entry only for active links that have driver state. On a station interface mac80211 activates only the association link at first, and drv_change_sta_links() masks the other links with vif->active_links, so the driver has no state for them. A station with valid_links 0x3 and active_links 0x2 thus sends STA_REC_MLD with link_num 2 and a second entry of zeroes, at association and again at authorization. The zeroed entry names WTBL index 0 and BSS index 0 as a link of the MLD. Count the links with driver state first and describe exactly those, with the secondary id taken from them. A link removed without flush keeps its mt7996_sta_link but is no longer valid, so leave it out as well. Fixes: 00cef41d9d8f ("wifi: mt76: mt7996: Add mt7996_mcu_sta_mld_setup_tlv() and mt7996_mcu_sta_eht_mld_tlv()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 30 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 26 +++++++--------- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 ++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 621e1337dec2..0c229a8f071f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1262,6 +1262,36 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, } } +u16 mt7996_mac_sta_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct ieee80211_link_sta *link_sta; + unsigned int link_id; + u16 links = 0; + + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt7996_sta_link *msta_link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (!msta_link) + continue; + + if (sta->valid_links && !msta_link->wcid.link_valid) + continue; + + if (!link_conf_dereference_protected(vif, link_id)) + continue; + + if (!mt7996_vif_link(dev, vif, link_id)) + continue; + + links |= BIT(link_id); + } + + return links; +} + static void mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long links, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 310ce18ec12c..02f9bcfdf5d9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2759,18 +2759,20 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, struct ieee80211_sta *sta) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - unsigned int nlinks = hweight16(sta->valid_links); + unsigned long links = mt7996_mac_sta_links(dev, vif, sta); + unsigned int nlinks = hweight16(links); struct mld_setup_link *mld_setup_link; - struct ieee80211_link_sta *link_sta; struct sta_rec_mld_setup *mld_setup; struct mt7996_sta_link *msta_link; + u8 seclink_id = msta->deflink_id; unsigned int link_id; struct tlv *tlv; - msta_link = mt7996_sta_link_protected(dev, msta, msta->deflink_id); - if (!msta_link) + if (!(links & BIT(msta->deflink_id))) return; + msta_link = mt7996_sta_link_protected(dev, msta, msta->deflink_id); + tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_MLD, sizeof(struct sta_rec_mld_setup) + sizeof(struct mld_setup_link) * nlinks); @@ -2780,26 +2782,18 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, mld_setup->setup_wcid = cpu_to_le16(msta_link->wcid.idx); mld_setup->primary_id = cpu_to_le16(msta_link->wcid.idx); - if (nlinks > 1) { - msta_link = mt7996_sta_link_protected(dev, msta, - msta->seclink_id); - if (!msta_link) - return; - } + 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; mld_setup_link = (struct mld_setup_link *)mld_setup->link_info; - for_each_sta_active_link(vif, sta, link_sta, link_id) { + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_vif_link *link; msta_link = mt7996_sta_link_protected(dev, msta, link_id); - if (!msta_link) - continue; - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; mld_setup_link->wcid = cpu_to_le16(msta_link->wcid.idx); mld_setup_link->bss_idx = link->mt76.idx; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index dcfdcd5975c3..82a2a8a5db69 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -894,6 +894,8 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev, void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, struct ieee80211_sta *sta, unsigned int link_id, bool flush); +u16 mt7996_mac_sta_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw, struct ieee80211_sta *sta, struct ieee80211_twt_setup *twt); -- 2.47.3 On disassociation, mt7996_mac_sta_event() sends STA_REC_MLD_OFF when the link it handles is the last of sta->valid_links, but it handles only active links with driver state. On a station interface with one active link out of two valid links, no link is ever the last one, so the firmware never gets STA_REC_MLD_OFF for the MLD. Walk the same set of links that mt7996_mcu_sta_mld_setup_tlv() describes, and detect the last link within that set. The per-link event handling moves into a helper that takes the set of links, so that a later change can bring up links that became active after association. Fixes: c1d6dd5d03eb ("wifi: mt76: mt7996: Add mt7996_mcu_teardown_mld_sta rouine") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 0c229a8f071f..e5f74f23d469 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1406,34 +1406,26 @@ mt7996_mac_sta_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, } static int -mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, enum mt76_sta_event ev) +mt7996_mac_sta_links_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links, + enum mt76_sta_event ev) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - unsigned long links = sta->valid_links; - struct ieee80211_link_sta *link_sta; + unsigned long iter_links = links; unsigned int link_id; - int err = 0; + int err; - mutex_lock(&dev->mt76.mutex); - - for_each_sta_active_link(vif, sta, link_sta, link_id) { + for_each_set_bit(link_id, &iter_links, IEEE80211_MLD_MAX_NUM_LINKS) { struct ieee80211_bss_conf *link_conf; + struct ieee80211_link_sta *link_sta; struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; int i; link_conf = link_conf_dereference_protected(vif, link_id); - if (!link_conf) - continue; - - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - + link_sta = link_sta_dereference_protected(sta, link_id); msta_link = mt7996_sta_link_protected(dev, msta, link_id); - if (!msta_link) - continue; + link = mt7996_vif_link(dev, vif, link_id); switch (ev) { case MT76_STA_EVENT_ASSOC: @@ -1441,12 +1433,12 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, link, msta_link, CONN_STATE_CONNECT, true); if (err) - goto unlock; + return err; err = mt7996_mcu_add_rate_ctrl(dev, msta_link->sta, vif, link_id, false); if (err) - goto unlock; + return err; msta_link->wcid.tx_info |= MT_WCID_TX_INFO_SET; break; @@ -1455,7 +1447,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, link, msta_link, CONN_STATE_PORT_SECURE, false); if (err) - goto unlock; + return err; break; case MT76_STA_EVENT_DISASSOC: for (i = 0; i < ARRAY_SIZE(msta_link->twt.flow); i++) @@ -1475,7 +1467,20 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, break; } } -unlock: + + return 0; +} + +static int +mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, enum mt76_sta_event ev) +{ + int err; + + mutex_lock(&dev->mt76.mutex); + err = mt7996_mac_sta_links_event(dev, vif, sta, + mt7996_mac_sta_links(dev, vif, sta), + ev); mutex_unlock(&dev->mt76.mutex); return err; -- 2.47.3 mt7996_vif_link_add() and mt7996_vif_link_destroy() walk the vif keys through mt7996_key_iter(), which passes every group key to mt7996_set_hw_key() for the link being set up. On an MLD interface each group key belongs to one link, as key->link_id says, so a new link gets the GTK and BIGTK of the other links written to its group WTBL entry, and its hw_key_idx set to their key index. Skip keys that belong to a different link, as mt7996_set_key() already does for keys that mac80211 installs. Fixes: eddc7286f6bb ("wifi: mt76: mt7996: fix key add/remove imbalance") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e5f74f23d469..e74254928578 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -291,6 +291,9 @@ mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (sta) return; + if (key->link_id >= 0 && key->link_id != it->link_id) + return; + WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, NULL, it->link_id, key)); } -- 2.47.3 Since commit eddc7286f6bb ("wifi: mt76: mt7996: fix key add/remove imbalance"), mt7996_vif_link_destroy() walks the vif keys with SET_KEY. Before that commit the walk used DISABLE_KEY. The commit message says a key for a link is added and removed only once, but a SET_KEY walk on destroy uploads the keys again, and for a BIGTK with beaconing enabled it also removes and adds the beacon of the link being destroyed. Remove the walk. mac80211 removes the keys itself with set_key() calls, which is the one removal that commit describes. The walk currently finds no keys: mt7996_vif_link_destroy() runs for a non-offchannel link only from mt7996_remove_interface(), and ieee80211_do_stop() frees the keys of the interface before drv_remove_interface(). An interface type change goes through the same path, since mt7996 has no .change_interface. Found by inspection. Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e74254928578..f94b5fdfca24 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -425,19 +425,12 @@ static void mt7996_vif_link_destroy(struct mt7996_phy *phy, 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_key_iter_data it = { - .cmd = SET_KEY, - .link_id = link_id, - }; struct mt7996_dev *dev = phy->dev; int idx = msta_link->wcid.idx; if (!link_conf) link_conf = &vif->bss_conf; - if (!mlink->wcid->offchannel) - ieee80211_iter_keys(phy->mt76->hw, vif, mt7996_key_iter, &it); - 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); -- 2.47.3 mt7996_sta_statistics() and mt7996_ethtool_worker() read the TX rate, the TX counters and the ACK signal from msta->deflink. That is the msta_link of the first link of the station, not of its primary link. When the first link of an MLD station is removed, msta->deflink keeps the values of the removed link, and the station reports them until it is removed. Read them from the link that deflink_id selects. msta->deflink also holds the RX reorder state, the TX aggregation state and the def_wcid target of all links. These are state of the whole station, and wcid_to_sta() needs def_wcid at the start of drv_priv, so they stay there. Fixes: f520eceacd24 ("wifi: mt76: mt7996: Add mt7996_mac_sta_change_links callback") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f94b5fdfca24..f44af31281b7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1920,9 +1920,16 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw, { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct mt7996_sta_link *msta_link = &msta->deflink; - struct rate_info *txrate = &msta_link->wcid.rate; + struct mt7996_sta_link *msta_link; + struct rate_info *txrate; + rcu_read_lock(); + + msta_link = mt7996_sta_link(msta, msta->deflink_id); + if (!msta_link) + goto out; + + txrate = &msta_link->wcid.rate; if (txrate->legacy || txrate->flags) { if (txrate->legacy) { sinfo->txrate.legacy = txrate->legacy; @@ -1965,6 +1972,9 @@ static void mt7996_sta_statistics(struct ieee80211_hw *hw, sinfo->rx_packets = msta_link->wcid.stats.rx_packets; sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS); } + +out: + rcu_read_unlock(); } static void mt7996_link_rate_ctrl_update(void *data, @@ -2264,11 +2274,15 @@ static void mt7996_ethtool_worker(void *wi_data, struct ieee80211_sta *sta) { struct mt76_ethtool_worker_info *wi = wi_data; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct mt7996_sta_link *msta_link = &msta->deflink; + struct mt7996_sta_link *msta_link; if (msta->vif->deflink.mt76.idx != wi->idx) return; + msta_link = mt7996_sta_link(msta, msta->deflink_id); + if (!msta_link) + return; + mt76_ethtool_worker(wi, &msta_link->wcid.stats, true); } -- 2.47.3 mt76_sta_pre_rcu_remove() clears the wcid table entry of msta->deflink only. mac80211 calls it before the synchronize_net() that ends the RCU readers of the station, and frees the station right after mt7996_mac_sta_remove(). For an MLD station, the wcid table entries of the other links stay set until mt7996_mac_sta_remove(), after that grace period. An RX or TX status reader that looks up one of these entries in that window can follow msta_link->sta or def_wcid into the freed station. Clear the wcid table entries of all links of the station in .sta_pre_rcu_remove. Fixes: dd82a9e02c05 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f44af31281b7..3c27cc02d527 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1513,6 +1513,30 @@ mt7996_set_active_links(struct ieee80211_vif *vif) ieee80211_set_active_links_async(vif, active_links); } +static void +mt7996_sta_pre_rcu_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_dev *dev = mt7996_hw_dev(hw); + unsigned int link_id; + + mutex_lock(&dev->mt76.mutex); + spin_lock_bh(&dev->mt76.status_lock); + + for (link_id = 0; link_id < ARRAY_SIZE(msta->link); link_id++) { + struct mt7996_sta_link *msta_link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (msta_link) + rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], + NULL); + } + + spin_unlock_bh(&dev->mt76.status_lock); + mutex_unlock(&dev->mt76.mutex); +} + static int mt7996_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, enum ieee80211_sta_state old_state, @@ -2618,7 +2642,7 @@ const struct ieee80211_ops mt7996_ops = { .vif_cfg_changed = mt7996_vif_cfg_changed, .link_info_changed = mt7996_link_info_changed, .sta_state = mt7996_sta_state, - .sta_pre_rcu_remove = mt76_sta_pre_rcu_remove, + .sta_pre_rcu_remove = mt7996_sta_pre_rcu_remove, .link_sta_rc_update = mt7996_link_sta_rc_update, .set_key = mt7996_set_key, .ampdu_action = mt7996_ampdu_action, -- 2.47.3 mt7996_mac_sta_init_link() takes a WTBL index from wcid_mask before it allocates the msta_link of a link other than the first one. If that allocation fails, the function returns -ENOMEM and the index stays set in wcid_mask, so no later station can use it. Return the index to wcid_mask on that error. 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/mt7996/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 3c27cc02d527..5207def2fd36 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1159,8 +1159,10 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, mt7996_sta_init_txq_wcid(sta, idx); } else { msta_link = kzalloc_obj(*msta_link); - if (!msta_link) + if (!msta_link) { + mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx); return -ENOMEM; + } if (msta->seclink_id == msta->deflink_id && (sta->valid_links & ~BIT(msta->deflink_id))) -- 2.47.3 mt7996_mac_sta_init_link() takes the secondary link from sta->valid_links. On a station interface mac80211 creates driver state only for active links, since drv_change_sta_links() masks both link sets with vif->active_links. With three valid links of which links 0 and 2 are active, seclink_id then points at link 1, which has no mt7996_sta_link. mt7996_tx_prepare_skb() maps odd TIDs to seclink_id, keeps the primary wcid for them and passes the inactive link id to mt7996_mac_write_txwi(), which finds no vif link and falls back to OMAC and WMM index 0. Choose the secondary link among the links that have driver state and are valid, and choose it again whenever such a link is added, restored or removed. A removed secondary link now hands over to another remaining link instead of the primary one, and a secondary link that was promoted to primary gets a successor. Fixes: 85cd5534a3f2 ("wifi: mt76: mt7996: use correct link_id when filling TXD and TXP") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 5207def2fd36..9de455939125 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1133,6 +1133,32 @@ mt7996_sta_init_txq_wcid(struct ieee80211_sta *sta, int idx) } } +static u8 +mt7996_sta_seclink_get(struct mt7996_dev *dev, struct mt7996_sta *msta) +{ + struct mt7996_sta_link *msta_link; + unsigned int link_id; + + if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) + return IEEE80211_LINK_UNSPECIFIED; + + msta_link = mt7996_sta_link_protected(dev, msta, msta->seclink_id); + if (msta->seclink_id != msta->deflink_id && msta_link && + msta_link->wcid.link_valid) + return msta->seclink_id; + + for (link_id = 0; link_id < ARRAY_SIZE(msta->link); link_id++) { + if (link_id == msta->deflink_id) + continue; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (msta_link && msta_link->wcid.link_valid) + return link_id; + } + + return msta->deflink_id; +} + static int mt7996_mac_sta_init_link(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, @@ -1155,7 +1181,6 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) { msta_link = &msta->deflink; msta->deflink_id = link_id; - msta->seclink_id = msta->deflink_id; mt7996_sta_init_txq_wcid(sta, idx); } else { msta_link = kzalloc_obj(*msta_link); @@ -1163,11 +1188,6 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx); return -ENOMEM; } - - if (msta->seclink_id == msta->deflink_id && - (sta->valid_links & ~BIT(msta->deflink_id))) - msta->seclink_id = __ffs(sta->valid_links & - ~BIT(msta->deflink_id)); } INIT_LIST_HEAD(&msta_link->rc_list); @@ -1186,6 +1206,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, ewma_signal_init(&msta_link->wcid.rssi); rcu_assign_pointer(msta->link[link_id], msta_link); + msta->seclink_id = mt7996_sta_seclink_get(dev, msta); mt7996_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); mt7996_mcu_add_sta(dev, link_conf, link_sta, link, msta_link, @@ -1223,10 +1244,7 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, if (msta->deflink_id == link_id) { msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; - if (msta->seclink_id == link_id) { - /* no secondary link available */ - msta->seclink_id = msta->deflink_id; - } else { + if (msta->seclink_id != link_id) { struct mt7996_sta_link *msta_seclink; /* switch to the secondary link */ @@ -1239,10 +1257,9 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, msta_seclink->wcid.idx); } } - } else if (msta->seclink_id == link_id) { - msta->seclink_id = msta->deflink_id; } msta_link->wcid.link_valid = false; + msta->seclink_id = mt7996_sta_seclink_get(dev, msta); } if (flush) { @@ -1319,6 +1336,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); if (msta_link) { msta_link->wcid.link_valid = true; + msta->seclink_id = mt7996_sta_seclink_get(dev, msta); continue; } -- 2.47.3 mt7996_mac_sta_remove_link() clears deflink_id when it removes the primary link and no secondary link is left to take its place. If the removed link then comes back, mt7996_mac_sta_add_links() finds its existing msta_link and only marks it valid again. deflink_id stays IEEE80211_LINK_UNSPECIFIED, so the station has no primary link: the TXQs keep the WLAN index that they had before, seclink_id stays unspecified, mt7996_sta_rate_ctrl_update() finds no primary link, and mt7996_sta_set_4addr() treats every link as a secondary link. Make a returning link the primary link when the station has none, in the same way as mt7996_mac_sta_init_link() does for a new link. Move the two steps that set the primary link, and the switch to the secondary link on removal, to helpers. Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 9de455939125..8f483b1de852 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1133,6 +1133,38 @@ mt7996_sta_init_txq_wcid(struct ieee80211_sta *sta, int idx) } } +static void +mt7996_sta_deflink_set(struct ieee80211_sta *sta, unsigned int link_id, + int idx) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + + msta->deflink_id = link_id; + mt7996_sta_init_txq_wcid(sta, idx); +} + +static void +mt7996_sta_deflink_release(struct mt7996_dev *dev, struct ieee80211_sta *sta, + unsigned int link_id) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_seclink; + + if (msta->deflink_id != link_id) + return; + + msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; + if (msta->seclink_id == link_id) + return; + + /* switch to the secondary link */ + msta_seclink = mt76_dereference(msta->link[msta->seclink_id], + &dev->mt76); + if (msta_seclink) + mt7996_sta_deflink_set(sta, msta->seclink_id, + msta_seclink->wcid.idx); +} + static u8 mt7996_sta_seclink_get(struct mt7996_dev *dev, struct mt7996_sta *msta) { @@ -1180,8 +1212,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) { msta_link = &msta->deflink; - msta->deflink_id = link_id; - mt7996_sta_init_txq_wcid(sta, idx); + mt7996_sta_deflink_set(sta, link_id, idx); } else { msta_link = kzalloc_obj(*msta_link); if (!msta_link) { @@ -1242,22 +1273,7 @@ void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - if (msta->deflink_id == link_id) { - msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; - if (msta->seclink_id != link_id) { - struct mt7996_sta_link *msta_seclink; - - /* switch to the secondary link */ - msta_seclink = mt76_dereference( - msta->link[msta->seclink_id], - &dev->mt76); - if (msta_seclink) { - msta->deflink_id = msta->seclink_id; - mt7996_sta_init_txq_wcid(sta, - msta_seclink->wcid.idx); - } - } - } + mt7996_sta_deflink_release(dev, sta, link_id); msta_link->wcid.link_valid = false; msta->seclink_id = mt7996_sta_seclink_get(dev, msta); } @@ -1336,6 +1352,9 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); if (msta_link) { msta_link->wcid.link_valid = true; + if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) + mt7996_sta_deflink_set(sta, link_id, + msta_link->wcid.idx); msta->seclink_id = mt7996_sta_seclink_get(dev, msta); continue; } -- 2.47.3 mt7996_mac_sta_init_link() puts a new link in msta->deflink whenever the station has no primary link. A removed link keeps its msta_link until the station is removed, and the station has no primary link after one mt7996_mac_sta_change_links() call removes all of its links and adds others. If the removed primary link was in msta->deflink, the new link then overwrites it: both link slots point to the same msta_link, and the WTBL index of the removed link is lost, so mt7996_mac_sta_remove() never returns it to wcid_mask. Use msta->deflink only if no link slot of the station points to it, and allocate the msta_link otherwise. Choose the primary link as before. Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8f483b1de852..8e70a4061ed6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1143,6 +1143,20 @@ mt7996_sta_deflink_set(struct ieee80211_sta *sta, unsigned int link_id, mt7996_sta_init_txq_wcid(sta, idx); } +static bool +mt7996_sta_deflink_busy(struct mt7996_dev *dev, struct mt7996_sta *msta) +{ + unsigned int link_id; + + for (link_id = 0; link_id < ARRAY_SIZE(msta->link); link_id++) { + if (mt7996_sta_link_protected(dev, msta, link_id) == + &msta->deflink) + return true; + } + + return false; +} + static void mt7996_sta_deflink_release(struct mt7996_dev *dev, struct ieee80211_sta *sta, unsigned int link_id) @@ -1210,17 +1224,19 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, if (idx < 0) return -ENOSPC; - if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) { - msta_link = &msta->deflink; - mt7996_sta_deflink_set(sta, link_id, idx); - } else { + if (mt7996_sta_deflink_busy(dev, msta)) { msta_link = kzalloc_obj(*msta_link); if (!msta_link) { mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx); return -ENOMEM; } + } else { + msta_link = &msta->deflink; } + if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) + mt7996_sta_deflink_set(sta, link_id, idx); + INIT_LIST_HEAD(&msta_link->rc_list); INIT_LIST_HEAD(&msta_link->wcid.poll_list); msta_link->sta = msta; -- 2.47.3 mt7996_mac_sta_add_links() removes all links that it was asked to add, with flush set, when one of them fails. A link that comes back after a removal kept its msta_link and may still have a firmware entry. The flush frees its msta_link and returns its WTBL index to wcid_mask, so a later station can be given an index whose entry still belongs to this station. Flush only the links that this call created, and remove a returning link again without flush. Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8e70a4061ed6..58a487549ddf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1355,6 +1355,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long new_links) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + unsigned long created = 0, returned = 0; unsigned int link_id; int err = 0; @@ -1372,6 +1373,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, mt7996_sta_deflink_set(sta, link_id, msta_link->wcid.idx); msta->seclink_id = mt7996_sta_seclink_get(dev, msta); + returned |= BIT(link_id); continue; } @@ -1405,12 +1407,14 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, goto error_unlink; mphy->num_sta++; + created |= BIT(link_id); } return 0; error_unlink: - mt7996_mac_sta_remove_links(dev, vif, sta, new_links, true); + mt7996_mac_sta_remove_links(dev, vif, sta, created, true); + mt7996_mac_sta_remove_links(dev, vif, sta, returned, false); return err; } -- 2.47.3 A station link sends its broadcast STA_REC with EXTRA_INFO_NEW on every BSS_CHANGED_BSSID. Commit 8e3e7567b8c1 ("mt76: mt7915: add sta_rec with EXTRA_INFO_NEW for the first time only") notes that the firmware may remove an entry that is added as new twice. Since a removed vif link keeps its firmware entry, this happens on a link that is deactivated and activated again: _ieee80211_set_active_links() reports BSS_CHANGED_BSSID with the same BSSID, after ieee80211_key_switch_links() has installed the group keys of the link, and nothing in the driver installs them again after the entry is added as new. Record the BSSID the entry was added with, and add it as new only for a different BSSID. A BSSID that goes to zero on disconnection clears the record, so that the next association adds the entry as new again. A link that is set up clears the record as well, since the firmware has no entry for it then. 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 | 38 ++++++++++++------- .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 58a487549ddf..d868e9641334 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -341,6 +341,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return -ENOSPC; link->mld_idx = mld_idx; + eth_zero_addr(link->bmc_bssid); mlink->omac_idx = idx; mlink->band_idx = band_idx; mlink->wmm_idx = vif->type == NL80211_IFTYPE_AP ? 0 : 3; @@ -898,6 +899,23 @@ mt7996_update_mu_group(struct ieee80211_hw *hw, struct mt7996_vif_link *link, mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS3(band), mu[3]); } +static void +mt7996_vif_link_bss_add(struct mt7996_phy *phy, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link, bool newly) +{ + if (vif->type == NL80211_IFTYPE_STATION) { + newly = !ether_addr_equal_unaligned(link->bmc_bssid, + link_conf->bssid); + memcpy(link->bmc_bssid, link_conf->bssid, ETH_ALEN); + } + + 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); +} + static void mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u64 changed) @@ -929,12 +947,8 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!phy) continue; - mt7996_mcu_add_bss_info(phy, vif, link_conf, - &link->mt76, &link->msta_link, - true); - mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, - CONN_STATE_PORT_SECURE, - !!(changed & BSS_CHANGED_BSSID)); + mt7996_vif_link_bss_add(phy, vif, link_conf, link, + !!(changed & BSS_CHANGED_BSSID)); } } @@ -980,13 +994,11 @@ mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, * and then peer references bss_info_rfch to set bandwidth cap. */ if ((changed & BSS_CHANGED_BSSID && !is_zero_ether_addr(info->bssid)) || - (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) { - mt7996_mcu_add_bss_info(phy, vif, info, &link->mt76, - &link->msta_link, true); - mt7996_mcu_add_sta(dev, info, NULL, link, NULL, - CONN_STATE_PORT_SECURE, - !!(changed & BSS_CHANGED_BSSID)); - } + (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) + mt7996_vif_link_bss_add(phy, vif, info, link, + !!(changed & BSS_CHANGED_BSSID)); + else if (changed & BSS_CHANGED_BSSID) + eth_zero_addr(link->bmc_bssid); if (changed & BSS_CHANGED_HT || changed & BSS_CHANGED_ERP_CTS_PROT) mt7996_mcu_set_protection(phy, link, info->ht_operation_mode, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 82a2a8a5db69..03fc0e7fd4f1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -279,6 +279,7 @@ struct mt7996_vif_link { struct cfg80211_bitrate_mask bitrate_mask; u8 mld_idx; + u8 bmc_bssid[ETH_ALEN]; }; struct mt7996_vif_link_info { -- 2.47.3 mac80211 reports 4-address mode and decap offload once per station, through sta_set_4addr and sta_set_decap_offload. mt7996_sta_set_4addr() and mt7996_sta_set_decap_offload() set MT_WCID_FLAG_4ADDR and MT_WCID_FLAG_HDR_TRANS only on the links that exist at that moment, but the hardware keeps header translation per link. A link that is added later starts without the flags, and a link that is removed at that time keeps its old flags when it comes back, so the firmware gets the wrong to_ds/from_ds pair for that link. Keep both states in the station, and apply them to a link when the callbacks run, when the link is set up and when it comes back. Fixes: f520eceacd24 ("wifi: mt76: mt7996: Add mt7996_mac_sta_change_links callback") Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 29 +++++++++++-------- .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 ++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d868e9641334..bba5a8c6dec8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1155,6 +1155,16 @@ mt7996_sta_deflink_set(struct ieee80211_sta *sta, unsigned int link_id, mt7996_sta_init_txq_wcid(sta, idx); } +static void +mt7996_sta_link_hdr_trans_set(struct mt7996_sta *msta, + struct mt7996_sta_link *msta_link) +{ + assign_bit(MT_WCID_FLAG_4ADDR, &msta_link->wcid.flags, + test_bit(MT_WCID_FLAG_4ADDR, &msta->wcid_flags)); + assign_bit(MT_WCID_FLAG_HDR_TRANS, &msta_link->wcid.flags, + test_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid_flags)); +} + static bool mt7996_sta_deflink_busy(struct mt7996_dev *dev, struct mt7996_sta *msta) { @@ -1261,6 +1271,8 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, if (link_sta->sta->tdls) set_bit(MT_WCID_FLAG_TDLS_PEER, &msta_link->wcid.flags); + mt7996_sta_link_hdr_trans_set(msta, msta_link); + ewma_avg_signal_init(&msta_link->avg_ack_signal); ewma_signal_init(&msta_link->wcid.rssi); @@ -1381,6 +1393,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); if (msta_link) { msta_link->wcid.link_valid = true; + mt7996_sta_link_hdr_trans_set(msta, msta_link); if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) mt7996_sta_deflink_set(sta, link_id, msta_link->wcid.idx); @@ -2166,6 +2179,7 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, mutex_lock(&dev->mt76.mutex); + assign_bit(MT_WCID_FLAG_4ADDR, &msta->wcid_flags, enabled); for_each_sta_active_link(vif, sta, link_sta, link_id) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; @@ -2178,11 +2192,7 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, if (!msta_link) continue; - if (enabled) - set_bit(MT_WCID_FLAG_4ADDR, &msta_link->wcid.flags); - else - clear_bit(MT_WCID_FLAG_4ADDR, &msta_link->wcid.flags); - + mt7996_sta_link_hdr_trans_set(msta, msta_link); if (!msta_link->wcid.sta) continue; @@ -2212,6 +2222,7 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw, mutex_lock(&dev->mt76.mutex); + assign_bit(MT_WCID_FLAG_HDR_TRANS, &msta->wcid_flags, enabled); for_each_sta_active_link(vif, sta, link_sta, link_id) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; @@ -2224,13 +2235,7 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw, if (!msta_link) continue; - if (enabled) - set_bit(MT_WCID_FLAG_HDR_TRANS, - &msta_link->wcid.flags); - else - clear_bit(MT_WCID_FLAG_HDR_TRANS, - &msta_link->wcid.flags); - + mt7996_sta_link_hdr_trans_set(msta, msta_link); if (!msta_link->wcid.sta) continue; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 03fc0e7fd4f1..949e659b8e2d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -269,6 +269,8 @@ struct mt7996_sta { u8 deflink_id; u8 seclink_id; + unsigned long wcid_flags; + struct mt7996_vif *vif; }; -- 2.47.3 From: Howard Hsu Since the driver uses the .sta_state op, mt7996_mac_sta_init_link() sends a STA_REC with CONN_STATE_DISCONNECT when a station link is set up. Before that change, the first STA_REC for a station was the one that connects it. MediaTek reports that DL OFDMA fails after a station reconnects. Only send a STA_REC for a station link when it connects. Until then the firmware has no STA_REC for the link, so track whether a link is connected, and skip rate control updates and the 4-address and decap updates for a link that is not. wcid.sta cannot be used for this, since it is set when the link is set up. Fixes: 13e2e9c6b348 ("wifi: mt76: mt7996: use mac80211 .sta_state op") Signed-off-by: Howard Hsu Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 4 +++ .../net/wireless/mediatek/mt76/mt7996/main.c | 33 +++++++++++++------ .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index eb39526548e8..c4e82b1e259c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2948,6 +2948,10 @@ void mt7996_mac_sta_rc_work(struct work_struct *work) changed = msta_link->changed; msta_link->changed = 0; + + if (!msta_link->connected) + continue; + mvif = msta_link->sta->vif; vif = container_of((void *)mvif, struct ieee80211_vif, drv_priv); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index bba5a8c6dec8..a23d148eafa6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1229,7 +1229,6 @@ mt7996_sta_seclink_get(struct mt7996_dev *dev, struct mt7996_sta *msta) static int mt7996_mac_sta_init_link(struct mt7996_dev *dev, - struct ieee80211_bss_conf *link_conf, struct ieee80211_link_sta *link_sta, struct mt7996_vif_link *link, unsigned int link_id) { @@ -1262,6 +1261,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, INIT_LIST_HEAD(&msta_link->rc_list); INIT_LIST_HEAD(&msta_link->wcid.poll_list); msta_link->sta = msta; + msta_link->connected = false; msta_link->wcid.sta = 1; msta_link->wcid.idx = idx; msta_link->wcid.link_id = link_id; @@ -1280,8 +1280,6 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, msta->seclink_id = mt7996_sta_seclink_get(dev, msta); mt7996_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - mt7996_mcu_add_sta(dev, link_conf, link_sta, link, msta_link, - CONN_STATE_DISCONNECT, true); rcu_assign_pointer(dev->mt76.wcid[idx], &msta_link->wcid); mt76_wcid_init(&msta_link->wcid, phy->mt76->band_idx); @@ -1384,7 +1382,6 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, int err = 0; for_each_set_bit(link_id, &new_links, IEEE80211_MLD_MAX_NUM_LINKS) { - struct ieee80211_bss_conf *link_conf; struct ieee80211_link_sta *link_sta; struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; @@ -1402,8 +1399,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, continue; } - link_conf = link_conf_dereference_protected(vif, link_id); - if (!link_conf) { + if (!link_conf_dereference_protected(vif, link_id)) { err = -EINVAL; goto error_unlink; } @@ -1426,8 +1422,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, goto error_unlink; } - err = mt7996_mac_sta_init_link(dev, link_conf, link_sta, link, - link_id); + err = mt7996_mac_sta_init_link(dev, link_sta, link, link_id); if (err) goto error_unlink; @@ -1515,6 +1510,7 @@ mt7996_mac_sta_links_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, if (err) return err; + msta_link->connected = true; err = mt7996_mcu_add_rate_ctrl(dev, msta_link->sta, vif, link_id, false); if (err) @@ -1551,16 +1547,33 @@ mt7996_mac_sta_links_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, return 0; } +static void +mt7996_mac_sta_clear_connected(struct mt7996_dev *dev, struct mt7996_sta *msta) +{ + unsigned int link_id; + + for (link_id = 0; link_id < ARRAY_SIZE(msta->link); link_id++) { + struct mt7996_sta_link *msta_link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (msta_link) + msta_link->connected = false; + } +} + static int mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, enum mt76_sta_event ev) { + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; int err; mutex_lock(&dev->mt76.mutex); err = mt7996_mac_sta_links_event(dev, vif, sta, mt7996_mac_sta_links(dev, vif, sta), ev); + if (!err && ev == MT76_STA_EVENT_DISASSOC) + mt7996_mac_sta_clear_connected(dev, msta); mutex_unlock(&dev->mt76.mutex); return err; @@ -2193,7 +2206,7 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, continue; mt7996_sta_link_hdr_trans_set(msta, msta_link); - if (!msta_link->wcid.sta) + if (!msta_link->connected) continue; mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link); @@ -2236,7 +2249,7 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw, continue; mt7996_sta_link_hdr_trans_set(msta, msta_link); - if (!msta_link->wcid.sta) + if (!msta_link->connected) continue; mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 949e659b8e2d..ec453f5ff3a5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -244,6 +244,7 @@ struct mt7996_sta_link { struct mt76_wcid wcid; /* must be first */ struct mt7996_sta *sta; + bool connected; struct list_head rc_list; u32 airtime_ac[8]; -- 2.47.3 mt7996_sta_state() turns the station state transitions into STA_REC updates for the links of the station, but the driver keeps no record of the state that the station reached. A link that is added after association needs that state to be brought up to the same level as the other links. Record the connection state after each station event, and lower it to CONN_STATE_CONNECT when the station leaves IEEE80211_STA_AUTHORIZED, for example when hostapd de-authorizes it for a re-authentication. No functional change on its own. Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 30 ++++++++++++++++++- .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index a23d148eafa6..7792c3a878af 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1472,6 +1472,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->vif = mvif; err = mt7996_mac_sta_add_links(dev, vif, sta, links); @@ -1569,11 +1570,27 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, int err; mutex_lock(&dev->mt76.mutex); + err = mt7996_mac_sta_links_event(dev, vif, sta, mt7996_mac_sta_links(dev, vif, sta), ev); - if (!err && ev == MT76_STA_EVENT_DISASSOC) + if (err) + goto out; + + switch (ev) { + case MT76_STA_EVENT_ASSOC: + msta->conn_state = CONN_STATE_CONNECT; + break; + case MT76_STA_EVENT_AUTHORIZE: + msta->conn_state = CONN_STATE_PORT_SECURE; + break; + case MT76_STA_EVENT_DISASSOC: + msta->conn_state = CONN_STATE_DISCONNECT; mt7996_mac_sta_clear_connected(dev, msta); + break; + } + +out: mutex_unlock(&dev->mt76.mutex); return err; @@ -1650,6 +1667,17 @@ mt7996_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, new_state == IEEE80211_STA_NOTEXIST) mt7996_mac_sta_remove(dev, vif, sta); + if (old_state == IEEE80211_STA_AUTHORIZED && + new_state == IEEE80211_STA_ASSOC) { + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + + mutex_lock(&dev->mt76.mutex); + msta->conn_state = CONN_STATE_CONNECT; + mutex_unlock(&dev->mt76.mutex); + + return 0; + } + if (old_state == IEEE80211_STA_AUTH && new_state == IEEE80211_STA_ASSOC) { mt7996_set_active_links(vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index ec453f5ff3a5..ead211391404 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -269,6 +269,7 @@ struct mt7996_sta { struct mt7996_sta_link __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; u8 deflink_id; u8 seclink_id; + u8 conn_state; unsigned long wcid_flags; -- 2.47.3 mt7996_mcu_add_tx_ba() and mt7996_mcu_add_rx_ba() send STA_REC_BA to the links that are active when mac80211 starts the session. To set up a running session on a link that is added later, the driver needs the parameters of that session, but it records a TX session only as a bit in ampdu_state and keeps no RX session parameters. Store the parameters of each running TX and RX session per TID, and pass the set of links to update to mt7996_mcu_add_tx_ba() and mt7996_mcu_add_rx_ba(). mt7996_ampdu_action() passes the links from mt7996_mac_sta_links(), which are the links that have driver state. No functional change on its own. Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 40 ++++++++++++++++--- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 12 +++--- .../wireless/mediatek/mt76/mt7996/mt7996.h | 17 +++++++- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 7792c3a878af..d90ada7f0f2a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1473,6 +1473,8 @@ 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->ba_tx_mask = 0; + msta->ba_rx_mask = 0; msta->vif = mvif; err = mt7996_mac_sta_add_links(dev, vif, sta, links); @@ -1809,6 +1811,21 @@ static int mt7996_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, return ret; } +static void +mt7996_sta_ba_update(u16 *mask, struct mt7996_sta_ba *ba, + struct ieee80211_ampdu_params *params, bool enable) +{ + if (!enable) { + *mask &= ~BIT(params->tid); + return; + } + + *mask |= BIT(params->tid); + ba[params->tid].ssn = params->ssn; + ba[params->tid].buf_size = params->buf_size; + ba[params->tid].amsdu = params->amsdu; +} + static int mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_ampdu_params *params) @@ -1820,6 +1837,7 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 tid = params->tid; u16 ssn = params->ssn; struct mt76_txq *mtxq; + unsigned long links; int ret = 0; if (!txq) @@ -1829,6 +1847,8 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mt76.mutex); + links = mt7996_mac_sta_links(dev, vif, sta); + switch (params->action) { case IEEE80211_AMPDU_RX_START: /* Since packets belonging to the same TID can be split over @@ -1837,22 +1857,30 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, */ mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn, params->buf_size); - ret = mt7996_mcu_add_rx_ba(dev, params, vif, true); + ret = mt7996_mcu_add_rx_ba(dev, params, vif, links, true); + mt7996_sta_ba_update(&msta->ba_rx_mask, msta->ba_rx, params, + !ret); break; case IEEE80211_AMPDU_RX_STOP: mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid); - ret = mt7996_mcu_add_rx_ba(dev, params, vif, false); + mt7996_sta_ba_update(&msta->ba_rx_mask, msta->ba_rx, params, + false); + ret = mt7996_mcu_add_rx_ba(dev, params, vif, links, false); break; case IEEE80211_AMPDU_TX_OPERATIONAL: mtxq->aggr = true; mtxq->send_bar = false; - ret = mt7996_mcu_add_tx_ba(dev, params, vif, true); + ret = mt7996_mcu_add_tx_ba(dev, params, vif, links, true); + mt7996_sta_ba_update(&msta->ba_tx_mask, msta->ba_tx, params, + !ret); break; case IEEE80211_AMPDU_TX_STOP_FLUSH: case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: mtxq->aggr = false; clear_bit(tid, &msta->deflink.wcid.ampdu_state); - ret = mt7996_mcu_add_tx_ba(dev, params, vif, false); + mt7996_sta_ba_update(&msta->ba_tx_mask, msta->ba_tx, params, + false); + ret = mt7996_mcu_add_tx_ba(dev, params, vif, links, false); break; case IEEE80211_AMPDU_TX_START: set_bit(tid, &msta->deflink.wcid.ampdu_state); @@ -1861,7 +1889,9 @@ mt7996_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, case IEEE80211_AMPDU_TX_STOP_CONT: mtxq->aggr = false; clear_bit(tid, &msta->deflink.wcid.ampdu_state); - ret = mt7996_mcu_add_tx_ba(dev, params, vif, false); + mt7996_sta_ba_update(&msta->ba_tx_mask, msta->ba_tx, params, + false); + ret = mt7996_mcu_add_tx_ba(dev, params, vif, links, false); ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); break; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 02f9bcfdf5d9..1d2ee1ca5298 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1511,15 +1511,15 @@ mt7996_mcu_sta_ba(struct mt7996_dev *dev, struct mt76_vif_link *mvif, /** starec & wtbl **/ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct ieee80211_vif *vif, bool enable) + struct ieee80211_vif *vif, unsigned long links, + bool enable) { struct ieee80211_sta *sta = params->sta; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct ieee80211_link_sta *link_sta; unsigned int link_id; int ret = 0; - for_each_sta_active_link(vif, sta, link_sta, link_id) { + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; @@ -1545,15 +1545,15 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct ieee80211_vif *vif, bool enable) + struct ieee80211_vif *vif, unsigned long links, + bool enable) { struct ieee80211_sta *sta = params->sta; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct ieee80211_link_sta *link_sta; unsigned int link_id; int ret = 0; - for_each_sta_active_link(vif, sta, link_sta, link_id) { + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index ead211391404..8744411e0717 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -264,6 +264,12 @@ struct mt7996_sta_link { struct rcu_head rcu_head; }; +struct mt7996_sta_ba { + u16 ssn; + u16 buf_size; + bool amsdu; +}; + struct mt7996_sta { struct mt7996_sta_link deflink; /* must be first */ struct mt7996_sta_link __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; @@ -273,6 +279,11 @@ struct mt7996_sta { unsigned long wcid_flags; + u16 ba_tx_mask; + u16 ba_rx_mask; + struct mt7996_sta_ba ba_tx[IEEE80211_NUM_TIDS]; + struct mt7996_sta_ba ba_rx[IEEE80211_NUM_TIDS]; + struct mt7996_vif *vif; }; @@ -733,10 +744,12 @@ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta); int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct ieee80211_vif *vif, bool enable); + struct ieee80211_vif *vif, unsigned long links, + bool enable); int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, - struct ieee80211_vif *vif, bool enable); + struct ieee80211_vif *vif, unsigned long links, + bool enable); int mt7996_mcu_update_bss_color(struct mt7996_dev *dev, struct mt76_vif_link *mlink, struct cfg80211_he_bss_color *he_bss_color); -- 2.47.3 On association, a station link gets its BSS_INFO with the BSSID and its broadcast STA_REC from BSS_CHANGED_BSSID before the peer STA_REC is created, and mt7996_link_info_changed() notes that station mode maps the peer entry through the BSSID. For a link that becomes active later, _ieee80211_set_active_links() calls drv_change_sta_links() first and reports BSS_CHANGED_BSSID for the link only afterwards. Send the BSS_INFO and the broadcast STA_REC of an added station link from mt7996_mac_sta_change_links() before its peer entry is set up when the station is already associated, in the order of an association. The later BSS_CHANGED_BSSID finds the BSSID recorded and does not add the broadcast entry as new again. This prepares for bringing up such links in the following change. Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d90ada7f0f2a..4f3ebe4cee65 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1439,26 +1439,6 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, return err; } -static int -mt7996_mac_sta_change_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, u16 old_links, - u16 new_links) -{ - struct mt7996_dev *dev = mt7996_hw_dev(hw); - unsigned long add = new_links & ~old_links; - unsigned long rem = old_links & ~new_links; - int ret; - - mutex_lock(&dev->mt76.mutex); - - mt7996_mac_sta_remove_links(dev, vif, sta, rem, false); - ret = mt7996_mac_sta_add_links(dev, vif, sta, add); - - mutex_unlock(&dev->mt76.mutex); - - return ret; -} - static int mt7996_mac_sta_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta) @@ -1598,6 +1578,58 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, return err; } +static void +mt7996_mac_sta_links_bss_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + unsigned int link_id; + + if (vif->type != NL80211_IFTYPE_STATION || + msta->conn_state == CONN_STATE_DISCONNECT) + return; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct ieee80211_bss_conf *link_conf; + struct mt7996_vif_link *link; + struct mt7996_phy *phy; + + link_conf = link_conf_dereference_protected(vif, link_id); + if (!link_conf || !link_conf->bssid || + is_zero_ether_addr(link_conf->bssid)) + continue; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + phy = mt7996_vif_link_phy(link); + if (phy) + mt7996_vif_link_bss_add(phy, vif, link_conf, link, true); + } +} + +static int +mt7996_mac_sta_change_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 old_links, + u16 new_links) +{ + struct mt7996_dev *dev = mt7996_hw_dev(hw); + unsigned long add = new_links & ~old_links; + unsigned long rem = old_links & ~new_links; + int ret; + + mutex_lock(&dev->mt76.mutex); + + mt7996_mac_sta_remove_links(dev, vif, sta, rem, false); + mt7996_mac_sta_links_bss_add(dev, vif, sta, add); + ret = mt7996_mac_sta_add_links(dev, vif, sta, add); + + mutex_unlock(&dev->mt76.mutex); + + return ret; +} + static void mt7996_mac_sta_remove(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta) -- 2.47.3 mt7996_mac_sta_change_links() only sets up the driver state of a link that is added to a station. The CONN_STATE_CONNECT and CONN_STATE_PORT_SECURE STA_RECs and the rate control setup come from mt7996_mac_sta_event(), which runs once per station state change, so a link that becomes active after association has no firmware entry. This is the normal case on a station interface: mac80211 activates only the association link at first, and mt7996_sta_state() requests the other links with ieee80211_set_active_links_async() when the station moves to IEEE80211_STA_ASSOC. The STA_REC_MLD of the links already connected also keeps the old set of links. When links are added, replay the association on the links that are not connected yet, install the keys of the station on them, and replay the authorization if the station is authorized. mac80211 does not install the pairwise key again, since ieee80211_key_switch_links() moves only keys with key->link_id >= 0. Send the other links a STA_REC with only STA_REC_MLD and STA_REC_EHT_MLD, so that their link list follows the change. A 4-address link other than the primary link also gets the PS leave request that mt7996_sta_set_4addr() sends on association. If bringing up the added links fails, reset and remove them again without flush, since the firmware can still hold an entry for their WTBL index, and send the remaining links their STA_REC_MLD again. While here, indent struct mt7996_key_iter_data with tabs. Fixes: f520eceacd24 ("wifi: mt76: mt7996: Add mt7996_mac_sta_change_links callback") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 152 ++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 21 +++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 5 + 3 files changed, 166 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 4f3ebe4cee65..324d3b195a3f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -277,8 +277,9 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, } struct mt7996_key_iter_data { - enum set_key_cmd cmd; - unsigned int link_id; + enum set_key_cmd cmd; + unsigned int link_id; + struct ieee80211_sta *sta; }; static void @@ -288,13 +289,13 @@ mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, { struct mt7996_key_iter_data *it = data; - if (sta) + if (sta != it->sta) return; if (key->link_id >= 0 && key->link_id != it->link_id) return; - WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, NULL, it->link_id, key)); + WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, sta, it->link_id, key)); } int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, @@ -1530,6 +1531,23 @@ mt7996_mac_sta_links_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, return 0; } +static void +mt7996_mac_sta_link_ps_leave(struct mt7996_dev *dev, + struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link) +{ + /* HW cannot derive the BSSID from 4-address non-AMSDU frames, + * so PS exit is missed on links other than the setup link. + * Let the firmware wake those links instead. + */ + if (!is_mt7996(&dev->mt76) || + !test_bit(MT_WCID_FLAG_4ADDR, &msta_link->wcid.flags) || + msta_link->wcid.link_id == msta_link->sta->deflink_id) + return; + + mt7996_mcu_ps_leave(dev, link, msta_link); +} + static void mt7996_mac_sta_clear_connected(struct mt7996_dev *dev, struct mt7996_sta *msta) { @@ -1578,6 +1596,113 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, return err; } +static void +mt7996_mac_sta_links_set_keys(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links) +{ + unsigned int link_id; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_key_iter_data it = { + .cmd = SET_KEY, + .link_id = link_id, + .sta = sta, + }; + + ieee80211_iter_keys(mt76_hw(dev), vif, mt7996_key_iter, &it); + } +} + +static int +mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links, + unsigned long add) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + unsigned long new_links = 0, updated; + unsigned int link_id; + int err; + + if (msta->conn_state == CONN_STATE_DISCONNECT) + return 0; + + links &= mt7996_mac_sta_links(dev, vif, sta); + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_sta_link *msta_link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (!msta_link->connected) + new_links |= BIT(link_id); + } + + err = mt7996_mac_sta_links_event(dev, vif, sta, new_links, + MT76_STA_EVENT_ASSOC); + if (err) + return err; + + mt7996_mac_sta_links_set_keys(dev, vif, sta, links & add); + + updated = new_links; + if (msta->conn_state == CONN_STATE_PORT_SECURE) { + err = mt7996_mac_sta_links_event(dev, vif, sta, updated, + MT76_STA_EVENT_AUTHORIZE); + if (err) + return err; + } + + for_each_set_bit(link_id, &new_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + link = mt7996_vif_link(dev, vif, link_id); + mt7996_mac_sta_link_ps_leave(dev, link, msta_link); + } + + updated = sta->mlo ? 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; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + link = mt7996_vif_link(dev, vif, link_id); + err = mt7996_mcu_update_mld_sta(dev, vif, sta, link, msta_link); + if (err) + return err; + } + + return 0; +} + +static void +mt7996_mac_sta_links_reset(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + unsigned int link_id; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + int i; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (!msta_link) + continue; + + link = mt7996_vif_link(dev, vif, link_id); + if (link) { + for (i = 0; i < ARRAY_SIZE(msta_link->twt.flow); i++) + mt7996_mac_twt_teardown_flow(dev, link, + msta_link, i); + } + + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; + msta_link->wcid.hw_key_idx = -1; + } +} + static void mt7996_mac_sta_links_bss_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long links) @@ -1624,7 +1749,17 @@ mt7996_mac_sta_change_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mt7996_mac_sta_remove_links(dev, vif, sta, rem, false); mt7996_mac_sta_links_bss_add(dev, vif, sta, add); ret = mt7996_mac_sta_add_links(dev, vif, sta, add); + if (ret) + goto out; + ret = mt7996_mac_sta_links_sync(dev, vif, sta, new_links, add); + if (ret) { + mt7996_mac_sta_links_reset(dev, vif, sta, add); + mt7996_mac_sta_remove_links(dev, vif, sta, add, false); + mt7996_mac_sta_links_sync(dev, vif, sta, new_links & ~add, 0); + } + +out: mutex_unlock(&dev->mt76.mutex); return ret; @@ -2300,14 +2435,7 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, continue; mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, msta_link); - - /* HW cannot derive the BSSID from 4-address non-AMSDU frames, - * so PS exit is missed on links other than the setup link. - * Let the firmware wake those links instead. - */ - if (enabled && msta->deflink_id != link_id && - is_mt7996(&dev->mt76)) - mt7996_mcu_ps_leave(dev, link, msta_link); + mt7996_mac_sta_link_ps_leave(dev, link, msta_link); } mutex_unlock(&dev->mt76.mutex); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 1d2ee1ca5298..c324d199b212 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2910,6 +2910,27 @@ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } +int mt7996_mcu_update_mld_sta(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link) +{ + struct sk_buff *skb; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, + &msta_link->wcid, + MT7996_STA_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + mt7996_mcu_sta_mld_setup_tlv(dev, skb, vif, sta); + mt7996_mcu_sta_eht_mld_tlv(dev, skb, sta); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); +} + void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) { struct mt7996_vif_link *link = (struct mt7996_vif_link *)data; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 8744411e0717..ae97ca091673 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -741,6 +741,11 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); +int mt7996_mcu_update_mld_sta(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct mt7996_vif_link *link, + struct mt7996_sta_link *msta_link); void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta); int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, -- 2.47.3 mt7996_ampdu_action() sends STA_REC_BA to the links that the station has when mac80211 starts a session. A link that is brought up later gets no STA_REC_BA, so it transmits without aggregation and, as a recipient, is not set up for hardware reordering. mac80211 does not start the sessions again when a link is added. Send STA_REC_BA for each running TX and RX session on the links that mt7996_mac_sta_change_links() brings up. For an RX session, use the current head of the reorder window, which mt76_rx_aggr_start() keeps on the default link, as the starting sequence number. That head moves only when the host reorders: with hardware RRO, mt7996 marks the frames as already aggregated and mt7996_mac_fill_rx() skips the host reorder state; with WED or the NPU active, mt76_rx_poll_complete() does not call mt76_rx_aggr_reorder(). The driver has no MCU query for the current window. In those modes, stop the RX sessions with ieee80211_stop_rx_ba_session() instead, so that the originator sets up new sessions, which then reach all links through the regular IEEE80211_AMPDU_RX_START path. Fixes: ef3f5941e560 ("wifi: mt76: mt7996: rework mt7996_ampdu_action to support MLO") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 324d3b195a3f..89e9ef855a78 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1614,6 +1614,85 @@ mt7996_mac_sta_links_set_keys(struct mt7996_dev *dev, } } +static bool +mt7996_rx_reorder_host(struct mt7996_dev *dev) +{ + return !mt7996_has_hwrro(dev) && + !mtk_wed_device_active(&dev->mt76.mmio.wed) && + !mt76_npu_device_active(&dev->mt76); +} + +static u16 +mt7996_mac_sta_ba_rx_ssn(struct mt7996_dev *dev, struct mt7996_sta *msta, + unsigned int tid) +{ + struct mt76_rx_tid *tid_rx; + u16 ssn; + + tid_rx = mt76_dereference(msta->deflink.wcid.aggr[tid], &dev->mt76); + if (!tid_rx) + return msta->ba_rx[tid].ssn; + + spin_lock_bh(&tid_rx->lock); + ssn = tid_rx->head; + spin_unlock_bh(&tid_rx->lock); + + return ssn; +} + +static int +mt7996_mac_sta_links_ba_add(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + u16 rx_restart = 0; + unsigned int tid; + int err; + + if (!links) + return 0; + + for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) { + struct ieee80211_ampdu_params params = { + .sta = sta, + .tid = tid, + }; + + if (msta->ba_tx_mask & BIT(tid)) { + params.ssn = msta->ba_tx[tid].ssn; + params.buf_size = msta->ba_tx[tid].buf_size; + params.amsdu = msta->ba_tx[tid].amsdu; + err = mt7996_mcu_add_tx_ba(dev, ¶ms, vif, links, + true); + if (err) + return err; + } + + if (!(msta->ba_rx_mask & BIT(tid))) + continue; + + /* without host reordering, the reorder head stays at the + * session start, so have the originator set up a new session + */ + if (!mt7996_rx_reorder_host(dev)) { + rx_restart |= BIT(tid); + continue; + } + + params.ssn = mt7996_mac_sta_ba_rx_ssn(dev, msta, tid); + params.buf_size = msta->ba_rx[tid].buf_size; + params.amsdu = msta->ba_rx[tid].amsdu; + err = mt7996_mcu_add_rx_ba(dev, ¶ms, vif, links, true); + if (err) + return err; + } + + if (rx_restart) + ieee80211_stop_rx_ba_session(vif, rx_restart, sta->addr); + + return 0; +} + static int mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long links, @@ -1672,7 +1751,7 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, return err; } - return 0; + return mt7996_mac_sta_links_ba_add(dev, vif, sta, new_links); } static void -- 2.47.3 When mt7996_mac_sta_change_links() removes a link from a station, it keeps the msta_link and the firmware entry of the link. Commit e077071e7ac4 ("wifi: mt76: mt7996: fix teardown command for an MLD peer") removed the per-link DISCONNECT command for MLD peers, and a link that comes back must not get a second STA_REC with EXTRA_INFO_NEW, which commit 8e3e7567b8c1 ("mt76: mt7915: add sta_rec with EXTRA_INFO_NEW for the first time only") says can remove the entry. A link that comes back is therefore not connected again, and gets no rate control, keys or authorization. On removal, reset only the driver state of the link: its TWT flows, the TX state and the key index. When the link comes back with its entry still connected, send it the header translation state, rate control, its keys and, for an authorized station, the CONN_STATE_PORT_SECURE update without EXTRA_INFO_NEW, which also carries the header translation state. Send the 4-address PS leave request and the block ack sessions as for a new link, since the link can come back as a link other than the primary link. Fixes: e8c819df0243 ("wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove()") Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 89e9ef855a78..9965ac136bec 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1263,6 +1263,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, INIT_LIST_HEAD(&msta_link->wcid.poll_list); msta_link->sta = msta; msta_link->connected = false; + msta_link->wcid.tx_info = 0; msta_link->wcid.sta = 1; msta_link->wcid.idx = idx; msta_link->wcid.link_id = link_id; @@ -1699,7 +1700,7 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, unsigned long add) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - unsigned long new_links = 0, updated; + unsigned long new_links = 0, resumed = 0, joined, updated; unsigned int link_id; int err; @@ -1713,6 +1714,8 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link->connected) new_links |= BIT(link_id); + else if (!(msta_link->wcid.tx_info & MT_WCID_TX_INFO_SET)) + resumed |= BIT(link_id); } err = mt7996_mac_sta_links_event(dev, vif, sta, new_links, @@ -1720,17 +1723,40 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, if (err) return err; + for_each_set_bit(link_id, &resumed, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + link = mt7996_vif_link(dev, vif, link_id); + + if (msta->conn_state != CONN_STATE_PORT_SECURE) { + err = mt7996_mcu_wtbl_update_hdr_trans(dev, vif, link, + msta_link); + if (err) + return err; + } + + err = mt7996_mcu_add_rate_ctrl(dev, msta, vif, link_id, false); + if (err) + return err; + + msta_link->wcid.tx_info |= MT_WCID_TX_INFO_SET; + } + mt7996_mac_sta_links_set_keys(dev, vif, sta, links & add); updated = new_links; if (msta->conn_state == CONN_STATE_PORT_SECURE) { + updated |= resumed; err = mt7996_mac_sta_links_event(dev, vif, sta, updated, MT76_STA_EVENT_AUTHORIZE); if (err) return err; } - for_each_set_bit(link_id, &new_links, IEEE80211_MLD_MAX_NUM_LINKS) { + joined = new_links | resumed; + for_each_set_bit(link_id, &joined, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; @@ -1751,7 +1777,7 @@ mt7996_mac_sta_links_sync(struct mt7996_dev *dev, struct ieee80211_vif *vif, return err; } - return mt7996_mac_sta_links_ba_add(dev, vif, sta, new_links); + return mt7996_mac_sta_links_ba_add(dev, vif, sta, new_links | resumed); } static void @@ -1825,6 +1851,7 @@ mt7996_mac_sta_change_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mt76.mutex); + mt7996_mac_sta_links_reset(dev, vif, sta, rem); mt7996_mac_sta_remove_links(dev, vif, sta, rem, false); mt7996_mac_sta_links_bss_add(dev, vif, sta, add); ret = mt7996_mac_sta_add_links(dev, vif, sta, add); -- 2.47.3 The PLE register map in the MediaTek vendor driver places the free page counter, the free page head and tail, and the HIF group page status at 0x3a0, 0x3a4 and 0x3a8. The driver reads them at 0x380, 0x384 and 0x388, and the vendor map has the band 1 TXCMD queue empty status at 0x388. The same map has 0x100 bytes per AC for the station queue empty bitmaps, with the MT7992 extension register at offset 0x80. The driver uses 0x80, which cannot hold the 34 registers per AC of MT7996, so the hw-queues file reads AC1 to AC3 of a station from the wrong register. Use the offsets of the vendor register map. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/regs.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 8ff78cf6eb04..30bec7a0a987 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -155,12 +155,12 @@ enum offs_rev { #define MT_FL_Q2_CTRL MT_PLE(0x3e8) #define MT_FL_Q3_CTRL MT_PLE(0x3ec) -#define MT_PLE_FREEPG_CNT MT_PLE(0x380) -#define MT_PLE_FREEPG_HEAD_TAIL MT_PLE(0x384) +#define MT_PLE_FREEPG_CNT MT_PLE(0x3a0) +#define MT_PLE_FREEPG_HEAD_TAIL MT_PLE(0x3a4) #define MT_PLE_PG_HIF_GROUP MT_PLE(0x00c) -#define MT_PLE_HIF_PG_INFO MT_PLE(0x388) +#define MT_PLE_HIF_PG_INFO MT_PLE(0x3a8) -#define MT_PLE_AC_QEMPTY(ac, n) MT_PLE(0x600 + 0x80 * (ac) + ((n) << 2)) +#define MT_PLE_AC_QEMPTY(ac, n) MT_PLE(0x600 + 0x100 * (ac) + ((n) << 2)) #define MT_PLE_AMSDU_PACK_MSDU_CNT(n) MT_PLE(0x10e0 + ((n) << 2)) /* WF MDP TOP */ -- 2.47.3 The hw-queues debugfs file builds its own queue requests, and these do not match the register map of the MediaTek vendor driver: - The port ID goes to bits 11:10 of the PLE queue control register, but the field is at bits 17:16. Bits 11:10 are part of the WLAN index, so each request reads port 0 and, for a station, the WLAN index with bit 11 set. - The head and tail frame IDs, the packet count and the page counters are read as 12-bit fields, but they have 13 bits. - The PSE queues are read from the PLE empty and queue control registers, with bit positions that do not match the PSE queue map. - The station queues of MT7992 and MT7990 with a WLAN index from 512 to 543 are read from register 16, but these chips keep that group in the extension register at index 32. - The file iterates the stations of the same ieee80211_hw once for each band, so it shows each station queue up to three times. - mt7996_hw_queue_read() takes the private data of the file as a struct mt7996_phy, but it is a struct mt7996_dev. The dev member of the phy then aliases mt76.phy.dev of the device, which points back to the device, so the file works by chance. A queue request also writes the queue control register 0 and then reads the result from the queue control registers 2 and 3, without a lock. When the global and a per-station hw-queues file are read at the same time, one reader can get the result of the request of the other. Describe the PLE and PSE queues in tables with the field layout of the vendor register map, and send all queue requests through one helper, which holds a spinlock from the write of the request to the read of its result. A spinlock is needed because the station requests are sent from ieee80211_iterate_stations_atomic(). Iterate the stations once. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/debugfs.c | 319 +++++++++++------- .../net/wireless/mediatek/mt76/mt7996/regs.h | 28 ++ 2 files changed, 230 insertions(+), 117 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c index b85c3e549f8b..644abd0a0216 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c @@ -13,13 +13,6 @@ /** global debugfs **/ -struct hw_queue_map { - const char *name; - u8 index; - u8 pid; - u8 qid; -}; - static int mt7996_implicit_txbf_set(void *data, u64 val) { @@ -604,87 +597,230 @@ mt7996_tx_stats_show(struct seq_file *file, void *data) DEFINE_SHOW_ATTRIBUTE(mt7996_tx_stats); +enum { + MT_QUEUE_PORT_HIF, + MT_QUEUE_PORT_CPU, + MT_QUEUE_PORT_LMAC, + MT_QUEUE_PORT_PLE_CTRL, +}; + +struct mt7996_hw_queue { + const char *name; + u8 pid; + u8 qid; + u8 tgid; +}; + +#define MT7996_HW_QUEUE(_name, _pid, _qid, _tgid) \ + { .name = _name, .pid = MT_QUEUE_PORT_##_pid, .qid = _qid, .tgid = _tgid } + +static const struct mt7996_hw_queue mt7996_ple_queues[32] = { + [0] = MT7996_HW_QUEUE("CPU Q0", CPU, 0, 0), + [1] = MT7996_HW_QUEUE("CPU Q1", CPU, 1, 0), + [2] = MT7996_HW_QUEUE("CPU Q2", CPU, 2, 0), + [3] = MT7996_HW_QUEUE("CPU Q3", CPU, 3, 0), + [4] = MT7996_HW_QUEUE("ALTX Q0", LMAC, 0x10, 0), + [5] = MT7996_HW_QUEUE("BMC Q0", LMAC, 0x11, 0), + [6] = MT7996_HW_QUEUE("BCN Q0", LMAC, 0x12, 0), + [7] = MT7996_HW_QUEUE("PSMP Q0", LMAC, 0x13, 0), + [8] = MT7996_HW_QUEUE("ALTX Q1", LMAC, 0x10, 1), + [9] = MT7996_HW_QUEUE("BMC Q1", LMAC, 0x11, 1), + [10] = MT7996_HW_QUEUE("BCN Q1", LMAC, 0x12, 1), + [11] = MT7996_HW_QUEUE("PSMP Q1", LMAC, 0x13, 1), + [12] = MT7996_HW_QUEUE("ALTX Q2", LMAC, 0x1c, 2), + [13] = MT7996_HW_QUEUE("BMC Q2", LMAC, 0x1d, 2), + [14] = MT7996_HW_QUEUE("BCN Q2", LMAC, 0x1e, 2), + [15] = MT7996_HW_QUEUE("PSMP Q2", LMAC, 0x1f, 2), + [16] = MT7996_HW_QUEUE("NAF Q", LMAC, 0x18, 0), + [17] = MT7996_HW_QUEUE("NBCN Q", LMAC, 0x19, 0), + [20] = MT7996_HW_QUEUE("FIXFID Q", LMAC, 0x1a, 0), + [28] = MT7996_HW_QUEUE("RLS4 Q", PLE_CTRL, 0x7c, 0), + [29] = MT7996_HW_QUEUE("RLS3 Q", PLE_CTRL, 0x7d, 0), + [30] = MT7996_HW_QUEUE("RLS2 Q", PLE_CTRL, 0x7e, 0), + [31] = MT7996_HW_QUEUE("RLS Q", PLE_CTRL, 0x7f, 0), +}; + +static const struct mt7996_hw_queue mt7996_pse_queues[32] = { + [0] = MT7996_HW_QUEUE("CPU Q0", CPU, 0, 0), + [1] = MT7996_HW_QUEUE("CPU Q1", CPU, 1, 0), + [2] = MT7996_HW_QUEUE("CPU Q2", CPU, 2, 0), + [3] = MT7996_HW_QUEUE("CPU Q3", CPU, 3, 0), + [16] = MT7996_HW_QUEUE("LMAC Q", LMAC, 0, 0), + [17] = MT7996_HW_QUEUE("MDP TX Q0", LMAC, 1, 0), + [18] = MT7996_HW_QUEUE("MDP RX Q", LMAC, 2, 0), + [19] = MT7996_HW_QUEUE("SEC TX Q0", LMAC, 3, 0), + [20] = MT7996_HW_QUEUE("SEC RX Q", LMAC, 4, 0), + [21] = MT7996_HW_QUEUE("SFD PARK Q", LMAC, 5, 0), + [22] = MT7996_HW_QUEUE("MDP TXIOC Q0", LMAC, 6, 0), + [23] = MT7996_HW_QUEUE("MDP RXIOC Q0", LMAC, 7, 0), + [24] = MT7996_HW_QUEUE("MDP TX Q1", LMAC, 0x11, 0), + [25] = MT7996_HW_QUEUE("SEC TX Q1", LMAC, 0x13, 0), + [26] = MT7996_HW_QUEUE("MDP TXIOC Q1", LMAC, 0x16, 0), + [27] = MT7996_HW_QUEUE("MDP RXIOC Q1", LMAC, 0x17, 0), + [28] = MT7996_HW_QUEUE("CPU Q4", CPU, 4, 0), + [31] = MT7996_HW_QUEUE("RLS Q", PLE_CTRL, 0x1f, 0), +}; + +static const struct mt7996_hw_queue mt7996_pse_queues_1[32] = { + [0] = MT7996_HW_QUEUE("MDP TDPIOC Q0", LMAC, 0x08, 0), + [1] = MT7996_HW_QUEUE("MDP RDPIOC Q0", LMAC, 0x09, 0), + [2] = MT7996_HW_QUEUE("MDP TDPIOC Q1", LMAC, 0x18, 0), + [3] = MT7996_HW_QUEUE("MDP RDPIOC Q1", LMAC, 0x19, 0), + [4] = MT7996_HW_QUEUE("MDP TDPIOC Q2", LMAC, 0x28, 0), + [5] = MT7996_HW_QUEUE("MDP RDPIOC Q2", LMAC, 0x29, 0), + [7] = MT7996_HW_QUEUE("MDP RDPIOC Q3", LMAC, 0x39, 0), + [8] = MT7996_HW_QUEUE("MDP TX Q2", LMAC, 0x21, 0), + [9] = MT7996_HW_QUEUE("SEC TX Q2", LMAC, 0x23, 0), + [10] = MT7996_HW_QUEUE("MDP TXIOC Q2", LMAC, 0x26, 0), + [11] = MT7996_HW_QUEUE("MDP RXIOC Q2", LMAC, 0x27, 0), + [15] = MT7996_HW_QUEUE("MDP RXIOC Q3", LMAC, 0x37, 0), + [16] = MT7996_HW_QUEUE("HIF Q0", HIF, 0, 0), + [17] = MT7996_HW_QUEUE("HIF Q1", HIF, 1, 0), + [18] = MT7996_HW_QUEUE("HIF Q2", HIF, 2, 0), + [19] = MT7996_HW_QUEUE("HIF Q3", HIF, 3, 0), + [20] = MT7996_HW_QUEUE("HIF Q4", HIF, 4, 0), + [21] = MT7996_HW_QUEUE("HIF Q5", HIF, 5, 0), + [22] = MT7996_HW_QUEUE("HIF Q6", HIF, 6, 0), + [23] = MT7996_HW_QUEUE("HIF Q7", HIF, 7, 0), + [24] = MT7996_HW_QUEUE("HIF Q8", HIF, 8, 0), + [25] = MT7996_HW_QUEUE("HIF Q9", HIF, 9, 0), + [26] = MT7996_HW_QUEUE("HIF Q10", HIF, 10, 0), + [27] = MT7996_HW_QUEUE("HIF Q11", HIF, 11, 0), + [28] = MT7996_HW_QUEUE("HIF Q12", HIF, 12, 0), + [29] = MT7996_HW_QUEUE("HIF Q13", HIF, 13, 0), +}; + static void -mt7996_hw_queue_read(struct seq_file *s, u32 size, - const struct hw_queue_map *map) +mt7996_fl_queue_show(struct seq_file *s, struct mt7996_dev *dev, bool pse, + const struct mt7996_hw_queue *q, u16 wlan_idx) { - struct mt7996_phy *phy = s->private; - struct mt7996_dev *dev = phy->dev; - u32 i, val; + static DEFINE_SPINLOCK(lock); + u32 val, fid, num; - val = mt76_rr(dev, MT_FL_Q_EMPTY); - for (i = 0; i < size; i++) { - u32 ctrl, head, tail, queued; + val = MT_FL_Q0_CTRL_EXECUTE | + u32_encode_bits(q->qid, MT_FL_Q0_CTRL_QID) | + u32_encode_bits(q->tgid, MT_FL_Q0_CTRL_TGID) | + u32_encode_bits(q->pid, MT_FL_Q0_CTRL_PID) | + u32_encode_bits(wlan_idx, MT_FL_Q0_CTRL_WLAN_IDX); - if (val & BIT(map[i].index)) + spin_lock(&lock); + mt76_wr(dev, pse ? MT_PSE_FL_Q0_CTRL : MT_FL_Q0_CTRL, val); + fid = mt76_rr(dev, pse ? MT_PSE_FL_Q2_CTRL : MT_FL_Q2_CTRL); + num = mt76_rr(dev, pse ? MT_PSE_FL_Q3_CTRL : MT_FL_Q3_CTRL); + spin_unlock(&lock); + + seq_printf(s, "tail/head fid 0x%04x/0x%04x, packets %u", + u32_get_bits(fid, MT_FL_Q2_CTRL_TAIL_FID), + u32_get_bits(fid, MT_FL_Q2_CTRL_HEAD_FID), + u32_get_bits(num, MT_FL_Q3_CTRL_PKT_NUM)); +} + +static void +mt7996_fl_queues_show(struct seq_file *s, struct mt7996_dev *dev, bool pse, + const struct mt7996_hw_queue *queues, unsigned long empty) +{ + unsigned int i; + + for_each_clear_bit(i, &empty, 32) { + if (!queues[i].name) continue; - ctrl = BIT(31) | (map[i].pid << 10) | ((u32)map[i].qid << 24); - mt76_wr(dev, MT_FL_Q0_CTRL, ctrl); - - head = mt76_get_field(dev, MT_FL_Q2_CTRL, - GENMASK(11, 0)); - tail = mt76_get_field(dev, MT_FL_Q2_CTRL, - GENMASK(27, 16)); - queued = mt76_get_field(dev, MT_FL_Q3_CTRL, - GENMASK(11, 0)); - - seq_printf(s, "\t%s: ", map[i].name); - seq_printf(s, "queued:0x%03x head:0x%03x tail:0x%03x\n", - queued, head, tail); + seq_printf(s, "\t%s: ", queues[i].name); + mt7996_fl_queue_show(s, dev, pse, &queues[i], 0); + seq_puts(s, "\n"); } } +static void +mt7996_freepg_show(struct seq_file *s, u32 cnt, u32 head_tail) +{ + seq_printf(s, "\tfree pages 0x%04x, free for all 0x%04x, head/tail 0x%04x/0x%04x\n", + u32_get_bits(cnt, MT_FREEPG_CNT_FREE), + u32_get_bits(cnt, MT_FREEPG_CNT_FFA), + u32_get_bits(head_tail, MT_FREEPG_HEAD_TAIL_HEAD), + u32_get_bits(head_tail, MT_FREEPG_HEAD_TAIL_TAIL)); +} + +static void +mt7996_pg_group_show(struct seq_file *s, const char *name, u32 group, u32 info) +{ + seq_printf(s, "\t%s group: max/min quota 0x%04x/0x%04x, used/reserved 0x%04x/0x%04x\n", + name, u32_get_bits(group, MT_PG_GROUP_MAX_QUOTA), + u32_get_bits(group, MT_PG_GROUP_MIN_QUOTA), + u32_get_bits(info, MT_PG_INFO_SRC_CNT), + u32_get_bits(info, MT_PG_INFO_RSV_CNT)); +} + +static unsigned int +mt7996_ple_sta_cr_num(struct mt7996_dev *dev) +{ + return is_mt7996(&dev->mt76) ? 34 : 17; +} + +static unsigned int +mt7996_ple_sta_cr(struct mt7996_dev *dev, unsigned int n) +{ + /* MT7992 and MT7990 keep the last station group in the + * extension register that follows the first 32 groups + */ + if (!is_mt7996(&dev->mt76) && n == mt7996_ple_sta_cr_num(dev) - 1) + return 32; + + return n; +} + static void mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; struct mt7996_vif *mvif = msta->vif; - struct mt7996_phy *phy = mt7996_vif_link_phy(&mvif->deflink); struct ieee80211_link_sta *link_sta; struct seq_file *s = data; struct ieee80211_vif *vif; - struct mt7996_dev *dev; unsigned int link_id; - if (!phy) - return; - vif = container_of((void *)mvif, struct ieee80211_vif, drv_priv); - dev = phy->dev; rcu_read_lock(); for_each_sta_active_link(vif, sta, link_sta, link_id) { struct mt7996_sta_link *msta_link; struct mt76_vif_link *mlink; + struct mt7996_phy *phy; + unsigned int cr; + u16 wlan_idx; u8 ac; mlink = rcu_dereference(mvif->mt76.link[link_id]); if (!mlink) continue; + phy = mt7996_vif_link_phy(container_of(mlink, + struct mt7996_vif_link, + mt76)); + if (!phy) + continue; + msta_link = mt7996_sta_link(msta, link_id); if (!msta_link) continue; - for (ac = 0; ac < 4; ac++) { - u32 idx = msta_link->wcid.idx >> 5, qlen, ctrl, val; - u8 offs = msta_link->wcid.idx & GENMASK(4, 0); + wlan_idx = msta_link->wcid.idx; + cr = mt7996_ple_sta_cr(phy->dev, wlan_idx / 32); + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { + struct mt7996_hw_queue q = { + .pid = MT_QUEUE_PORT_LMAC, + .qid = ac, + }; - ctrl = BIT(31) | BIT(11) | (ac << 24); - val = mt76_rr(dev, MT_PLE_AC_QEMPTY(ac, idx)); - - if (val & BIT(offs)) + if (mt76_rr(phy->dev, MT_PLE_AC_QEMPTY(ac, cr)) & + BIT(wlan_idx % 32)) continue; - mt76_wr(dev, - MT_FL_Q0_CTRL, ctrl | msta_link->wcid.idx); - qlen = mt76_get_field(dev, MT_FL_Q3_CTRL, - GENMASK(11, 0)); - seq_printf(s, "\tSTA %pM wcid %d: AC%d%d queued:%d\n", - sta->addr, msta_link->wcid.idx, - mlink->wmm_idx, ac, qlen); + seq_printf(s, "\tSTA %pM wcid %u AC%u%u: ", sta->addr, + wlan_idx, mlink->wmm_idx, ac); + mt7996_fl_queue_show(s, phy->dev, false, &q, wlan_idx); + seq_puts(s, "\n"); } } @@ -692,78 +828,27 @@ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta) } static int -mt7996_hw_queues_show(struct seq_file *file, void *data) +mt7996_hw_queues_show(struct seq_file *s, void *data) { - struct mt7996_dev *dev = file->private; - struct mt7996_phy *phy = &dev->phy; - static const struct hw_queue_map ple_queue_map[] = { - { "CPU_Q0", 0, 1, MT_CTX0 }, - { "CPU_Q1", 1, 1, MT_CTX0 + 1 }, - { "CPU_Q2", 2, 1, MT_CTX0 + 2 }, - { "CPU_Q3", 3, 1, MT_CTX0 + 3 }, - { "ALTX_Q0", 8, 2, MT_LMAC_ALTX0 }, - { "BMC_Q0", 9, 2, MT_LMAC_BMC0 }, - { "BCN_Q0", 10, 2, MT_LMAC_BCN0 }, - { "PSMP_Q0", 11, 2, MT_LMAC_PSMP0 }, - { "ALTX_Q1", 12, 2, MT_LMAC_ALTX0 + 4 }, - { "BMC_Q1", 13, 2, MT_LMAC_BMC0 + 4 }, - { "BCN_Q1", 14, 2, MT_LMAC_BCN0 + 4 }, - { "PSMP_Q1", 15, 2, MT_LMAC_PSMP0 + 4 }, - }; - static const struct hw_queue_map pse_queue_map[] = { - { "CPU Q0", 0, 1, MT_CTX0 }, - { "CPU Q1", 1, 1, MT_CTX0 + 1 }, - { "CPU Q2", 2, 1, MT_CTX0 + 2 }, - { "CPU Q3", 3, 1, MT_CTX0 + 3 }, - { "HIF_Q0", 8, 0, MT_HIF0 }, - { "HIF_Q1", 9, 0, MT_HIF0 + 1 }, - { "HIF_Q2", 10, 0, MT_HIF0 + 2 }, - { "HIF_Q3", 11, 0, MT_HIF0 + 3 }, - { "HIF_Q4", 12, 0, MT_HIF0 + 4 }, - { "HIF_Q5", 13, 0, MT_HIF0 + 5 }, - { "LMAC_Q", 16, 2, 0 }, - { "MDP_TXQ", 17, 2, 1 }, - { "MDP_RXQ", 18, 2, 2 }, - { "SEC_TXQ", 19, 2, 3 }, - { "SEC_RXQ", 20, 2, 4 }, - }; - u32 val, head, tail; + struct mt7996_dev *dev = s->private; - /* ple queue */ - val = mt76_rr(dev, MT_PLE_FREEPG_CNT); - head = mt76_get_field(dev, MT_PLE_FREEPG_HEAD_TAIL, GENMASK(11, 0)); - tail = mt76_get_field(dev, MT_PLE_FREEPG_HEAD_TAIL, GENMASK(27, 16)); - seq_puts(file, "PLE page info:\n"); - seq_printf(file, - "\tTotal free page: 0x%08x head: 0x%03x tail: 0x%03x\n", - val, head, tail); + seq_puts(s, "PLE page info:\n"); + mt7996_freepg_show(s, mt76_rr(dev, MT_PLE_FREEPG_CNT), + mt76_rr(dev, MT_PLE_FREEPG_HEAD_TAIL)); + mt7996_pg_group_show(s, "HIF", mt76_rr(dev, MT_PLE_PG_HIF_GROUP), + mt76_rr(dev, MT_PLE_HIF_PG_INFO)); - val = mt76_rr(dev, MT_PLE_PG_HIF_GROUP); - head = mt76_get_field(dev, MT_PLE_HIF_PG_INFO, GENMASK(11, 0)); - tail = mt76_get_field(dev, MT_PLE_HIF_PG_INFO, GENMASK(27, 16)); - seq_printf(file, "\tHIF free page: 0x%03x res: 0x%03x used: 0x%03x\n", - val, head, tail); + seq_puts(s, "PLE non-empty queue info:\n"); + mt7996_fl_queues_show(s, dev, false, mt7996_ple_queues, + mt76_rr(dev, MT_FL_Q_EMPTY)); + ieee80211_iterate_stations_atomic(dev->mphy.hw, + mt7996_sta_hw_queue_read, s); - seq_puts(file, "PLE non-empty queue info:\n"); - mt7996_hw_queue_read(file, ARRAY_SIZE(ple_queue_map), - &ple_queue_map[0]); - - /* iterate per-sta ple queue */ - ieee80211_iterate_stations_atomic(phy->mt76->hw, - mt7996_sta_hw_queue_read, file); - phy = mt7996_phy2(dev); - if (phy) - ieee80211_iterate_stations_atomic(phy->mt76->hw, - mt7996_sta_hw_queue_read, file); - phy = mt7996_phy3(dev); - if (phy) - ieee80211_iterate_stations_atomic(phy->mt76->hw, - mt7996_sta_hw_queue_read, file); - - /* pse queue */ - seq_puts(file, "PSE non-empty queue info:\n"); - mt7996_hw_queue_read(file, ARRAY_SIZE(pse_queue_map), - &pse_queue_map[0]); + seq_puts(s, "PSE non-empty queue info:\n"); + mt7996_fl_queues_show(s, dev, true, mt7996_pse_queues, + mt76_rr(dev, MT_PSE_QUEUE_EMPTY)); + mt7996_fl_queues_show(s, dev, true, mt7996_pse_queues_1, + mt76_rr(dev, MT_PSE_QUEUE_EMPTY_1)); return 0; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 30bec7a0a987..6df6e4c3d45d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -155,6 +155,15 @@ enum offs_rev { #define MT_FL_Q2_CTRL MT_PLE(0x3e8) #define MT_FL_Q3_CTRL MT_PLE(0x3ec) +#define MT_FL_Q0_CTRL_EXECUTE BIT(31) +#define MT_FL_Q0_CTRL_QID GENMASK(30, 24) +#define MT_FL_Q0_CTRL_TGID GENMASK(21, 20) +#define MT_FL_Q0_CTRL_PID GENMASK(17, 16) +#define MT_FL_Q0_CTRL_WLAN_IDX GENMASK(11, 0) +#define MT_FL_Q2_CTRL_TAIL_FID GENMASK(28, 16) +#define MT_FL_Q2_CTRL_HEAD_FID GENMASK(12, 0) +#define MT_FL_Q3_CTRL_PKT_NUM GENMASK(12, 0) + #define MT_PLE_FREEPG_CNT MT_PLE(0x3a0) #define MT_PLE_FREEPG_HEAD_TAIL MT_PLE(0x3a4) #define MT_PLE_PG_HIF_GROUP MT_PLE(0x00c) @@ -163,6 +172,25 @@ enum offs_rev { #define MT_PLE_AC_QEMPTY(ac, n) MT_PLE(0x600 + 0x100 * (ac) + ((n) << 2)) #define MT_PLE_AMSDU_PACK_MSDU_CNT(n) MT_PLE(0x10e0 + ((n) << 2)) +#define MT_FREEPG_CNT_FFA GENMASK(28, 16) +#define MT_FREEPG_CNT_FREE GENMASK(12, 0) +#define MT_FREEPG_HEAD_TAIL_TAIL GENMASK(28, 16) +#define MT_FREEPG_HEAD_TAIL_HEAD GENMASK(12, 0) +#define MT_PG_GROUP_MAX_QUOTA GENMASK(28, 16) +#define MT_PG_GROUP_MIN_QUOTA GENMASK(12, 0) +#define MT_PG_INFO_SRC_CNT GENMASK(28, 16) +#define MT_PG_INFO_RSV_CNT GENMASK(12, 0) + +/* PSE */ +#define MT_PSE_BASE 0x820c8000 +#define MT_PSE(ofs) (MT_PSE_BASE + (ofs)) + +#define MT_PSE_QUEUE_EMPTY MT_PSE(0x0b0) +#define MT_PSE_QUEUE_EMPTY_1 MT_PSE(0x0bc) +#define MT_PSE_FL_Q0_CTRL MT_PSE(0x1b0) +#define MT_PSE_FL_Q2_CTRL MT_PSE(0x1b8) +#define MT_PSE_FL_Q3_CTRL MT_PSE(0x1bc) + /* WF MDP TOP */ #define MT_MDP_BASE 0x820cc000 #define MT_MDP(ofs) (MT_MDP_BASE + (ofs)) -- 2.47.3 From: John Crispin mt7996_unregister_device() leaves the RX NAPIs enabled, so mt76_dma_cleanup() deletes enabled NAPIs and destroys page pools that are still bound to them. On rmmod, the kernel warns in __netif_napi_del_locked() for every RX queue and in page_pool_disable_direct_recycling(). Until the IRQ tasklet is disabled, an interrupt can still schedule a NAPI that was already deleted. Disable every RX NAPI that was added before the MCU exit and the DMA cleanup. This includes the host RRO queues: mt76_dma_init() skips them, but mt7996_dma_rro_start() adds their NAPIs later when WED is not active, so a check for mt76_queue_is_wed_rro() would miss them. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: John Crispin Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2db63c3be9d1..ad9919c516e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1810,6 +1810,8 @@ int mt7996_register_device(struct mt7996_dev *dev) void mt7996_unregister_device(struct mt7996_dev *dev) { + int i; + cancel_work_sync(&dev->dump_work); cancel_work_sync(&dev->wed_rro.work); cancel_work_sync(&dev->reset_work); @@ -1820,6 +1822,10 @@ void mt7996_unregister_device(struct mt7996_dev *dev) mt7996_coredump_unregister(dev); mt76_unregister_device(&dev->mt76); mt7996_wed_rro_free(dev); + for (i = 0; i < ARRAY_SIZE(dev->mt76.napi); i++) { + if (dev->mt76.napi[i].dev) + napi_disable(&dev->mt76.napi[i]); + } mt7996_mcu_exit(dev); mt7996_tx_token_put(dev); mt7996_dma_cleanup(dev); -- 2.47.3