Blame view

net/mac80211/offchannel.c 8.04 KB
b203ffc3a   Jouni Malinen   mac80211: General...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /*
   * Off-channel operation helpers
   *
   * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi>
   * Copyright 2004, Instant802 Networks, Inc.
   * Copyright 2005, Devicescape Software, Inc.
   * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz>
   * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
   * Copyright 2009	Johannes Berg <johannes@sipsolutions.net>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   */
bc3b2d7fb   Paul Gortmaker   net: Add export.h...
15
  #include <linux/export.h>
b203ffc3a   Jouni Malinen   mac80211: General...
16
17
  #include <net/mac80211.h>
  #include "ieee80211_i.h"
21f835896   Johannes Berg   mac80211: impleme...
18
  #include "driver-trace.h"
b203ffc3a   Jouni Malinen   mac80211: General...
19
20
  
  /*
b23b025fe   Ben Greear   mac80211: Optimiz...
21
22
23
24
25
   * Tell our hardware to disable PS.
   * Optionally inform AP that we will go to sleep so that it will buffer
   * the frames while we are doing off-channel work.  This is optional
   * because we *may* be doing work on-operating channel, and want our
   * hardware unconditionally awake, but still let the AP send us normal frames.
b203ffc3a   Jouni Malinen   mac80211: General...
26
   */
b23b025fe   Ben Greear   mac80211: Optimiz...
27
28
  static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata,
  					   bool tell_ap)
b203ffc3a   Jouni Malinen   mac80211: General...
29
30
  {
  	struct ieee80211_local *local = sdata->local;
4730d5977   Luis R. Rodriguez   mac80211: reset c...
31
  	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
b203ffc3a   Jouni Malinen   mac80211: General...
32
33
34
35
36
37
  
  	local->offchannel_ps_enabled = false;
  
  	/* FIXME: what to do when local->pspolling is true? */
  
  	del_timer_sync(&local->dynamic_ps_timer);
3bc3c0d74   Luis R. Rodriguez   mac80211: disable...
38
  	del_timer_sync(&ifmgd->bcn_mon_timer);
4730d5977   Luis R. Rodriguez   mac80211: reset c...
39
  	del_timer_sync(&ifmgd->conn_mon_timer);
b203ffc3a   Jouni Malinen   mac80211: General...
40
41
42
43
44
45
46
  	cancel_work_sync(&local->dynamic_ps_enable_work);
  
  	if (local->hw.conf.flags & IEEE80211_CONF_PS) {
  		local->offchannel_ps_enabled = true;
  		local->hw.conf.flags &= ~IEEE80211_CONF_PS;
  		ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
  	}
b23b025fe   Ben Greear   mac80211: Optimiz...
47
48
  	if (tell_ap && (!local->offchannel_ps_enabled ||
  			!(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)))
b203ffc3a   Jouni Malinen   mac80211: General...
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
  		/*
  		 * If power save was enabled, no need to send a nullfunc
  		 * frame because AP knows that we are sleeping. But if the
  		 * hardware is creating the nullfunc frame for power save
  		 * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
  		 * enabled) and power save was enabled, the firmware just
  		 * sent a null frame with power save disabled. So we need
  		 * to send a new nullfunc frame to inform the AP that we
  		 * are again sleeping.
  		 */
  		ieee80211_send_nullfunc(local, sdata, 1);
  }
  
  /* inform AP that we are awake again, unless power save is enabled */
  static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata)
  {
  	struct ieee80211_local *local = sdata->local;
  
  	if (!local->ps_sdata)
  		ieee80211_send_nullfunc(local, sdata, 0);
  	else if (local->offchannel_ps_enabled) {
  		/*
  		 * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware
  		 * will send a nullfunc frame with the powersave bit set
  		 * even though the AP already knows that we are sleeping.
  		 * This could be avoided by sending a null frame with power
  		 * save bit disabled before enabling the power save, but
  		 * this doesn't gain anything.
  		 *
  		 * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need
  		 * to send a nullfunc frame because AP already knows that
  		 * we are sleeping, let's just enable power save mode in
  		 * hardware.
  		 */
b23b025fe   Ben Greear   mac80211: Optimiz...
83
84
85
  		/* TODO:  Only set hardware if CONF_PS changed?
  		 * TODO:  Should we set offchannel_ps_enabled to false?
  		 */
b203ffc3a   Jouni Malinen   mac80211: General...
86
87
88
89
90
91
92
93
94
95
96
97
98
  		local->hw.conf.flags |= IEEE80211_CONF_PS;
  		ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
  	} else if (local->hw.conf.dynamic_ps_timeout > 0) {
  		/*
  		 * If IEEE80211_CONF_PS was not set and the dynamic_ps_timer
  		 * had been running before leaving the operating channel,
  		 * restart the timer now and send a nullfunc frame to inform
  		 * the AP that we are awake.
  		 */
  		ieee80211_send_nullfunc(local, sdata, 0);
  		mod_timer(&local->dynamic_ps_timer, jiffies +
  			  msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
  	}
4730d5977   Luis R. Rodriguez   mac80211: reset c...
99

3bc3c0d74   Luis R. Rodriguez   mac80211: disable...
100
  	ieee80211_sta_reset_beacon_monitor(sdata);
4730d5977   Luis R. Rodriguez   mac80211: reset c...
101
  	ieee80211_sta_reset_conn_monitor(sdata);
b203ffc3a   Jouni Malinen   mac80211: General...
102
  }
b23b025fe   Ben Greear   mac80211: Optimiz...
103
104
  void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local,
  				    bool offchannel_ps_enable)
b203ffc3a   Jouni Malinen   mac80211: General...
105
106
  {
  	struct ieee80211_sub_if_data *sdata;
b23b025fe   Ben Greear   mac80211: Optimiz...
107
108
109
110
  	/*
  	 * notify the AP about us leaving the channel and stop all
  	 * STA interfaces.
  	 */
b203ffc3a   Jouni Malinen   mac80211: General...
111
112
113
114
  	mutex_lock(&local->iflist_mtx);
  	list_for_each_entry(sdata, &local->interfaces, list) {
  		if (!ieee80211_sdata_running(sdata))
  			continue;
b23b025fe   Ben Greear   mac80211: Optimiz...
115
116
117
118
  		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
  			set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
  
  		/* Check to see if we should disable beaconing. */
b203ffc3a   Jouni Malinen   mac80211: General...
119
120
121
122
123
  		if (sdata->vif.type == NL80211_IFTYPE_AP ||
  		    sdata->vif.type == NL80211_IFTYPE_ADHOC ||
  		    sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
  			ieee80211_bss_info_change_notify(
  				sdata, BSS_CHANGED_BEACON_ENABLED);
b23b025fe   Ben Greear   mac80211: Optimiz...
124
  		if (sdata->vif.type != NL80211_IFTYPE_MONITOR) {
cfa6cb204   John W. Linville   Merge git://git.k...
125
  			netif_tx_stop_all_queues(sdata->dev);
b23b025fe   Ben Greear   mac80211: Optimiz...
126
127
128
129
  			if (offchannel_ps_enable &&
  			    (sdata->vif.type == NL80211_IFTYPE_STATION) &&
  			    sdata->u.mgd.associated)
  				ieee80211_offchannel_ps_enable(sdata, true);
5b714c6a3   Johannes Berg   mac80211: fix off...
130
  		}
b203ffc3a   Jouni Malinen   mac80211: General...
131
132
133
  	}
  	mutex_unlock(&local->iflist_mtx);
  }
b203ffc3a   Jouni Malinen   mac80211: General...
134
  void ieee80211_offchannel_return(struct ieee80211_local *local,
b23b025fe   Ben Greear   mac80211: Optimiz...
135
  				 bool offchannel_ps_disable)
b203ffc3a   Jouni Malinen   mac80211: General...
136
137
138
139
140
  {
  	struct ieee80211_sub_if_data *sdata;
  
  	mutex_lock(&local->iflist_mtx);
  	list_for_each_entry(sdata, &local->interfaces, list) {
f6e8cb72a   Eliad Peller   mac80211: always ...
141
142
  		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
  			clear_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
b203ffc3a   Jouni Malinen   mac80211: General...
143
144
145
146
  		if (!ieee80211_sdata_running(sdata))
  			continue;
  
  		/* Tell AP we're back */
b23b025fe   Ben Greear   mac80211: Optimiz...
147
148
  		if (offchannel_ps_disable &&
  		    sdata->vif.type == NL80211_IFTYPE_STATION) {
b203ffc3a   Jouni Malinen   mac80211: General...
149
150
  			if (sdata->u.mgd.associated)
  				ieee80211_offchannel_ps_disable(sdata);
b203ffc3a   Jouni Malinen   mac80211: General...
151
  		}
5b714c6a3   Johannes Berg   mac80211: fix off...
152
  		if (sdata->vif.type != NL80211_IFTYPE_MONITOR) {
5b714c6a3   Johannes Berg   mac80211: fix off...
153
154
155
156
157
158
159
160
161
162
  			/*
  			 * This may wake up queues even though the driver
  			 * currently has them stopped. This is not very
  			 * likely, since the driver won't have gotten any
  			 * (or hardly any) new packets while we weren't
  			 * on the right channel, and even if it happens
  			 * it will at most lead to queueing up one more
  			 * packet per queue in mac80211 rather than on
  			 * the interface qdisc.
  			 */
93895757d   Benoit Papillault   mac80211: Fixed n...
163
  			netif_tx_wake_all_queues(sdata->dev);
5b714c6a3   Johannes Berg   mac80211: fix off...
164
  		}
93895757d   Benoit Papillault   mac80211: Fixed n...
165

e76aadc57   Johannes Berg   mac80211: revert ...
166
167
168
  		if (sdata->vif.type == NL80211_IFTYPE_AP ||
  		    sdata->vif.type == NL80211_IFTYPE_ADHOC ||
  		    sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
b203ffc3a   Jouni Malinen   mac80211: General...
169
170
171
172
173
  			ieee80211_bss_info_change_notify(
  				sdata, BSS_CHANGED_BEACON_ENABLED);
  	}
  	mutex_unlock(&local->iflist_mtx);
  }
21f835896   Johannes Berg   mac80211: impleme...
174
175
176
177
178
  
  static void ieee80211_hw_roc_start(struct work_struct *work)
  {
  	struct ieee80211_local *local =
  		container_of(work, struct ieee80211_local, hw_roc_start);
90fc4b3a5   Johannes Berg   mac80211: impleme...
179
  	struct ieee80211_sub_if_data *sdata;
21f835896   Johannes Berg   mac80211: impleme...
180
181
182
183
184
185
186
  
  	mutex_lock(&local->mtx);
  
  	if (!local->hw_roc_channel) {
  		mutex_unlock(&local->mtx);
  		return;
  	}
90fc4b3a5   Johannes Berg   mac80211: impleme...
187
188
189
190
191
192
193
194
195
196
197
198
  	if (local->hw_roc_skb) {
  		sdata = IEEE80211_DEV_TO_SUB_IF(local->hw_roc_dev);
  		ieee80211_tx_skb(sdata, local->hw_roc_skb);
  		local->hw_roc_skb = NULL;
  	} else {
  		cfg80211_ready_on_channel(local->hw_roc_dev,
  					  local->hw_roc_cookie,
  					  local->hw_roc_channel,
  					  local->hw_roc_channel_type,
  					  local->hw_roc_duration,
  					  GFP_KERNEL);
  	}
fcac4fb00   Felix Fietkau   mac80211: call ie...
199
  	ieee80211_recalc_idle(local);
21f835896   Johannes Berg   mac80211: impleme...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
  	mutex_unlock(&local->mtx);
  }
  
  void ieee80211_ready_on_channel(struct ieee80211_hw *hw)
  {
  	struct ieee80211_local *local = hw_to_local(hw);
  
  	trace_api_ready_on_channel(local);
  
  	ieee80211_queue_work(hw, &local->hw_roc_start);
  }
  EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel);
  
  static void ieee80211_hw_roc_done(struct work_struct *work)
  {
  	struct ieee80211_local *local =
  		container_of(work, struct ieee80211_local, hw_roc_done);
  
  	mutex_lock(&local->mtx);
  
  	if (!local->hw_roc_channel) {
  		mutex_unlock(&local->mtx);
  		return;
  	}
90fc4b3a5   Johannes Berg   mac80211: impleme...
224
225
226
227
228
229
  	if (!local->hw_roc_for_tx)
  		cfg80211_remain_on_channel_expired(local->hw_roc_dev,
  						   local->hw_roc_cookie,
  						   local->hw_roc_channel,
  						   local->hw_roc_channel_type,
  						   GFP_KERNEL);
21f835896   Johannes Berg   mac80211: impleme...
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
  
  	local->hw_roc_channel = NULL;
  	local->hw_roc_cookie = 0;
  
  	ieee80211_recalc_idle(local);
  
  	mutex_unlock(&local->mtx);
  }
  
  void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw)
  {
  	struct ieee80211_local *local = hw_to_local(hw);
  
  	trace_api_remain_on_channel_expired(local);
  
  	ieee80211_queue_work(hw, &local->hw_roc_done);
  }
  EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired);
  
  void ieee80211_hw_roc_setup(struct ieee80211_local *local)
  {
  	INIT_WORK(&local->hw_roc_start, ieee80211_hw_roc_start);
  	INIT_WORK(&local->hw_roc_done, ieee80211_hw_roc_done);
  }