| // SPDX-License-Identifier: BSD-3-Clause-Clear | |
| /* | |
| * Copyright (C) 2024 Felix Fietkau <nbd@nbd.name> | |
| */ | |
| #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, | |
| }; | |
| 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); | |
| } | |
| 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); | |
| mt76_scan_complete(dev, true); | |
| } | |
| 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) { | |
| mt76_scan_complete(dev, false); | |
| 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); |