]> nv-tegra.nvidia Code Review - linux-3.10.git/blobdiff - net/mac80211/scan.c
mac80211: correctly close cancelled scans
[linux-3.10.git] / net / mac80211 / scan.c
index e80a8b644aa04cdf2d4e39933a12a49f3b376072..eb03337b65450f15118313a0367abd4e420e677d 100644 (file)
 
 #define IEEE80211_PROBE_DELAY (HZ / 33)
 #define IEEE80211_CHANNEL_TIME (HZ / 33)
-#define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 8)
-
-static void ieee80211_rx_bss_free(struct cfg80211_bss *cbss)
-{
-       struct ieee80211_bss *bss = (void *)cbss->priv;
-
-       kfree(bss_mesh_id(bss));
-       kfree(bss_mesh_cfg(bss));
-}
+#define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 9)
 
 void ieee80211_rx_bss_put(struct ieee80211_local *local,
                          struct ieee80211_bss *bss)
 {
        if (!bss)
                return;
-       cfg80211_put_bss(container_of((void *)bss, struct cfg80211_bss, priv));
+       cfg80211_put_bss(local->hw.wiphy,
+                        container_of((void *)bss, struct cfg80211_bss, priv));
 }
 
 static bool is_uapsd_supported(struct ieee802_11_elems *elems)
@@ -65,12 +58,11 @@ static bool is_uapsd_supported(struct ieee802_11_elems *elems)
 struct ieee80211_bss *
 ieee80211_bss_info_update(struct ieee80211_local *local,
                          struct ieee80211_rx_status *rx_status,
-                         struct ieee80211_mgmt *mgmt,
-                         size_t len,
+                         struct ieee80211_mgmt *mgmt, size_t len,
                          struct ieee802_11_elems *elems,
-                         struct ieee80211_channel *channel,
-                         bool beacon)
+                         struct ieee80211_channel *channel)
 {
+       bool beacon = ieee80211_is_beacon(mgmt->frame_control);
        struct cfg80211_bss *cbss;
        struct ieee80211_bss *bss;
        int clen, srlen;
@@ -86,10 +78,12 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
        if (!cbss)
                return NULL;
 
-       cbss->free_priv = ieee80211_rx_bss_free;
        bss = (void *)cbss->priv;
 
-       bss->device_ts = rx_status->device_timestamp;
+       if (beacon)
+               bss->device_ts_beacon = rx_status->device_timestamp;
+       else
+               bss->device_ts_presp = rx_status->device_timestamp;
 
        if (elems->parse_error) {
                if (beacon)
@@ -104,28 +98,14 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
        }
 
        /* save the ERP value so that it is available at association time */
-       if (elems->erp_info && elems->erp_info_len >= 1 &&
-                       (!elems->parse_error ||
-                        !(bss->valid_data & IEEE80211_BSS_VALID_ERP))) {
+       if (elems->erp_info && (!elems->parse_error ||
+                               !(bss->valid_data & IEEE80211_BSS_VALID_ERP))) {
                bss->erp_value = elems->erp_info[0];
                bss->has_erp_value = true;
                if (!elems->parse_error)
                        bss->valid_data |= IEEE80211_BSS_VALID_ERP;
        }
 
-       if (elems->tim && (!elems->parse_error ||
-                          !(bss->valid_data & IEEE80211_BSS_VALID_DTIM))) {
-               struct ieee80211_tim_ie *tim_ie =
-                       (struct ieee80211_tim_ie *)elems->tim;
-               bss->dtim_period = tim_ie->dtim_period;
-               if (!elems->parse_error)
-                               bss->valid_data |= IEEE80211_BSS_VALID_DTIM;
-       }
-
-       /* If the beacon had no TIM IE, or it was invalid, use 1 */
-       if (beacon && !bss->dtim_period)
-               bss->dtim_period = 1;
-
        /* replace old supported rates if we get new values */
        if (!elems->parse_error ||
            !(bss->valid_data & IEEE80211_BSS_VALID_RATES)) {
@@ -160,9 +140,6 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
                        bss->valid_data |= IEEE80211_BSS_VALID_WMM;
        }
 
-       if (!beacon)
-               bss->last_probe_resp = jiffies;
-
        return bss;
 }
 
@@ -175,8 +152,6 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
        u8 *elements;
        struct ieee80211_channel *channel;
        size_t baselen;
-       int freq;
-       bool beacon;
        struct ieee802_11_elems elems;
 
        if (skb->len < 24 ||
@@ -198,32 +173,24 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
 
                elements = mgmt->u.probe_resp.variable;
                baselen = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
-               beacon = false;
        } else {
                baselen = offsetof(struct ieee80211_mgmt, u.beacon.variable);
                elements = mgmt->u.beacon.variable;
-               beacon = true;
        }
 
        if (baselen > skb->len)
                return;
 
-       ieee802_11_parse_elems(elements, skb->len - baselen, &elems);
+       ieee802_11_parse_elems(elements, skb->len - baselen, false, &elems);
 
-       if (elems.ds_params && elems.ds_params_len == 1)
-               freq = ieee80211_channel_to_frequency(elems.ds_params[0],
-                                                     rx_status->band);
-       else
-               freq = rx_status->freq;
-
-       channel = ieee80211_get_channel(local->hw.wiphy, freq);
+       channel = ieee80211_get_channel(local->hw.wiphy, rx_status->freq);
 
        if (!channel || channel->flags & IEEE80211_CHAN_DISABLED)
                return;
 
        bss = ieee80211_bss_info_update(local, rx_status,
                                        mgmt, skb->len, &elems,
-                                       channel, beacon);
+                                       channel);
        if (bss)
                ieee80211_rx_bss_put(local, bss);
 }
@@ -235,6 +202,9 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
        enum ieee80211_band band;
        int i, ielen, n_chans;
 
+       if (test_bit(SCAN_HW_CANCELLED, &local->scanning))
+               return false;
+
        do {
                if (local->hw_scan_band == IEEE80211_NUM_BANDS)
                        return false;
@@ -255,6 +225,7 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
        local->hw_scan_req->n_channels = n_chans;
 
        ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie,
+                                        local->hw_scan_ies_bufsize,
                                         req->ie, req->ie_len, band,
                                         req->rates[band], 0);
        local->hw_scan_req->ie_len = ielen;
@@ -300,7 +271,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted,
        if (local->scan_req != local->int_scan_req)
                cfg80211_scan_done(local->scan_req, aborted);
        local->scan_req = NULL;
-       local->scan_sdata = NULL;
+       rcu_assign_pointer(local->scan_sdata, NULL);
 
        local->scanning = 0;
        local->scan_channel = NULL;
@@ -311,7 +282,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted,
        if (!was_hw_scan) {
                ieee80211_configure_filter(local);
                drv_sw_scan_complete(local);
-               ieee80211_offchannel_return(local, true);
+               ieee80211_offchannel_return(local);
        }
 
        ieee80211_recalc_idle(local);
@@ -337,6 +308,10 @@ EXPORT_SYMBOL(ieee80211_scan_completed);
 
 static int ieee80211_start_sw_scan(struct ieee80211_local *local)
 {
+       /* Software scan is not supported in multi-channel cases */
+       if (local->use_chanctx)
+               return -EOPNOTSUPP;
+
        /*
         * Hardware/driver doesn't support hw_scan, so use software
         * scanning instead. First send a nullfunc frame with power save
@@ -356,7 +331,10 @@ static int ieee80211_start_sw_scan(struct ieee80211_local *local)
        local->next_scan_state = SCAN_DECISION;
        local->scan_channel_idx = 0;
 
-       ieee80211_offchannel_stop_vifs(local, true);
+       ieee80211_offchannel_stop_vifs(local);
+
+       /* ensure nullfunc is transmitted before leaving operating channel */
+       ieee80211_flush_queues(local, NULL);
 
        ieee80211_configure_filter(local);
 
@@ -372,6 +350,9 @@ static int ieee80211_start_sw_scan(struct ieee80211_local *local)
 static bool ieee80211_can_scan(struct ieee80211_local *local,
                               struct ieee80211_sub_if_data *sdata)
 {
+       if (local->radar_detect_enabled)
+               return false;
+
        if (!list_empty(&local->roc_list))
                return false;
 
@@ -405,10 +386,15 @@ static void ieee80211_scan_state_send_probe(struct ieee80211_local *local,
 {
        int i;
        struct ieee80211_sub_if_data *sdata;
-       enum ieee80211_band band = local->hw.conf.channel->band;
+       enum ieee80211_band band = local->hw.conf.chandef.chan->band;
+       u32 tx_flags;
+
+       tx_flags = IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
+       if (local->scan_req->no_cck)
+               tx_flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
 
        sdata = rcu_dereference_protected(local->scan_sdata,
-                                         lockdep_is_held(&local->mtx));;
+                                         lockdep_is_held(&local->mtx));
 
        for (i = 0; i < local->scan_req->n_ssids; i++)
                ieee80211_send_probe_req(
@@ -417,7 +403,7 @@ static void ieee80211_scan_state_send_probe(struct ieee80211_local *local,
                        local->scan_req->ssids[i].ssid_len,
                        local->scan_req->ie, local->scan_req->ie_len,
                        local->scan_req->rates[band], false,
-                       local->scan_req->no_cck);
+                       tx_flags, local->hw.conf.chandef.chan, true);
 
        /*
         * After sending probe requests, wait for probe responses
@@ -448,11 +434,13 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
        if (local->ops->hw_scan) {
                u8 *ies;
 
+               local->hw_scan_ies_bufsize = 2 + IEEE80211_MAX_SSID_LEN +
+                                            local->scan_ies_len +
+                                            req->ie_len;
                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);
+                               local->hw_scan_ies_bufsize, GFP_KERNEL);
                if (!local->hw_scan_req)
                        return -ENOMEM;
 
@@ -462,6 +450,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
                        sizeof(*local->hw_scan_req) +
                        req->n_channels * sizeof(req->channels[0]);
                local->hw_scan_req->ie = ies;
+               local->hw_scan_req->flags = req->flags;
 
                local->hw_scan_band = 0;
 
@@ -480,11 +469,10 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
        if (local->ops->hw_scan) {
                __set_bit(SCAN_HW_SCANNING, &local->scanning);
        } else if ((req->n_channels == 1) &&
-                  (req->channels[0]->center_freq ==
-                   local->hw.conf.channel->center_freq)) {
-
-               /* If we are scanning only on the current channel, then
-                * we do not need to stop normal activities
+                  (req->channels[0] == local->_oper_chandef.chan)) {
+               /*
+                * If we are scanning only on the operating channel
+                * then we do not need to stop normal activities
                 */
                unsigned long next_delay;
 
@@ -559,10 +547,9 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
        bool associated = false;
        bool tx_empty = true;
        bool bad_latency;
-       bool listen_int_exceeded;
-       unsigned long min_beacon_int = 0;
        struct ieee80211_sub_if_data *sdata;
        struct ieee80211_channel *next_chan;
+       enum mac80211_scan_state next_scan_state;
 
        /*
         * check if at least one STA interface is associated,
@@ -578,11 +565,6 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
                        if (sdata->u.mgd.associated) {
                                associated = true;
 
-                               if (sdata->vif.bss_conf.beacon_int <
-                                   min_beacon_int || min_beacon_int == 0)
-                                       min_beacon_int =
-                                               sdata->vif.bss_conf.beacon_int;
-
                                if (!qdisc_all_tx_empty(sdata->dev)) {
                                        tx_empty = false;
                                        break;
@@ -599,32 +581,25 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local,
         * see if we can scan another channel without interfering
         * with the current traffic situation.
         *
-        * Since we don't know if the AP has pending frames for us
-        * we can only check for our tx queues and use the current
-        * pm_qos requirements for rx. Hence, if no tx traffic occurs
-        * at all we will scan as many channels in a row as the pm_qos
-        * latency allows us to. Additionally we also check for the
-        * currently negotiated listen interval to prevent losing
-        * frames unnecessarily.
-        *
-        * Otherwise switch back to the operating channel.
+        * Keep good latency, do not stay off-channel more than 125 ms.
         */
 
        bad_latency = time_after(jiffies +
-                       ieee80211_scan_get_channel_time(next_chan),
-                       local->leave_oper_channel_time +
-                       usecs_to_jiffies(pm_qos_request(PM_QOS_NETWORK_LATENCY)));
-
-       listen_int_exceeded = time_after(jiffies +
-                       ieee80211_scan_get_channel_time(next_chan),
-                       local->leave_oper_channel_time +
-                       usecs_to_jiffies(min_beacon_int * 1024) *
-                       local->hw.conf.listen_interval);
-
-       if (associated && (!tx_empty || bad_latency || listen_int_exceeded))
-               local->next_scan_state = SCAN_SUSPEND;
-       else
-               local->next_scan_state = SCAN_SET_CHANNEL;
+                                ieee80211_scan_get_channel_time(next_chan),
+                                local->leave_oper_channel_time + HZ / 8);
+
+       if (associated && !tx_empty) {
+               if (local->scan_req->flags & NL80211_SCAN_FLAG_LOW_PRIORITY)
+                       next_scan_state = SCAN_ABORT;
+               else
+                       next_scan_state = SCAN_SUSPEND;
+       } else if (associated && bad_latency) {
+               next_scan_state = SCAN_SUSPEND;
+       } else {
+               next_scan_state = SCAN_SET_CHANNEL;
+       }
+
+       local->next_scan_state = next_scan_state;
 
        *next_delay = 0;
 }
@@ -681,12 +656,8 @@ static void ieee80211_scan_state_suspend(struct ieee80211_local *local,
        local->scan_channel = NULL;
        ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
 
-       /*
-        * Re-enable vifs and beaconing.  Leave PS
-        * in off-channel state..will put that back
-        * on-channel at the end of scanning.
-        */
-       ieee80211_offchannel_return(local, false);
+       /* disable PS */
+       ieee80211_offchannel_return(local);
 
        *next_delay = HZ / 5;
        /* afterwards, resume scan & go to next channel */
@@ -696,11 +667,10 @@ static void ieee80211_scan_state_suspend(struct ieee80211_local *local,
 static void ieee80211_scan_state_resume(struct ieee80211_local *local,
                                        unsigned long *next_delay)
 {
-       /* PS already is in off-channel mode */
-       ieee80211_offchannel_stop_vifs(local, false);
+       ieee80211_offchannel_stop_vifs(local);
 
        if (local->ops->flush) {
-               drv_flush(local, false);
+               ieee80211_flush_queues(local, NULL);
                *next_delay = 0;
        } else
                *next_delay = HZ / 10;
@@ -795,6 +765,9 @@ void ieee80211_scan_work(struct work_struct *work)
                case SCAN_RESUME:
                        ieee80211_scan_state_resume(local, &next_delay);
                        break;
+               case SCAN_ABORT:
+                       aborted = true;
+                       goto out_complete;
                }
        } while (next_delay == 0);
 
@@ -820,9 +793,9 @@ int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata,
        return res;
 }
 
-int ieee80211_request_internal_scan(struct ieee80211_sub_if_data *sdata,
-                                   const u8 *ssid, u8 ssid_len,
-                                   struct ieee80211_channel *chan)
+int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata,
+                               const u8 *ssid, u8 ssid_len,
+                               struct ieee80211_channel *chan)
 {
        struct ieee80211_local *local = sdata->local;
        int ret = -EBUSY;
@@ -836,22 +809,36 @@ int ieee80211_request_internal_scan(struct ieee80211_sub_if_data *sdata,
 
        /* fill internal scan request */
        if (!chan) {
-               int i, nchan = 0;
+               int i, max_n;
+               int n_ch = 0;
 
                for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
                        if (!local->hw.wiphy->bands[band])
                                continue;
-                       for (i = 0;
-                            i < local->hw.wiphy->bands[band]->n_channels;
-                            i++) {
-                               local->int_scan_req->channels[nchan] =
+
+                       max_n = local->hw.wiphy->bands[band]->n_channels;
+                       for (i = 0; i < max_n; i++) {
+                               struct ieee80211_channel *tmp_ch =
                                    &local->hw.wiphy->bands[band]->channels[i];
-                               nchan++;
+
+                               if (tmp_ch->flags & (IEEE80211_CHAN_NO_IBSS |
+                                                    IEEE80211_CHAN_DISABLED))
+                                       continue;
+
+                               local->int_scan_req->channels[n_ch] = tmp_ch;
+                               n_ch++;
                        }
                }
 
-               local->int_scan_req->n_channels = nchan;
+               if (WARN_ON_ONCE(n_ch == 0))
+                       goto unlock;
+
+               local->int_scan_req->n_channels = n_ch;
        } else {
+               if (WARN_ON_ONCE(chan->flags & (IEEE80211_CHAN_NO_IBSS |
+                                               IEEE80211_CHAN_DISABLED)))
+                       goto unlock;
+
                local->int_scan_req->channels[0] = chan;
                local->int_scan_req->n_channels = 1;
        }
@@ -894,7 +881,23 @@ void ieee80211_scan_cancel(struct ieee80211_local *local)
        if (!local->scan_req)
                goto out;
 
+       /*
+        * We have a scan running and the driver already reported completion,
+        * but the worker hasn't run yet or is stuck on the mutex - mark it as
+        * cancelled.
+        */
+       if (test_bit(SCAN_HW_SCANNING, &local->scanning) &&
+           test_bit(SCAN_COMPLETED, &local->scanning)) {
+               set_bit(SCAN_HW_CANCELLED, &local->scanning);
+               goto out;
+       }
+
        if (test_bit(SCAN_HW_SCANNING, &local->scanning)) {
+               /*
+                * Make sure that __ieee80211_scan_completed doesn't trigger a
+                * scan on another band.
+                */
+               set_bit(SCAN_HW_CANCELLED, &local->scanning);
                if (local->ops->cancel_hw_scan)
                        drv_cancel_hw_scan(local,
                                rcu_dereference_protected(local->scan_sdata,
@@ -918,7 +921,11 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
                                       struct cfg80211_sched_scan_request *req)
 {
        struct ieee80211_local *local = sdata->local;
-       int ret, i;
+       struct ieee80211_sched_scan_ies sched_scan_ies = {};
+       int ret, i, iebufsz;
+
+       iebufsz = 2 + IEEE80211_MAX_SSID_LEN +
+                 local->scan_ies_len + req->ie_len;
 
        mutex_lock(&local->mtx);
 
@@ -936,33 +943,25 @@ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
                if (!local->hw.wiphy->bands[i])
                        continue;
 
-               local->sched_scan_ies.ie[i] = kzalloc(2 +
-                                                     IEEE80211_MAX_SSID_LEN +
-                                                     local->scan_ies_len +
-                                                     req->ie_len,
-                                                     GFP_KERNEL);
-               if (!local->sched_scan_ies.ie[i]) {
+               sched_scan_ies.ie[i] = kzalloc(iebufsz, GFP_KERNEL);
+               if (!sched_scan_ies.ie[i]) {
                        ret = -ENOMEM;
                        goto out_free;
                }
 
-               local->sched_scan_ies.len[i] =
-                       ieee80211_build_preq_ies(local,
-                                                local->sched_scan_ies.ie[i],
-                                                req->ie, req->ie_len, i,
-                                                (u32) -1, 0);
+               sched_scan_ies.len[i] =
+                       ieee80211_build_preq_ies(local, sched_scan_ies.ie[i],
+                                                iebufsz, req->ie, req->ie_len,
+                                                i, (u32) -1, 0);
        }
 
-       ret = drv_sched_scan_start(local, sdata, req,
-                                  &local->sched_scan_ies);
-       if (ret == 0) {
+       ret = drv_sched_scan_start(local, sdata, req, &sched_scan_ies);
+       if (ret == 0)
                rcu_assign_pointer(local->sched_scan_sdata, sdata);
-               goto out;
-       }
 
 out_free:
        while (i > 0)
-               kfree(local->sched_scan_ies.ie[--i]);
+               kfree(sched_scan_ies.ie[--i]);
 out:
        mutex_unlock(&local->mtx);
        return ret;
@@ -971,7 +970,7 @@ out:
 int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata)
 {
        struct ieee80211_local *local = sdata->local;
-       int ret = 0, i;
+       int ret = 0;
 
        mutex_lock(&local->mtx);
 
@@ -980,13 +979,9 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata)
                goto out;
        }
 
-       if (rcu_access_pointer(local->sched_scan_sdata)) {
-               for (i = 0; i < IEEE80211_NUM_BANDS; i++)
-                       kfree(local->sched_scan_ies.ie[i]);
-
+       if (rcu_access_pointer(local->sched_scan_sdata))
                drv_sched_scan_stop(local, sdata);
-               rcu_assign_pointer(local->sched_scan_sdata, NULL);
-       }
+
 out:
        mutex_unlock(&local->mtx);
 
@@ -1008,7 +1003,6 @@ void ieee80211_sched_scan_stopped_work(struct work_struct *work)
        struct ieee80211_local *local =
                container_of(work, struct ieee80211_local,
                             sched_scan_stopped_work);
-       int i;
 
        mutex_lock(&local->mtx);
 
@@ -1017,9 +1011,6 @@ void ieee80211_sched_scan_stopped_work(struct work_struct *work)
                return;
        }
 
-       for (i = 0; i < IEEE80211_NUM_BANDS; i++)
-               kfree(local->sched_scan_ies.ie[i]);
-
        rcu_assign_pointer(local->sched_scan_sdata, NULL);
 
        mutex_unlock(&local->mtx);