From: james-ly huang The FW tells the host with a BSS_IS_ABSENCE event when a P2P GO's BSS currently has no channel - latch the absent / free-quota state per bss_conf (mt7925_p2p_mcu_bss_is_absence_event in p2p.c), and kick the TX path (tx_worker) on the absent->present edge so whatever piled up while the BSS had no channel is dequeued immediately; - clear the latched state where the BSS index is (re)assigned: a GO stopped while absent and restarted on the same netdev would otherwise come back with absent still latched and drop every Probe Response until the FW sends a presence event for the new bss_idx; - the TX path (mt792x_tx) drops a GO Probe Response while the BSS is absent - by the time the FW comes back the requester has moved on, so the response is stale and only costs airtime and a TX-abort. Signed-off-by: james-ly huang --- v2: no change --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7925/main.c | 12 +++ .../net/wireless/mediatek/mt76/mt7925/mcu.c | 3 + .../net/wireless/mediatek/mt76/mt7925/p2p.c | 97 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7925/p2p.h | 20 ++++ drivers/net/wireless/mediatek/mt76/mt792x.h | 25 +++++ .../net/wireless/mediatek/mt76/mt792x_core.c | 16 +++ 7 files changed, 174 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 817554ffb9d8..2acaae9830fb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1102,6 +1102,7 @@ enum { MCU_UNI_EVENT_SAP = 0x1c, MCU_UNI_EVENT_ROC = 0x27, MCU_UNI_EVENT_MBMC = 0x28, + MCU_UNI_EVENT_BSS_IS_ABSENCE = 0x29, MCU_UNI_EVENT_TX_DONE = 0x2d, MCU_UNI_EVENT_THERMAL = 0x35, MCU_UNI_EVENT_RSSI_MONITOR = 0x41, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index cfdb736f53ca..25e981e38ea9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -401,6 +401,18 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev, 0 : mconf->mt76.idx; mconf->mt76.band_idx = 0xff; + /* A freshly configured BSS is on channel until the FW says otherwise. + * mt792x_bss_conf lives in vif->drv_priv / mvif->link_conf[], which + * mac80211 only zeroes when the interface is created - a GO that is + * stopped while absent and restarted on the same netdev would come back + * with absent still latched, and then drop every Probe Response until + * the FW happens to send a presence event for the new bss_idx. Clear it + * here, where the BSS index is (re)assigned. + */ + mconf->absent = false; + mconf->free_quota = 0; + mconf->absence_tstamp = 0; + if (is_mt7927(&dev->mt76)) { struct ieee80211_channel *chan; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index d9a5b5f57fba..24de2ff3a15d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -738,6 +738,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, case MCU_UNI_EVENT_SAP: mt7925_p2p_mcu_sap_event(dev, skb); break; + case MCU_UNI_EVENT_BSS_IS_ABSENCE: + mt7925_p2p_mcu_bss_is_absence_event(dev, skb); + break; case MCU_UNI_EVENT_RSSI_MONITOR: mt7925_mcu_rssi_monitor_event(dev, skb); break; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c index 1fa6787600b2..e5748496a3a6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c @@ -603,3 +603,100 @@ void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb) } } +struct mt7925_bss_absence_data { + u8 bss_idx; + u8 is_absent; + u8 free_quota; +}; + +/* Per-BSS handling of the BSS_IS_ABSENCE event: latch the absent / + * free-quota state, and on the absent->present edge kick the TX path so + * whatever piled up while the BSS had no channel gets dequeued + * immediately (the kick is the tx_worker). + */ +static void +mt7925_mcu_bss_absence_iter(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt7925_bss_absence_data *data = priv; + struct mt792x_bss_conf *mconf = NULL; + struct mt792x_dev *dev = mvif->phy->dev; + unsigned long valid_links, now = jiffies; + unsigned int link_id; + bool absent, was_absent; + + if (ieee80211_vif_is_mld(vif)) { + valid_links = mvif->valid_links; + for_each_set_bit(link_id, &valid_links, + IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt792x_bss_conf *link_mconf; + + link_mconf = rcu_dereference(mvif->link_conf[link_id]); + if (link_mconf && link_mconf->mt76.idx == data->bss_idx) { + mconf = link_mconf; + break; + } + } + } else if (mvif->bss_conf.mt76.idx == data->bss_idx) { + mconf = &mvif->bss_conf; + } + + if (!mconf) + return; + + absent = !!data->is_absent; + was_absent = mconf->absent; + + mconf->free_quota = data->free_quota; + if (was_absent != absent) { + mconf->absent = absent; + mconf->absence_tstamp = now; + } + + /* From Absent to Present */ + if (was_absent && !absent) + mt76_worker_schedule(&dev->mt76.tx_worker); +} + +void mt7925_p2p_mcu_bss_is_absence_event(struct mt792x_dev *dev, struct sk_buff *skb) +{ + struct mt7925_uni_bss_is_absence_event *event; + struct mt7925_bss_absence_data data; + u32 tlv_len; + u8 *tlv; + + skb_pull(skb, sizeof(struct mt7925_mcu_rxd)); + if (skb->len < sizeof(*event)) + return; + + event = (struct mt7925_uni_bss_is_absence_event *)skb->data; + tlv = event->tlv; + tlv_len = skb->len - sizeof(*event); + + while (tlv_len >= sizeof(struct tlv)) { + struct tlv *hdr = (struct tlv *)tlv; + u16 tag = le16_to_cpu(hdr->tag); + u16 len = le16_to_cpu(hdr->len); + + if (len < sizeof(*hdr) || len > tlv_len) + break; + + if (tag == MT7925_BSS_IS_ABSENCE_TAG_INFO && + len >= sizeof(struct mt7925_bss_absence_info_tlv)) { + struct mt7925_bss_absence_info_tlv *info = + (struct mt7925_bss_absence_info_tlv *)tlv; + + data.bss_idx = event->bss_idx; + data.is_absent = info->is_absent; + data.free_quota = info->free_quota; + + ieee80211_iterate_active_interfaces_atomic(dev->mt76.phy.hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + mt7925_mcu_bss_absence_iter, + &data); + } + + tlv += len; + tlv_len -= len; + } +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h index 9afae222845f..e189f9e56e93 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h @@ -53,6 +53,24 @@ struct mt7925_uni_sap_event { u8 pad[4]; /* fixed field */ u8 tlv[]; } __packed; + +/* BSS absence/presence event (FW event UNI_EVENT_ID_BSS_IS_ABSENCE) */ +#define MT7925_BSS_IS_ABSENCE_TAG_INFO 0 + +struct mt7925_bss_absence_info_tlv { + __le16 tag; + __le16 len; + u8 is_absent; + u8 free_quota; + u8 pad[2]; +} __packed; + +struct mt7925_uni_bss_is_absence_event { + u8 bss_idx; + u8 pad[3]; + u8 tlv[]; +} __packed; + /* TX/RX path overrides, BMC wcid bookkeeping and the P2P-only MCU event * handlers. */ @@ -69,5 +87,7 @@ bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb); +void mt7925_p2p_mcu_bss_is_absence_event(struct mt792x_dev *dev, + struct sk_buff *skb); #endif /* __MT7925_P2P_H */ diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 94186e9f77c8..6bfbeceaf026 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -163,6 +163,17 @@ struct mt792x_bss_conf { struct ewma_rssi rssi; struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; unsigned int link_id; + /* FW BSS absence/presence state, updated by the BSS_IS_ABSENCE + * event handler. + */ + bool absent; + u8 free_quota; + unsigned long absence_tstamp; + + /* Dedicated BMC entry, only allocated for a p2p GO. Group-addressed + * frames are transmitted on this wcid instead of the vif wcid, so + * they no longer share the wcid + */ struct mt792x_link_sta bmc; bool bmc_valid; }; @@ -407,6 +418,20 @@ mt792x_link_conf_to_mconf(struct ieee80211_bss_conf *link_conf) return mt792x_vif_to_link(mvif, link_conf->link_id); } +/* TRUE only while the BSS has no channel, so nothing we hand to the FW + * for it can go on air. Callable from the TX path: reads a single byte + * updated by the BSS_IS_ABSENCE event handler. + */ +static inline bool mt792x_vif_is_absent(struct ieee80211_vif *vif) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + + if (ieee80211_vif_is_mld(vif)) + return false; + + return mvif->bss_conf.absent; +} + static inline struct ieee80211_bss_conf * mt792x_vif_to_bss_conf(struct ieee80211_vif *vif, unsigned int link_id) { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index fa29af8fff49..98fa46e85c1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -178,6 +178,22 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, u8 link_id; int qid; + /* Drop a GO Probe Response outright when mt792x_vif_is_absent() + * says the BSS has no channel right now: by the time the FW comes + * back the requester has moved on, so the response is stale and + * only costs airtime and a TX-abort in the FW. + */ + if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && skb->len >= 2) { + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + + if (vif && ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_P2P_GO && + ieee80211_is_probe_resp(mgmt->frame_control) && + mt792x_vif_is_absent(vif)) { + ieee80211_free_txskb(hw, skb); + return; + } + } + if (control->sta) { struct mt792x_link_sta *mlink; struct mt792x_sta *sta; -- 2.45.2