Files
mt76/scan.c
T
Felix Fietkau 90f731f6c9 wifi: mt76: clear offchannel state if a scan has no channel to restore
mt76_scan_complete() restores the operating channel only if the phy has one.
__mt76_set_channel() is the only function that clears phy->offchannel.
A phy can run a scan while it has no operating channel. A 6 GHz radio with
no configured interface is one example. After such a scan, the flag stays
set.

mt76_txq_schedule_list() skips each TXQ whose wcid points to a phy in this
state. All traffic for the stations on that band stops. The frames stay in
the mac80211 queues. They do not reach the hardware, and no counter shows
them. Only an unrelated channel set clears the flag.

Link: https://patch.msgid.link/[email protected]
Signed-off-by: Felix Fietkau <[email protected]>
2026-08-18 15:42:32 +02:00

252 lines
6.2 KiB
C

// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (C) 2024 Felix Fietkau <[email protected]>
*/
#include "mt76.h"
static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
{
struct mt76_phy *phy = dev->scan.phy;
struct cfg80211_scan_info info = {
.aborted = abort,
};
lockdep_assert_held(&dev->mutex);
if (!phy)
return;
clear_bit(MT76_SCANNING, &phy->state);
/* Re-program the operating channel even when the scan never left it:
* any channel set during the scan ran with MT76_SCANNING held, which
* left DFS radar detection disabled
*/
if (phy->main_chandef.chan &&
!test_bit(MT76_MCU_RESET, &dev->phy.state)) {
bool offchannel = phy->offchannel;
__mt76_set_channel(phy, &phy->main_chandef, false);
if (offchannel)
mt76_offchannel_notify(phy, false);
} else {
/* A phy that has no operating channel has nothing to restore,
* but the flag also gates the TX queues of every station whose
* wcid points at this band, so leaving it set stalls them until
* something else happens to set a channel.
*/
phy->offchannel = false;
}
mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink);
memset(&dev->scan, 0, sizeof(dev->scan));
if (!test_bit(MT76_MCU_RESET, &dev->phy.state))
ieee80211_scan_completed(phy->hw, &info);
}
void mt76_abort_scan(struct mt76_dev *dev)
{
spin_lock_bh(&dev->scan_lock);
dev->scan.beacon_wait = false;
spin_unlock_bh(&dev->scan_lock);
cancel_delayed_work_sync(&dev->scan_work);
mutex_lock(&dev->mutex);
mt76_scan_complete(dev, true);
mutex_unlock(&dev->mutex);
}
EXPORT_SYMBOL_GPL(mt76_abort_scan);
static void
mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid)
{
struct cfg80211_scan_request *req = dev->scan.req;
struct ieee80211_vif *vif = dev->scan.vif;
struct mt76_vif_link *mvif = dev->scan.mlink;
enum nl80211_band band = dev->scan.chan->band;
struct mt76_phy *phy = dev->scan.phy;
struct ieee80211_tx_info *info;
struct sk_buff *skb;
u8 link_id;
skb = ieee80211_probereq_get(phy->hw, vif->addr, ssid->ssid,
ssid->ssid_len, req->ie_len);
if (!skb)
return;
if (is_unicast_ether_addr(req->bssid)) {
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
ether_addr_copy(hdr->addr1, req->bssid);
ether_addr_copy(hdr->addr3, req->bssid);
}
if (req->ie_len)
skb_put_data(skb, req->ie, req->ie_len);
skb->priority = 7;
skb_set_queue_mapping(skb, IEEE80211_AC_VO);
rcu_read_lock();
if (!ieee80211_tx_prepare_skb(phy->hw, vif, skb, band, NULL))
goto out;
info = IEEE80211_SKB_CB(skb);
if (req->no_cck)
info->flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
info->control.flags |= IEEE80211_TX_CTRL_DONT_USE_RATE_MASK;
link_id = mvif->wcid ? mvif->wcid->link_id : IEEE80211_LINK_UNSPECIFIED;
info->control.flags &= ~IEEE80211_TX_CTRL_MLO_LINK;
info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK);
mt76_tx(phy, NULL, mvif->wcid, skb);
out:
rcu_read_unlock();
}
void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan)
{
struct mt76_phy *phy;
spin_lock(&dev->scan_lock);
if (!dev->scan.beacon_wait || dev->scan.beacon_received ||
dev->scan.chan != chan)
goto out;
phy = dev->scan.phy;
if (!phy)
goto out;
dev->scan.beacon_received = true;
ieee80211_queue_delayed_work(phy->hw, &dev->scan_work, 0);
out:
spin_unlock(&dev->scan_lock);
}
void mt76_scan_work(struct work_struct *work)
{
struct mt76_dev *dev = container_of(work, struct mt76_dev,
scan_work.work);
struct cfg80211_scan_request *req = dev->scan.req;
struct cfg80211_chan_def chandef = {};
struct mt76_phy *phy = dev->scan.phy;
int duration = HZ / 9; /* ~110 ms */
bool beacon_rx, offchannel = true;
int i;
if (!phy || !req)
return;
spin_lock_bh(&dev->scan_lock);
beacon_rx = dev->scan.beacon_wait && dev->scan.beacon_received;
dev->scan.beacon_wait = false;
spin_unlock_bh(&dev->scan_lock);
if (beacon_rx)
goto probe;
if (dev->scan.chan_idx >= req->n_channels) {
mutex_lock(&dev->mutex);
mt76_scan_complete(dev, false);
mutex_unlock(&dev->mutex);
return;
}
if (dev->scan.chan && phy->num_sta && phy->offchannel) {
dev->scan.chan = NULL;
mt76_set_channel(phy, &phy->main_chandef, false);
mt76_offchannel_notify(phy, false);
goto out;
}
dev->scan.chan = req->channels[dev->scan.chan_idx++];
offchannel = mt76_offchannel_chandef(phy, dev->scan.chan, &chandef);
if (offchannel)
mt76_offchannel_notify(phy, true);
mt76_set_channel(phy, &chandef, offchannel);
if (!req->n_ssids)
goto out;
if (chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) {
spin_lock_bh(&dev->scan_lock);
dev->scan.beacon_received = false;
dev->scan.beacon_wait = true;
spin_unlock_bh(&dev->scan_lock);
goto out;
}
probe:
if (phy->offchannel)
duration = HZ / 16; /* ~60 ms */
local_bh_disable();
for (i = 0; i < req->n_ssids; i++)
mt76_scan_send_probe(dev, &req->ssids[i]);
local_bh_enable();
out:
if (dev->scan.chan && phy->offchannel)
duration = max_t(int, duration,
msecs_to_jiffies(req->duration +
(req->duration >> 5)));
ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, duration);
}
int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *req)
{
struct mt76_phy *phy = hw->priv;
struct mt76_dev *dev = phy->dev;
struct mt76_vif_link *mlink;
int ret = 0;
if (hw->wiphy->n_radio > 1) {
phy = dev->band_phys[req->req.channels[0]->band];
if (!phy)
return -EINVAL;
}
mutex_lock(&dev->mutex);
if (dev->scan.req || phy->roc_vif ||
test_bit(MT76_MCU_RESET, &dev->phy.state)) {
ret = -EBUSY;
goto out;
}
mlink = mt76_get_vif_phy_link(phy, vif);
if (IS_ERR(mlink)) {
ret = PTR_ERR(mlink);
goto out;
}
memset(&dev->scan, 0, sizeof(dev->scan));
dev->scan.req = &req->req;
dev->scan.vif = vif;
dev->scan.phy = phy;
dev->scan.mlink = mlink;
set_bit(MT76_SCANNING, &phy->state);
ieee80211_queue_delayed_work(dev->phy.hw, &dev->scan_work, 0);
out:
mutex_unlock(&dev->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(mt76_hw_scan);
void mt76_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct mt76_phy *phy = hw->priv;
mt76_abort_scan(phy->dev);
}
EXPORT_SYMBOL_GPL(mt76_cancel_hw_scan);