mac80211: add support for HW scheduled scan
Luciano Coelho [Wed, 11 May 2011 14:09:36 +0000 (17:09 +0300)]
Implement support for HW scheduled scan.  The mac80211 code doesn't perform
scheduled scans itself, but calls the driver to start and stop scheduled
scans.

This patch also creates a trace event class to be used by drv_hw_scan
and the new drv_sched_scan_start and drv_sched_stop functions, in
order to avoid duplicate code.

Signed-off-by: Luciano Coelho <coelho@ti.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/main.c
net/mac80211/rx.c
net/mac80211/scan.c

index 9e55427..62a1c22 100644 (file)
@@ -537,6 +537,21 @@ struct ieee80211_tx_info {
        };
 };
 
+/**
+ * ieee80211_sched_scan_ies - scheduled scan IEs
+ *
+ * This structure is used to pass the appropriate IEs to be used in scheduled
+ * scans for all bands.  It contains both the IEs passed from the userspace
+ * and the ones generated by mac80211.
+ *
+ * @ie: array with the IEs for each supported band
+ * @len: array with the total length of the IEs for each band
+ */
+struct ieee80211_sched_scan_ies {
+       u8 *ie[IEEE80211_NUM_BANDS];
+       size_t len[IEEE80211_NUM_BANDS];
+};
+
 static inline struct ieee80211_tx_info *IEEE80211_SKB_CB(struct sk_buff *skb)
 {
        return (struct ieee80211_tx_info *)skb->cb;
@@ -1693,6 +1708,13 @@ enum ieee80211_ampdu_mlme_action {
  *     any error unless this callback returned a negative error code.
  *     The callback can sleep.
  *
+ * @sched_scan_start: Ask the hardware to start scanning repeatedly at
+ *     specific intervals.  The driver must call the
+ *     ieee80211_sched_scan_results() function whenever it finds results.
+ *     This process will continue until sched_scan_stop is called.
+ *
+ * @sched_scan_stop: Tell the hardware to stop an ongoing scheduled scan.
+ *
  * @sw_scan_start: Notifier function that is called just before a software scan
  *     is started. Can be NULL, if the driver doesn't need this notification.
  *     The callback can sleep.
@@ -1877,6 +1899,12 @@ struct ieee80211_ops {
                                u32 iv32, u16 *phase1key);
        int (*hw_scan)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
                       struct cfg80211_scan_request *req);
+       int (*sched_scan_start)(struct ieee80211_hw *hw,
+                               struct ieee80211_vif *vif,
+                               struct cfg80211_sched_scan_request *req,
+                               struct ieee80211_sched_scan_ies *ies);
+       void (*sched_scan_stop)(struct ieee80211_hw *hw,
+                              struct ieee80211_vif *vif);
        void (*sw_scan_start)(struct ieee80211_hw *hw);
        void (*sw_scan_complete)(struct ieee80211_hw *hw);
        int (*get_stats)(struct ieee80211_hw *hw,
@@ -2594,6 +2622,28 @@ void ieee80211_wake_queues(struct ieee80211_hw *hw);
 void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted);
 
 /**
+ * ieee80211_sched_scan_results - got results from scheduled scan
+ *
+ * When a scheduled scan is running, this function needs to be called by the
+ * driver whenever there are new scan results available.
+ *
+ * @hw: the hardware that is performing scheduled scans
+ */
+void ieee80211_sched_scan_results(struct ieee80211_hw *hw);
+
+/**
+ * ieee80211_sched_scan_stopped - inform that the scheduled scan has stopped
+ *
+ * When a scheduled scan is running, this function can be called by
+ * the driver if it needs to stop the scan to perform another task.
+ * Usual scenarios are drivers that cannot continue the scheduled scan
+ * while associating, for instance.
+ *
+ * @hw: the hardware that is performing scheduled scans
+ */
+void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw);
+
+/**
  * ieee80211_iterate_active_interfaces - iterate active interfaces
  *
  * This function iterates over the interfaces associated with a given
index c416cce..a2ff474 100644 (file)
@@ -1362,6 +1362,31 @@ static int ieee80211_scan(struct wiphy *wiphy,
        return ieee80211_request_scan(sdata, req);
 }
 
+static int
+ieee80211_sched_scan_start(struct wiphy *wiphy,
+                          struct net_device *dev,
+                          struct cfg80211_sched_scan_request *req)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       if (!sdata->local->ops->sched_scan_start)
+               return -EOPNOTSUPP;
+
+       return ieee80211_request_sched_scan_start(sdata, req);
+}
+
+static int
+ieee80211_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev,
+                         bool driver_initiated)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       if (!sdata->local->ops->sched_scan_stop)
+               return -EOPNOTSUPP;
+
+       return ieee80211_request_sched_scan_stop(sdata, driver_initiated);
+}
+
 static int ieee80211_auth(struct wiphy *wiphy, struct net_device *dev,
                          struct cfg80211_auth_request *req)
 {
@@ -2103,6 +2128,8 @@ struct cfg80211_ops mac80211_config_ops = {
        .suspend = ieee80211_suspend,
        .resume = ieee80211_resume,
        .scan = ieee80211_scan,
+       .sched_scan_start = ieee80211_sched_scan_start,
+       .sched_scan_stop = ieee80211_sched_scan_stop,
        .auth = ieee80211_auth,
        .assoc = ieee80211_assoc,
        .deauth = ieee80211_deauth,
index aa16bd8..eebf7a6 100644 (file)
@@ -212,12 +212,39 @@ static inline int drv_hw_scan(struct ieee80211_local *local,
 
        might_sleep();
 
-       trace_drv_hw_scan(local, sdata, req);
+       trace_drv_hw_scan(local, sdata);
        ret = local->ops->hw_scan(&local->hw, &sdata->vif, req);
        trace_drv_return_int(local, ret);
        return ret;
 }
 
+static inline int
+drv_sched_scan_start(struct ieee80211_local *local,
+                    struct ieee80211_sub_if_data *sdata,
+                    struct cfg80211_sched_scan_request *req,
+                    struct ieee80211_sched_scan_ies *ies)
+{
+       int ret;
+
+       might_sleep();
+
+       trace_drv_sched_scan_start(local, sdata);
+       ret = local->ops->sched_scan_start(&local->hw, &sdata->vif,
+                                             req, ies);
+       trace_drv_return_int(local, ret);
+       return ret;
+}
+
+static inline void drv_sched_scan_stop(struct ieee80211_local *local,
+                                      struct ieee80211_sub_if_data *sdata)
+{
+       might_sleep();
+
+       trace_drv_sched_scan_stop(local, sdata);
+       local->ops->sched_scan_stop(&local->hw, &sdata->vif);
+       trace_drv_return_void(local);
+}
+
 static inline void drv_sw_scan_start(struct ieee80211_local *local)
 {
        might_sleep();
index dd9779d..ed9edcb 100644 (file)
@@ -98,6 +98,27 @@ DECLARE_EVENT_CLASS(local_u32_evt,
        )
 );
 
+DECLARE_EVENT_CLASS(local_sdata_evt,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata),
+       TP_ARGS(local, sdata),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT VIF_PR_FMT,
+               LOCAL_PR_ARG, VIF_PR_ARG
+       )
+);
+
 DEFINE_EVENT(local_only_evt, drv_return_void,
        TP_PROTO(struct ieee80211_local *local),
        TP_ARGS(local)
@@ -433,27 +454,22 @@ TRACE_EVENT(drv_update_tkip_key,
        )
 );
 
-TRACE_EVENT(drv_hw_scan,
+DEFINE_EVENT(local_sdata_evt, drv_hw_scan,
        TP_PROTO(struct ieee80211_local *local,
-                struct ieee80211_sub_if_data *sdata,
-                struct cfg80211_scan_request *req),
-
-       TP_ARGS(local, sdata, req),
-
-       TP_STRUCT__entry(
-               LOCAL_ENTRY
-               VIF_ENTRY
-       ),
+                struct ieee80211_sub_if_data *sdata),
+       TP_ARGS(local, sdata)
+);
 
-       TP_fast_assign(
-               LOCAL_ASSIGN;
-               VIF_ASSIGN;
-       ),
+DEFINE_EVENT(local_sdata_evt, drv_sched_scan_start,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata),
+       TP_ARGS(local, sdata)
+);
 
-       TP_printk(
-               LOCAL_PR_FMT VIF_PR_FMT,
-               LOCAL_PR_ARG,VIF_PR_ARG
-       )
+DEFINE_EVENT(local_sdata_evt, drv_sched_scan_stop,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata),
+       TP_ARGS(local, sdata)
 );
 
 DEFINE_EVENT(local_only_evt, drv_sw_scan_start,
@@ -1180,6 +1196,42 @@ TRACE_EVENT(api_scan_completed,
        )
 );
 
+TRACE_EVENT(api_sched_scan_results,
+       TP_PROTO(struct ieee80211_local *local),
+
+       TP_ARGS(local),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT, LOCAL_PR_ARG
+       )
+);
+
+TRACE_EVENT(api_sched_scan_stopped,
+       TP_PROTO(struct ieee80211_local *local),
+
+       TP_ARGS(local),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT, LOCAL_PR_ARG
+       )
+);
+
 TRACE_EVENT(api_sta_block_awake,
        TP_PROTO(struct ieee80211_local *local,
                 struct ieee80211_sta *sta, bool block),
index 7f4d0dc..6f55a78 100644 (file)
@@ -847,6 +847,9 @@ struct ieee80211_local {
        int scan_channel_idx;
        int scan_ies_len;
 
+       bool sched_scanning;
+       struct ieee80211_sched_scan_ies sched_scan_ies;
+
        unsigned long leave_oper_channel_time;
        enum mac80211_scan_state next_scan_state;
        struct delayed_work scan_work;
@@ -1154,6 +1157,12 @@ ieee80211_rx_bss_get(struct ieee80211_local *local, u8 *bssid, int freq,
 void ieee80211_rx_bss_put(struct ieee80211_local *local,
                          struct ieee80211_bss *bss);
 
+/* scheduled scan handling */
+int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
+                                      struct cfg80211_sched_scan_request *req);
+int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata,
+                                     bool driver_initiated);
+
 /* off-channel helpers */
 bool ieee80211_cfg_on_oper_channel(struct ieee80211_local *local);
 void ieee80211_offchannel_enable_all_ps(struct ieee80211_local *local,
index ab1f464..30e6a68 100644 (file)
@@ -358,7 +358,8 @@ static void ieee80211_restart_work(struct work_struct *work)
        flush_workqueue(local->workqueue);
 
        mutex_lock(&local->mtx);
-       WARN(test_bit(SCAN_HW_SCANNING, &local->scanning),
+       WARN(test_bit(SCAN_HW_SCANNING, &local->scanning) ||
+            local->sched_scanning,
                "%s called with hardware scan in progress\n", __func__);
        mutex_unlock(&local->mtx);
 
@@ -833,6 +834,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
        if (!local->ops->remain_on_channel)
                local->hw.wiphy->max_remain_on_channel_duration = 5000;
 
+       if (local->ops->sched_scan_start)
+               local->hw.wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
+
        result = wiphy_register(local->hw.wiphy);
        if (result < 0)
                goto fail_wiphy_register;
index 634f3d9..1b9413f 100644 (file)
@@ -404,11 +404,13 @@ ieee80211_rx_h_passive_scan(struct ieee80211_rx_data *rx)
        struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
        struct sk_buff *skb = rx->skb;
 
-       if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN)))
+       if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN) &&
+                  !local->sched_scanning))
                return RX_CONTINUE;
 
        if (test_bit(SCAN_HW_SCANNING, &local->scanning) ||
-           test_bit(SCAN_SW_SCANNING, &local->scanning))
+           test_bit(SCAN_SW_SCANNING, &local->scanning) ||
+           local->sched_scanning)
                return ieee80211_scan_rx(rx->sdata, skb);
 
        /* scanning finished during invoking of handlers */
index 8acce72..ea44a8e 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/pm_qos_params.h>
+#include <linux/slab.h>
 #include <net/sch_generic.h>
 #include <linux/slab.h>
 #include <net/mac80211.h>
@@ -850,3 +851,101 @@ void ieee80211_scan_cancel(struct ieee80211_local *local)
        }
        mutex_unlock(&local->mtx);
 }
+
+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;
+
+       mutex_lock(&sdata->local->mtx);
+
+       if (local->sched_scanning) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       if (!local->ops->sched_scan_start) {
+               ret = -ENOTSUPP;
+               goto out;
+       }
+
+       for (i = 0; i < IEEE80211_NUM_BANDS; i++) {
+               local->sched_scan_ies.ie[i] = kzalloc(2 +
+                                                     IEEE80211_MAX_SSID_LEN +
+                                                     local->scan_ies_len,
+                                                     GFP_KERNEL);
+               if (!local->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);
+       }
+
+       ret = drv_sched_scan_start(local, sdata, req,
+                                  &local->sched_scan_ies);
+       if (ret == 0) {
+               local->sched_scanning = true;
+               goto out;
+       }
+
+out_free:
+       while (i > 0)
+               kfree(local->sched_scan_ies.ie[--i]);
+out:
+       mutex_unlock(&sdata->local->mtx);
+       return ret;
+}
+
+int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata,
+                                     bool driver_initiated)
+{
+       struct ieee80211_local *local = sdata->local;
+       int ret = 0, i;
+
+       mutex_lock(&sdata->local->mtx);
+
+       if (!local->ops->sched_scan_stop) {
+               ret = -ENOTSUPP;
+               goto out;
+       }
+
+       if (local->sched_scanning) {
+               for (i = 0; i < IEEE80211_NUM_BANDS; i++)
+                       kfree(local->sched_scan_ies.ie[i]);
+
+               if (!driver_initiated)
+                       drv_sched_scan_stop(local, sdata);
+               local->sched_scanning = false;
+       }
+
+out:
+       mutex_unlock(&sdata->local->mtx);
+
+       return ret;
+}
+
+void ieee80211_sched_scan_results(struct ieee80211_hw *hw)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+
+       trace_api_sched_scan_results(local);
+
+       cfg80211_sched_scan_results(hw->wiphy);
+}
+EXPORT_SYMBOL(ieee80211_sched_scan_results);
+
+void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+
+       trace_api_sched_scan_stopped(local);
+
+       cfg80211_sched_scan_stopped(hw->wiphy);
+}
+EXPORT_SYMBOL(ieee80211_sched_scan_stopped);