diff options
author | Arik Nemtsov <arik@wizery.com> | 2014-11-09 11:50:19 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2014-11-19 12:45:21 -0500 |
commit | a7a6bdd0670feb8bfc26d41cda32b6064dbca50e (patch) | |
tree | d343591f508af2d1a15f5b265e7ddaf182e34129 | |
parent | 53837584438f8899e061ada4663ae1d09b49b96a (diff) |
mac80211: introduce TDLS channel switch ops
Implement the cfg80211 TDLS channel switch ops and introduce new mac80211
ones for low-level drivers.
Verify low-level driver support for the new ops when using the relevant
wiphy feature bit. Also verify the peer supports channel switching before
passing the command down.
Add a new STA flag to track the off-channel state with the TDLS peer and
make sure to cancel the channel-switch if the peer STA is unexpectedly
removed.
Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
Signed-off-by: Arik Nemtsov <arik@wizery.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
-rw-r--r-- | include/net/mac80211.h | 19 | ||||
-rw-r--r-- | net/mac80211/cfg.c | 2 | ||||
-rw-r--r-- | net/mac80211/debugfs_sta.c | 10 | ||||
-rw-r--r-- | net/mac80211/driver-ops.h | 41 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 6 | ||||
-rw-r--r-- | net/mac80211/main.c | 5 | ||||
-rw-r--r-- | net/mac80211/sta_info.c | 9 | ||||
-rw-r--r-- | net/mac80211/sta_info.h | 3 | ||||
-rw-r--r-- | net/mac80211/tdls.c | 234 | ||||
-rw-r--r-- | net/mac80211/trace.h | 57 |
10 files changed, 381 insertions, 5 deletions
diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 83232aa2f077..fdedceb7adcb 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h | |||
@@ -2915,6 +2915,16 @@ enum ieee80211_reconfig_type { | |||
2915 | * | 2915 | * |
2916 | * @get_txpower: get current maximum tx power (in dBm) based on configuration | 2916 | * @get_txpower: get current maximum tx power (in dBm) based on configuration |
2917 | * and hardware limits. | 2917 | * and hardware limits. |
2918 | * | ||
2919 | * @tdls_channel_switch: Start channel-switching with a TDLS peer. The driver | ||
2920 | * is responsible for continually initiating channel-switching operations | ||
2921 | * and returning to the base channel for communication with the AP. The | ||
2922 | * driver receives a channel-switch request template and the location of | ||
2923 | * the switch-timing IE within the template as part of the invocation. | ||
2924 | * The template is valid only within the call, and the driver can | ||
2925 | * optionally copy the skb for further re-use. | ||
2926 | * @tdls_cancel_channel_switch: Stop channel-switching with a TDLS peer. Both | ||
2927 | * peers must be on the base channel when the call completes. | ||
2918 | */ | 2928 | */ |
2919 | struct ieee80211_ops { | 2929 | struct ieee80211_ops { |
2920 | void (*tx)(struct ieee80211_hw *hw, | 2930 | void (*tx)(struct ieee80211_hw *hw, |
@@ -3126,6 +3136,15 @@ struct ieee80211_ops { | |||
3126 | u32 (*get_expected_throughput)(struct ieee80211_sta *sta); | 3136 | u32 (*get_expected_throughput)(struct ieee80211_sta *sta); |
3127 | int (*get_txpower)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, | 3137 | int (*get_txpower)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, |
3128 | int *dbm); | 3138 | int *dbm); |
3139 | |||
3140 | int (*tdls_channel_switch)(struct ieee80211_hw *hw, | ||
3141 | struct ieee80211_vif *vif, | ||
3142 | struct ieee80211_sta *sta, u8 oper_class, | ||
3143 | struct cfg80211_chan_def *chandef, | ||
3144 | struct sk_buff *skb, u32 ch_sw_tm_ie); | ||
3145 | void (*tdls_cancel_channel_switch)(struct ieee80211_hw *hw, | ||
3146 | struct ieee80211_vif *vif, | ||
3147 | struct ieee80211_sta *sta); | ||
3129 | }; | 3148 | }; |
3130 | 3149 | ||
3131 | /** | 3150 | /** |
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 8195e65d8a91..e75d5c53e97b 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c | |||
@@ -3752,6 +3752,8 @@ const struct cfg80211_ops mac80211_config_ops = { | |||
3752 | .set_rekey_data = ieee80211_set_rekey_data, | 3752 | .set_rekey_data = ieee80211_set_rekey_data, |
3753 | .tdls_oper = ieee80211_tdls_oper, | 3753 | .tdls_oper = ieee80211_tdls_oper, |
3754 | .tdls_mgmt = ieee80211_tdls_mgmt, | 3754 | .tdls_mgmt = ieee80211_tdls_mgmt, |
3755 | .tdls_channel_switch = ieee80211_tdls_channel_switch, | ||
3756 | .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch, | ||
3755 | .probe_client = ieee80211_probe_client, | 3757 | .probe_client = ieee80211_probe_client, |
3756 | .set_noack_map = ieee80211_set_noack_map, | 3758 | .set_noack_map = ieee80211_set_noack_map, |
3757 | #ifdef CONFIG_PM | 3759 | #ifdef CONFIG_PM |
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c index 2ba7f53746da..94c70091bbd7 100644 --- a/net/mac80211/debugfs_sta.c +++ b/net/mac80211/debugfs_sta.c | |||
@@ -74,7 +74,7 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf, | |||
74 | test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : "" | 74 | test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : "" |
75 | 75 | ||
76 | int res = scnprintf(buf, sizeof(buf), | 76 | int res = scnprintf(buf, sizeof(buf), |
77 | "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", | 77 | "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", |
78 | TEST(AUTH), TEST(ASSOC), TEST(PS_STA), | 78 | TEST(AUTH), TEST(ASSOC), TEST(PS_STA), |
79 | TEST(PS_DRIVER), TEST(AUTHORIZED), | 79 | TEST(PS_DRIVER), TEST(AUTHORIZED), |
80 | TEST(SHORT_PREAMBLE), | 80 | TEST(SHORT_PREAMBLE), |
@@ -83,10 +83,10 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf, | |||
83 | TEST(MFP), TEST(BLOCK_BA), TEST(PSPOLL), | 83 | TEST(MFP), TEST(BLOCK_BA), TEST(PSPOLL), |
84 | TEST(UAPSD), TEST(SP), TEST(TDLS_PEER), | 84 | TEST(UAPSD), TEST(SP), TEST(TDLS_PEER), |
85 | TEST(TDLS_PEER_AUTH), TEST(TDLS_INITIATOR), | 85 | TEST(TDLS_PEER_AUTH), TEST(TDLS_INITIATOR), |
86 | TEST(TDLS_CHAN_SWITCH), TEST(4ADDR_EVENT), | 86 | TEST(TDLS_CHAN_SWITCH), TEST(TDLS_OFF_CHANNEL), |
87 | TEST(INSERTED), TEST(RATE_CONTROL), | 87 | TEST(4ADDR_EVENT), TEST(INSERTED), |
88 | TEST(TOFFSET_KNOWN), TEST(MPSP_OWNER), | 88 | TEST(RATE_CONTROL), TEST(TOFFSET_KNOWN), |
89 | TEST(MPSP_RECIPIENT)); | 89 | TEST(MPSP_OWNER), TEST(MPSP_RECIPIENT)); |
90 | #undef TEST | 90 | #undef TEST |
91 | return simple_read_from_buffer(userbuf, count, ppos, buf, res); | 91 | return simple_read_from_buffer(userbuf, count, ppos, buf, res); |
92 | } | 92 | } |
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 9759dd1f0734..ec4ae42ac15f 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h | |||
@@ -1296,4 +1296,45 @@ static inline int drv_get_txpower(struct ieee80211_local *local, | |||
1296 | return ret; | 1296 | return ret; |
1297 | } | 1297 | } |
1298 | 1298 | ||
1299 | static inline int | ||
1300 | drv_tdls_channel_switch(struct ieee80211_local *local, | ||
1301 | struct ieee80211_sub_if_data *sdata, | ||
1302 | struct ieee80211_sta *sta, u8 oper_class, | ||
1303 | struct cfg80211_chan_def *chandef, | ||
1304 | struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie) | ||
1305 | { | ||
1306 | int ret; | ||
1307 | |||
1308 | might_sleep(); | ||
1309 | if (!check_sdata_in_driver(sdata)) | ||
1310 | return -EIO; | ||
1311 | |||
1312 | if (!local->ops->tdls_channel_switch) | ||
1313 | return -EOPNOTSUPP; | ||
1314 | |||
1315 | trace_drv_tdls_channel_switch(local, sdata, sta, oper_class, chandef); | ||
1316 | ret = local->ops->tdls_channel_switch(&local->hw, &sdata->vif, sta, | ||
1317 | oper_class, chandef, tmpl_skb, | ||
1318 | ch_sw_tm_ie); | ||
1319 | trace_drv_return_int(local, ret); | ||
1320 | return ret; | ||
1321 | } | ||
1322 | |||
1323 | static inline void | ||
1324 | drv_tdls_cancel_channel_switch(struct ieee80211_local *local, | ||
1325 | struct ieee80211_sub_if_data *sdata, | ||
1326 | struct ieee80211_sta *sta) | ||
1327 | { | ||
1328 | might_sleep(); | ||
1329 | if (!check_sdata_in_driver(sdata)) | ||
1330 | return; | ||
1331 | |||
1332 | if (!local->ops->tdls_cancel_channel_switch) | ||
1333 | return; | ||
1334 | |||
1335 | trace_drv_tdls_cancel_channel_switch(local, sdata, sta); | ||
1336 | local->ops->tdls_cancel_channel_switch(&local->hw, &sdata->vif, sta); | ||
1337 | trace_drv_return_void(local); | ||
1338 | } | ||
1339 | |||
1299 | #endif /* __MAC80211_DRIVER_OPS */ | 1340 | #endif /* __MAC80211_DRIVER_OPS */ |
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index e786ab6bc72c..2c7abc077b6b 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -2007,6 +2007,12 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, | |||
2007 | int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, | 2007 | int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, |
2008 | const u8 *peer, enum nl80211_tdls_operation oper); | 2008 | const u8 *peer, enum nl80211_tdls_operation oper); |
2009 | void ieee80211_tdls_peer_del_work(struct work_struct *wk); | 2009 | void ieee80211_tdls_peer_del_work(struct work_struct *wk); |
2010 | int ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, | ||
2011 | const u8 *addr, u8 oper_class, | ||
2012 | struct cfg80211_chan_def *chandef); | ||
2013 | void ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy, | ||
2014 | struct net_device *dev, | ||
2015 | const u8 *addr); | ||
2010 | 2016 | ||
2011 | extern const struct ethtool_ops ieee80211_ethtool_ops; | 2017 | extern const struct ethtool_ops ieee80211_ethtool_ops; |
2012 | 2018 | ||
diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 282a4f36eb92..774ccb2d9a76 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c | |||
@@ -764,6 +764,11 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) | |||
764 | local->hw.offchannel_tx_hw_queue >= local->hw.queues)) | 764 | local->hw.offchannel_tx_hw_queue >= local->hw.queues)) |
765 | return -EINVAL; | 765 | return -EINVAL; |
766 | 766 | ||
767 | if ((hw->wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) && | ||
768 | (!local->ops->tdls_channel_switch || | ||
769 | !local->ops->tdls_cancel_channel_switch)) | ||
770 | return -EOPNOTSUPP; | ||
771 | |||
767 | #ifdef CONFIG_PM | 772 | #ifdef CONFIG_PM |
768 | if (hw->wiphy->wowlan && (!local->ops->suspend || !local->ops->resume)) | 773 | if (hw->wiphy->wowlan && (!local->ops->suspend || !local->ops->resume)) |
769 | return -EINVAL; | 774 | return -EINVAL; |
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 97372514f287..86ca62765699 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c | |||
@@ -847,6 +847,15 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta) | |||
847 | if (WARN_ON(ret)) | 847 | if (WARN_ON(ret)) |
848 | return ret; | 848 | return ret; |
849 | 849 | ||
850 | /* | ||
851 | * for TDLS peers, make sure to return to the base channel before | ||
852 | * removal. | ||
853 | */ | ||
854 | if (test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) { | ||
855 | drv_tdls_cancel_channel_switch(local, sdata, &sta->sta); | ||
856 | clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); | ||
857 | } | ||
858 | |||
850 | list_del_rcu(&sta->list); | 859 | list_del_rcu(&sta->list); |
851 | 860 | ||
852 | drv_sta_pre_rcu_remove(local, sta->sdata, sta); | 861 | drv_sta_pre_rcu_remove(local, sta->sdata, sta); |
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index b6702c810ad3..00f56eb72c60 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h | |||
@@ -50,6 +50,8 @@ | |||
50 | * @WLAN_STA_TDLS_INITIATOR: We are the initiator of the TDLS link with this | 50 | * @WLAN_STA_TDLS_INITIATOR: We are the initiator of the TDLS link with this |
51 | * station. | 51 | * station. |
52 | * @WLAN_STA_TDLS_CHAN_SWITCH: This TDLS peer supports TDLS channel-switching | 52 | * @WLAN_STA_TDLS_CHAN_SWITCH: This TDLS peer supports TDLS channel-switching |
53 | * @WLAN_STA_TDLS_OFF_CHANNEL: The local STA is currently off-channel with this | ||
54 | * TDLS peer | ||
53 | * @WLAN_STA_UAPSD: Station requested unscheduled SP while driver was | 55 | * @WLAN_STA_UAPSD: Station requested unscheduled SP while driver was |
54 | * keeping station in power-save mode, reply when the driver | 56 | * keeping station in power-save mode, reply when the driver |
55 | * unblocks the station. | 57 | * unblocks the station. |
@@ -80,6 +82,7 @@ enum ieee80211_sta_info_flags { | |||
80 | WLAN_STA_TDLS_PEER_AUTH, | 82 | WLAN_STA_TDLS_PEER_AUTH, |
81 | WLAN_STA_TDLS_INITIATOR, | 83 | WLAN_STA_TDLS_INITIATOR, |
82 | WLAN_STA_TDLS_CHAN_SWITCH, | 84 | WLAN_STA_TDLS_CHAN_SWITCH, |
85 | WLAN_STA_TDLS_OFF_CHANNEL, | ||
83 | WLAN_STA_UAPSD, | 86 | WLAN_STA_UAPSD, |
84 | WLAN_STA_SP, | 87 | WLAN_STA_SP, |
85 | WLAN_STA_4ADDR_EVENT, | 88 | WLAN_STA_4ADDR_EVENT, |
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index fa141aecd986..358f9a4512ad 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c | |||
@@ -449,6 +449,48 @@ ieee80211_tdls_add_setup_cfm_ies(struct ieee80211_sub_if_data *sdata, | |||
449 | ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); | 449 | ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); |
450 | } | 450 | } |
451 | 451 | ||
452 | static void | ||
453 | ieee80211_tdls_add_chan_switch_req_ies(struct ieee80211_sub_if_data *sdata, | ||
454 | struct sk_buff *skb, const u8 *peer, | ||
455 | bool initiator, const u8 *extra_ies, | ||
456 | size_t extra_ies_len, u8 oper_class, | ||
457 | struct cfg80211_chan_def *chandef) | ||
458 | { | ||
459 | struct ieee80211_tdls_data *tf; | ||
460 | size_t offset = 0, noffset; | ||
461 | u8 *pos; | ||
462 | |||
463 | if (WARN_ON_ONCE(!chandef)) | ||
464 | return; | ||
465 | |||
466 | tf = (void *)skb->data; | ||
467 | tf->u.chan_switch_req.target_channel = | ||
468 | ieee80211_frequency_to_channel(chandef->chan->center_freq); | ||
469 | tf->u.chan_switch_req.oper_class = oper_class; | ||
470 | |||
471 | if (extra_ies_len) { | ||
472 | static const u8 before_lnkie[] = { | ||
473 | WLAN_EID_SECONDARY_CHANNEL_OFFSET, | ||
474 | }; | ||
475 | noffset = ieee80211_ie_split(extra_ies, extra_ies_len, | ||
476 | before_lnkie, | ||
477 | ARRAY_SIZE(before_lnkie), | ||
478 | offset); | ||
479 | pos = skb_put(skb, noffset - offset); | ||
480 | memcpy(pos, extra_ies + offset, noffset - offset); | ||
481 | offset = noffset; | ||
482 | } | ||
483 | |||
484 | ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); | ||
485 | |||
486 | /* add any remaining IEs */ | ||
487 | if (extra_ies_len) { | ||
488 | noffset = extra_ies_len; | ||
489 | pos = skb_put(skb, noffset - offset); | ||
490 | memcpy(pos, extra_ies + offset, noffset - offset); | ||
491 | } | ||
492 | } | ||
493 | |||
452 | static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, | 494 | static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, |
453 | struct sk_buff *skb, const u8 *peer, | 495 | struct sk_buff *skb, const u8 *peer, |
454 | u8 action_code, u16 status_code, | 496 | u8 action_code, u16 status_code, |
@@ -481,6 +523,12 @@ static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, | |||
481 | if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN) | 523 | if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN) |
482 | ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); | 524 | ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); |
483 | break; | 525 | break; |
526 | case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: | ||
527 | ieee80211_tdls_add_chan_switch_req_ies(sdata, skb, peer, | ||
528 | initiator, extra_ies, | ||
529 | extra_ies_len, | ||
530 | oper_class, chandef); | ||
531 | break; | ||
484 | } | 532 | } |
485 | 533 | ||
486 | } | 534 | } |
@@ -547,6 +595,12 @@ ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, | |||
547 | skb_put(skb, sizeof(tf->u.discover_req)); | 595 | skb_put(skb, sizeof(tf->u.discover_req)); |
548 | tf->u.discover_req.dialog_token = dialog_token; | 596 | tf->u.discover_req.dialog_token = dialog_token; |
549 | break; | 597 | break; |
598 | case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: | ||
599 | tf->category = WLAN_CATEGORY_TDLS; | ||
600 | tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST; | ||
601 | |||
602 | skb_put(skb, sizeof(tf->u.chan_switch_req)); | ||
603 | break; | ||
550 | default: | 604 | default: |
551 | return -EINVAL; | 605 | return -EINVAL; |
552 | } | 606 | } |
@@ -626,6 +680,7 @@ ieee80211_tdls_build_mgmt_packet_data(struct ieee80211_sub_if_data *sdata, | |||
626 | case WLAN_TDLS_SETUP_CONFIRM: | 680 | case WLAN_TDLS_SETUP_CONFIRM: |
627 | case WLAN_TDLS_TEARDOWN: | 681 | case WLAN_TDLS_TEARDOWN: |
628 | case WLAN_TDLS_DISCOVERY_REQUEST: | 682 | case WLAN_TDLS_DISCOVERY_REQUEST: |
683 | case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: | ||
629 | ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy, | 684 | ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy, |
630 | sdata->dev, peer, | 685 | sdata->dev, peer, |
631 | action_code, dialog_token, | 686 | action_code, dialog_token, |
@@ -699,6 +754,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, | |||
699 | initiator = false; | 754 | initiator = false; |
700 | break; | 755 | break; |
701 | case WLAN_TDLS_TEARDOWN: | 756 | case WLAN_TDLS_TEARDOWN: |
757 | case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: | ||
702 | /* any value is ok */ | 758 | /* any value is ok */ |
703 | break; | 759 | break; |
704 | default: | 760 | default: |
@@ -1046,3 +1102,181 @@ void ieee80211_tdls_oper_request(struct ieee80211_vif *vif, const u8 *peer, | |||
1046 | cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp); | 1102 | cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp); |
1047 | } | 1103 | } |
1048 | EXPORT_SYMBOL(ieee80211_tdls_oper_request); | 1104 | EXPORT_SYMBOL(ieee80211_tdls_oper_request); |
1105 | |||
1106 | static void | ||
1107 | iee80211_tdls_add_ch_switch_timing(u8 *buf, u16 switch_time, u16 switch_timeout) | ||
1108 | { | ||
1109 | struct ieee80211_ch_switch_timing *ch_sw; | ||
1110 | |||
1111 | *buf++ = WLAN_EID_CHAN_SWITCH_TIMING; | ||
1112 | *buf++ = sizeof(struct ieee80211_ch_switch_timing); | ||
1113 | |||
1114 | ch_sw = (void *)buf; | ||
1115 | ch_sw->switch_time = cpu_to_le16(switch_time); | ||
1116 | ch_sw->switch_timeout = cpu_to_le16(switch_timeout); | ||
1117 | } | ||
1118 | |||
1119 | /* find switch timing IE in SKB ready for Tx */ | ||
1120 | static const u8 *ieee80211_tdls_find_sw_timing_ie(struct sk_buff *skb) | ||
1121 | { | ||
1122 | struct ieee80211_tdls_data *tf; | ||
1123 | const u8 *ie_start; | ||
1124 | |||
1125 | /* | ||
1126 | * Get the offset for the new location of the switch timing IE. | ||
1127 | * The SKB network header will now point to the "payload_type" | ||
1128 | * element of the TDLS data frame struct. | ||
1129 | */ | ||
1130 | tf = container_of(skb->data + skb_network_offset(skb), | ||
1131 | struct ieee80211_tdls_data, payload_type); | ||
1132 | ie_start = tf->u.chan_switch_req.variable; | ||
1133 | return cfg80211_find_ie(WLAN_EID_CHAN_SWITCH_TIMING, ie_start, | ||
1134 | skb->len - (ie_start - skb->data)); | ||
1135 | } | ||
1136 | |||
1137 | static struct sk_buff * | ||
1138 | ieee80211_tdls_ch_sw_tmpl_get(struct sta_info *sta, u8 oper_class, | ||
1139 | struct cfg80211_chan_def *chandef, | ||
1140 | u32 *ch_sw_tm_ie_offset) | ||
1141 | { | ||
1142 | struct ieee80211_sub_if_data *sdata = sta->sdata; | ||
1143 | u8 extra_ies[2 + sizeof(struct ieee80211_sec_chan_offs_ie) + | ||
1144 | 2 + sizeof(struct ieee80211_ch_switch_timing)]; | ||
1145 | int extra_ies_len = 2 + sizeof(struct ieee80211_ch_switch_timing); | ||
1146 | u8 *pos = extra_ies; | ||
1147 | struct sk_buff *skb; | ||
1148 | |||
1149 | /* | ||
1150 | * if chandef points to a wide channel add a Secondary-Channel | ||
1151 | * Offset information element | ||
1152 | */ | ||
1153 | if (chandef->width == NL80211_CHAN_WIDTH_40) { | ||
1154 | struct ieee80211_sec_chan_offs_ie *sec_chan_ie; | ||
1155 | bool ht40plus; | ||
1156 | |||
1157 | *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; | ||
1158 | *pos++ = sizeof(*sec_chan_ie); | ||
1159 | sec_chan_ie = (void *)pos; | ||
1160 | |||
1161 | ht40plus = cfg80211_get_chandef_type(chandef) == | ||
1162 | NL80211_CHAN_HT40PLUS; | ||
1163 | sec_chan_ie->sec_chan_offs = ht40plus ? | ||
1164 | IEEE80211_HT_PARAM_CHA_SEC_ABOVE : | ||
1165 | IEEE80211_HT_PARAM_CHA_SEC_BELOW; | ||
1166 | pos += sizeof(*sec_chan_ie); | ||
1167 | |||
1168 | extra_ies_len += 2 + sizeof(struct ieee80211_sec_chan_offs_ie); | ||
1169 | } | ||
1170 | |||
1171 | /* just set the values to 0, this is a template */ | ||
1172 | iee80211_tdls_add_ch_switch_timing(pos, 0, 0); | ||
1173 | |||
1174 | skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr, | ||
1175 | WLAN_TDLS_CHANNEL_SWITCH_REQUEST, | ||
1176 | 0, 0, !sta->sta.tdls_initiator, | ||
1177 | extra_ies, extra_ies_len, | ||
1178 | oper_class, chandef); | ||
1179 | if (!skb) | ||
1180 | return NULL; | ||
1181 | |||
1182 | skb = ieee80211_build_data_template(sdata, skb, 0); | ||
1183 | if (IS_ERR(skb)) { | ||
1184 | tdls_dbg(sdata, "Failed building TDLS channel switch frame\n"); | ||
1185 | return NULL; | ||
1186 | } | ||
1187 | |||
1188 | if (ch_sw_tm_ie_offset) { | ||
1189 | const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb); | ||
1190 | |||
1191 | if (!tm_ie) { | ||
1192 | tdls_dbg(sdata, "No switch timing IE in TDLS switch\n"); | ||
1193 | dev_kfree_skb_any(skb); | ||
1194 | return NULL; | ||
1195 | } | ||
1196 | |||
1197 | *ch_sw_tm_ie_offset = tm_ie - skb->data; | ||
1198 | } | ||
1199 | |||
1200 | tdls_dbg(sdata, | ||
1201 | "TDLS channel switch request template for %pM ch %d width %d\n", | ||
1202 | sta->sta.addr, chandef->chan->center_freq, chandef->width); | ||
1203 | return skb; | ||
1204 | } | ||
1205 | |||
1206 | int | ||
1207 | ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, | ||
1208 | const u8 *addr, u8 oper_class, | ||
1209 | struct cfg80211_chan_def *chandef) | ||
1210 | { | ||
1211 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
1212 | struct ieee80211_local *local = sdata->local; | ||
1213 | struct sta_info *sta; | ||
1214 | struct sk_buff *skb = NULL; | ||
1215 | u32 ch_sw_tm_ie; | ||
1216 | int ret; | ||
1217 | |||
1218 | mutex_lock(&local->sta_mtx); | ||
1219 | sta = sta_info_get(sdata, addr); | ||
1220 | if (!sta) { | ||
1221 | tdls_dbg(sdata, | ||
1222 | "Invalid TDLS peer %pM for channel switch request\n", | ||
1223 | addr); | ||
1224 | ret = -ENOENT; | ||
1225 | goto out; | ||
1226 | } | ||
1227 | |||
1228 | if (!test_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH)) { | ||
1229 | tdls_dbg(sdata, "TDLS channel switch unsupported by %pM\n", | ||
1230 | addr); | ||
1231 | ret = -ENOTSUPP; | ||
1232 | goto out; | ||
1233 | } | ||
1234 | |||
1235 | skb = ieee80211_tdls_ch_sw_tmpl_get(sta, oper_class, chandef, | ||
1236 | &ch_sw_tm_ie); | ||
1237 | if (!skb) { | ||
1238 | ret = -ENOENT; | ||
1239 | goto out; | ||
1240 | } | ||
1241 | |||
1242 | ret = drv_tdls_channel_switch(local, sdata, &sta->sta, oper_class, | ||
1243 | chandef, skb, ch_sw_tm_ie); | ||
1244 | if (!ret) | ||
1245 | set_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); | ||
1246 | |||
1247 | out: | ||
1248 | mutex_unlock(&local->sta_mtx); | ||
1249 | dev_kfree_skb_any(skb); | ||
1250 | return ret; | ||
1251 | } | ||
1252 | |||
1253 | void | ||
1254 | ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy, | ||
1255 | struct net_device *dev, | ||
1256 | const u8 *addr) | ||
1257 | { | ||
1258 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
1259 | struct ieee80211_local *local = sdata->local; | ||
1260 | struct sta_info *sta; | ||
1261 | |||
1262 | mutex_lock(&local->sta_mtx); | ||
1263 | sta = sta_info_get(sdata, addr); | ||
1264 | if (!sta) { | ||
1265 | tdls_dbg(sdata, | ||
1266 | "Invalid TDLS peer %pM for channel switch cancel\n", | ||
1267 | addr); | ||
1268 | goto out; | ||
1269 | } | ||
1270 | |||
1271 | if (!test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) { | ||
1272 | tdls_dbg(sdata, "TDLS channel switch not initiated by %pM\n", | ||
1273 | addr); | ||
1274 | goto out; | ||
1275 | } | ||
1276 | |||
1277 | drv_tdls_cancel_channel_switch(local, sdata, &sta->sta); | ||
1278 | clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); | ||
1279 | |||
1280 | out: | ||
1281 | mutex_unlock(&local->sta_mtx); | ||
1282 | } | ||
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h index 96847e788488..c0c0fcace9d8 100644 --- a/net/mac80211/trace.h +++ b/net/mac80211/trace.h | |||
@@ -2196,6 +2196,63 @@ TRACE_EVENT(drv_get_txpower, | |||
2196 | ) | 2196 | ) |
2197 | ); | 2197 | ); |
2198 | 2198 | ||
2199 | TRACE_EVENT(drv_tdls_channel_switch, | ||
2200 | TP_PROTO(struct ieee80211_local *local, | ||
2201 | struct ieee80211_sub_if_data *sdata, | ||
2202 | struct ieee80211_sta *sta, u8 oper_class, | ||
2203 | struct cfg80211_chan_def *chandef), | ||
2204 | |||
2205 | TP_ARGS(local, sdata, sta, oper_class, chandef), | ||
2206 | |||
2207 | TP_STRUCT__entry( | ||
2208 | LOCAL_ENTRY | ||
2209 | VIF_ENTRY | ||
2210 | STA_ENTRY | ||
2211 | __field(u8, oper_class) | ||
2212 | CHANDEF_ENTRY | ||
2213 | ), | ||
2214 | |||
2215 | TP_fast_assign( | ||
2216 | LOCAL_ASSIGN; | ||
2217 | VIF_ASSIGN; | ||
2218 | STA_ASSIGN; | ||
2219 | __entry->oper_class = oper_class; | ||
2220 | CHANDEF_ASSIGN(chandef) | ||
2221 | ), | ||
2222 | |||
2223 | TP_printk( | ||
2224 | LOCAL_PR_FMT VIF_PR_FMT " tdls channel switch to" | ||
2225 | CHANDEF_PR_FMT " oper_class:%d " STA_PR_FMT, | ||
2226 | LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG, __entry->oper_class, | ||
2227 | STA_PR_ARG | ||
2228 | ) | ||
2229 | ); | ||
2230 | |||
2231 | TRACE_EVENT(drv_tdls_cancel_channel_switch, | ||
2232 | TP_PROTO(struct ieee80211_local *local, | ||
2233 | struct ieee80211_sub_if_data *sdata, | ||
2234 | struct ieee80211_sta *sta), | ||
2235 | |||
2236 | TP_ARGS(local, sdata, sta), | ||
2237 | |||
2238 | TP_STRUCT__entry( | ||
2239 | LOCAL_ENTRY | ||
2240 | VIF_ENTRY | ||
2241 | STA_ENTRY | ||
2242 | ), | ||
2243 | |||
2244 | TP_fast_assign( | ||
2245 | LOCAL_ASSIGN; | ||
2246 | VIF_ASSIGN; | ||
2247 | STA_ASSIGN; | ||
2248 | ), | ||
2249 | |||
2250 | TP_printk( | ||
2251 | LOCAL_PR_FMT VIF_PR_FMT | ||
2252 | " tdls cancel channel switch with " STA_PR_FMT, | ||
2253 | LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG | ||
2254 | ) | ||
2255 | ); | ||
2199 | 2256 | ||
2200 | #ifdef CONFIG_MAC80211_MESSAGE_TRACING | 2257 | #ifdef CONFIG_MAC80211_MESSAGE_TRACING |
2201 | #undef TRACE_SYSTEM | 2258 | #undef TRACE_SYSTEM |