diff options
author | Johannes Berg <johannes.berg@intel.com> | 2015-11-23 11:18:35 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2015-12-04 08:43:32 -0500 |
commit | a2fcfccbad43e413de7e7ac39879ba91548f06c1 (patch) | |
tree | 619f32e285dea1400b52e7acb0d72d22f68df64c /net/mac80211/cfg.c | |
parent | e673a65952b4ab045a3e3eb200fdf408004fb4fd (diff) |
mac80211: move off-channel/mgmt-tx code to offchannel.c
This is quite a bit of code that logically depends here since
it has to deal with all the remain-on-channel logic.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211/cfg.c')
-rw-r--r-- | net/mac80211/cfg.c | 504 |
1 files changed, 16 insertions, 488 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 6bcdbab65a8c..b8ef33e62851 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c | |||
@@ -2504,294 +2504,6 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, | |||
2504 | return 0; | 2504 | return 0; |
2505 | } | 2505 | } |
2506 | 2506 | ||
2507 | static bool ieee80211_coalesce_started_roc(struct ieee80211_local *local, | ||
2508 | struct ieee80211_roc_work *new_roc, | ||
2509 | struct ieee80211_roc_work *cur_roc) | ||
2510 | { | ||
2511 | unsigned long now = jiffies; | ||
2512 | unsigned long remaining = cur_roc->hw_start_time + | ||
2513 | msecs_to_jiffies(cur_roc->duration) - | ||
2514 | now; | ||
2515 | |||
2516 | if (WARN_ON(!cur_roc->started || !cur_roc->hw_begun)) | ||
2517 | return false; | ||
2518 | |||
2519 | /* if it doesn't fit entirely, schedule a new one */ | ||
2520 | if (new_roc->duration > jiffies_to_msecs(remaining)) | ||
2521 | return false; | ||
2522 | |||
2523 | ieee80211_handle_roc_started(new_roc); | ||
2524 | |||
2525 | /* add to dependents so we send the expired event properly */ | ||
2526 | list_add_tail(&new_roc->list, &cur_roc->dependents); | ||
2527 | return true; | ||
2528 | } | ||
2529 | |||
2530 | static u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local) | ||
2531 | { | ||
2532 | lockdep_assert_held(&local->mtx); | ||
2533 | |||
2534 | local->roc_cookie_counter++; | ||
2535 | |||
2536 | /* wow, you wrapped 64 bits ... more likely a bug */ | ||
2537 | if (WARN_ON(local->roc_cookie_counter == 0)) | ||
2538 | local->roc_cookie_counter++; | ||
2539 | |||
2540 | return local->roc_cookie_counter; | ||
2541 | } | ||
2542 | |||
2543 | static int ieee80211_start_roc_work(struct ieee80211_local *local, | ||
2544 | struct ieee80211_sub_if_data *sdata, | ||
2545 | struct ieee80211_channel *channel, | ||
2546 | unsigned int duration, u64 *cookie, | ||
2547 | struct sk_buff *txskb, | ||
2548 | enum ieee80211_roc_type type) | ||
2549 | { | ||
2550 | struct ieee80211_roc_work *roc, *tmp; | ||
2551 | bool queued = false; | ||
2552 | int ret; | ||
2553 | |||
2554 | lockdep_assert_held(&local->mtx); | ||
2555 | |||
2556 | if (local->use_chanctx && !local->ops->remain_on_channel) | ||
2557 | return -EOPNOTSUPP; | ||
2558 | |||
2559 | roc = kzalloc(sizeof(*roc), GFP_KERNEL); | ||
2560 | if (!roc) | ||
2561 | return -ENOMEM; | ||
2562 | |||
2563 | /* | ||
2564 | * If the duration is zero, then the driver | ||
2565 | * wouldn't actually do anything. Set it to | ||
2566 | * 10 for now. | ||
2567 | * | ||
2568 | * TODO: cancel the off-channel operation | ||
2569 | * when we get the SKB's TX status and | ||
2570 | * the wait time was zero before. | ||
2571 | */ | ||
2572 | if (!duration) | ||
2573 | duration = 10; | ||
2574 | |||
2575 | roc->chan = channel; | ||
2576 | roc->duration = duration; | ||
2577 | roc->req_duration = duration; | ||
2578 | roc->frame = txskb; | ||
2579 | roc->type = type; | ||
2580 | roc->sdata = sdata; | ||
2581 | INIT_DELAYED_WORK(&roc->work, ieee80211_sw_roc_work); | ||
2582 | INIT_LIST_HEAD(&roc->dependents); | ||
2583 | |||
2584 | /* | ||
2585 | * cookie is either the roc cookie (for normal roc) | ||
2586 | * or the SKB (for mgmt TX) | ||
2587 | */ | ||
2588 | if (!txskb) { | ||
2589 | roc->cookie = ieee80211_mgmt_tx_cookie(local); | ||
2590 | *cookie = roc->cookie; | ||
2591 | } else { | ||
2592 | roc->mgmt_tx_cookie = *cookie; | ||
2593 | } | ||
2594 | |||
2595 | /* if there's one pending or we're scanning, queue this one */ | ||
2596 | if (!list_empty(&local->roc_list) || | ||
2597 | local->scanning || ieee80211_is_radar_required(local)) | ||
2598 | goto out_check_combine; | ||
2599 | |||
2600 | /* if not HW assist, just queue & schedule work */ | ||
2601 | if (!local->ops->remain_on_channel) { | ||
2602 | ieee80211_queue_delayed_work(&local->hw, &roc->work, 0); | ||
2603 | goto out_queue; | ||
2604 | } | ||
2605 | |||
2606 | /* otherwise actually kick it off here (for error handling) */ | ||
2607 | |||
2608 | ret = drv_remain_on_channel(local, sdata, channel, duration, type); | ||
2609 | if (ret) { | ||
2610 | kfree(roc); | ||
2611 | return ret; | ||
2612 | } | ||
2613 | |||
2614 | roc->started = true; | ||
2615 | goto out_queue; | ||
2616 | |||
2617 | out_check_combine: | ||
2618 | list_for_each_entry(tmp, &local->roc_list, list) { | ||
2619 | if (tmp->chan != channel || tmp->sdata != sdata) | ||
2620 | continue; | ||
2621 | |||
2622 | /* | ||
2623 | * Extend this ROC if possible: | ||
2624 | * | ||
2625 | * If it hasn't started yet, just increase the duration | ||
2626 | * and add the new one to the list of dependents. | ||
2627 | * If the type of the new ROC has higher priority, modify the | ||
2628 | * type of the previous one to match that of the new one. | ||
2629 | */ | ||
2630 | if (!tmp->started) { | ||
2631 | list_add_tail(&roc->list, &tmp->dependents); | ||
2632 | tmp->duration = max(tmp->duration, roc->duration); | ||
2633 | tmp->type = max(tmp->type, roc->type); | ||
2634 | queued = true; | ||
2635 | break; | ||
2636 | } | ||
2637 | |||
2638 | /* If it has already started, it's more difficult ... */ | ||
2639 | if (local->ops->remain_on_channel) { | ||
2640 | /* | ||
2641 | * In the offloaded ROC case, if it hasn't begun, add | ||
2642 | * this new one to the dependent list to be handled | ||
2643 | * when the master one begins. If it has begun, | ||
2644 | * check if it fits entirely within the existing one, | ||
2645 | * in which case it will just be dependent as well. | ||
2646 | * Otherwise, schedule it by itself. | ||
2647 | */ | ||
2648 | if (!tmp->hw_begun) { | ||
2649 | list_add_tail(&roc->list, &tmp->dependents); | ||
2650 | queued = true; | ||
2651 | break; | ||
2652 | } | ||
2653 | |||
2654 | if (ieee80211_coalesce_started_roc(local, roc, tmp)) | ||
2655 | queued = true; | ||
2656 | } else if (del_timer_sync(&tmp->work.timer)) { | ||
2657 | unsigned long new_end; | ||
2658 | |||
2659 | /* | ||
2660 | * In the software ROC case, cancel the timer, if | ||
2661 | * that fails then the finish work is already | ||
2662 | * queued/pending and thus we queue the new ROC | ||
2663 | * normally, if that succeeds then we can extend | ||
2664 | * the timer duration and TX the frame (if any.) | ||
2665 | */ | ||
2666 | |||
2667 | list_add_tail(&roc->list, &tmp->dependents); | ||
2668 | queued = true; | ||
2669 | |||
2670 | new_end = jiffies + msecs_to_jiffies(roc->duration); | ||
2671 | |||
2672 | /* ok, it was started & we canceled timer */ | ||
2673 | if (time_after(new_end, tmp->work.timer.expires)) | ||
2674 | mod_timer(&tmp->work.timer, new_end); | ||
2675 | else | ||
2676 | add_timer(&tmp->work.timer); | ||
2677 | |||
2678 | ieee80211_handle_roc_started(roc); | ||
2679 | } | ||
2680 | break; | ||
2681 | } | ||
2682 | |||
2683 | out_queue: | ||
2684 | if (!queued) | ||
2685 | list_add_tail(&roc->list, &local->roc_list); | ||
2686 | |||
2687 | return 0; | ||
2688 | } | ||
2689 | |||
2690 | static int ieee80211_remain_on_channel(struct wiphy *wiphy, | ||
2691 | struct wireless_dev *wdev, | ||
2692 | struct ieee80211_channel *chan, | ||
2693 | unsigned int duration, | ||
2694 | u64 *cookie) | ||
2695 | { | ||
2696 | struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); | ||
2697 | struct ieee80211_local *local = sdata->local; | ||
2698 | int ret; | ||
2699 | |||
2700 | mutex_lock(&local->mtx); | ||
2701 | ret = ieee80211_start_roc_work(local, sdata, chan, | ||
2702 | duration, cookie, NULL, | ||
2703 | IEEE80211_ROC_TYPE_NORMAL); | ||
2704 | mutex_unlock(&local->mtx); | ||
2705 | |||
2706 | return ret; | ||
2707 | } | ||
2708 | |||
2709 | static int ieee80211_cancel_roc(struct ieee80211_local *local, | ||
2710 | u64 cookie, bool mgmt_tx) | ||
2711 | { | ||
2712 | struct ieee80211_roc_work *roc, *tmp, *found = NULL; | ||
2713 | int ret; | ||
2714 | |||
2715 | mutex_lock(&local->mtx); | ||
2716 | list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { | ||
2717 | struct ieee80211_roc_work *dep, *tmp2; | ||
2718 | |||
2719 | list_for_each_entry_safe(dep, tmp2, &roc->dependents, list) { | ||
2720 | if (!mgmt_tx && dep->cookie != cookie) | ||
2721 | continue; | ||
2722 | else if (mgmt_tx && dep->mgmt_tx_cookie != cookie) | ||
2723 | continue; | ||
2724 | /* found dependent item -- just remove it */ | ||
2725 | list_del(&dep->list); | ||
2726 | mutex_unlock(&local->mtx); | ||
2727 | |||
2728 | ieee80211_roc_notify_destroy(dep, true); | ||
2729 | return 0; | ||
2730 | } | ||
2731 | |||
2732 | if (!mgmt_tx && roc->cookie != cookie) | ||
2733 | continue; | ||
2734 | else if (mgmt_tx && roc->mgmt_tx_cookie != cookie) | ||
2735 | continue; | ||
2736 | |||
2737 | found = roc; | ||
2738 | break; | ||
2739 | } | ||
2740 | |||
2741 | if (!found) { | ||
2742 | mutex_unlock(&local->mtx); | ||
2743 | return -ENOENT; | ||
2744 | } | ||
2745 | |||
2746 | /* | ||
2747 | * We found the item to cancel, so do that. Note that it | ||
2748 | * may have dependents, which we also cancel (and send | ||
2749 | * the expired signal for.) Not doing so would be quite | ||
2750 | * tricky here, but we may need to fix it later. | ||
2751 | */ | ||
2752 | |||
2753 | if (local->ops->remain_on_channel) { | ||
2754 | if (found->started) { | ||
2755 | ret = drv_cancel_remain_on_channel(local); | ||
2756 | if (WARN_ON_ONCE(ret)) { | ||
2757 | mutex_unlock(&local->mtx); | ||
2758 | return ret; | ||
2759 | } | ||
2760 | } | ||
2761 | |||
2762 | list_del(&found->list); | ||
2763 | |||
2764 | if (found->started) | ||
2765 | ieee80211_start_next_roc(local); | ||
2766 | mutex_unlock(&local->mtx); | ||
2767 | |||
2768 | ieee80211_roc_notify_destroy(found, true); | ||
2769 | } else { | ||
2770 | /* work may be pending so use it all the time */ | ||
2771 | found->abort = true; | ||
2772 | ieee80211_queue_delayed_work(&local->hw, &found->work, 0); | ||
2773 | |||
2774 | mutex_unlock(&local->mtx); | ||
2775 | |||
2776 | /* work will clean up etc */ | ||
2777 | flush_delayed_work(&found->work); | ||
2778 | WARN_ON(!found->to_be_freed); | ||
2779 | kfree(found); | ||
2780 | } | ||
2781 | |||
2782 | return 0; | ||
2783 | } | ||
2784 | |||
2785 | static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, | ||
2786 | struct wireless_dev *wdev, | ||
2787 | u64 cookie) | ||
2788 | { | ||
2789 | struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); | ||
2790 | struct ieee80211_local *local = sdata->local; | ||
2791 | |||
2792 | return ieee80211_cancel_roc(local, cookie, false); | ||
2793 | } | ||
2794 | |||
2795 | static int ieee80211_start_radar_detection(struct wiphy *wiphy, | 2507 | static int ieee80211_start_radar_detection(struct wiphy *wiphy, |
2796 | struct net_device *dev, | 2508 | struct net_device *dev, |
2797 | struct cfg80211_chan_def *chandef, | 2509 | struct cfg80211_chan_def *chandef, |
@@ -3262,9 +2974,22 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, | |||
3262 | return err; | 2974 | return err; |
3263 | } | 2975 | } |
3264 | 2976 | ||
3265 | static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local, | 2977 | u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local) |
3266 | struct sk_buff *skb, u64 *cookie, | 2978 | { |
3267 | gfp_t gfp) | 2979 | lockdep_assert_held(&local->mtx); |
2980 | |||
2981 | local->roc_cookie_counter++; | ||
2982 | |||
2983 | /* wow, you wrapped 64 bits ... more likely a bug */ | ||
2984 | if (WARN_ON(local->roc_cookie_counter == 0)) | ||
2985 | local->roc_cookie_counter++; | ||
2986 | |||
2987 | return local->roc_cookie_counter; | ||
2988 | } | ||
2989 | |||
2990 | struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local, | ||
2991 | struct sk_buff *skb, u64 *cookie, | ||
2992 | gfp_t gfp) | ||
3268 | { | 2993 | { |
3269 | unsigned long spin_flags; | 2994 | unsigned long spin_flags; |
3270 | struct sk_buff *ack_skb; | 2995 | struct sk_buff *ack_skb; |
@@ -3292,203 +3017,6 @@ static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local, | |||
3292 | return ack_skb; | 3017 | return ack_skb; |
3293 | } | 3018 | } |
3294 | 3019 | ||
3295 | static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, | ||
3296 | struct cfg80211_mgmt_tx_params *params, | ||
3297 | u64 *cookie) | ||
3298 | { | ||
3299 | struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); | ||
3300 | struct ieee80211_local *local = sdata->local; | ||
3301 | struct sk_buff *skb, *ack_skb; | ||
3302 | struct sta_info *sta; | ||
3303 | const struct ieee80211_mgmt *mgmt = (void *)params->buf; | ||
3304 | bool need_offchan = false; | ||
3305 | u32 flags; | ||
3306 | int ret; | ||
3307 | u8 *data; | ||
3308 | |||
3309 | if (params->dont_wait_for_ack) | ||
3310 | flags = IEEE80211_TX_CTL_NO_ACK; | ||
3311 | else | ||
3312 | flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX | | ||
3313 | IEEE80211_TX_CTL_REQ_TX_STATUS; | ||
3314 | |||
3315 | if (params->no_cck) | ||
3316 | flags |= IEEE80211_TX_CTL_NO_CCK_RATE; | ||
3317 | |||
3318 | switch (sdata->vif.type) { | ||
3319 | case NL80211_IFTYPE_ADHOC: | ||
3320 | if (!sdata->vif.bss_conf.ibss_joined) | ||
3321 | need_offchan = true; | ||
3322 | /* fall through */ | ||
3323 | #ifdef CONFIG_MAC80211_MESH | ||
3324 | case NL80211_IFTYPE_MESH_POINT: | ||
3325 | if (ieee80211_vif_is_mesh(&sdata->vif) && | ||
3326 | !sdata->u.mesh.mesh_id_len) | ||
3327 | need_offchan = true; | ||
3328 | /* fall through */ | ||
3329 | #endif | ||
3330 | case NL80211_IFTYPE_AP: | ||
3331 | case NL80211_IFTYPE_AP_VLAN: | ||
3332 | case NL80211_IFTYPE_P2P_GO: | ||
3333 | if (sdata->vif.type != NL80211_IFTYPE_ADHOC && | ||
3334 | !ieee80211_vif_is_mesh(&sdata->vif) && | ||
3335 | !rcu_access_pointer(sdata->bss->beacon)) | ||
3336 | need_offchan = true; | ||
3337 | if (!ieee80211_is_action(mgmt->frame_control) || | ||
3338 | mgmt->u.action.category == WLAN_CATEGORY_PUBLIC || | ||
3339 | mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED || | ||
3340 | mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) | ||
3341 | break; | ||
3342 | rcu_read_lock(); | ||
3343 | sta = sta_info_get(sdata, mgmt->da); | ||
3344 | rcu_read_unlock(); | ||
3345 | if (!sta) | ||
3346 | return -ENOLINK; | ||
3347 | break; | ||
3348 | case NL80211_IFTYPE_STATION: | ||
3349 | case NL80211_IFTYPE_P2P_CLIENT: | ||
3350 | sdata_lock(sdata); | ||
3351 | if (!sdata->u.mgd.associated || | ||
3352 | (params->offchan && params->wait && | ||
3353 | local->ops->remain_on_channel && | ||
3354 | memcmp(sdata->u.mgd.associated->bssid, | ||
3355 | mgmt->bssid, ETH_ALEN))) | ||
3356 | need_offchan = true; | ||
3357 | sdata_unlock(sdata); | ||
3358 | break; | ||
3359 | case NL80211_IFTYPE_P2P_DEVICE: | ||
3360 | need_offchan = true; | ||
3361 | break; | ||
3362 | default: | ||
3363 | return -EOPNOTSUPP; | ||
3364 | } | ||
3365 | |||
3366 | /* configurations requiring offchan cannot work if no channel has been | ||
3367 | * specified | ||
3368 | */ | ||
3369 | if (need_offchan && !params->chan) | ||
3370 | return -EINVAL; | ||
3371 | |||
3372 | mutex_lock(&local->mtx); | ||
3373 | |||
3374 | /* Check if the operating channel is the requested channel */ | ||
3375 | if (!need_offchan) { | ||
3376 | struct ieee80211_chanctx_conf *chanctx_conf; | ||
3377 | |||
3378 | rcu_read_lock(); | ||
3379 | chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); | ||
3380 | |||
3381 | if (chanctx_conf) { | ||
3382 | need_offchan = params->chan && | ||
3383 | (params->chan != | ||
3384 | chanctx_conf->def.chan); | ||
3385 | } else if (!params->chan) { | ||
3386 | ret = -EINVAL; | ||
3387 | rcu_read_unlock(); | ||
3388 | goto out_unlock; | ||
3389 | } else { | ||
3390 | need_offchan = true; | ||
3391 | } | ||
3392 | rcu_read_unlock(); | ||
3393 | } | ||
3394 | |||
3395 | if (need_offchan && !params->offchan) { | ||
3396 | ret = -EBUSY; | ||
3397 | goto out_unlock; | ||
3398 | } | ||
3399 | |||
3400 | skb = dev_alloc_skb(local->hw.extra_tx_headroom + params->len); | ||
3401 | if (!skb) { | ||
3402 | ret = -ENOMEM; | ||
3403 | goto out_unlock; | ||
3404 | } | ||
3405 | skb_reserve(skb, local->hw.extra_tx_headroom); | ||
3406 | |||
3407 | data = skb_put(skb, params->len); | ||
3408 | memcpy(data, params->buf, params->len); | ||
3409 | |||
3410 | /* Update CSA counters */ | ||
3411 | if (sdata->vif.csa_active && | ||
3412 | (sdata->vif.type == NL80211_IFTYPE_AP || | ||
3413 | sdata->vif.type == NL80211_IFTYPE_MESH_POINT || | ||
3414 | sdata->vif.type == NL80211_IFTYPE_ADHOC) && | ||
3415 | params->n_csa_offsets) { | ||
3416 | int i; | ||
3417 | struct beacon_data *beacon = NULL; | ||
3418 | |||
3419 | rcu_read_lock(); | ||
3420 | |||
3421 | if (sdata->vif.type == NL80211_IFTYPE_AP) | ||
3422 | beacon = rcu_dereference(sdata->u.ap.beacon); | ||
3423 | else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) | ||
3424 | beacon = rcu_dereference(sdata->u.ibss.presp); | ||
3425 | else if (ieee80211_vif_is_mesh(&sdata->vif)) | ||
3426 | beacon = rcu_dereference(sdata->u.mesh.beacon); | ||
3427 | |||
3428 | if (beacon) | ||
3429 | for (i = 0; i < params->n_csa_offsets; i++) | ||
3430 | data[params->csa_offsets[i]] = | ||
3431 | beacon->csa_current_counter; | ||
3432 | |||
3433 | rcu_read_unlock(); | ||
3434 | } | ||
3435 | |||
3436 | IEEE80211_SKB_CB(skb)->flags = flags; | ||
3437 | |||
3438 | skb->dev = sdata->dev; | ||
3439 | |||
3440 | if (!params->dont_wait_for_ack) { | ||
3441 | /* make a copy to preserve the frame contents | ||
3442 | * in case of encryption. | ||
3443 | */ | ||
3444 | ack_skb = ieee80211_make_ack_skb(local, skb, cookie, | ||
3445 | GFP_KERNEL); | ||
3446 | if (IS_ERR(ack_skb)) { | ||
3447 | ret = PTR_ERR(ack_skb); | ||
3448 | kfree_skb(skb); | ||
3449 | goto out_unlock; | ||
3450 | } | ||
3451 | } else { | ||
3452 | /* Assign a dummy non-zero cookie, it's not sent to | ||
3453 | * userspace in this case but we rely on its value | ||
3454 | * internally in the need_offchan case to distinguish | ||
3455 | * mgmt-tx from remain-on-channel. | ||
3456 | */ | ||
3457 | *cookie = 0xffffffff; | ||
3458 | } | ||
3459 | |||
3460 | if (!need_offchan) { | ||
3461 | ieee80211_tx_skb(sdata, skb); | ||
3462 | ret = 0; | ||
3463 | goto out_unlock; | ||
3464 | } | ||
3465 | |||
3466 | IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN | | ||
3467 | IEEE80211_TX_INTFL_OFFCHAN_TX_OK; | ||
3468 | if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL)) | ||
3469 | IEEE80211_SKB_CB(skb)->hw_queue = | ||
3470 | local->hw.offchannel_tx_hw_queue; | ||
3471 | |||
3472 | /* This will handle all kinds of coalescing and immediate TX */ | ||
3473 | ret = ieee80211_start_roc_work(local, sdata, params->chan, | ||
3474 | params->wait, cookie, skb, | ||
3475 | IEEE80211_ROC_TYPE_MGMT_TX); | ||
3476 | if (ret) | ||
3477 | ieee80211_free_txskb(&local->hw, skb); | ||
3478 | out_unlock: | ||
3479 | mutex_unlock(&local->mtx); | ||
3480 | return ret; | ||
3481 | } | ||
3482 | |||
3483 | static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, | ||
3484 | struct wireless_dev *wdev, | ||
3485 | u64 cookie) | ||
3486 | { | ||
3487 | struct ieee80211_local *local = wiphy_priv(wiphy); | ||
3488 | |||
3489 | return ieee80211_cancel_roc(local, cookie, true); | ||
3490 | } | ||
3491 | |||
3492 | static void ieee80211_mgmt_frame_register(struct wiphy *wiphy, | 3020 | static void ieee80211_mgmt_frame_register(struct wiphy *wiphy, |
3493 | struct wireless_dev *wdev, | 3021 | struct wireless_dev *wdev, |
3494 | u16 frame_type, bool reg) | 3022 | u16 frame_type, bool reg) |