diff options
Diffstat (limited to 'net/mac80211/offchannel.c')
-rw-r--r-- | net/mac80211/offchannel.c | 280 |
1 files changed, 236 insertions, 44 deletions
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 935aa4b6deee..abb226dc4753 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <net/mac80211.h> | 16 | #include <net/mac80211.h> |
17 | #include "ieee80211_i.h" | 17 | #include "ieee80211_i.h" |
18 | #include "driver-trace.h" | 18 | #include "driver-trace.h" |
19 | #include "driver-ops.h" | ||
19 | 20 | ||
20 | /* | 21 | /* |
21 | * Tell our hardware to disable PS. | 22 | * Tell our hardware to disable PS. |
@@ -181,34 +182,58 @@ void ieee80211_offchannel_return(struct ieee80211_local *local, | |||
181 | mutex_unlock(&local->iflist_mtx); | 182 | mutex_unlock(&local->iflist_mtx); |
182 | } | 183 | } |
183 | 184 | ||
185 | void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc) | ||
186 | { | ||
187 | if (roc->notified) | ||
188 | return; | ||
189 | |||
190 | if (roc->mgmt_tx_cookie) { | ||
191 | if (!WARN_ON(!roc->frame)) { | ||
192 | ieee80211_tx_skb(roc->sdata, roc->frame); | ||
193 | roc->frame = NULL; | ||
194 | } | ||
195 | } else { | ||
196 | cfg80211_ready_on_channel(roc->sdata->dev, (unsigned long)roc, | ||
197 | roc->chan, roc->chan_type, | ||
198 | roc->req_duration, GFP_KERNEL); | ||
199 | } | ||
200 | |||
201 | roc->notified = true; | ||
202 | } | ||
203 | |||
184 | static void ieee80211_hw_roc_start(struct work_struct *work) | 204 | static void ieee80211_hw_roc_start(struct work_struct *work) |
185 | { | 205 | { |
186 | struct ieee80211_local *local = | 206 | struct ieee80211_local *local = |
187 | container_of(work, struct ieee80211_local, hw_roc_start); | 207 | container_of(work, struct ieee80211_local, hw_roc_start); |
188 | struct ieee80211_sub_if_data *sdata; | 208 | struct ieee80211_roc_work *roc, *dep, *tmp; |
189 | 209 | ||
190 | mutex_lock(&local->mtx); | 210 | mutex_lock(&local->mtx); |
191 | 211 | ||
192 | if (!local->hw_roc_channel) { | 212 | if (list_empty(&local->roc_list)) |
193 | mutex_unlock(&local->mtx); | 213 | goto out_unlock; |
194 | return; | ||
195 | } | ||
196 | 214 | ||
197 | if (local->hw_roc_skb) { | 215 | roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, |
198 | sdata = IEEE80211_DEV_TO_SUB_IF(local->hw_roc_dev); | 216 | list); |
199 | ieee80211_tx_skb(sdata, local->hw_roc_skb); | 217 | |
200 | local->hw_roc_skb = NULL; | 218 | if (!roc->started) |
201 | } else { | 219 | goto out_unlock; |
202 | cfg80211_ready_on_channel(local->hw_roc_dev, | ||
203 | local->hw_roc_cookie, | ||
204 | local->hw_roc_channel, | ||
205 | local->hw_roc_channel_type, | ||
206 | local->hw_roc_duration, | ||
207 | GFP_KERNEL); | ||
208 | } | ||
209 | 220 | ||
210 | ieee80211_recalc_idle(local); | 221 | roc->hw_begun = true; |
222 | roc->hw_start_time = local->hw_roc_start_time; | ||
211 | 223 | ||
224 | ieee80211_handle_roc_started(roc); | ||
225 | list_for_each_entry_safe(dep, tmp, &roc->dependents, list) { | ||
226 | ieee80211_handle_roc_started(dep); | ||
227 | |||
228 | if (dep->duration > roc->duration) { | ||
229 | u32 dur = dep->duration; | ||
230 | dep->duration = dur - roc->duration; | ||
231 | roc->duration = dur; | ||
232 | list_del(&dep->list); | ||
233 | list_add(&dep->list, &roc->list); | ||
234 | } | ||
235 | } | ||
236 | out_unlock: | ||
212 | mutex_unlock(&local->mtx); | 237 | mutex_unlock(&local->mtx); |
213 | } | 238 | } |
214 | 239 | ||
@@ -216,52 +241,179 @@ void ieee80211_ready_on_channel(struct ieee80211_hw *hw) | |||
216 | { | 241 | { |
217 | struct ieee80211_local *local = hw_to_local(hw); | 242 | struct ieee80211_local *local = hw_to_local(hw); |
218 | 243 | ||
244 | local->hw_roc_start_time = jiffies; | ||
245 | |||
219 | trace_api_ready_on_channel(local); | 246 | trace_api_ready_on_channel(local); |
220 | 247 | ||
221 | ieee80211_queue_work(hw, &local->hw_roc_start); | 248 | ieee80211_queue_work(hw, &local->hw_roc_start); |
222 | } | 249 | } |
223 | EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel); | 250 | EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel); |
224 | 251 | ||
225 | static void ieee80211_hw_roc_done(struct work_struct *work) | 252 | void ieee80211_start_next_roc(struct ieee80211_local *local) |
226 | { | 253 | { |
227 | struct ieee80211_local *local = | 254 | struct ieee80211_roc_work *roc; |
228 | container_of(work, struct ieee80211_local, hw_roc_done); | ||
229 | 255 | ||
230 | mutex_lock(&local->mtx); | 256 | lockdep_assert_held(&local->mtx); |
231 | 257 | ||
232 | if (!local->hw_roc_channel) { | 258 | if (list_empty(&local->roc_list)) { |
233 | mutex_unlock(&local->mtx); | 259 | ieee80211_run_deferred_scan(local); |
234 | return; | 260 | return; |
235 | } | 261 | } |
236 | 262 | ||
237 | /* was never transmitted */ | 263 | roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, |
238 | if (local->hw_roc_skb) { | 264 | list); |
239 | u64 cookie; | ||
240 | 265 | ||
241 | cookie = local->hw_roc_cookie ^ 2; | 266 | if (local->ops->remain_on_channel) { |
267 | int ret, duration = roc->duration; | ||
242 | 268 | ||
243 | cfg80211_mgmt_tx_status(local->hw_roc_dev, cookie, | 269 | /* XXX: duplicated, see ieee80211_start_roc_work() */ |
244 | local->hw_roc_skb->data, | 270 | if (!duration) |
245 | local->hw_roc_skb->len, false, | 271 | duration = 10; |
246 | GFP_KERNEL); | ||
247 | 272 | ||
248 | kfree_skb(local->hw_roc_skb); | 273 | ret = drv_remain_on_channel(local, roc->chan, |
249 | local->hw_roc_skb = NULL; | 274 | roc->chan_type, |
250 | local->hw_roc_skb_for_status = NULL; | 275 | duration); |
276 | |||
277 | roc->started = true; | ||
278 | |||
279 | if (ret) { | ||
280 | wiphy_warn(local->hw.wiphy, | ||
281 | "failed to start next HW ROC (%d)\n", ret); | ||
282 | /* | ||
283 | * queue the work struct again to avoid recursion | ||
284 | * when multiple failures occur | ||
285 | */ | ||
286 | ieee80211_remain_on_channel_expired(&local->hw); | ||
287 | } | ||
288 | } else { | ||
289 | /* delay it a bit */ | ||
290 | ieee80211_queue_delayed_work(&local->hw, &roc->work, | ||
291 | round_jiffies_relative(HZ/2)); | ||
292 | } | ||
293 | } | ||
294 | |||
295 | void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc) | ||
296 | { | ||
297 | struct ieee80211_roc_work *dep, *tmp; | ||
298 | |||
299 | /* was never transmitted */ | ||
300 | if (roc->frame) { | ||
301 | cfg80211_mgmt_tx_status(roc->sdata->dev, | ||
302 | (unsigned long)roc->frame, | ||
303 | roc->frame->data, roc->frame->len, | ||
304 | false, GFP_KERNEL); | ||
305 | kfree_skb(roc->frame); | ||
251 | } | 306 | } |
252 | 307 | ||
253 | if (!local->hw_roc_for_tx) | 308 | if (!roc->mgmt_tx_cookie) |
254 | cfg80211_remain_on_channel_expired(local->hw_roc_dev, | 309 | cfg80211_remain_on_channel_expired(roc->sdata->dev, |
255 | local->hw_roc_cookie, | 310 | (unsigned long)roc, |
256 | local->hw_roc_channel, | 311 | roc->chan, roc->chan_type, |
257 | local->hw_roc_channel_type, | ||
258 | GFP_KERNEL); | 312 | GFP_KERNEL); |
259 | 313 | ||
260 | local->hw_roc_channel = NULL; | 314 | list_for_each_entry_safe(dep, tmp, &roc->dependents, list) |
261 | local->hw_roc_cookie = 0; | 315 | ieee80211_roc_notify_destroy(dep); |
316 | |||
317 | kfree(roc); | ||
318 | } | ||
319 | |||
320 | void ieee80211_sw_roc_work(struct work_struct *work) | ||
321 | { | ||
322 | struct ieee80211_roc_work *roc = | ||
323 | container_of(work, struct ieee80211_roc_work, work.work); | ||
324 | struct ieee80211_sub_if_data *sdata = roc->sdata; | ||
325 | struct ieee80211_local *local = sdata->local; | ||
326 | |||
327 | mutex_lock(&local->mtx); | ||
328 | |||
329 | if (roc->abort) | ||
330 | goto finish; | ||
331 | |||
332 | if (WARN_ON(list_empty(&local->roc_list))) | ||
333 | goto out_unlock; | ||
334 | |||
335 | if (WARN_ON(roc != list_first_entry(&local->roc_list, | ||
336 | struct ieee80211_roc_work, | ||
337 | list))) | ||
338 | goto out_unlock; | ||
339 | |||
340 | if (!roc->started) { | ||
341 | struct ieee80211_roc_work *dep; | ||
342 | |||
343 | /* start this ROC */ | ||
262 | 344 | ||
263 | ieee80211_recalc_idle(local); | 345 | /* switch channel etc */ |
346 | ieee80211_recalc_idle(local); | ||
264 | 347 | ||
348 | local->tmp_channel = roc->chan; | ||
349 | local->tmp_channel_type = roc->chan_type; | ||
350 | ieee80211_hw_config(local, 0); | ||
351 | |||
352 | /* tell userspace or send frame */ | ||
353 | ieee80211_handle_roc_started(roc); | ||
354 | list_for_each_entry(dep, &roc->dependents, list) | ||
355 | ieee80211_handle_roc_started(dep); | ||
356 | |||
357 | /* if it was pure TX, just finish right away */ | ||
358 | if (!roc->duration) | ||
359 | goto finish; | ||
360 | |||
361 | roc->started = true; | ||
362 | ieee80211_queue_delayed_work(&local->hw, &roc->work, | ||
363 | msecs_to_jiffies(roc->duration)); | ||
364 | } else { | ||
365 | /* finish this ROC */ | ||
366 | finish: | ||
367 | list_del(&roc->list); | ||
368 | ieee80211_roc_notify_destroy(roc); | ||
369 | |||
370 | if (roc->started) { | ||
371 | drv_flush(local, false); | ||
372 | |||
373 | local->tmp_channel = NULL; | ||
374 | ieee80211_hw_config(local, 0); | ||
375 | |||
376 | ieee80211_offchannel_return(local, true); | ||
377 | } | ||
378 | |||
379 | ieee80211_recalc_idle(local); | ||
380 | |||
381 | ieee80211_start_next_roc(local); | ||
382 | ieee80211_run_deferred_scan(local); | ||
383 | } | ||
384 | |||
385 | out_unlock: | ||
386 | mutex_unlock(&local->mtx); | ||
387 | } | ||
388 | |||
389 | static void ieee80211_hw_roc_done(struct work_struct *work) | ||
390 | { | ||
391 | struct ieee80211_local *local = | ||
392 | container_of(work, struct ieee80211_local, hw_roc_done); | ||
393 | struct ieee80211_roc_work *roc; | ||
394 | |||
395 | mutex_lock(&local->mtx); | ||
396 | |||
397 | if (list_empty(&local->roc_list)) | ||
398 | goto out_unlock; | ||
399 | |||
400 | roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work, | ||
401 | list); | ||
402 | |||
403 | if (!roc->started) | ||
404 | goto out_unlock; | ||
405 | |||
406 | list_del(&roc->list); | ||
407 | |||
408 | ieee80211_roc_notify_destroy(roc); | ||
409 | |||
410 | /* if there's another roc, start it now */ | ||
411 | ieee80211_start_next_roc(local); | ||
412 | |||
413 | /* or scan maybe */ | ||
414 | ieee80211_run_deferred_scan(local); | ||
415 | |||
416 | out_unlock: | ||
265 | mutex_unlock(&local->mtx); | 417 | mutex_unlock(&local->mtx); |
266 | } | 418 | } |
267 | 419 | ||
@@ -275,8 +427,48 @@ void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw) | |||
275 | } | 427 | } |
276 | EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired); | 428 | EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired); |
277 | 429 | ||
278 | void ieee80211_hw_roc_setup(struct ieee80211_local *local) | 430 | void ieee80211_roc_setup(struct ieee80211_local *local) |
279 | { | 431 | { |
280 | INIT_WORK(&local->hw_roc_start, ieee80211_hw_roc_start); | 432 | INIT_WORK(&local->hw_roc_start, ieee80211_hw_roc_start); |
281 | INIT_WORK(&local->hw_roc_done, ieee80211_hw_roc_done); | 433 | INIT_WORK(&local->hw_roc_done, ieee80211_hw_roc_done); |
434 | INIT_LIST_HEAD(&local->roc_list); | ||
435 | } | ||
436 | |||
437 | void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata) | ||
438 | { | ||
439 | struct ieee80211_local *local = sdata->local; | ||
440 | struct ieee80211_roc_work *roc, *tmp; | ||
441 | LIST_HEAD(tmp_list); | ||
442 | |||
443 | mutex_lock(&local->mtx); | ||
444 | list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { | ||
445 | if (roc->sdata != sdata) | ||
446 | continue; | ||
447 | |||
448 | if (roc->started && local->ops->remain_on_channel) { | ||
449 | /* can race, so ignore return value */ | ||
450 | drv_cancel_remain_on_channel(local); | ||
451 | } | ||
452 | |||
453 | list_move_tail(&roc->list, &tmp_list); | ||
454 | roc->abort = true; | ||
455 | } | ||
456 | |||
457 | ieee80211_start_next_roc(local); | ||
458 | ieee80211_run_deferred_scan(local); | ||
459 | mutex_unlock(&local->mtx); | ||
460 | |||
461 | list_for_each_entry_safe(roc, tmp, &tmp_list, list) { | ||
462 | if (local->ops->remain_on_channel) { | ||
463 | list_del(&roc->list); | ||
464 | ieee80211_roc_notify_destroy(roc); | ||
465 | } else { | ||
466 | ieee80211_queue_delayed_work(&local->hw, &roc->work, 0); | ||
467 | |||
468 | /* work will clean up etc */ | ||
469 | flush_delayed_work(&roc->work); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | WARN_ON_ONCE(!list_empty(&tmp_list)); | ||
282 | } | 474 | } |