]> git.karo-electronics.de Git - linux-beck.git/commitdiff
mac80211: add offload channel switch support
authorJohannes Berg <johannes@sipsolutions.net>
Tue, 11 May 2010 14:20:57 +0000 (16:20 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Wed, 12 May 2010 20:39:05 +0000 (16:39 -0400)
This adds support for offloading the channel switch
operation to devices that support such, typically
by having specific firmware API for it. The reasons
for this could be that the firmware provides better
timing or that regulatory enforcement done by the
device requires special handling of CSAs.

In order to allow drivers to specify the timing to
the device, the new channel_switch callback will
pass through the received frame's mactime, where
available.

Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
include/net/mac80211.h
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/mlme.c

index 9448a5b1bb1567083bcc2f81cf71e9a4a9c5c4d0..389e86a54fc458e6fec269e7bc16c831cfdda4b3 100644 (file)
@@ -711,6 +711,28 @@ struct ieee80211_conf {
        enum ieee80211_smps_mode smps_mode;
 };
 
+/**
+ * struct ieee80211_channel_switch - holds the channel switch data
+ *
+ * The information provided in this structure is required for channel switch
+ * operation.
+ *
+ * @timestamp: value in microseconds of the 64-bit Time Synchronization
+ *     Function (TSF) timer when the frame containing the channel switch
+ *     announcement was received. This is simply the rx.mactime parameter
+ *     the driver passed into mac80211.
+ * @block_tx: Indicates whether transmission must be blocked before the
+ *     scheduled channel switch, as indicated by the AP.
+ * @channel: the new channel to switch to
+ * @count: the number of TBTT's until the channel switch event
+ */
+struct ieee80211_channel_switch {
+       u64 timestamp;
+       bool block_tx;
+       struct ieee80211_channel *channel;
+       u8 count;
+};
+
 /**
  * struct ieee80211_vif - per-interface data
  *
@@ -1631,6 +1653,11 @@ enum ieee80211_ampdu_mlme_action {
  * @flush: Flush all pending frames from the hardware queue, making sure
  *     that the hardware queues are empty. If the parameter @drop is set
  *     to %true, pending frames may be dropped. The callback can sleep.
+ *
+ * @channel_switch: Drivers that need (or want) to offload the channel
+ *     switch operation for CSAs received from the AP may implement this
+ *     callback. They must then call ieee80211_chswitch_done() to indicate
+ *     completion of the channel switch.
  */
 struct ieee80211_ops {
        int (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
@@ -1694,6 +1721,8 @@ struct ieee80211_ops {
        int (*testmode_cmd)(struct ieee80211_hw *hw, void *data, int len);
 #endif
        void (*flush)(struct ieee80211_hw *hw, bool drop);
+       void (*channel_switch)(struct ieee80211_hw *hw,
+                              struct ieee80211_channel_switch *ch_switch);
 };
 
 /**
@@ -2444,6 +2473,16 @@ void ieee80211_cqm_rssi_notify(struct ieee80211_vif *vif,
                               enum nl80211_cqm_rssi_threshold_event rssi_event,
                               gfp_t gfp);
 
+/**
+ * ieee80211_chswitch_done - Complete channel switch process
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ * @success: make the channel switch successful or not
+ *
+ * Complete the channel switch post-process: set the new operational channel
+ * and wake up the suspended queues.
+ */
+void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success);
+
 /* Rate control API */
 
 /**
index 997008e236fff4e162b51907529cf758d00ab984..5662bb5190c3199f8def1194d69d19af1bae81e8 100644 (file)
@@ -373,4 +373,15 @@ static inline void drv_flush(struct ieee80211_local *local, bool drop)
        if (local->ops->flush)
                local->ops->flush(&local->hw, drop);
 }
+
+static inline void drv_channel_switch(struct ieee80211_local *local,
+                                    struct ieee80211_channel_switch *ch_switch)
+{
+       might_sleep();
+
+       local->ops->channel_switch(&local->hw, ch_switch);
+
+       trace_drv_channel_switch(local, ch_switch);
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index ce734b58d07ab6b0ac14cbfa88b6f6bb915c6b98..6a9b2342a9c2d3982c83d235149762271a4f9c83 100644 (file)
@@ -774,6 +774,34 @@ TRACE_EVENT(drv_flush,
        )
 );
 
+TRACE_EVENT(drv_channel_switch,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_channel_switch *ch_switch),
+
+       TP_ARGS(local, ch_switch),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               __field(u64, timestamp)
+               __field(bool, block_tx)
+               __field(u16, freq)
+               __field(u8, count)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               __entry->timestamp = ch_switch->timestamp;
+               __entry->block_tx = ch_switch->block_tx;
+               __entry->freq = ch_switch->channel->center_freq;
+               __entry->count = ch_switch->count;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT " new freq:%u count:%d",
+               LOCAL_PR_ARG, __entry->freq, __entry->count
+       )
+);
+
 /*
  * Tracing for API calls that drivers call.
  */
@@ -992,6 +1020,27 @@ TRACE_EVENT(api_sta_block_awake,
        )
 );
 
+TRACE_EVENT(api_chswitch_done,
+       TP_PROTO(struct ieee80211_sub_if_data *sdata, bool success),
+
+       TP_ARGS(sdata, success),
+
+       TP_STRUCT__entry(
+               VIF_ENTRY
+               __field(bool, success)
+       ),
+
+       TP_fast_assign(
+               VIF_ASSIGN;
+               __entry->success = success;
+       ),
+
+       TP_printk(
+               VIF_PR_FMT " success=%d",
+               VIF_PR_ARG, __entry->success
+       )
+);
+
 /*
  * Tracing for internal functions
  * (which may also be called in response to driver calls)
index 69e7f4131f465493d73b4be833d779dd312c433e..1c8e247066854fc090799508a703298e0020b33b 100644 (file)
@@ -999,7 +999,8 @@ int ieee80211_max_network_latency(struct notifier_block *nb,
                                  unsigned long data, void *dummy);
 void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                                      struct ieee80211_channel_sw_ie *sw_elem,
-                                     struct ieee80211_bss *bss);
+                                     struct ieee80211_bss *bss,
+                                     u64 timestamp);
 void ieee80211_sta_quiesce(struct ieee80211_sub_if_data *sdata);
 void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata);
 
index 7bfb0ebaaf00c9a3fe619d76a5f3ac8e0f4b1b89..6b74489fb9c65198dd63d727798b7d40e4f38a8e 100644 (file)
@@ -341,7 +341,11 @@ static void ieee80211_chswitch_work(struct work_struct *work)
                goto out;
 
        sdata->local->oper_channel = sdata->local->csa_channel;
-       ieee80211_hw_config(sdata->local, IEEE80211_CONF_CHANGE_CHANNEL);
+       if (!sdata->local->ops->channel_switch) {
+               /* call "hw_config" only if doing sw channel switch */
+               ieee80211_hw_config(sdata->local,
+                       IEEE80211_CONF_CHANGE_CHANNEL);
+       }
 
        /* XXX: shouldn't really modify cfg80211-owned data! */
        ifmgd->associated->channel = sdata->local->oper_channel;
@@ -353,6 +357,29 @@ static void ieee80211_chswitch_work(struct work_struct *work)
        mutex_unlock(&ifmgd->mtx);
 }
 
+void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success)
+{
+       struct ieee80211_sub_if_data *sdata;
+       struct ieee80211_if_managed *ifmgd;
+
+       sdata = vif_to_sdata(vif);
+       ifmgd = &sdata->u.mgd;
+
+       trace_api_chswitch_done(sdata, success);
+       if (!success) {
+               /*
+                * If the channel switch was not successful, stay
+                * around on the old channel. We currently lack
+                * good handling of this situation, possibly we
+                * should just drop the association.
+                */
+               sdata->local->csa_channel = sdata->local->oper_channel;
+       }
+
+       ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
+}
+EXPORT_SYMBOL(ieee80211_chswitch_done);
+
 static void ieee80211_chswitch_timer(unsigned long data)
 {
        struct ieee80211_sub_if_data *sdata =
@@ -369,7 +396,8 @@ static void ieee80211_chswitch_timer(unsigned long data)
 
 void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                                      struct ieee80211_channel_sw_ie *sw_elem,
-                                     struct ieee80211_bss *bss)
+                                     struct ieee80211_bss *bss,
+                                     u64 timestamp)
 {
        struct cfg80211_bss *cbss =
                container_of((void *)bss, struct cfg80211_bss, priv);
@@ -397,6 +425,24 @@ void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
        sdata->local->csa_channel = new_ch;
 
+       if (sdata->local->ops->channel_switch) {
+               /* use driver's channel switch callback */
+               struct ieee80211_channel_switch ch_switch;
+               memset(&ch_switch, 0, sizeof(ch_switch));
+               ch_switch.timestamp = timestamp;
+               if (sw_elem->mode) {
+                       ch_switch.block_tx = true;
+                       ieee80211_stop_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+               }
+               ch_switch.channel = new_ch;
+               ch_switch.count = sw_elem->count;
+               ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
+               drv_channel_switch(sdata->local, &ch_switch);
+               return;
+       }
+
+       /* channel switch handled in software */
        if (sw_elem->count <= 1) {
                ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
        } else {
@@ -1316,7 +1362,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
                                                        ETH_ALEN) == 0)) {
                struct ieee80211_channel_sw_ie *sw_elem =
                        (struct ieee80211_channel_sw_ie *)elems->ch_switch_elem;
-               ieee80211_sta_process_chanswitch(sdata, sw_elem, bss);
+               ieee80211_sta_process_chanswitch(sdata, sw_elem,
+                                                bss, rx_status->mactime);
        }
 }
 
@@ -1648,7 +1695,8 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
 
                        ieee80211_sta_process_chanswitch(sdata,
                                        &mgmt->u.action.u.chan_switch.sw_elem,
-                                       (void *)ifmgd->associated->priv);
+                                       (void *)ifmgd->associated->priv,
+                                       rx_status->mactime);
                        break;
                }
                mutex_unlock(&ifmgd->mtx);