X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=net%2Fmac80211%2Fscan.c;h=b822dce9786706e65a30480aa97b1cfd031211e6;hb=070bb5477fb4029131aad4941d7aaf0093db0c38;hp=f883ab9f1e6e436de1f4a311963410a341a931cd;hpb=00d3f14cf9f12c21428121026a5e1d5f65926447;p=safe%2Fjmp%2Flinux-2.6 diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index f883ab9..b822dce 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -12,35 +12,35 @@ * published by the Free Software Foundation. */ -/* TODO: figure out how to avoid that the "current BSS" expires */ - -#include #include #include #include -#include #include "ieee80211_i.h" +#include "driver-ops.h" #include "mesh.h" #define IEEE80211_PROBE_DELAY (HZ / 33) #define IEEE80211_CHANNEL_TIME (HZ / 33) -#define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 5) +#define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 8) struct ieee80211_bss * ieee80211_rx_bss_get(struct ieee80211_local *local, u8 *bssid, int freq, u8 *ssid, u8 ssid_len) { - return (void *)cfg80211_get_bss(local->hw.wiphy, - ieee80211_get_channel(local->hw.wiphy, - freq), - bssid, ssid, ssid_len, - 0, 0); + struct cfg80211_bss *cbss; + + cbss = cfg80211_get_bss(local->hw.wiphy, + ieee80211_get_channel(local->hw.wiphy, freq), + bssid, ssid, ssid_len, 0, 0); + if (!cbss) + return NULL; + return (void *)cbss->priv; } static void ieee80211_rx_bss_free(struct cfg80211_bss *cbss) { - struct ieee80211_bss *bss = (void *)cbss; + struct ieee80211_bss *bss = (void *)cbss->priv; kfree(bss_mesh_id(bss)); kfree(bss_mesh_cfg(bss)); @@ -49,7 +49,26 @@ static void ieee80211_rx_bss_free(struct cfg80211_bss *cbss) void ieee80211_rx_bss_put(struct ieee80211_local *local, struct ieee80211_bss *bss) { - cfg80211_put_bss((struct cfg80211_bss *)bss); + if (!bss) + return; + cfg80211_put_bss(container_of((void *)bss, struct cfg80211_bss, priv)); +} + +static bool is_uapsd_supported(struct ieee802_11_elems *elems) +{ + u8 qos_info; + + if (elems->wmm_info && elems->wmm_info_len == 7 + && elems->wmm_info[5] == 1) + qos_info = elems->wmm_info[6]; + else if (elems->wmm_param && elems->wmm_param_len == 24 + && elems->wmm_param[5] == 1) + qos_info = elems->wmm_param[6]; + else + /* no valid wmm information or parameter element found */ + return false; + + return qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD; } struct ieee80211_bss * @@ -61,27 +80,24 @@ ieee80211_bss_info_update(struct ieee80211_local *local, struct ieee80211_channel *channel, bool beacon) { + struct cfg80211_bss *cbss; struct ieee80211_bss *bss; int clen; - enum cfg80211_signal_type sigtype = CFG80211_SIGNAL_TYPE_NONE; s32 signal = 0; - if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) { - sigtype = CFG80211_SIGNAL_TYPE_MBM; + if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) signal = rx_status->signal * 100; - } else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) { - sigtype = CFG80211_SIGNAL_TYPE_UNSPEC; + else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) signal = (rx_status->signal * 100) / local->hw.max_signal; - } - bss = (void *)cfg80211_inform_bss_frame(local->hw.wiphy, channel, - mgmt, len, signal, sigtype, - GFP_ATOMIC); + cbss = cfg80211_inform_bss_frame(local->hw.wiphy, channel, + mgmt, len, signal, GFP_ATOMIC); - if (!bss) + if (!cbss) return NULL; - bss->cbss.free_priv = ieee80211_rx_bss_free; + cbss->free_priv = ieee80211_rx_bss_free; + bss = (void *)cbss->priv; /* save the ERP value so that it is available at association time */ if (elems->erp_info && elems->erp_info_len >= 1) { @@ -95,10 +111,6 @@ ieee80211_bss_info_update(struct ieee80211_local *local, bss->dtim_period = tim_ie->dtim_period; } - /* set default value for buggy APs */ - if (!elems->tim || bss->dtim_period == 0) - bss->dtim_period = 1; - bss->supp_rates_len = 0; if (elems->supp_rates) { clen = IEEE80211_MAX_SUPP_RATES - bss->supp_rates_len; @@ -118,6 +130,7 @@ ieee80211_bss_info_update(struct ieee80211_local *local, } bss->wmm_used = elems->wmm_param || elems->wmm_info; + bss->uapsd_supported = is_uapsd_supported(elems); if (!beacon) bss->last_probe_resp = jiffies; @@ -125,23 +138,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local, return bss; } -void ieee80211_rx_bss_remove(struct ieee80211_sub_if_data *sdata, u8 *bssid, - int freq, u8 *ssid, u8 ssid_len) -{ - struct ieee80211_bss *bss; - struct ieee80211_local *local = sdata->local; - - bss = ieee80211_rx_bss_get(local, bssid, freq, ssid, ssid_len); - if (bss) { - cfg80211_unlink_bss(local->hw.wiphy, (void *)bss); - ieee80211_rx_bss_put(local, bss); - } -} - ieee80211_rx_result -ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct ieee80211_rx_status *rx_status) +ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) { + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); struct ieee80211_mgmt *mgmt; struct ieee80211_bss *bss; u8 *elements; @@ -167,7 +167,7 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, presp = ieee80211_is_probe_resp(fc); if (presp) { /* ignore ProbeResp to foreign address */ - if (memcmp(mgmt->da, sdata->dev->dev_addr, ETH_ALEN)) + if (memcmp(mgmt->da, sdata->vif.addr, ETH_ALEN)) return RX_DROP_MONITOR; presp = true; @@ -207,304 +207,500 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, return RX_QUEUED; } -void ieee80211_send_nullfunc(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata, - int powersave) +/* return false if no more work */ +static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) { - struct sk_buff *skb; - struct ieee80211_hdr *nullfunc; - __le16 fc; + struct cfg80211_scan_request *req = local->scan_req; + enum ieee80211_band band; + int i, ielen, n_chans; + + do { + if (local->hw_scan_band == IEEE80211_NUM_BANDS) + return false; + + band = local->hw_scan_band; + n_chans = 0; + for (i = 0; i < req->n_channels; i++) { + if (req->channels[i]->band == band) { + local->hw_scan_req->channels[n_chans] = + req->channels[i]; + n_chans++; + } + } - skb = dev_alloc_skb(local->hw.extra_tx_headroom + 24); - if (!skb) { - printk(KERN_DEBUG "%s: failed to allocate buffer for nullfunc " - "frame\n", sdata->dev->name); - return; - } - skb_reserve(skb, local->hw.extra_tx_headroom); - - nullfunc = (struct ieee80211_hdr *) skb_put(skb, 24); - memset(nullfunc, 0, 24); - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC | - IEEE80211_FCTL_TODS); - if (powersave) - fc |= cpu_to_le16(IEEE80211_FCTL_PM); - nullfunc->frame_control = fc; - memcpy(nullfunc->addr1, sdata->u.sta.bssid, ETH_ALEN); - memcpy(nullfunc->addr2, sdata->dev->dev_addr, ETH_ALEN); - memcpy(nullfunc->addr3, sdata->u.sta.bssid, ETH_ALEN); - - ieee80211_tx_skb(sdata, skb, 0); + local->hw_scan_band++; + } while (!n_chans); + + local->hw_scan_req->n_channels = n_chans; + + ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie, + req->ie, req->ie_len, band); + local->hw_scan_req->ie_len = ielen; + + return true; } void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) { struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_sub_if_data *sdata; + bool was_hw_scan; + + mutex_lock(&local->scan_mtx); + + /* + * It's ok to abort a not-yet-running scan (that + * we have one at all will be verified by checking + * local->scan_req next), but not to complete it + * successfully. + */ + if (WARN_ON(!local->scanning && !aborted)) + aborted = true; - if (WARN_ON(!local->hw_scanning && !local->sw_scanning)) + if (WARN_ON(!local->scan_req)) { + mutex_unlock(&local->scan_mtx); return; + } - if (WARN_ON(!local->scan_req)) + was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning); + if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) { + ieee80211_queue_delayed_work(&local->hw, + &local->scan_work, 0); + mutex_unlock(&local->scan_mtx); return; + } + + kfree(local->hw_scan_req); + local->hw_scan_req = NULL; - if (local->scan_req != &local->int_scan_req) + if (local->scan_req != local->int_scan_req) cfg80211_scan_done(local->scan_req, aborted); local->scan_req = NULL; + local->scan_sdata = NULL; - local->last_scan_completed = jiffies; + local->scanning = 0; + local->scan_channel = NULL; - if (local->hw_scanning) { - local->hw_scanning = false; - /* - * Somebody might have requested channel change during scan - * that we won't have acted upon, try now. ieee80211_hw_config - * will set the flag based on actual changes. - */ - ieee80211_hw_config(local, 0); - goto done; - } + /* we only have to protect scan_req and hw/sw scan */ + mutex_unlock(&local->scan_mtx); - local->sw_scanning = false; ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); + if (was_hw_scan) + goto done; - netif_tx_lock_bh(local->mdev); - netif_addr_lock(local->mdev); - local->filter_flags &= ~FIF_BCN_PRBRESP_PROMISC; - local->ops->configure_filter(local_to_hw(local), - FIF_BCN_PRBRESP_PROMISC, - &local->filter_flags, - local->mdev->mc_count, - local->mdev->mc_list); - - netif_addr_unlock(local->mdev); - netif_tx_unlock_bh(local->mdev); + ieee80211_configure_filter(local); - mutex_lock(&local->iflist_mtx); - list_for_each_entry(sdata, &local->interfaces, list) { - if (!netif_running(sdata->dev)) - continue; + drv_sw_scan_complete(local); - /* Tell AP we're back */ - if (sdata->vif.type == NL80211_IFTYPE_STATION) { - if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) { - ieee80211_send_nullfunc(local, sdata, 0); - netif_tx_wake_all_queues(sdata->dev); - } - } else - netif_tx_wake_all_queues(sdata->dev); - - /* re-enable beaconing */ - if (sdata->vif.type == NL80211_IFTYPE_AP || - sdata->vif.type == NL80211_IFTYPE_ADHOC || - sdata->vif.type == NL80211_IFTYPE_MESH_POINT) - ieee80211_if_config(sdata, - IEEE80211_IFCC_BEACON_ENABLED); - } - mutex_unlock(&local->iflist_mtx); + ieee80211_offchannel_return(local, true); done: + ieee80211_recalc_idle(local); ieee80211_mlme_notify_scan_completed(local); + ieee80211_ibss_notify_scan_completed(local); ieee80211_mesh_notify_scan_completed(local); + ieee80211_queue_work(&local->hw, &local->work_work); } EXPORT_SYMBOL(ieee80211_scan_completed); -void ieee80211_scan_work(struct work_struct *work) +static int ieee80211_start_sw_scan(struct ieee80211_local *local) { - struct ieee80211_local *local = - container_of(work, struct ieee80211_local, scan_work.work); - struct ieee80211_sub_if_data *sdata = local->scan_sdata; - struct ieee80211_channel *chan; - int skip, i; - unsigned long next_delay = 0; - /* - * Avoid re-scheduling when the sdata is going away. + * Hardware/driver doesn't support hw_scan, so use software + * scanning instead. First send a nullfunc frame with power save + * bit on so that AP will buffer the frames for us while we are not + * listening, then send probe requests to each channel and wait for + * the responses. After all channels are scanned, tune back to the + * original channel and send a nullfunc frame with power save bit + * off to trigger the AP to send us all the buffered frames. + * + * Note that while local->sw_scanning is true everything else but + * nullfunc frames and probe requests will be dropped in + * ieee80211_tx_h_check_assoc(). */ - if (!netif_running(sdata->dev)) - return; + drv_sw_scan_start(local); - switch (local->scan_state) { - case SCAN_SET_CHANNEL: - /* if no more bands/channels left, complete scan */ - if (local->scan_channel_idx >= local->scan_req->n_channels) { - ieee80211_scan_completed(local_to_hw(local), false); - return; - } - skip = 0; - chan = local->scan_req->channels[local->scan_channel_idx]; - - if (chan->flags & IEEE80211_CHAN_DISABLED || - (sdata->vif.type == NL80211_IFTYPE_ADHOC && - chan->flags & IEEE80211_CHAN_NO_IBSS)) - skip = 1; - - if (!skip) { - local->scan_channel = chan; - if (ieee80211_hw_config(local, - IEEE80211_CONF_CHANGE_CHANNEL)) - skip = 1; - } + ieee80211_offchannel_stop_beaconing(local); - /* advance state machine to next channel/band */ - local->scan_channel_idx++; + local->next_scan_state = SCAN_DECISION; + local->scan_channel_idx = 0; - if (skip) - break; + drv_flush(local, false); - next_delay = IEEE80211_PROBE_DELAY + - usecs_to_jiffies(local->hw.channel_change_time); - local->scan_state = SCAN_SEND_PROBE; - break; - case SCAN_SEND_PROBE: - next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; - local->scan_state = SCAN_SET_CHANNEL; + ieee80211_configure_filter(local); - if (local->scan_channel->flags & IEEE80211_CHAN_PASSIVE_SCAN || - !local->scan_req->n_ssids) - break; - for (i = 0; i < local->scan_req->n_ssids; i++) - ieee80211_send_probe_req( - sdata, NULL, - local->scan_req->ssids[i].ssid, - local->scan_req->ssids[i].ssid_len); - next_delay = IEEE80211_CHANNEL_TIME; - break; - } + ieee80211_queue_delayed_work(&local->hw, + &local->scan_work, + IEEE80211_CHANNEL_TIME); - queue_delayed_work(local->hw.workqueue, &local->scan_work, - next_delay); + return 0; } -int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata, - struct cfg80211_scan_request *req) +static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, + struct cfg80211_scan_request *req) { - struct ieee80211_local *local = scan_sdata->local; - struct ieee80211_sub_if_data *sdata; - - if (!req) - return -EINVAL; + struct ieee80211_local *local = sdata->local; + int rc; - if (local->scan_req && local->scan_req != req) + if (local->scan_req) return -EBUSY; + if (!list_empty(&local->work_list)) { + /* wait for the work to finish/time out */ + local->scan_req = req; + local->scan_sdata = sdata; + return 0; + } + + if (local->ops->hw_scan) { + u8 *ies; + + local->hw_scan_req = kmalloc( + sizeof(*local->hw_scan_req) + + req->n_channels * sizeof(req->channels[0]) + + 2 + IEEE80211_MAX_SSID_LEN + local->scan_ies_len + + req->ie_len, GFP_KERNEL); + if (!local->hw_scan_req) + return -ENOMEM; + + local->hw_scan_req->ssids = req->ssids; + local->hw_scan_req->n_ssids = req->n_ssids; + ies = (u8 *)local->hw_scan_req + + sizeof(*local->hw_scan_req) + + req->n_channels * sizeof(req->channels[0]); + local->hw_scan_req->ie = ies; + + local->hw_scan_band = 0; + + /* + * After allocating local->hw_scan_req, we must + * go through until ieee80211_prep_hw_scan(), so + * anything that might be changed here and leave + * this function early must not go after this + * allocation. + */ + } + local->scan_req = req; + local->scan_sdata = sdata; - /* MLME-SCAN.request (page 118) page 144 (11.1.3.1) - * BSSType: INFRASTRUCTURE, INDEPENDENT, ANY_BSS - * BSSID: MACAddress - * SSID - * ScanType: ACTIVE, PASSIVE - * ProbeDelay: delay (in microseconds) to be used prior to transmitting - * a Probe frame during active scanning - * ChannelList - * MinChannelTime (>= ProbeDelay), in TU - * MaxChannelTime: (>= MinChannelTime), in TU - */ + if (local->ops->hw_scan) + __set_bit(SCAN_HW_SCANNING, &local->scanning); + else + __set_bit(SCAN_SW_SCANNING, &local->scanning); - /* MLME-SCAN.confirm - * BSSDescriptionSet - * ResultCode: SUCCESS, INVALID_PARAMETERS + /* + * Kicking off the scan need not be protected, + * only the scan variable stuff, since now + * local->scan_req is assigned and other callers + * will abort their scan attempts. + * + * This avoids too many locking dependencies + * so that the scan completed calls have more + * locking freedom. */ - if (local->sw_scanning || local->hw_scanning) { - if (local->scan_sdata == scan_sdata) - return 0; - return -EBUSY; - } + ieee80211_recalc_idle(local); + mutex_unlock(&local->scan_mtx); if (local->ops->hw_scan) { - int rc; + WARN_ON(!ieee80211_prep_hw_scan(local)); + rc = drv_hw_scan(local, local->hw_scan_req); + } else + rc = ieee80211_start_sw_scan(local); - local->hw_scanning = true; - rc = local->ops->hw_scan(local_to_hw(local), req); - if (rc) { - local->hw_scanning = false; - return rc; - } - local->scan_sdata = scan_sdata; - return 0; + mutex_lock(&local->scan_mtx); + + if (rc) { + kfree(local->hw_scan_req); + local->hw_scan_req = NULL; + local->scanning = 0; + + ieee80211_recalc_idle(local); + + local->scan_req = NULL; + local->scan_sdata = NULL; } - local->sw_scanning = true; + return rc; +} + +static int ieee80211_scan_state_decision(struct ieee80211_local *local, + unsigned long *next_delay) +{ + bool associated = false; + struct ieee80211_sub_if_data *sdata; + + /* if no more bands/channels left, complete scan and advance to the idle state */ + if (local->scan_channel_idx >= local->scan_req->n_channels) { + ieee80211_scan_completed(&local->hw, false); + return 1; + } + /* check if at least one STA interface is associated */ mutex_lock(&local->iflist_mtx); list_for_each_entry(sdata, &local->interfaces, list) { - if (!netif_running(sdata->dev)) + if (!ieee80211_sdata_running(sdata)) continue; - /* disable beaconing */ - if (sdata->vif.type == NL80211_IFTYPE_AP || - sdata->vif.type == NL80211_IFTYPE_ADHOC || - sdata->vif.type == NL80211_IFTYPE_MESH_POINT) - ieee80211_if_config(sdata, - IEEE80211_IFCC_BEACON_ENABLED); - if (sdata->vif.type == NL80211_IFTYPE_STATION) { - if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) { - netif_tx_stop_all_queues(sdata->dev); - ieee80211_send_nullfunc(local, sdata, 1); + if (sdata->u.mgd.associated) { + associated = true; + break; } - } else - netif_tx_stop_all_queues(sdata->dev); + } } mutex_unlock(&local->iflist_mtx); - local->scan_state = SCAN_SET_CHANNEL; - local->scan_channel_idx = 0; - local->scan_sdata = scan_sdata; - local->scan_req = req; + if (local->scan_channel) { + /* + * we're currently scanning a different channel, let's + * switch back to the operating channel now if at least + * one interface is associated. Otherwise just scan the + * next channel + */ + if (associated) + local->next_scan_state = SCAN_ENTER_OPER_CHANNEL; + else + local->next_scan_state = SCAN_SET_CHANNEL; + } else { + /* + * we're on the operating channel currently, let's + * leave that channel now to scan another one + */ + local->next_scan_state = SCAN_LEAVE_OPER_CHANNEL; + } - netif_addr_lock_bh(local->mdev); - local->filter_flags |= FIF_BCN_PRBRESP_PROMISC; - local->ops->configure_filter(local_to_hw(local), - FIF_BCN_PRBRESP_PROMISC, - &local->filter_flags, - local->mdev->mc_count, - local->mdev->mc_list); - netif_addr_unlock_bh(local->mdev); + *next_delay = 0; + return 0; +} - /* TODO: start scan as soon as all nullfunc frames are ACKed */ - queue_delayed_work(local->hw.workqueue, &local->scan_work, - IEEE80211_CHANNEL_TIME); +static void ieee80211_scan_state_leave_oper_channel(struct ieee80211_local *local, + unsigned long *next_delay) +{ + ieee80211_offchannel_stop_station(local); - return 0; + __set_bit(SCAN_OFF_CHANNEL, &local->scanning); + + /* + * What if the nullfunc frames didn't arrive? + */ + drv_flush(local, false); + if (local->ops->flush) + *next_delay = 0; + else + *next_delay = HZ / 10; + + /* advance to the next channel to be scanned */ + local->next_scan_state = SCAN_SET_CHANNEL; } +static void ieee80211_scan_state_enter_oper_channel(struct ieee80211_local *local, + unsigned long *next_delay) +{ + /* switch back to the operating channel */ + local->scan_channel = NULL; + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); -int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, - struct cfg80211_scan_request *req) + /* + * Only re-enable station mode interface now; beaconing will be + * re-enabled once the full scan has been completed. + */ + ieee80211_offchannel_return(local, false); + + __clear_bit(SCAN_OFF_CHANNEL, &local->scanning); + + *next_delay = HZ / 5; + local->next_scan_state = SCAN_DECISION; +} + +static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, + unsigned long *next_delay) { - struct ieee80211_local *local = sdata->local; - struct ieee80211_if_sta *ifsta; + int skip; + struct ieee80211_channel *chan; - if (!req) - return -EINVAL; + skip = 0; + chan = local->scan_req->channels[local->scan_channel_idx]; - if (local->scan_req && local->scan_req != req) - return -EBUSY; + local->scan_channel = chan; + if (ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL)) + skip = 1; - local->scan_req = req; + /* advance state machine to next channel/band */ + local->scan_channel_idx++; - if (sdata->vif.type != NL80211_IFTYPE_STATION) - return ieee80211_start_scan(sdata, req); + if (skip) { + /* if we skip this channel return to the decision state */ + local->next_scan_state = SCAN_DECISION; + return; + } /* - * STA has a state machine that might need to defer scanning - * while it's trying to associate/authenticate, therefore we - * queue it up to the state machine in that case. + * Probe delay is used to update the NAV, cf. 11.1.3.2.2 + * (which unfortunately doesn't say _why_ step a) is done, + * but it waits for the probe delay or until a frame is + * received - and the received frame would update the NAV). + * For now, we do not support waiting until a frame is + * received. + * + * In any case, it is not necessary for a passive scan. */ + if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN || + !local->scan_req->n_ssids) { + *next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; + local->next_scan_state = SCAN_DECISION; + return; + } - if (local->sw_scanning || local->hw_scanning) { - if (local->scan_sdata == sdata) - return 0; - return -EBUSY; + /* active scan, send probes */ + *next_delay = IEEE80211_PROBE_DELAY; + local->next_scan_state = SCAN_SEND_PROBE; +} + +static void ieee80211_scan_state_send_probe(struct ieee80211_local *local, + unsigned long *next_delay) +{ + int i; + struct ieee80211_sub_if_data *sdata = local->scan_sdata; + + for (i = 0; i < local->scan_req->n_ssids; i++) + ieee80211_send_probe_req( + sdata, NULL, + local->scan_req->ssids[i].ssid, + local->scan_req->ssids[i].ssid_len, + local->scan_req->ie, local->scan_req->ie_len); + + /* + * After sending probe requests, wait for probe responses + * on the channel. + */ + *next_delay = IEEE80211_CHANNEL_TIME; + local->next_scan_state = SCAN_DECISION; +} + +void ieee80211_scan_work(struct work_struct *work) +{ + struct ieee80211_local *local = + container_of(work, struct ieee80211_local, scan_work.work); + struct ieee80211_sub_if_data *sdata = local->scan_sdata; + unsigned long next_delay = 0; + + mutex_lock(&local->scan_mtx); + if (!sdata || !local->scan_req) { + mutex_unlock(&local->scan_mtx); + return; } - ifsta = &sdata->u.sta; - set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request); - queue_work(local->hw.workqueue, &ifsta->work); + if (local->hw_scan_req) { + int rc = drv_hw_scan(local, local->hw_scan_req); + mutex_unlock(&local->scan_mtx); + if (rc) + ieee80211_scan_completed(&local->hw, true); + return; + } - return 0; + if (local->scan_req && !local->scanning) { + struct cfg80211_scan_request *req = local->scan_req; + int rc; + + local->scan_req = NULL; + local->scan_sdata = NULL; + + rc = __ieee80211_start_scan(sdata, req); + mutex_unlock(&local->scan_mtx); + + if (rc) + ieee80211_scan_completed(&local->hw, true); + return; + } + + mutex_unlock(&local->scan_mtx); + + /* + * Avoid re-scheduling when the sdata is going away. + */ + if (!ieee80211_sdata_running(sdata)) { + ieee80211_scan_completed(&local->hw, true); + return; + } + + /* + * as long as no delay is required advance immediately + * without scheduling a new work + */ + do { + switch (local->next_scan_state) { + case SCAN_DECISION: + if (ieee80211_scan_state_decision(local, &next_delay)) + return; + break; + case SCAN_SET_CHANNEL: + ieee80211_scan_state_set_channel(local, &next_delay); + break; + case SCAN_SEND_PROBE: + ieee80211_scan_state_send_probe(local, &next_delay); + break; + case SCAN_LEAVE_OPER_CHANNEL: + ieee80211_scan_state_leave_oper_channel(local, &next_delay); + break; + case SCAN_ENTER_OPER_CHANNEL: + ieee80211_scan_state_enter_oper_channel(local, &next_delay); + break; + } + } while (next_delay == 0); + + ieee80211_queue_delayed_work(&local->hw, &local->scan_work, next_delay); +} + +int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata, + struct cfg80211_scan_request *req) +{ + int res; + + mutex_lock(&sdata->local->scan_mtx); + res = __ieee80211_start_scan(sdata, req); + mutex_unlock(&sdata->local->scan_mtx); + + return res; +} + +int ieee80211_request_internal_scan(struct ieee80211_sub_if_data *sdata, + const u8 *ssid, u8 ssid_len) +{ + struct ieee80211_local *local = sdata->local; + int ret = -EBUSY; + + mutex_lock(&local->scan_mtx); + + /* busy scanning */ + if (local->scan_req) + goto unlock; + + memcpy(local->int_scan_req->ssids[0].ssid, ssid, IEEE80211_MAX_SSID_LEN); + local->int_scan_req->ssids[0].ssid_len = ssid_len; + + ret = __ieee80211_start_scan(sdata, sdata->local->int_scan_req); + unlock: + mutex_unlock(&local->scan_mtx); + return ret; +} + +void ieee80211_scan_cancel(struct ieee80211_local *local) +{ + bool abortscan; + + cancel_delayed_work_sync(&local->scan_work); + + /* + * Only call this function when a scan can't be + * queued -- mostly at suspend under RTNL. + */ + mutex_lock(&local->scan_mtx); + abortscan = test_bit(SCAN_SW_SCANNING, &local->scanning) || + (!local->scanning && local->scan_req); + mutex_unlock(&local->scan_mtx); + + if (abortscan) + ieee80211_scan_completed(&local->hw, true); }