Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Arik Nemtsov and committed by
Johannes Berg
a7a6bdd0 53837584

+381 -5
+19
include/net/mac80211.h
··· 2915 2915 * 2916 2916 * @get_txpower: get current maximum tx power (in dBm) based on configuration 2917 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 2929 struct ieee80211_ops { 2920 2930 void (*tx)(struct ieee80211_hw *hw, ··· 3136 3126 u32 (*get_expected_throughput)(struct ieee80211_sta *sta); 3137 3127 int (*get_txpower)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 3138 3128 int *dbm); 3129 + 3130 + int (*tdls_channel_switch)(struct ieee80211_hw *hw, 3131 + struct ieee80211_vif *vif, 3132 + struct ieee80211_sta *sta, u8 oper_class, 3133 + struct cfg80211_chan_def *chandef, 3134 + struct sk_buff *skb, u32 ch_sw_tm_ie); 3135 + void (*tdls_cancel_channel_switch)(struct ieee80211_hw *hw, 3136 + struct ieee80211_vif *vif, 3137 + struct ieee80211_sta *sta); 3139 3138 }; 3140 3139 3141 3140 /**
+2
net/mac80211/cfg.c
··· 3752 3752 .set_rekey_data = ieee80211_set_rekey_data, 3753 3753 .tdls_oper = ieee80211_tdls_oper, 3754 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 3757 .probe_client = ieee80211_probe_client, 3756 3758 .set_noack_map = ieee80211_set_noack_map, 3757 3759 #ifdef CONFIG_PM
+5 -5
net/mac80211/debugfs_sta.c
··· 74 74 test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : "" 75 75 76 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 78 TEST(AUTH), TEST(ASSOC), TEST(PS_STA), 79 79 TEST(PS_DRIVER), TEST(AUTHORIZED), 80 80 TEST(SHORT_PREAMBLE), ··· 83 83 TEST(MFP), TEST(BLOCK_BA), TEST(PSPOLL), 84 84 TEST(UAPSD), TEST(SP), TEST(TDLS_PEER), 85 85 TEST(TDLS_PEER_AUTH), TEST(TDLS_INITIATOR), 86 - TEST(TDLS_CHAN_SWITCH), TEST(4ADDR_EVENT), 87 - TEST(INSERTED), TEST(RATE_CONTROL), 88 - TEST(TOFFSET_KNOWN), TEST(MPSP_OWNER), 89 - TEST(MPSP_RECIPIENT)); 86 + TEST(TDLS_CHAN_SWITCH), TEST(TDLS_OFF_CHANNEL), 87 + TEST(4ADDR_EVENT), TEST(INSERTED), 88 + TEST(RATE_CONTROL), TEST(TOFFSET_KNOWN), 89 + TEST(MPSP_OWNER), TEST(MPSP_RECIPIENT)); 90 90 #undef TEST 91 91 return simple_read_from_buffer(userbuf, count, ppos, buf, res); 92 92 }
+41
net/mac80211/driver-ops.h
··· 1296 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 1340 #endif /* __MAC80211_DRIVER_OPS */
+6
net/mac80211/ieee80211_i.h
··· 2007 2007 int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, 2008 2008 const u8 *peer, enum nl80211_tdls_operation oper); 2009 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 2017 extern const struct ethtool_ops ieee80211_ethtool_ops; 2012 2018
+5
net/mac80211/main.c
··· 764 764 local->hw.offchannel_tx_hw_queue >= local->hw.queues)) 765 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 772 #ifdef CONFIG_PM 768 773 if (hw->wiphy->wowlan && (!local->ops->suspend || !local->ops->resume)) 769 774 return -EINVAL;
+9
net/mac80211/sta_info.c
··· 847 847 if (WARN_ON(ret)) 848 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 859 list_del_rcu(&sta->list); 851 860 852 861 drv_sta_pre_rcu_remove(local, sta->sdata, sta);
+3
net/mac80211/sta_info.h
··· 50 50 * @WLAN_STA_TDLS_INITIATOR: We are the initiator of the TDLS link with this 51 51 * station. 52 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 55 * @WLAN_STA_UAPSD: Station requested unscheduled SP while driver was 54 56 * keeping station in power-save mode, reply when the driver 55 57 * unblocks the station. ··· 82 80 WLAN_STA_TDLS_PEER_AUTH, 83 81 WLAN_STA_TDLS_INITIATOR, 84 82 WLAN_STA_TDLS_CHAN_SWITCH, 83 + WLAN_STA_TDLS_OFF_CHANNEL, 85 84 WLAN_STA_UAPSD, 86 85 WLAN_STA_SP, 87 86 WLAN_STA_4ADDR_EVENT,
+234
net/mac80211/tdls.c
··· 449 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 494 static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, 453 495 struct sk_buff *skb, const u8 *peer, 454 496 u8 action_code, u16 status_code, ··· 522 480 extra_ies_len); 523 481 if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN) 524 482 ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); 483 + break; 484 + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: 485 + ieee80211_tdls_add_chan_switch_req_ies(sdata, skb, peer, 486 + initiator, extra_ies, 487 + extra_ies_len, 488 + oper_class, chandef); 525 489 break; 526 490 } 527 491 ··· 594 546 595 547 skb_put(skb, sizeof(tf->u.discover_req)); 596 548 tf->u.discover_req.dialog_token = dialog_token; 549 + break; 550 + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: 551 + tf->category = WLAN_CATEGORY_TDLS; 552 + tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST; 553 + 554 + skb_put(skb, sizeof(tf->u.chan_switch_req)); 597 555 break; 598 556 default: 599 557 return -EINVAL; ··· 680 626 case WLAN_TDLS_SETUP_CONFIRM: 681 627 case WLAN_TDLS_TEARDOWN: 682 628 case WLAN_TDLS_DISCOVERY_REQUEST: 629 + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: 683 630 ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy, 684 631 sdata->dev, peer, 685 632 action_code, dialog_token, ··· 754 699 initiator = false; 755 700 break; 756 701 case WLAN_TDLS_TEARDOWN: 702 + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: 757 703 /* any value is ok */ 758 704 break; 759 705 default: ··· 1102 1046 cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp); 1103 1047 } 1104 1048 EXPORT_SYMBOL(ieee80211_tdls_oper_request); 1049 + 1050 + static void 1051 + iee80211_tdls_add_ch_switch_timing(u8 *buf, u16 switch_time, u16 switch_timeout) 1052 + { 1053 + struct ieee80211_ch_switch_timing *ch_sw; 1054 + 1055 + *buf++ = WLAN_EID_CHAN_SWITCH_TIMING; 1056 + *buf++ = sizeof(struct ieee80211_ch_switch_timing); 1057 + 1058 + ch_sw = (void *)buf; 1059 + ch_sw->switch_time = cpu_to_le16(switch_time); 1060 + ch_sw->switch_timeout = cpu_to_le16(switch_timeout); 1061 + } 1062 + 1063 + /* find switch timing IE in SKB ready for Tx */ 1064 + static const u8 *ieee80211_tdls_find_sw_timing_ie(struct sk_buff *skb) 1065 + { 1066 + struct ieee80211_tdls_data *tf; 1067 + const u8 *ie_start; 1068 + 1069 + /* 1070 + * Get the offset for the new location of the switch timing IE. 1071 + * The SKB network header will now point to the "payload_type" 1072 + * element of the TDLS data frame struct. 1073 + */ 1074 + tf = container_of(skb->data + skb_network_offset(skb), 1075 + struct ieee80211_tdls_data, payload_type); 1076 + ie_start = tf->u.chan_switch_req.variable; 1077 + return cfg80211_find_ie(WLAN_EID_CHAN_SWITCH_TIMING, ie_start, 1078 + skb->len - (ie_start - skb->data)); 1079 + } 1080 + 1081 + static struct sk_buff * 1082 + ieee80211_tdls_ch_sw_tmpl_get(struct sta_info *sta, u8 oper_class, 1083 + struct cfg80211_chan_def *chandef, 1084 + u32 *ch_sw_tm_ie_offset) 1085 + { 1086 + struct ieee80211_sub_if_data *sdata = sta->sdata; 1087 + u8 extra_ies[2 + sizeof(struct ieee80211_sec_chan_offs_ie) + 1088 + 2 + sizeof(struct ieee80211_ch_switch_timing)]; 1089 + int extra_ies_len = 2 + sizeof(struct ieee80211_ch_switch_timing); 1090 + u8 *pos = extra_ies; 1091 + struct sk_buff *skb; 1092 + 1093 + /* 1094 + * if chandef points to a wide channel add a Secondary-Channel 1095 + * Offset information element 1096 + */ 1097 + if (chandef->width == NL80211_CHAN_WIDTH_40) { 1098 + struct ieee80211_sec_chan_offs_ie *sec_chan_ie; 1099 + bool ht40plus; 1100 + 1101 + *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; 1102 + *pos++ = sizeof(*sec_chan_ie); 1103 + sec_chan_ie = (void *)pos; 1104 + 1105 + ht40plus = cfg80211_get_chandef_type(chandef) == 1106 + NL80211_CHAN_HT40PLUS; 1107 + sec_chan_ie->sec_chan_offs = ht40plus ? 1108 + IEEE80211_HT_PARAM_CHA_SEC_ABOVE : 1109 + IEEE80211_HT_PARAM_CHA_SEC_BELOW; 1110 + pos += sizeof(*sec_chan_ie); 1111 + 1112 + extra_ies_len += 2 + sizeof(struct ieee80211_sec_chan_offs_ie); 1113 + } 1114 + 1115 + /* just set the values to 0, this is a template */ 1116 + iee80211_tdls_add_ch_switch_timing(pos, 0, 0); 1117 + 1118 + skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr, 1119 + WLAN_TDLS_CHANNEL_SWITCH_REQUEST, 1120 + 0, 0, !sta->sta.tdls_initiator, 1121 + extra_ies, extra_ies_len, 1122 + oper_class, chandef); 1123 + if (!skb) 1124 + return NULL; 1125 + 1126 + skb = ieee80211_build_data_template(sdata, skb, 0); 1127 + if (IS_ERR(skb)) { 1128 + tdls_dbg(sdata, "Failed building TDLS channel switch frame\n"); 1129 + return NULL; 1130 + } 1131 + 1132 + if (ch_sw_tm_ie_offset) { 1133 + const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb); 1134 + 1135 + if (!tm_ie) { 1136 + tdls_dbg(sdata, "No switch timing IE in TDLS switch\n"); 1137 + dev_kfree_skb_any(skb); 1138 + return NULL; 1139 + } 1140 + 1141 + *ch_sw_tm_ie_offset = tm_ie - skb->data; 1142 + } 1143 + 1144 + tdls_dbg(sdata, 1145 + "TDLS channel switch request template for %pM ch %d width %d\n", 1146 + sta->sta.addr, chandef->chan->center_freq, chandef->width); 1147 + return skb; 1148 + } 1149 + 1150 + int 1151 + ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, 1152 + const u8 *addr, u8 oper_class, 1153 + struct cfg80211_chan_def *chandef) 1154 + { 1155 + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); 1156 + struct ieee80211_local *local = sdata->local; 1157 + struct sta_info *sta; 1158 + struct sk_buff *skb = NULL; 1159 + u32 ch_sw_tm_ie; 1160 + int ret; 1161 + 1162 + mutex_lock(&local->sta_mtx); 1163 + sta = sta_info_get(sdata, addr); 1164 + if (!sta) { 1165 + tdls_dbg(sdata, 1166 + "Invalid TDLS peer %pM for channel switch request\n", 1167 + addr); 1168 + ret = -ENOENT; 1169 + goto out; 1170 + } 1171 + 1172 + if (!test_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH)) { 1173 + tdls_dbg(sdata, "TDLS channel switch unsupported by %pM\n", 1174 + addr); 1175 + ret = -ENOTSUPP; 1176 + goto out; 1177 + } 1178 + 1179 + skb = ieee80211_tdls_ch_sw_tmpl_get(sta, oper_class, chandef, 1180 + &ch_sw_tm_ie); 1181 + if (!skb) { 1182 + ret = -ENOENT; 1183 + goto out; 1184 + } 1185 + 1186 + ret = drv_tdls_channel_switch(local, sdata, &sta->sta, oper_class, 1187 + chandef, skb, ch_sw_tm_ie); 1188 + if (!ret) 1189 + set_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); 1190 + 1191 + out: 1192 + mutex_unlock(&local->sta_mtx); 1193 + dev_kfree_skb_any(skb); 1194 + return ret; 1195 + } 1196 + 1197 + void 1198 + ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy, 1199 + struct net_device *dev, 1200 + const u8 *addr) 1201 + { 1202 + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); 1203 + struct ieee80211_local *local = sdata->local; 1204 + struct sta_info *sta; 1205 + 1206 + mutex_lock(&local->sta_mtx); 1207 + sta = sta_info_get(sdata, addr); 1208 + if (!sta) { 1209 + tdls_dbg(sdata, 1210 + "Invalid TDLS peer %pM for channel switch cancel\n", 1211 + addr); 1212 + goto out; 1213 + } 1214 + 1215 + if (!test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) { 1216 + tdls_dbg(sdata, "TDLS channel switch not initiated by %pM\n", 1217 + addr); 1218 + goto out; 1219 + } 1220 + 1221 + drv_tdls_cancel_channel_switch(local, sdata, &sta->sta); 1222 + clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); 1223 + 1224 + out: 1225 + mutex_unlock(&local->sta_mtx); 1226 + }
+57
net/mac80211/trace.h
··· 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 2257 #ifdef CONFIG_MAC80211_MESSAGE_TRACING 2201 2258 #undef TRACE_SYSTEM