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 |
