diff options
author | John W. Linville | 2014-11-20 16:09:30 -0500 |
---|---|---|
committer | John W. Linville | 2014-11-20 16:09:30 -0500 |
commit | 9a638ddfb09e5aa17158842c95526e1aa79f92e6 (patch) | |
tree | 7edf2e128b23f711cf28273dca3ff69f3706925a | |
parent | b4c1b70823721e9edb19a839188e4dae50ce878d (diff) | |
parent | 75769c80e381653994293b5aa5a8cfec50088f9f (diff) |
Merge tag 'mac80211-next-for-john-2014-11-20' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211-next
Johannes Berg <johannes@sipsolutions.net> says:
"It has been a while since my last pull request, so we accumulated
another relatively large set of changes:
* TDLS off-channel support set from Arik/Liad, with some support
patches I did
* custom regulatory fixes from Arik
* minstrel VHT fix (and a small optimisation) from Felix
* add back radiotap vendor namespace support (myself)
* random MAC address scanning for cfg80211/mac80211/hwsim (myself)
* CSA improvements (Luca)
* WoWLAN Net Detect (wake on network found) support (Luca)
* and lots of other smaller changes from many people"
Signed-off-by: John W. Linville <linville@tuxdriver.com>
48 files changed, 2951 insertions, 384 deletions
diff --git a/drivers/net/wireless/ath/ath5k/mac80211-ops.c b/drivers/net/wireless/ath/ath5k/mac80211-ops.c index ab2709a43768..19eab2a69ad5 100644 --- a/drivers/net/wireless/ath/ath5k/mac80211-ops.c +++ b/drivers/net/wireless/ath/ath5k/mac80211-ops.c @@ -547,7 +547,9 @@ ath5k_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, static void -ath5k_sw_scan_start(struct ieee80211_hw *hw) +ath5k_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct ath5k_hw *ah = hw->priv; if (!ah->assoc) @@ -556,7 +558,7 @@ ath5k_sw_scan_start(struct ieee80211_hw *hw) static void -ath5k_sw_scan_complete(struct ieee80211_hw *hw) +ath5k_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct ath5k_hw *ah = hw->priv; ath5k_hw_set_ledstate(ah, ah->assoc ? diff --git a/drivers/net/wireless/ath/ath9k/channel.c b/drivers/net/wireless/ath/ath9k/channel.c index 794d52016437..206665059d66 100644 --- a/drivers/net/wireless/ath/ath9k/channel.c +++ b/drivers/net/wireless/ath/ath9k/channel.c @@ -963,7 +963,7 @@ static void ath_scan_send_probe(struct ath_softc *sc, struct ieee80211_tx_info *info; int band = sc->offchannel.chan.chandef.chan->band; - skb = ieee80211_probereq_get(sc->hw, vif, + skb = ieee80211_probereq_get(sc->hw, vif->addr, ssid->ssid, ssid->ssid_len, req->ie_len); if (!skb) return; diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_main.c b/drivers/net/wireless/ath/ath9k/htc_drv_main.c index c7d12efaa86a..92d5a6c5a225 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_main.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_main.c @@ -1691,7 +1691,9 @@ static int ath9k_htc_ampdu_action(struct ieee80211_hw *hw, return ret; } -static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw) +static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct ath9k_htc_priv *priv = hw->priv; struct ath_common *common = ath9k_hw_common(priv->ah); @@ -1705,7 +1707,8 @@ static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw) mutex_unlock(&priv->mutex); } -static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw) +static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct ath9k_htc_priv *priv = hw->priv; struct ath_common *common = ath9k_hw_common(priv->ah); diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index f95283f3c350..7c63976b5b0c 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -2183,14 +2183,17 @@ static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) return 0; } -static void ath9k_sw_scan_start(struct ieee80211_hw *hw) +static void ath9k_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct ath_softc *sc = hw->priv; struct ath_common *common = ath9k_hw_common(sc->sc_ah); set_bit(ATH_OP_SCANNING, &common->op_flags); } -static void ath9k_sw_scan_complete(struct ieee80211_hw *hw) +static void ath9k_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct ath_softc *sc = hw->priv; struct ath_common *common = ath9k_hw_common(sc->sc_ah); diff --git a/drivers/net/wireless/ath/wcn36xx/main.c b/drivers/net/wireless/ath/wcn36xx/main.c index b71d2b33532d..267c35d1f699 100644 --- a/drivers/net/wireless/ath/wcn36xx/main.c +++ b/drivers/net/wireless/ath/wcn36xx/main.c @@ -494,7 +494,9 @@ out: return ret; } -static void wcn36xx_sw_scan_start(struct ieee80211_hw *hw) +static void wcn36xx_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct wcn36xx *wcn = hw->priv; @@ -502,7 +504,8 @@ static void wcn36xx_sw_scan_start(struct ieee80211_hw *hw) wcn36xx_smd_start_scan(wcn); } -static void wcn36xx_sw_scan_complete(struct ieee80211_hw *hw) +static void wcn36xx_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct wcn36xx *wcn = hw->priv; diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c index 5d4173ee55bc..47731cb0d815 100644 --- a/drivers/net/wireless/b43/main.c +++ b/drivers/net/wireless/b43/main.c @@ -5110,7 +5110,9 @@ static void b43_op_sta_notify(struct ieee80211_hw *hw, B43_WARN_ON(!vif || wl->vif != vif); } -static void b43_op_sw_scan_start_notifier(struct ieee80211_hw *hw) +static void b43_op_sw_scan_start_notifier(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct b43_wl *wl = hw_to_b43_wl(hw); struct b43_wldev *dev; @@ -5124,7 +5126,8 @@ static void b43_op_sw_scan_start_notifier(struct ieee80211_hw *hw) mutex_unlock(&wl->mutex); } -static void b43_op_sw_scan_complete_notifier(struct ieee80211_hw *hw) +static void b43_op_sw_scan_complete_notifier(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct b43_wl *wl = hw_to_b43_wl(hw); struct b43_wldev *dev; diff --git a/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c index 43c71bfaa474..f95b52442281 100644 --- a/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c +++ b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c @@ -764,7 +764,9 @@ brcms_ops_configure_filter(struct ieee80211_hw *hw, return; } -static void brcms_ops_sw_scan_start(struct ieee80211_hw *hw) +static void brcms_ops_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct brcms_info *wl = hw->priv; spin_lock_bh(&wl->lock); @@ -773,7 +775,8 @@ static void brcms_ops_sw_scan_start(struct ieee80211_hw *hw) return; } -static void brcms_ops_sw_scan_complete(struct ieee80211_hw *hw) +static void brcms_ops_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct brcms_info *wl = hw->priv; spin_lock_bh(&wl->lock); diff --git a/drivers/net/wireless/cw1200/scan.c b/drivers/net/wireless/cw1200/scan.c index b2fb6c632092..f2e276faca70 100644 --- a/drivers/net/wireless/cw1200/scan.c +++ b/drivers/net/wireless/cw1200/scan.c @@ -78,7 +78,7 @@ int cw1200_hw_scan(struct ieee80211_hw *hw, if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS) return -EINVAL; - frame.skb = ieee80211_probereq_get(hw, priv->vif, NULL, 0, + frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0, req->ie_len); if (!frame.skb) return -ENOMEM; diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c index 77fbf3035038..2371d11e4190 100644 --- a/drivers/net/wireless/mac80211_hwsim.c +++ b/drivers/net/wireless/mac80211_hwsim.c @@ -415,6 +415,8 @@ struct mac80211_hwsim_data { bool destroy_on_close; struct work_struct destroy_work; u32 portid; + char alpha2[2]; + const struct ieee80211_regdomain *regd; struct ieee80211_channel *tmp_chan; struct delayed_work roc_done; @@ -422,6 +424,7 @@ struct mac80211_hwsim_data { struct cfg80211_scan_request *hw_scan_request; struct ieee80211_vif *hw_scan_vif; int scan_chan_idx; + u8 scan_addr[ETH_ALEN]; struct ieee80211_channel *channel; u64 beacon_int /* beacon interval in us */; @@ -830,6 +833,9 @@ static bool mac80211_hwsim_addr_match(struct mac80211_hwsim_data *data, .ret = false, }; + if (data->scanning && memcmp(addr, data->scan_addr, ETH_ALEN) == 0) + return true; + memcpy(md.addr, addr, ETH_ALEN); ieee80211_iterate_active_interfaces_atomic(data->hw, @@ -984,6 +990,53 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr, data->receive = true; } +static void mac80211_hwsim_add_vendor_rtap(struct sk_buff *skb) +{ + /* + * To enable this code, #define the HWSIM_RADIOTAP_OUI, + * e.g. like this: + * #define HWSIM_RADIOTAP_OUI "\x02\x00\x00" + * (but you should use a valid OUI, not that) + * + * If anyone wants to 'donate' a radiotap OUI/subns code + * please send a patch removing this #ifdef and changing + * the values accordingly. + */ +#ifdef HWSIM_RADIOTAP_OUI + struct ieee80211_vendor_radiotap *rtap; + + /* + * Note that this code requires the headroom in the SKB + * that was allocated earlier. + */ + rtap = (void *)skb_push(skb, sizeof(*rtap) + 8 + 4); + rtap->oui[0] = HWSIM_RADIOTAP_OUI[0]; + rtap->oui[1] = HWSIM_RADIOTAP_OUI[1]; + rtap->oui[2] = HWSIM_RADIOTAP_OUI[2]; + rtap->subns = 127; + + /* + * Radiotap vendor namespaces can (and should) also be + * split into fields by using the standard radiotap + * presence bitmap mechanism. Use just BIT(0) here for + * the presence bitmap. + */ + rtap->present = BIT(0); + /* We have 8 bytes of (dummy) data */ + rtap->len = 8; + /* For testing, also require it to be aligned */ + rtap->align = 8; + /* And also test that padding works, 4 bytes */ + rtap->pad = 4; + /* push the data */ + memcpy(rtap->data, "ABCDEFGH", 8); + /* make sure to clear padding, mac80211 doesn't */ + memset(rtap->data + 8, 0, 4); + + IEEE80211_SKB_RXCB(skb)->flag |= RX_FLAG_RADIOTAP_VENDOR_DATA; +#endif +} + static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw, struct sk_buff *skb, struct ieee80211_channel *chan) @@ -1098,6 +1151,9 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw, rx_status.mactime = now + data2->tsf_offset; memcpy(IEEE80211_SKB_RXCB(nskb), &rx_status, sizeof(rx_status)); + + mac80211_hwsim_add_vendor_rtap(nskb); + data2->rx_pkts++; data2->rx_bytes += nskb->len; ieee80211_rx_irqsafe(data2->hw, nskb); @@ -1752,7 +1808,7 @@ static void hw_scan_work(struct work_struct *work) struct sk_buff *probe; probe = ieee80211_probereq_get(hwsim->hw, - hwsim->hw_scan_vif, + hwsim->scan_addr, req->ssids[i].ssid, req->ssids[i].ssid_len, req->ie_len); @@ -1790,6 +1846,12 @@ static int mac80211_hwsim_hw_scan(struct ieee80211_hw *hw, hwsim->hw_scan_request = req; hwsim->hw_scan_vif = vif; hwsim->scan_chan_idx = 0; + if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + get_random_mask_addr(hwsim->scan_addr, + hw_req->req.mac_addr, + hw_req->req.mac_addr_mask); + else + memcpy(hwsim->scan_addr, vif->addr, ETH_ALEN); mutex_unlock(&hwsim->mutex); wiphy_debug(hw->wiphy, "hwsim hw_scan request\n"); @@ -1816,7 +1878,9 @@ static void mac80211_hwsim_cancel_hw_scan(struct ieee80211_hw *hw, mutex_unlock(&hwsim->mutex); } -static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw) +static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct mac80211_hwsim_data *hwsim = hw->priv; @@ -1828,13 +1892,16 @@ static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw) } printk(KERN_DEBUG "hwsim sw_scan request, prepping stuff\n"); + + memcpy(hwsim->scan_addr, mac_addr, ETH_ALEN); hwsim->scanning = true; out: mutex_unlock(&hwsim->mutex); } -static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw) +static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct mac80211_hwsim_data *hwsim = hw->priv; @@ -1842,6 +1909,7 @@ static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw) printk(KERN_DEBUG "hwsim sw_scan_complete\n"); hwsim->scanning = false; + memset(hwsim->scan_addr, 0, ETH_ALEN); mutex_unlock(&hwsim->mutex); } @@ -2057,36 +2125,26 @@ static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb, HWSIM_MCGRP_CONFIG, GFP_KERNEL); } -static struct sk_buff *build_radio_msg(int cmd, int id, - struct hwsim_new_radio_params *param) +static int append_radio_msg(struct sk_buff *skb, int id, + struct hwsim_new_radio_params *param) { - struct sk_buff *skb; - void *data; int ret; - skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!skb) - return NULL; - - data = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, cmd); - if (!data) - goto error; - ret = nla_put_u32(skb, HWSIM_ATTR_RADIO_ID, id); if (ret < 0) - goto error; + return ret; if (param->channels) { ret = nla_put_u32(skb, HWSIM_ATTR_CHANNELS, param->channels); if (ret < 0) - goto error; + return ret; } if (param->reg_alpha2) { ret = nla_put(skb, HWSIM_ATTR_REG_HINT_ALPHA2, 2, param->reg_alpha2); if (ret < 0) - goto error; + return ret; } if (param->regd) { @@ -2099,54 +2157,64 @@ static struct sk_buff *build_radio_msg(int cmd, int id, if (i < ARRAY_SIZE(hwsim_world_regdom_custom)) { ret = nla_put_u32(skb, HWSIM_ATTR_REG_CUSTOM_REG, i); if (ret < 0) - goto error; + return ret; } } if (param->reg_strict) { ret = nla_put_flag(skb, HWSIM_ATTR_REG_STRICT_REG); if (ret < 0) - goto error; + return ret; } if (param->p2p_device) { ret = nla_put_flag(skb, HWSIM_ATTR_SUPPORT_P2P_DEVICE); if (ret < 0) - goto error; + return ret; } if (param->use_chanctx) { ret = nla_put_flag(skb, HWSIM_ATTR_USE_CHANCTX); if (ret < 0) - goto error; + return ret; } if (param->hwname) { ret = nla_put(skb, HWSIM_ATTR_RADIO_NAME, strlen(param->hwname), param->hwname); if (ret < 0) - goto error; + return ret; } - genlmsg_end(skb, data); - - return skb; - -error: - nlmsg_free(skb); - return NULL; + return 0; } -static void hswim_mcast_new_radio(int id, struct genl_info *info, +static void hwsim_mcast_new_radio(int id, struct genl_info *info, struct hwsim_new_radio_params *param) { struct sk_buff *mcast_skb; + void *data; - mcast_skb = build_radio_msg(HWSIM_CMD_NEW_RADIO, id, param); + mcast_skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); if (!mcast_skb) return; + data = genlmsg_put(mcast_skb, 0, 0, &hwsim_genl_family, 0, + HWSIM_CMD_NEW_RADIO); + if (!data) + goto out_err; + + if (append_radio_msg(mcast_skb, id, param) < 0) + goto out_err; + + genlmsg_end(mcast_skb, data); + hwsim_mcast_config_msg(mcast_skb, info); + return; + +out_err: + genlmsg_cancel(mcast_skb, data); + nlmsg_free(mcast_skb); } static int mac80211_hwsim_new_radio(struct genl_info *info, @@ -2267,7 +2335,8 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, hw->wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | NL80211_FEATURE_STATIC_SMPS | - NL80211_FEATURE_DYNAMIC_SMPS; + NL80211_FEATURE_DYNAMIC_SMPS | + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR; /* ask mac80211 to reserve space for magic */ hw->vif_data_size = sizeof(struct hwsim_vif_priv); @@ -2353,6 +2422,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, if (param->reg_strict) hw->wiphy->regulatory_flags |= REGULATORY_STRICT_REG; if (param->regd) { + data->regd = param->regd; hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; wiphy_apply_custom_regulatory(hw->wiphy, param->regd); /* give the regulatory workqueue a chance to run */ @@ -2371,8 +2441,11 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, wiphy_debug(hw->wiphy, "hwaddr %pM registered\n", hw->wiphy->perm_addr); - if (param->reg_alpha2) + if (param->reg_alpha2) { + data->alpha2[0] = param->reg_alpha2[0]; + data->alpha2[1] = param->reg_alpha2[1]; regulatory_hint(hw->wiphy, param->reg_alpha2); + } data->debugfs = debugfs_create_dir("hwsim", hw->wiphy->debugfsdir); debugfs_create_file("ps", 0666, data->debugfs, data, &hwsim_fops_ps); @@ -2392,7 +2465,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, spin_unlock_bh(&hwsim_radio_lock); if (idx > 0) - hswim_mcast_new_radio(idx, info, param); + hwsim_mcast_new_radio(idx, info, param); return idx; @@ -2426,12 +2499,10 @@ static void hwsim_mcast_del_radio(int id, const char *hwname, if (ret < 0) goto error; - if (hwname) { - ret = nla_put(skb, HWSIM_ATTR_RADIO_NAME, strlen(hwname), - hwname); - if (ret < 0) - goto error; - } + ret = nla_put(skb, HWSIM_ATTR_RADIO_NAME, strlen(hwname), + hwname); + if (ret < 0) + goto error; genlmsg_end(skb, data); @@ -2455,6 +2526,44 @@ static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data, ieee80211_free_hw(data->hw); } +static int mac80211_hwsim_get_radio(struct sk_buff *skb, + struct mac80211_hwsim_data *data, + u32 portid, u32 seq, + struct netlink_callback *cb, int flags) +{ + void *hdr; + struct hwsim_new_radio_params param = { }; + int res = -EMSGSIZE; + + hdr = genlmsg_put(skb, portid, seq, &hwsim_genl_family, flags, + HWSIM_CMD_GET_RADIO); + if (!hdr) + return -EMSGSIZE; + + if (cb) + genl_dump_check_consistent(cb, hdr, &hwsim_genl_family); + + param.reg_alpha2 = data->alpha2; + param.reg_strict = !!(data->hw->wiphy->regulatory_flags & + REGULATORY_STRICT_REG); + param.p2p_device = !!(data->hw->wiphy->interface_modes & + BIT(NL80211_IFTYPE_P2P_DEVICE)); + param.use_chanctx = data->use_chanctx; + param.regd = data->regd; + param.channels = data->channels; + param.hwname = wiphy_name(data->hw->wiphy); + + res = append_radio_msg(skb, data->idx, ¶m); + if (res < 0) + goto out_err; + + return genlmsg_end(skb, hdr); + +out_err: + genlmsg_cancel(skb, hdr); + return res; +} + static void mac80211_hwsim_free(void) { struct mac80211_hwsim_data *data; @@ -2465,7 +2574,8 @@ static void mac80211_hwsim_free(void) list))) { list_del(&data->list); spin_unlock_bh(&hwsim_radio_lock); - mac80211_hwsim_del_radio(data, NULL, NULL); + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), + NULL); spin_lock_bh(&hwsim_radio_lock); } spin_unlock_bh(&hwsim_radio_lock); @@ -2744,14 +2854,14 @@ static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info) if (data->idx != idx) continue; } else { - if (hwname && - strcmp(hwname, wiphy_name(data->hw->wiphy))) + if (strcmp(hwname, wiphy_name(data->hw->wiphy))) continue; } list_del(&data->list); spin_unlock_bh(&hwsim_radio_lock); - mac80211_hwsim_del_radio(data, hwname, info); + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), + info); return 0; } spin_unlock_bh(&hwsim_radio_lock); @@ -2759,6 +2869,77 @@ static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info) return -ENODEV; } +static int hwsim_get_radio_nl(struct sk_buff *msg, struct genl_info *info) +{ + struct mac80211_hwsim_data *data; + struct sk_buff *skb; + int idx, res = -ENODEV; + + if (!info->attrs[HWSIM_ATTR_RADIO_ID]) + return -EINVAL; + idx = nla_get_u32(info->attrs[HWSIM_ATTR_RADIO_ID]); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_radios, list) { + if (data->idx != idx) + continue; + + skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) { + res = -ENOMEM; + goto out_err; + } + + res = mac80211_hwsim_get_radio(skb, data, info->snd_portid, + info->snd_seq, NULL, 0); + if (res < 0) { + nlmsg_free(skb); + goto out_err; + } + + genlmsg_reply(skb, info); + break; + } + +out_err: + spin_unlock_bh(&hwsim_radio_lock); + + return res; +} + +static int hwsim_dump_radio_nl(struct sk_buff *skb, + struct netlink_callback *cb) +{ + int idx = cb->args[0]; + struct mac80211_hwsim_data *data = NULL; + int res; + + spin_lock_bh(&hwsim_radio_lock); + + if (idx == hwsim_radio_idx) + goto done; + + list_for_each_entry(data, &hwsim_radios, list) { + if (data->idx < idx) + continue; + + res = mac80211_hwsim_get_radio(skb, data, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, cb, + NLM_F_MULTI); + if (res < 0) + break; + + idx = data->idx + 1; + } + + cb->args[0] = idx; + +done: + spin_unlock_bh(&hwsim_radio_lock); + return skb->len; +} + /* Generic Netlink operations array */ static const struct genl_ops hwsim_ops[] = { { @@ -2789,6 +2970,12 @@ static const struct genl_ops hwsim_ops[] = { .doit = hwsim_del_radio_nl, .flags = GENL_ADMIN_PERM, }, + { + .cmd = HWSIM_CMD_GET_RADIO, + .policy = hwsim_genl_policy, + .doit = hwsim_get_radio_nl, + .dumpit = hwsim_dump_radio_nl, + }, }; static void destroy_radio(struct work_struct *work) @@ -2796,7 +2983,7 @@ static void destroy_radio(struct work_struct *work) struct mac80211_hwsim_data *data = container_of(work, struct mac80211_hwsim_data, destroy_work); - mac80211_hwsim_del_radio(data, NULL, NULL); + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), NULL); } static void remove_user_radios(u32 portid) diff --git a/drivers/net/wireless/mac80211_hwsim.h b/drivers/net/wireless/mac80211_hwsim.h index f08debdd639b..66e1c73bd507 100644 --- a/drivers/net/wireless/mac80211_hwsim.h +++ b/drivers/net/wireless/mac80211_hwsim.h @@ -69,6 +69,8 @@ enum hwsim_tx_control_flags { * returns the radio ID (>= 0) or negative on errors, if successful * then multicast the result * @HWSIM_CMD_DEL_RADIO: destroy a radio, reply is multicasted + * @HWSIM_CMD_GET_RADIO: fetch information about existing radios, uses: + * %HWSIM_ATTR_RADIO_ID * @__HWSIM_CMD_MAX: enum limit */ enum { @@ -78,6 +80,7 @@ enum { HWSIM_CMD_TX_INFO_FRAME, HWSIM_CMD_NEW_RADIO, HWSIM_CMD_DEL_RADIO, + HWSIM_CMD_GET_RADIO, __HWSIM_CMD_MAX, }; #define HWSIM_CMD_MAX (_HWSIM_CMD_MAX - 1) diff --git a/drivers/net/wireless/mwl8k.c b/drivers/net/wireless/mwl8k.c index ef1104476bd8..b8d1e04aa9b9 100644 --- a/drivers/net/wireless/mwl8k.c +++ b/drivers/net/wireless/mwl8k.c @@ -5548,7 +5548,9 @@ mwl8k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return rc; } -static void mwl8k_sw_scan_start(struct ieee80211_hw *hw) +static void mwl8k_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct mwl8k_priv *priv = hw->priv; u8 tmp; @@ -5565,7 +5567,8 @@ static void mwl8k_sw_scan_start(struct ieee80211_hw *hw) priv->sw_scan_start = true; } -static void mwl8k_sw_scan_complete(struct ieee80211_hw *hw) +static void mwl8k_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct mwl8k_priv *priv = hw->priv; u8 tmp; diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h index d13f25cd70d5..1ff81afb672c 100644 --- a/drivers/net/wireless/rt2x00/rt2x00.h +++ b/drivers/net/wireless/rt2x00/rt2x00.h @@ -1437,8 +1437,11 @@ int rt2x00mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta); int rt2x00mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta); -void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw); -void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw); +void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr); +void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); int rt2x00mac_get_stats(struct ieee80211_hw *hw, struct ieee80211_low_level_stats *stats); void rt2x00mac_bss_info_changed(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/rt2x00/rt2x00mac.c b/drivers/net/wireless/rt2x00/rt2x00mac.c index ad6e5a8d1e10..cb40245a0695 100644 --- a/drivers/net/wireless/rt2x00/rt2x00mac.c +++ b/drivers/net/wireless/rt2x00/rt2x00mac.c @@ -568,7 +568,9 @@ int rt2x00mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(rt2x00mac_sta_remove); -void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw) +void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct rt2x00_dev *rt2x00dev = hw->priv; set_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags); @@ -576,7 +578,8 @@ void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw) } EXPORT_SYMBOL_GPL(rt2x00mac_sw_scan_start); -void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw) +void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct rt2x00_dev *rt2x00dev = hw->priv; clear_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags); diff --git a/drivers/net/wireless/rtlwifi/core.c b/drivers/net/wireless/rtlwifi/core.c index 07dae0d44abc..af52f0bdb71e 100644 --- a/drivers/net/wireless/rtlwifi/core.c +++ b/drivers/net/wireless/rtlwifi/core.c @@ -1361,7 +1361,9 @@ static int rtl_op_ampdu_action(struct ieee80211_hw *hw, return 0; } -static void rtl_op_sw_scan_start(struct ieee80211_hw *hw) +static void rtl_op_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); @@ -1396,7 +1398,8 @@ static void rtl_op_sw_scan_start(struct ieee80211_hw *hw) rtlpriv->cfg->ops->scan_operation_backup(hw, SCAN_OPT_BACKUP_BAND0); } -static void rtl_op_sw_scan_complete(struct ieee80211_hw *hw) +static void rtl_op_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct rtl_priv *rtlpriv = rtl_priv(hw); struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); diff --git a/drivers/net/wireless/ti/wl1251/main.c b/drivers/net/wireless/ti/wl1251/main.c index 38234851457e..0b30a7b4d663 100644 --- a/drivers/net/wireless/ti/wl1251/main.c +++ b/drivers/net/wireless/ti/wl1251/main.c @@ -1029,7 +1029,7 @@ static int wl1251_op_hw_scan(struct ieee80211_hw *hw, goto out_sleep; } - skb = ieee80211_probereq_get(wl->hw, wl->vif, ssid, ssid_len, + skb = ieee80211_probereq_get(wl->hw, wl->vif->addr, ssid, ssid_len, req->ie_len); if (!skb) { ret = -ENOMEM; diff --git a/drivers/net/wireless/ti/wlcore/cmd.c b/drivers/net/wireless/ti/wlcore/cmd.c index dd2e448c3e2b..b82661962d33 100644 --- a/drivers/net/wireless/ti/wlcore/cmd.c +++ b/drivers/net/wireless/ti/wlcore/cmd.c @@ -1145,7 +1145,7 @@ int wl12xx_cmd_build_probe_req(struct wl1271 *wl, struct wl12xx_vif *wlvif, wl1271_debug(DEBUG_SCAN, "build probe request band %d", band); - skb = ieee80211_probereq_get(wl->hw, vif, ssid, ssid_len, + skb = ieee80211_probereq_get(wl->hw, vif->addr, ssid, ssid_len, ie0_len + ie1_len); if (!skb) { ret = -ENOMEM; diff --git a/drivers/staging/vt6656/main_usb.c b/drivers/staging/vt6656/main_usb.c index 2fbff907ce8a..dbc311c3dc37 100644 --- a/drivers/staging/vt6656/main_usb.c +++ b/drivers/staging/vt6656/main_usb.c @@ -856,7 +856,9 @@ static int vnt_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, return 0; } -static void vnt_sw_scan_start(struct ieee80211_hw *hw) +static void vnt_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *addr) { struct vnt_private *priv = hw->priv; @@ -865,7 +867,8 @@ static void vnt_sw_scan_start(struct ieee80211_hw *hw) vnt_update_pre_ed_threshold(priv, true); } -static void vnt_sw_scan_complete(struct ieee80211_hw *hw) +static void vnt_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct vnt_private *priv = hw->priv; diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index f65b5446d983..4f4eea8a6288 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -19,6 +19,7 @@ #include <linux/types.h> #include <linux/if_ether.h> #include <asm/byteorder.h> +#include <asm/unaligned.h> /* * DS bit usage @@ -1066,6 +1067,12 @@ struct ieee80211_pspoll { /* TDLS */ +/* Channel switch timing */ +struct ieee80211_ch_switch_timing { + __le16 switch_time; + __le16 switch_timeout; +} __packed; + /* Link-id information element */ struct ieee80211_tdls_lnkie { u8 ie_type; /* Link Identifier IE */ @@ -1107,6 +1114,15 @@ struct ieee80211_tdls_data { u8 dialog_token; u8 variable[0]; } __packed discover_req; + struct { + u8 target_channel; + u8 oper_class; + u8 variable[0]; + } __packed chan_switch_req; + struct { + __le16 status_code; + u8 variable[0]; + } __packed chan_switch_resp; } u; } __packed; @@ -2018,6 +2034,11 @@ enum ieee80211_tdls_actioncode { */ #define WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING BIT(2) +/* TDLS capabilities in the the 4th byte of @WLAN_EID_EXT_CAPABILITY */ +#define WLAN_EXT_CAPA4_TDLS_BUFFER_STA BIT(4) +#define WLAN_EXT_CAPA4_TDLS_PEER_PSM BIT(5) +#define WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH BIT(6) + /* Interworking capabilities are set in 7th bit of 4th byte of the * @WLAN_EID_EXT_CAPABILITY information element */ @@ -2029,6 +2050,7 @@ enum ieee80211_tdls_actioncode { */ #define WLAN_EXT_CAPA5_TDLS_ENABLED BIT(5) #define WLAN_EXT_CAPA5_TDLS_PROHIBITED BIT(6) +#define WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED BIT(7) #define WLAN_EXT_CAPA8_OPMODE_NOTIF BIT(6) #define WLAN_EXT_CAPA8_TDLS_WIDE_BW_ENABLED BIT(7) @@ -2036,6 +2058,9 @@ enum ieee80211_tdls_actioncode { /* TDLS specific payload type in the LLC/SNAP header */ #define WLAN_TDLS_SNAP_RFTYPE 0x2 +/* BSS Coex IE information field bits */ +#define WLAN_BSS_COEX_INFORMATION_REQUEST BIT(0) + /** * enum - mesh synchronization method identifier * @@ -2418,6 +2443,30 @@ static inline bool ieee80211_check_tim(const struct ieee80211_tim_ie *tim, return !!(tim->virtual_map[index] & mask); } +/** + * ieee80211_get_tdls_action - get tdls packet action (or -1, if not tdls packet) + * @skb: the skb containing the frame, length will not be checked + * @hdr_size: the size of the ieee80211_hdr that starts at skb->data + * + * This function assumes the frame is a data frame, and that the network header + * is in the correct place. + */ +static inline int ieee80211_get_tdls_action(struct sk_buff *skb, u32 hdr_size) +{ + if (!skb_is_nonlinear(skb) && + skb->len > (skb_network_offset(skb) + 2)) { + /* Point to where the indication of TDLS should start */ + const u8 *tdls_data = skb_network_header(skb) - 2; + + if (get_unaligned_be16(tdls_data) == ETH_P_TDLS && + tdls_data[2] == WLAN_TDLS_SNAP_RFTYPE && + tdls_data[3] == WLAN_CATEGORY_TDLS) + return tdls_data[4]; + } + + return -1; +} + /* convert time units */ #define TU_TO_JIFFIES(x) (usecs_to_jiffies((x) * 1024)) #define TU_TO_EXP_TIME(x) (jiffies + TU_TO_JIFFIES(x)) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 5c3acd07acd9..bb748c4da5af 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1437,6 +1437,10 @@ struct cfg80211_ssid { * @aborted: (internal) scan request was notified as aborted * @notified: (internal) scan request was notified as done or aborted * @no_cck: used to send probe requests at non CCK rate in 2GHz band + * @mac_addr: MAC address used with randomisation + * @mac_addr_mask: MAC address mask used with randomisation, bits that + * are 0 in the mask should be randomised, bits that are 1 should + * be taken from the @mac_addr */ struct cfg80211_scan_request { struct cfg80211_ssid *ssids; @@ -1451,6 +1455,9 @@ struct cfg80211_scan_request { struct wireless_dev *wdev; + u8 mac_addr[ETH_ALEN] __aligned(2); + u8 mac_addr_mask[ETH_ALEN] __aligned(2); + /* internal */ struct wiphy *wiphy; unsigned long scan_start; @@ -1461,6 +1468,17 @@ struct cfg80211_scan_request { struct ieee80211_channel *channels[0]; }; +static inline void get_random_mask_addr(u8 *buf, const u8 *addr, const u8 *mask) +{ + int i; + + get_random_bytes(buf, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) { + buf[i] &= ~mask[i]; + buf[i] |= addr[i] & mask[i]; + } +} + /** * struct cfg80211_match_set - sets of attributes to match * @@ -1494,6 +1512,10 @@ struct cfg80211_match_set { * @channels: channels to scan * @min_rssi_thold: for drivers only supporting a single threshold, this * contains the minimum over all matchsets + * @mac_addr: MAC address used with randomisation + * @mac_addr_mask: MAC address mask used with randomisation, bits that + * are 0 in the mask should be randomised, bits that are 1 should + * be taken from the @mac_addr */ struct cfg80211_sched_scan_request { struct cfg80211_ssid *ssids; @@ -1508,6 +1530,9 @@ struct cfg80211_sched_scan_request { int n_match_sets; s32 min_rssi_thold; + u8 mac_addr[ETH_ALEN] __aligned(2); + u8 mac_addr_mask[ETH_ALEN] __aligned(2); + /* internal */ struct wiphy *wiphy; struct net_device *dev; @@ -1940,6 +1965,7 @@ struct cfg80211_wowlan_tcp { * @rfkill_release: wake up when rfkill is released * @tcp: TCP connection establishment/wakeup parameters, see nl80211.h. * NULL if not configured. + * @nd_config: configuration for the scan to be used for net detect wake. */ struct cfg80211_wowlan { bool any, disconnect, magic_pkt, gtk_rekey_failure, @@ -1948,6 +1974,7 @@ struct cfg80211_wowlan { struct cfg80211_pkt_pattern *patterns; struct cfg80211_wowlan_tcp *tcp; int n_patterns; + struct cfg80211_sched_scan_request *nd_config; }; /** @@ -1980,6 +2007,35 @@ struct cfg80211_coalesce { }; /** + * struct cfg80211_wowlan_nd_match - information about the match + * + * @ssid: SSID of the match that triggered the wake up + * @n_channels: Number of channels where the match occurred. This + * value may be zero if the driver can't report the channels. + * @channels: center frequencies of the channels where a match + * occurred (in MHz) + */ +struct cfg80211_wowlan_nd_match { + struct cfg80211_ssid ssid; + int n_channels; + u32 channels[]; +}; + +/** + * struct cfg80211_wowlan_nd_info - net detect wake up information + * + * @n_matches: Number of match information instances provided in + * @matches. This value may be zero if the driver can't provide + * match information. + * @matches: Array of pointers to matches containing information about + * the matches that triggered the wake up. + */ +struct cfg80211_wowlan_nd_info { + int n_matches; + struct cfg80211_wowlan_nd_match *matches[]; +}; + +/** * struct cfg80211_wowlan_wakeup - wakeup report * @disconnect: woke up by getting disconnected * @magic_pkt: woke up by receiving magic packet @@ -1998,6 +2054,7 @@ struct cfg80211_coalesce { * @tcp_match: TCP wakeup packet received * @tcp_connlost: TCP connection lost or failed to establish * @tcp_nomoretokens: TCP data ran out of tokens + * @net_detect: if not %NULL, woke up because of net detect */ struct cfg80211_wowlan_wakeup { bool disconnect, magic_pkt, gtk_rekey_failure, @@ -2007,6 +2064,7 @@ struct cfg80211_wowlan_wakeup { s32 pattern_idx; u32 packet_present_len, packet_len; const void *packet; + struct cfg80211_wowlan_nd_info *net_detect; }; /** @@ -2367,6 +2425,12 @@ struct cfg80211_qos_map { * (invoked with the wireless_dev mutex held) * @leave_ocb: leave the current OCB network * (invoked with the wireless_dev mutex held) + * + * @tdls_channel_switch: Start channel-switching with a TDLS peer. The driver + * is responsible for continually initiating channel-switching operations + * and returning to the base channel for communication with the AP. + * @tdls_cancel_channel_switch: Stop channel-switching with a TDLS peer. Both + * peers must be on the base channel when the call completes. */ struct cfg80211_ops { int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow); @@ -2622,6 +2686,14 @@ struct cfg80211_ops { u16 admitted_time); int (*del_tx_ts)(struct wiphy *wiphy, struct net_device *dev, u8 tsid, const u8 *peer); + + int (*tdls_channel_switch)(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr, u8 oper_class, + struct cfg80211_chan_def *chandef); + void (*tdls_cancel_channel_switch)(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr); }; /* @@ -2796,6 +2868,7 @@ struct ieee80211_txrx_stypes { * @WIPHY_WOWLAN_EAP_IDENTITY_REQ: supports wakeup on EAP identity request * @WIPHY_WOWLAN_4WAY_HANDSHAKE: supports wakeup on 4-way handshake failure * @WIPHY_WOWLAN_RFKILL_RELEASE: supports wakeup on RF-kill release + * @WIPHY_WOWLAN_NET_DETECT: supports wakeup on network detection */ enum wiphy_wowlan_support_flags { WIPHY_WOWLAN_ANY = BIT(0), @@ -2806,6 +2879,7 @@ enum wiphy_wowlan_support_flags { WIPHY_WOWLAN_EAP_IDENTITY_REQ = BIT(5), WIPHY_WOWLAN_4WAY_HANDSHAKE = BIT(6), WIPHY_WOWLAN_RFKILL_RELEASE = BIT(7), + WIPHY_WOWLAN_NET_DETECT = BIT(8), }; struct wiphy_wowlan_tcp_support { @@ -2824,6 +2898,11 @@ struct wiphy_wowlan_tcp_support { * @pattern_max_len: maximum length of each pattern * @pattern_min_len: minimum length of each pattern * @max_pkt_offset: maximum Rx packet offset + * @max_nd_match_sets: maximum number of matchsets for net-detect, + * similar, but not necessarily identical, to max_match_sets for + * scheduled scans. + * See &struct cfg80211_sched_scan_request.@match_sets for more + * details. * @tcp: TCP wakeup support information */ struct wiphy_wowlan_support { @@ -2832,6 +2911,7 @@ struct wiphy_wowlan_support { int pattern_max_len; int pattern_min_len; int max_pkt_offset; + int max_nd_match_sets; const struct wiphy_wowlan_tcp_support *tcp; }; @@ -4719,6 +4799,20 @@ bool cfg80211_reg_can_beacon(struct wiphy *wiphy, void cfg80211_ch_switch_notify(struct net_device *dev, struct cfg80211_chan_def *chandef); +/* + * cfg80211_ch_switch_started_notify - notify channel switch start + * @dev: the device on which the channel switch started + * @chandef: the future channel definition + * @count: the number of TBTTs until the channel switch happens + * + * Inform the userspace about the channel switch that has just + * started, so that it can take appropriate actions (eg. starting + * channel switch on other vifs), if necessary. + */ +void cfg80211_ch_switch_started_notify(struct net_device *dev, + struct cfg80211_chan_def *chandef, + u8 count); + /** * ieee80211_operating_class_to_band - convert operating class to band * diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 5f203a6a5e7e..cff3a26a9dae 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -882,6 +882,9 @@ ieee80211_tx_info_clear_status(struct ieee80211_tx_info *info) * subframes share the same sequence number. Reported subframes can be * either regular MSDU or singly A-MSDUs. Subframes must not be * interleaved with other frames. + * @RX_FLAG_RADIOTAP_VENDOR_DATA: This frame contains vendor-specific + * radiotap data in the skb->data (before the frame) as described by + * the &struct ieee80211_vendor_radiotap. */ enum mac80211_rx_flags { RX_FLAG_MMIC_ERROR = BIT(0), @@ -911,6 +914,7 @@ enum mac80211_rx_flags { RX_FLAG_10MHZ = BIT(28), RX_FLAG_5MHZ = BIT(29), RX_FLAG_AMSDU_MORE = BIT(30), + RX_FLAG_RADIOTAP_VENDOR_DATA = BIT(31), }; #define RX_FLAG_STBC_SHIFT 26 @@ -982,6 +986,39 @@ struct ieee80211_rx_status { }; /** + * struct ieee80211_vendor_radiotap - vendor radiotap data information + * @present: presence bitmap for this vendor namespace + * (this could be extended in the future if any vendor needs more + * bits, the radiotap spec does allow for that) + * @align: radiotap vendor namespace alignment. This defines the needed + * alignment for the @data field below, not for the vendor namespace + * description itself (which has a fixed 2-byte alignment) + * Must be a power of two, and be set to at least 1! + * @oui: radiotap vendor namespace OUI + * @subns: radiotap vendor sub namespace + * @len: radiotap vendor sub namespace skip length, if alignment is done + * then that's added to this, i.e. this is only the length of the + * @data field. + * @pad: number of bytes of padding after the @data, this exists so that + * the skb data alignment can be preserved even if the data has odd + * length + * @data: the actual vendor namespace data + * + * This struct, including the vendor data, goes into the skb->data before + * the 802.11 header. It's split up in mac80211 using the align/oui/subns + * data. + */ +struct ieee80211_vendor_radiotap { + u32 present; + u8 align; + u8 oui[3]; + u8 subns; + u8 pad; + u16 len; + u8 data[]; +} __packed; + +/** * enum ieee80211_conf_flags - configuration flags * * Flags to define PHY configuration options @@ -1790,6 +1827,31 @@ struct ieee80211_scan_request { }; /** + * struct ieee80211_tdls_ch_sw_params - TDLS channel switch parameters + * + * @sta: peer this TDLS channel-switch request/response came from + * @chandef: channel referenced in a TDLS channel-switch request + * @action_code: see &enum ieee80211_tdls_actioncode + * @status: channel-switch response status + * @timestamp: time at which the frame was received + * @switch_time: switch-timing parameter received in the frame + * @switch_timeout: switch-timing parameter received in the frame + * @tmpl_skb: TDLS switch-channel response template + * @ch_sw_tm_ie: offset of the channel-switch timing IE inside @tmpl_skb + */ +struct ieee80211_tdls_ch_sw_params { + struct ieee80211_sta *sta; + struct cfg80211_chan_def *chandef; + u8 action_code; + u32 status; + u32 timestamp; + u16 switch_time; + u16 switch_timeout; + struct sk_buff *tmpl_skb; + u32 ch_sw_tm_ie; +}; + +/** * wiphy_to_ieee80211_hw - return a mac80211 driver hw struct from a wiphy * * @wiphy: the &struct wiphy which we want to query @@ -2560,7 +2622,9 @@ enum ieee80211_reconfig_type { * * @sw_scan_start: Notifier function that is called just before a software scan * is started. Can be NULL, if the driver doesn't need this notification. - * The callback can sleep. + * The mac_addr parameter allows supporting NL80211_SCAN_FLAG_RANDOM_ADDR, + * the driver may set the NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR flag if it + * can use this parameter. The callback can sleep. * * @sw_scan_complete: Notifier function that is called just after a * software scan finished. Can be NULL, if the driver doesn't need @@ -2631,6 +2695,9 @@ enum ieee80211_reconfig_type { * uses hardware rate control (%IEEE80211_HW_HAS_RATE_CONTROL) since * otherwise the rate control algorithm is notified directly. * Must be atomic. + * @sta_rate_tbl_update: Notifies the driver that the rate table changed. This + * is only used if the configured rate control algorithm actually uses + * the new rate table API, and is therefore optional. Must be atomic. * * @conf_tx: Configure TX queue parameters (EDCF (aifs, cw_min, cw_max), * bursting) for a hardware TX queue. @@ -2878,6 +2945,23 @@ enum ieee80211_reconfig_type { * * @get_txpower: get current maximum tx power (in dBm) based on configuration * and hardware limits. + * + * @tdls_channel_switch: Start channel-switching with a TDLS peer. The driver + * is responsible for continually initiating channel-switching operations + * and returning to the base channel for communication with the AP. The + * driver receives a channel-switch request template and the location of + * the switch-timing IE within the template as part of the invocation. + * The template is valid only within the call, and the driver can + * optionally copy the skb for further re-use. + * @tdls_cancel_channel_switch: Stop channel-switching with a TDLS peer. Both + * peers must be on the base channel when the call completes. + * @tdls_recv_channel_switch: a TDLS channel-switch related frame (request or + * response) has been received from a remote peer. The driver gets + * parameters parsed from the incoming frame and may use them to continue + * an ongoing channel-switch operation. In addition, a channel-switch + * response template is provided, together with the location of the + * switch-timing IE within the template. The skb can only be used within + * the function call. */ struct ieee80211_ops { void (*tx)(struct ieee80211_hw *hw, @@ -2937,8 +3021,11 @@ struct ieee80211_ops { struct ieee80211_scan_ies *ies); int (*sched_scan_stop)(struct ieee80211_hw *hw, struct ieee80211_vif *vif); - void (*sw_scan_start)(struct ieee80211_hw *hw); - void (*sw_scan_complete)(struct ieee80211_hw *hw); + void (*sw_scan_start)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr); + void (*sw_scan_complete)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); int (*get_stats)(struct ieee80211_hw *hw, struct ieee80211_low_level_stats *stats); void (*get_tkip_seq)(struct ieee80211_hw *hw, u8 hw_key_idx, @@ -2972,6 +3059,9 @@ struct ieee80211_ops { struct ieee80211_vif *vif, struct ieee80211_sta *sta, u32 changed); + void (*sta_rate_tbl_update)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); int (*conf_tx)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 ac, const struct ieee80211_tx_queue_params *params); @@ -3089,6 +3179,18 @@ struct ieee80211_ops { u32 (*get_expected_throughput)(struct ieee80211_sta *sta); int (*get_txpower)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int *dbm); + + int (*tdls_channel_switch)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie); + void (*tdls_cancel_channel_switch)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + void (*tdls_recv_channel_switch)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_tdls_ch_sw_params *params); }; /** @@ -3729,7 +3831,7 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw, /** * ieee80211_probereq_get - retrieve a Probe Request template * @hw: pointer obtained from ieee80211_alloc_hw(). - * @vif: &struct ieee80211_vif pointer from the add_interface callback. + * @src_addr: source MAC address * @ssid: SSID buffer * @ssid_len: length of SSID * @tailroom: tailroom to reserve at end of SKB for IEs @@ -3740,7 +3842,7 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw, * Return: The Probe Request template. %NULL on error. */ struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, + const u8 *src_addr, const u8 *ssid, size_t ssid_len, size_t tailroom); @@ -4980,6 +5082,43 @@ void ieee80211_tdls_oper_request(struct ieee80211_vif *vif, const u8 *peer, u16 reason_code, gfp_t gfp); /** + * ieee80211_reserve_tid - request to reserve a specific TID + * + * There is sometimes a need (such as in TDLS) for blocking the driver from + * using a specific TID so that the FW can use it for certain operations such + * as sending PTI requests. To make sure that the driver doesn't use that TID, + * this function must be called as it flushes out packets on this TID and marks + * it as blocked, so that any transmit for the station on this TID will be + * redirected to the alternative TID in the same AC. + * + * Note that this function blocks and may call back into the driver, so it + * should be called without driver locks held. Also note this function should + * only be called from the driver's @sta_state callback. + * + * @sta: the station to reserve the TID for + * @tid: the TID to reserve + * + * Returns: 0 on success, else on failure + */ +int ieee80211_reserve_tid(struct ieee80211_sta *sta, u8 tid); + +/** + * ieee80211_unreserve_tid - request to unreserve a specific TID + * + * Once there is no longer any need for reserving a certain TID, this function + * should be called, and no longer will packets have their TID modified for + * preventing use of this TID in the driver. + * + * Note that this function blocks and acquires a lock, so it should be called + * without driver locks held. Also note this function should only be called + * from the driver's @sta_state callback. + * + * @sta: the station + * @tid: the TID to unreserve + */ +void ieee80211_unreserve_tid(struct ieee80211_sta *sta, u8 tid); + +/** * ieee80211_ie_split - split an IE buffer according to ordering * * @ies: the IE buffer diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 9b3025e4377a..d77524510435 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -643,7 +643,18 @@ * @NL80211_CMD_CH_SWITCH_NOTIFY: An AP or GO may decide to switch channels * independently of the userspace SME, send this event indicating * %NL80211_ATTR_IFINDEX is now on %NL80211_ATTR_WIPHY_FREQ and the - * attributes determining channel width. + * attributes determining channel width. This indication may also be + * sent when a remotely-initiated switch (e.g., when a STA receives a CSA + * from the remote AP) is completed; + * + * @NL80211_CMD_CH_SWITCH_STARTED_NOTIFY: Notify that a channel switch + * has been started on an interface, regardless of the initiator + * (ie. whether it was requested from a remote device or + * initiated on our own). It indicates that + * %NL80211_ATTR_IFINDEX will be on %NL80211_ATTR_WIPHY_FREQ + * after %NL80211_ATTR_CH_SWITCH_COUNT TBTT's. The userspace may + * decide to react to this indication by requesting other + * interfaces to change channel as well. * * @NL80211_CMD_START_P2P_DEVICE: Start the given P2P Device, identified by * its %NL80211_ATTR_WDEV identifier. It must have been created with @@ -751,6 +762,18 @@ * @NL80211_CMD_LEAVE_OCB: Leave the OCB network -- no special arguments, the * network is determined by the network interface. * + * @NL80211_CMD_TDLS_CHANNEL_SWITCH: Start channel-switching with a TDLS peer, + * identified by the %NL80211_ATTR_MAC parameter. A target channel is + * provided via %NL80211_ATTR_WIPHY_FREQ and other attributes determining + * channel width/type. The target operating class is given via + * %NL80211_ATTR_OPER_CLASS. + * The driver is responsible for continually initiating channel-switching + * operations and returning to the base channel for communication with the + * AP. + * @NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH: Stop channel-switching with a TDLS + * peer given by %NL80211_ATTR_MAC. Both peers must be on the base channel + * when this command completes. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -930,6 +953,11 @@ enum nl80211_commands { NL80211_CMD_JOIN_OCB, NL80211_CMD_LEAVE_OCB, + NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, + + NL80211_CMD_TDLS_CHANNEL_SWITCH, + NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -1624,9 +1652,9 @@ enum nl80211_commands { * @NL80211_ATTR_TDLS_PEER_CAPABILITY: flags for TDLS peer capabilities, u32. * As specified in the &enum nl80211_tdls_peer_capability. * - * @NL80211_ATTR_IFACE_SOCKET_OWNER: flag attribute, if set during interface + * @NL80211_ATTR_SOCKET_OWNER: Flag attribute, if set during interface * creation then the new interface will be owned by the netlink socket - * that created it and will be destroyed when the socket is closed + * that created it and will be destroyed when the socket is closed. * * @NL80211_ATTR_TDLS_INITIATOR: flag attribute indicating the current end is * the TDLS link initiator. @@ -1656,6 +1684,11 @@ enum nl80211_commands { * @NL80211_ATTR_SMPS_MODE: SMPS mode to use (ap mode). see * &enum nl80211_smps_mode. * + * @NL80211_ATTR_OPER_CLASS: operating class + * + * @NL80211_ATTR_MAC_MASK: MAC address mask + * + * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1991,7 +2024,7 @@ enum nl80211_attrs { NL80211_ATTR_TDLS_PEER_CAPABILITY, - NL80211_ATTR_IFACE_SOCKET_OWNER, + NL80211_ATTR_SOCKET_OWNER, NL80211_ATTR_CSA_C_OFFSETS_TX, NL80211_ATTR_MAX_CSA_COUNTERS, @@ -2008,15 +2041,21 @@ enum nl80211_attrs { NL80211_ATTR_SMPS_MODE, + NL80211_ATTR_OPER_CLASS, + + NL80211_ATTR_MAC_MASK, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, + NUM_NL80211_ATTR = __NL80211_ATTR_AFTER_LAST, NL80211_ATTR_MAX = __NL80211_ATTR_AFTER_LAST - 1 }; /* source-level API compatibility */ #define NL80211_ATTR_SCAN_GENERATION NL80211_ATTR_GENERATION #define NL80211_ATTR_MESH_PARAMS NL80211_ATTR_MESH_CONFIG +#define NL80211_ATTR_IFACE_SOCKET_OWNER NL80211_ATTR_SOCKET_OWNER /* * Allow user space programs to use #ifdef on new attributes by defining them @@ -2652,6 +2691,11 @@ enum nl80211_sched_scan_match_attr { * @NL80211_RRF_AUTO_BW: maximum available bandwidth should be calculated * base on contiguous rules and wider channels will be allowed to cross * multiple contiguous/overlapping frequency ranges. + * @NL80211_RRF_GO_CONCURRENT: See &NL80211_FREQUENCY_ATTR_GO_CONCURRENT + * @NL80211_RRF_NO_HT40MINUS: channels can't be used in HT40- operation + * @NL80211_RRF_NO_HT40PLUS: channels can't be used in HT40+ operation + * @NL80211_RRF_NO_80MHZ: 80MHz operation not allowed + * @NL80211_RRF_NO_160MHZ: 160MHz operation not allowed */ enum nl80211_reg_rule_flags { NL80211_RRF_NO_OFDM = 1<<0, @@ -2664,11 +2708,18 @@ enum nl80211_reg_rule_flags { NL80211_RRF_NO_IR = 1<<7, __NL80211_RRF_NO_IBSS = 1<<8, NL80211_RRF_AUTO_BW = 1<<11, + NL80211_RRF_GO_CONCURRENT = 1<<12, + NL80211_RRF_NO_HT40MINUS = 1<<13, + NL80211_RRF_NO_HT40PLUS = 1<<14, + NL80211_RRF_NO_80MHZ = 1<<15, + NL80211_RRF_NO_160MHZ = 1<<16, }; #define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR #define NL80211_RRF_NO_IBSS NL80211_RRF_NO_IR #define NL80211_RRF_NO_IR NL80211_RRF_NO_IR +#define NL80211_RRF_NO_HT40 (NL80211_RRF_NO_HT40MINUS |\ + NL80211_RRF_NO_HT40PLUS) /* For backport compatibility with older userspace */ #define NL80211_RRF_NO_IR_ALL (NL80211_RRF_NO_IR | __NL80211_RRF_NO_IBSS) @@ -3566,6 +3617,25 @@ struct nl80211_pattern_support { * @NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS: For wakeup reporting only, * the TCP connection ran out of tokens to use for data to send to the * service + * @NL80211_WOWLAN_TRIG_NET_DETECT: wake up when a configured network + * is detected. This is a nested attribute that contains the + * same attributes used with @NL80211_CMD_START_SCHED_SCAN. It + * specifies how the scan is performed (e.g. the interval and the + * channels to scan) as well as the scan results that will + * trigger a wake (i.e. the matchsets). + * @NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS: nested attribute + * containing an array with information about what triggered the + * wake up. If no elements are present in the array, it means + * that the information is not available. If more than one + * element is present, it means that more than one match + * occurred. + * Each element in the array is a nested attribute that contains + * one optional %NL80211_ATTR_SSID attribute and one optional + * %NL80211_ATTR_SCAN_FREQUENCIES attribute. At least one of + * these attributes must be present. If + * %NL80211_ATTR_SCAN_FREQUENCIES contains more than one + * frequency, it means that the match occurred in more than one + * channel. * @NUM_NL80211_WOWLAN_TRIG: number of wake on wireless triggers * @MAX_NL80211_WOWLAN_TRIG: highest wowlan trigger attribute number * @@ -3591,6 +3661,8 @@ enum nl80211_wowlan_triggers { NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH, NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST, NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS, + NL80211_WOWLAN_TRIG_NET_DETECT, + NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS, /* keep last */ NUM_NL80211_WOWLAN_TRIG, @@ -4070,6 +4142,20 @@ enum nl80211_ap_sme_features { * @NL80211_FEATURE_MAC_ON_CREATE: Device supports configuring * the vif's MAC address upon creation. * See 'macaddr' field in the vif_params (cfg80211.h). + * @NL80211_FEATURE_TDLS_CHANNEL_SWITCH: Driver supports channel switching when + * operating as a TDLS peer. + * @NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR: This device/driver supports using a + * random MAC address during scan (if the device is unassociated); the + * %NL80211_SCAN_FLAG_RANDOM_ADDR flag may be set for scans and the MAC + * address mask/value will be used. + * @NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR: This device/driver supports + * using a random MAC address for every scan iteration during scheduled + * scan (while not associated), the %NL80211_SCAN_FLAG_RANDOM_ADDR may + * be set for scheduled scan and the MAC address mask/value will be used. + * @NL80211_FEATURE_ND_RANDOM_MAC_ADDR: This device/driver supports using a + * random MAC address for every scan iteration during "net detect", i.e. + * scan in unassociated WoWLAN, the %NL80211_SCAN_FLAG_RANDOM_ADDR may + * be set for scheduled scan and the MAC address mask/value will be used. */ enum nl80211_feature_flags { NL80211_FEATURE_SK_TX_STATUS = 1 << 0, @@ -4100,6 +4186,10 @@ enum nl80211_feature_flags { NL80211_FEATURE_DYNAMIC_SMPS = 1 << 25, NL80211_FEATURE_SUPPORTS_WMM_ADMISSION = 1 << 26, NL80211_FEATURE_MAC_ON_CREATE = 1 << 27, + NL80211_FEATURE_TDLS_CHANNEL_SWITCH = 1 << 28, + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR = 1 << 29, + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR = 1 << 30, + NL80211_FEATURE_ND_RANDOM_MAC_ADDR = 1 << 31, }; /** @@ -4148,11 +4238,21 @@ enum nl80211_connect_failed_reason { * dangerous because will destroy stations performance as a lot of frames * will be lost while scanning off-channel, therefore it must be used only * when really needed + * @NL80211_SCAN_FLAG_RANDOM_ADDR: use a random MAC address for this scan (or + * for scheduled scan: a different one for every scan iteration). When the + * flag is set, depending on device capabilities the @NL80211_ATTR_MAC and + * @NL80211_ATTR_MAC_MASK attributes may also be given in which case only + * the masked bits will be preserved from the MAC address and the remainder + * randomised. If the attributes are not given full randomisation (46 bits, + * locally administered 1, multicast 0) is assumed. + * This flag must not be requested when the feature isn't supported, check + * the nl80211 feature flags for the device. */ enum nl80211_scan_flags { NL80211_SCAN_FLAG_LOW_PRIORITY = 1<<0, NL80211_SCAN_FLAG_FLUSH = 1<<1, NL80211_SCAN_FLAG_AP = 1<<2, + NL80211_SCAN_FLAG_RANDOM_ADDR = 1<<3, }; /** diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index 9242c60048cf..a360c15cc978 100644 --- a/net/mac80211/agg-tx.c +++ b/net/mac80211/agg-tx.c @@ -509,6 +509,10 @@ int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid, struct tid_ampdu_tx *tid_tx; int ret = 0; + if (WARN(sta->reserved_tid == tid, + "Requested to start BA session on reserved tid=%d", tid)) + return -EINVAL; + trace_api_start_tx_ba_session(pubsta, tid); if (WARN_ON_ONCE(!local->ops->ampdu_action)) @@ -765,6 +769,9 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid) goto unlock; } + WARN(sta->reserved_tid == tid, + "Requested to stop BA session on reserved tid=%d", tid); + if (test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { /* already in progress stopping it */ ret = 0; diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 06185940cbb6..e75d5c53e97b 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1042,6 +1042,13 @@ static int sta_apply_parameters(struct ieee80211_local *local, clear_sta_flag(sta, WLAN_STA_TDLS_PEER); } + /* mark TDLS channel switch support, if the AP allows it */ + if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) && + !sdata->u.mgd.tdls_chan_switch_prohibited && + params->ext_capab_len >= 4 && + params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) + set_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH); + if (params->sta_modify_mask & STATION_PARAM_APPLY_UAPSD) { sta->sta.uapsd_queues = params->uapsd_queues; sta->sta.max_sp = params->max_sp; @@ -3158,6 +3165,12 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, goto out; } + ch_switch.timestamp = 0; + ch_switch.device_timestamp = 0; + ch_switch.block_tx = params->block_tx; + ch_switch.chandef = params->chandef; + ch_switch.count = params->count; + err = drv_pre_channel_switch(sdata, &ch_switch); if (err) goto out; @@ -3175,12 +3188,6 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, goto out; } - ch_switch.timestamp = 0; - ch_switch.device_timestamp = 0; - ch_switch.block_tx = params->block_tx; - ch_switch.chandef = params->chandef; - ch_switch.count = params->count; - err = ieee80211_set_csa_beacon(sdata, params, &changed); if (err) { ieee80211_vif_unreserve_chanctx(sdata); @@ -3195,6 +3202,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, ieee80211_stop_vif_queues(local, sdata, IEEE80211_QUEUE_STOP_REASON_CSA); + cfg80211_ch_switch_started_notify(sdata->dev, &sdata->csa_chandef, + params->count); + if (changed) { ieee80211_bss_info_change_notify(sdata, changed); drv_channel_switch_beacon(sdata, ¶ms->chandef); @@ -3511,6 +3521,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | IEEE80211_TX_INTFL_NL80211_FRAME_TX; + info->band = band; skb_set_queue_mapping(skb, IEEE80211_AC_VO); skb->priority = 7; @@ -3518,7 +3529,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, nullfunc->qos_ctrl = cpu_to_le16(7); local_bh_disable(); - ieee80211_xmit(sdata, skb, band); + ieee80211_xmit(sdata, skb); local_bh_enable(); rcu_read_unlock(); @@ -3741,6 +3752,8 @@ const struct cfg80211_ops mac80211_config_ops = { .set_rekey_data = ieee80211_set_rekey_data, .tdls_oper = ieee80211_tdls_oper, .tdls_mgmt = ieee80211_tdls_mgmt, + .tdls_channel_switch = ieee80211_tdls_channel_switch, + .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch, .probe_client = ieee80211_probe_client, .set_noack_map = ieee80211_set_noack_map, #ifdef CONFIG_PM diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c index bafe48916229..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, test_sta_flag(sta, WLAN_STA_##flg) ? #flg "\n" : "" int res = scnprintf(buf, sizeof(buf), - "%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%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", TEST(AUTH), TEST(ASSOC), TEST(PS_STA), TEST(PS_DRIVER), TEST(AUTHORIZED), TEST(SHORT_PREAMBLE), @@ -82,10 +82,11 @@ static ssize_t sta_flags_read(struct file *file, char __user *userbuf, TEST(WDS), TEST(CLEAR_PS_FILT), TEST(MFP), TEST(BLOCK_BA), TEST(PSPOLL), TEST(UAPSD), TEST(SP), TEST(TDLS_PEER), - TEST(TDLS_PEER_AUTH), TEST(4ADDR_EVENT), - TEST(INSERTED), TEST(RATE_CONTROL), - TEST(TOFFSET_KNOWN), TEST(MPSP_OWNER), - TEST(MPSP_RECIPIENT)); + TEST(TDLS_PEER_AUTH), TEST(TDLS_INITIATOR), + TEST(TDLS_CHAN_SWITCH), TEST(TDLS_OFF_CHANNEL), + TEST(4ADDR_EVENT), TEST(INSERTED), + TEST(RATE_CONTROL), TEST(TOFFSET_KNOWN), + TEST(MPSP_OWNER), TEST(MPSP_RECIPIENT)); #undef TEST return simple_read_from_buffer(userbuf, count, ppos, buf, res); } diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 9759dd1f0734..2ebc9ead9695 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -380,23 +380,26 @@ static inline int drv_sched_scan_stop(struct ieee80211_local *local, return ret; } -static inline void drv_sw_scan_start(struct ieee80211_local *local) +static inline void drv_sw_scan_start(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + const u8 *mac_addr) { might_sleep(); - trace_drv_sw_scan_start(local); + trace_drv_sw_scan_start(local, sdata, mac_addr); if (local->ops->sw_scan_start) - local->ops->sw_scan_start(&local->hw); + local->ops->sw_scan_start(&local->hw, &sdata->vif, mac_addr); trace_drv_return_void(local); } -static inline void drv_sw_scan_complete(struct ieee80211_local *local) +static inline void drv_sw_scan_complete(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) { might_sleep(); - trace_drv_sw_scan_complete(local); + trace_drv_sw_scan_complete(local, sdata); if (local->ops->sw_scan_complete) - local->ops->sw_scan_complete(&local->hw); + local->ops->sw_scan_complete(&local->hw, &sdata->vif); trace_drv_return_void(local); } @@ -621,6 +624,21 @@ static inline void drv_sta_rc_update(struct ieee80211_local *local, trace_drv_return_void(local); } +static inline void drv_sta_rate_tbl_update(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta) +{ + sdata = get_bss_sdata(sdata); + if (!check_sdata_in_driver(sdata)) + return; + + trace_drv_sta_rate_tbl_update(local, sdata, sta); + if (local->ops->sta_rate_tbl_update) + local->ops->sta_rate_tbl_update(&local->hw, &sdata->vif, sta); + + trace_drv_return_void(local); +} + static inline int drv_conf_tx(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, u16 ac, const struct ieee80211_tx_queue_params *params) @@ -1296,4 +1314,57 @@ static inline int drv_get_txpower(struct ieee80211_local *local, return ret; } +static inline int +drv_tdls_channel_switch(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie) +{ + int ret; + + might_sleep(); + if (!check_sdata_in_driver(sdata)) + return -EIO; + + if (!local->ops->tdls_channel_switch) + return -EOPNOTSUPP; + + trace_drv_tdls_channel_switch(local, sdata, sta, oper_class, chandef); + ret = local->ops->tdls_channel_switch(&local->hw, &sdata->vif, sta, + oper_class, chandef, tmpl_skb, + ch_sw_tm_ie); + trace_drv_return_int(local, ret); + return ret; +} + +static inline void +drv_tdls_cancel_channel_switch(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta) +{ + might_sleep(); + if (!check_sdata_in_driver(sdata)) + return; + + if (!local->ops->tdls_cancel_channel_switch) + return; + + trace_drv_tdls_cancel_channel_switch(local, sdata, sta); + local->ops->tdls_cancel_channel_switch(&local->hw, &sdata->vif, sta); + trace_drv_return_void(local); +} + +static inline void +drv_tdls_recv_channel_switch(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_tdls_ch_sw_params *params) +{ + trace_drv_tdls_recv_channel_switch(local, sdata, params); + if (local->ops->tdls_recv_channel_switch) + local->ops->tdls_recv_channel_switch(&local->hw, &sdata->vif, + params); + trace_drv_return_void(local); +} + #endif /* __MAC80211_DRIVER_OPS */ diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 842e0661fb57..cc6e964d9837 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -525,8 +525,13 @@ struct ieee80211_if_managed { struct ieee80211_vht_cap vht_capa; /* configured VHT overrides */ struct ieee80211_vht_cap vht_capa_mask; /* Valid parts of vht_capa */ + /* TDLS support */ u8 tdls_peer[ETH_ALEN] __aligned(2); struct delayed_work tdls_peer_del_work; + struct sk_buff *orig_teardown_skb; /* The original teardown skb */ + struct sk_buff *teardown_skb; /* A copy to send through the AP */ + spinlock_t teardown_lock; /* To lock changing teardown_skb */ + bool tdls_chan_switch_prohibited; /* WMM-AC TSPEC support */ struct ieee80211_sta_tx_tspec tx_tspec[IEEE80211_NUM_ACS]; @@ -988,6 +993,7 @@ enum sdata_queue_type { IEEE80211_SDATA_QUEUE_AGG_STOP = 2, IEEE80211_SDATA_QUEUE_RX_AGG_START = 3, IEEE80211_SDATA_QUEUE_RX_AGG_STOP = 4, + IEEE80211_SDATA_QUEUE_TDLS_CHSW = 5, }; enum { @@ -1005,6 +1011,7 @@ enum queue_stop_reason { IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL, IEEE80211_QUEUE_STOP_REASON_FLUSH, IEEE80211_QUEUE_STOP_REASON_TDLS_TEARDOWN, + IEEE80211_QUEUE_STOP_REASON_RESERVE_TID, IEEE80211_QUEUE_STOP_REASONS, }; @@ -1231,7 +1238,7 @@ struct ieee80211_local { unsigned long scanning; struct cfg80211_ssid scan_ssid; struct cfg80211_scan_request *int_scan_req; - struct cfg80211_scan_request *scan_req; + struct cfg80211_scan_request __rcu *scan_req; struct ieee80211_scan_request *hw_scan_req; struct cfg80211_chan_def scan_chandef; enum ieee80211_band hw_scan_band; @@ -1241,7 +1248,8 @@ struct ieee80211_local { struct work_struct sched_scan_stopped_work; struct ieee80211_sub_if_data __rcu *sched_scan_sdata; - struct cfg80211_sched_scan_request *sched_scan_req; + struct cfg80211_sched_scan_request __rcu *sched_scan_req; + u8 scan_addr[ETH_ALEN]; unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; @@ -1395,6 +1403,9 @@ struct ieee802_11_elems { size_t total_len; /* pointers to IEs */ + const struct ieee80211_tdls_lnkie *lnk_id; + const struct ieee80211_ch_switch_timing *ch_sw_timing; + const u8 *ext_capab; const u8 *ssid; const u8 *supp_rates; const u8 *ds_params; @@ -1429,6 +1440,7 @@ struct ieee802_11_elems { const struct ieee80211_mesh_chansw_params_ie *mesh_chansw_params_ie; /* length of them, respectively */ + u8 ext_capab_len; u8 ssid_len; u8 supp_rates_len; u8 tim_len; @@ -1625,8 +1637,14 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev); netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev); +void __ieee80211_subif_start_xmit(struct sk_buff *skb, + struct net_device *dev, + u32 info_flags); void ieee80211_purge_tx_queue(struct ieee80211_hw *hw, struct sk_buff_head *skbs); +struct sk_buff * +ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, u32 info_flags); /* HT */ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata, @@ -1753,8 +1771,7 @@ void mac80211_ev_michael_mic_failure(struct ieee80211_sub_if_data *sdata, int ke gfp_t gfp); void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata, bool bss_notify); -void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - enum ieee80211_band band); +void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, int tid, @@ -1865,6 +1882,9 @@ void ieee80211_add_pending_skbs(struct ieee80211_local *local, struct sk_buff_head *skbs); void ieee80211_flush_queues(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); +void __ieee80211_flush_queues(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + unsigned int queues); void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, u16 transaction, u16 auth_alg, u16 status, @@ -1881,12 +1901,14 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, u8 bands_used, u32 *rate_masks, struct cfg80211_chan_def *chandef); struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, - u8 *dst, u32 ratemask, + const u8 *src, const u8 *dst, + u32 ratemask, struct ieee80211_channel *chan, const u8 *ssid, size_t ssid_len, const u8 *ie, size_t ie_len, bool directed); -void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, +void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, + const u8 *src, const u8 *dst, const u8 *ssid, size_t ssid_len, const u8 *ie, size_t ie_len, u32 ratemask, bool directed, u32 tx_flags, @@ -1992,6 +2014,14 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper); void ieee80211_tdls_peer_del_work(struct work_struct *wk); +int ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, + const u8 *addr, u8 oper_class, + struct cfg80211_chan_def *chandef); +void ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr); +void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb); extern const struct ethtool_ops ieee80211_ethtool_ops; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 9df26adb864a..538fe4ef5c85 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1208,6 +1208,8 @@ static void ieee80211_iface_work(struct work_struct *work) WLAN_BACK_RECIPIENT, 0, false); mutex_unlock(&local->sta_mtx); + } else if (skb->pkt_type == IEEE80211_SDATA_QUEUE_TDLS_CHSW) { + ieee80211_process_tdls_channel_switch(sdata, skb); } else if (ieee80211_is_action(mgmt->frame_control) && mgmt->u.action.category == WLAN_CATEGORY_BACK) { int len = skb->len; diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 282a4f36eb92..6ab99da38db9 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -764,6 +764,12 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) local->hw.offchannel_tx_hw_queue >= local->hw.queues)) return -EINVAL; + if ((hw->wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) && + (!local->ops->tdls_channel_switch || + !local->ops->tdls_cancel_channel_switch || + !local->ops->tdls_recv_channel_switch)) + return -EOPNOTSUPP; + #ifdef CONFIG_PM if (hw->wiphy->wowlan && (!local->ops->suspend || !local->ops->resume)) return -EINVAL; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 0d166e766dad..ba06cd003375 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1049,6 +1049,8 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_sub_if_data *sdata) sdata->csa_block_tx = false; } + cfg80211_ch_switch_notify(sdata->dev, &sdata->reserved_chandef); + sdata->vif.csa_active = false; ifmgd->csa_waiting_bcn = false; @@ -1205,6 +1207,9 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, IEEE80211_QUEUE_STOP_REASON_CSA); mutex_unlock(&local->mtx); + cfg80211_ch_switch_started_notify(sdata->dev, &csa_ie.chandef, + csa_ie.count); + if (local->ops->channel_switch) { /* use driver's channel switch callback */ drv_channel_switch(local, sdata, &ch_switch); @@ -2221,7 +2226,8 @@ static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata) else ssid_len = ssid[1]; - ieee80211_send_probe_req(sdata, dst, ssid + 2, ssid_len, NULL, + ieee80211_send_probe_req(sdata, sdata->vif.addr, NULL, + ssid + 2, ssid_len, NULL, 0, (u32) -1, true, 0, ifmgd->associated->channel, false); rcu_read_unlock(); @@ -2324,7 +2330,7 @@ struct sk_buff *ieee80211_ap_probereq_get(struct ieee80211_hw *hw, else ssid_len = ssid[1]; - skb = ieee80211_build_probe_req(sdata, cbss->bssid, + skb = ieee80211_build_probe_req(sdata, sdata->vif.addr, cbss->bssid, (u32) -1, cbss->channel, ssid + 2, ssid_len, NULL, 0, true); @@ -2798,6 +2804,9 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, } ifmgd->aid = aid; + ifmgd->tdls_chan_switch_prohibited = + elems.ext_capab && elems.ext_capab_len >= 5 && + (elems.ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED); /* * Some APs are erroneously not including some information in their @@ -3642,7 +3651,8 @@ static int ieee80211_probe_auth(struct ieee80211_sub_if_data *sdata) * Direct probe is sent to broadcast address as some APs * will not answer to direct packet in unassociated state. */ - ieee80211_send_probe_req(sdata, NULL, ssidie + 2, ssidie[1], + ieee80211_send_probe_req(sdata, sdata->vif.addr, NULL, + ssidie + 2, ssidie[1], NULL, 0, (u32) -1, true, 0, auth_data->bss->channel, false); rcu_read_unlock(); @@ -3999,6 +4009,11 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata) ifmgd->req_smps = IEEE80211_SMPS_AUTOMATIC; else ifmgd->req_smps = IEEE80211_SMPS_OFF; + + /* Setup TDLS data */ + spin_lock_init(&ifmgd->teardown_lock); + ifmgd->teardown_skb = NULL; + ifmgd->orig_teardown_skb = NULL; } /* scan finished notification */ @@ -4861,6 +4876,13 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata) } if (ifmgd->auth_data) ieee80211_destroy_auth_data(sdata, false); + spin_lock_bh(&ifmgd->teardown_lock); + if (ifmgd->teardown_skb) { + kfree_skb(ifmgd->teardown_skb); + ifmgd->teardown_skb = NULL; + ifmgd->orig_teardown_skb = NULL; + } + spin_unlock_bh(&ifmgd->teardown_lock); del_timer_sync(&ifmgd->timer); sdata_unlock(sdata); } diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 6081329784dd..08ab7d6d1517 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -385,7 +385,7 @@ static void rate_idx_match_mask(struct ieee80211_tx_rate *rate, *rate = alt_rate; return; } - } else { + } else if (!(rate->flags & IEEE80211_TX_RC_VHT_MCS)) { /* handle legacy rates */ if (rate_idx_match_legacy_mask(rate, sband->n_bitrates, mask)) return; @@ -696,6 +696,7 @@ int rate_control_set_rates(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, struct ieee80211_sta_rates *rates) { + struct sta_info *sta = container_of(pubsta, struct sta_info, sta); struct ieee80211_sta_rates *old; /* @@ -709,6 +710,8 @@ int rate_control_set_rates(struct ieee80211_hw *hw, if (old) kfree_rcu(old, rcu_head); + drv_sta_rate_tbl_update(hw_to_local(hw), sta->sdata, pubsta); + return 0; } EXPORT_SYMBOL(rate_control_set_rates); diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index c50fd94d2aef..62ff7cfb2723 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -690,6 +690,9 @@ minstrel_aggr_check(struct ieee80211_sta *pubsta, struct sk_buff *skb) struct sta_info *sta = container_of(pubsta, struct sta_info, sta); u16 tid; + if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO) + return; + if (unlikely(!ieee80211_is_data_qos(hdr->frame_control))) return; @@ -700,9 +703,6 @@ minstrel_aggr_check(struct ieee80211_sta *pubsta, struct sk_buff *skb) if (likely(sta->ampdu_mlme.tid_tx[tid])) return; - if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO) - return; - ieee80211_start_tx_ba_session(pubsta, tid, 5000); } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index a726bb169302..49c23bdf08bb 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -39,7 +39,8 @@ * only useful for monitoring. */ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local, - struct sk_buff *skb) + struct sk_buff *skb, + unsigned int rtap_vendor_space) { if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) { if (likely(skb->len > FCS_LEN)) @@ -52,20 +53,25 @@ static struct sk_buff *remove_monitor_info(struct ieee80211_local *local, } } + __pskb_pull(skb, rtap_vendor_space); + return skb; } -static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len) +static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len, + unsigned int rtap_vendor_space) { struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - struct ieee80211_hdr *hdr = (void *)skb->data; + struct ieee80211_hdr *hdr; + + hdr = (void *)(skb->data + rtap_vendor_space); if (status->flag & (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_FAILED_PLCP_CRC | RX_FLAG_AMPDU_IS_ZEROLEN)) return true; - if (unlikely(skb->len < 16 + present_fcs_len)) + if (unlikely(skb->len < 16 + present_fcs_len + rtap_vendor_space)) return true; if (ieee80211_is_ctl(hdr->frame_control) && @@ -77,8 +83,9 @@ static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len) } static int -ieee80211_rx_radiotap_space(struct ieee80211_local *local, - struct ieee80211_rx_status *status) +ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local, + struct ieee80211_rx_status *status, + struct sk_buff *skb) { int len; @@ -121,6 +128,21 @@ ieee80211_rx_radiotap_space(struct ieee80211_local *local, len += 2 * hweight8(status->chains); } + if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + struct ieee80211_vendor_radiotap *rtap = (void *)skb->data; + + /* vendor presence bitmap */ + len += 4; + /* alignment for fixed 6-byte vendor data header */ + len = ALIGN(len, 2); + /* vendor data header */ + len += 6; + if (WARN_ON(rtap->align == 0)) + rtap->align = 1; + len = ALIGN(len, rtap->align); + len += rtap->len + rtap->pad; + } + return len; } @@ -144,13 +166,20 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, u16 channel_flags = 0; int mpdulen, chain; unsigned long chains = status->chains; + struct ieee80211_vendor_radiotap rtap = {}; + + if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + rtap = *(struct ieee80211_vendor_radiotap *)skb->data; + /* rtap.len and rtap.pad are undone immediately */ + skb_pull(skb, sizeof(rtap) + rtap.len + rtap.pad); + } mpdulen = skb->len; if (!(has_fcs && (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS))) mpdulen += FCS_LEN; rthdr = (struct ieee80211_radiotap_header *)skb_push(skb, rtap_len); - memset(rthdr, 0, rtap_len); + memset(rthdr, 0, rtap_len - rtap.len - rtap.pad); it_present = &rthdr->it_present; /* radiotap header, set always present flags */ @@ -172,6 +201,14 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL); } + if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | + BIT(IEEE80211_RADIOTAP_EXT); + put_unaligned_le32(it_present_val, it_present); + it_present++; + it_present_val = rtap.present; + } + put_unaligned_le32(it_present_val, it_present); pos = (void *)(it_present + 1); @@ -366,6 +403,22 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, *pos++ = status->chain_signal[chain]; *pos++ = chain; } + + if (status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + /* ensure 2 byte alignment for the vendor field as required */ + if ((pos - (u8 *)rthdr) & 1) + *pos++ = 0; + *pos++ = rtap.oui[0]; + *pos++ = rtap.oui[1]; + *pos++ = rtap.oui[2]; + *pos++ = rtap.subns; + put_unaligned_le16(rtap.len, pos); + pos += 2; + /* align the actual payload as requested */ + while ((pos - (u8 *)rthdr) & (rtap.align - 1)) + *pos++ = 0; + /* data (and possible padding) already follows */ + } } /* @@ -379,10 +432,17 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb, { struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(origskb); struct ieee80211_sub_if_data *sdata; - int needed_headroom; + int rt_hdrlen, needed_headroom; struct sk_buff *skb, *skb2; struct net_device *prev_dev = NULL; int present_fcs_len = 0; + unsigned int rtap_vendor_space = 0; + + if (unlikely(status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA)) { + struct ieee80211_vendor_radiotap *rtap = (void *)origskb->data; + + rtap_vendor_space = sizeof(*rtap) + rtap->len + rtap->pad; + } /* * First, we may need to make a copy of the skb because @@ -396,25 +456,27 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb, if (local->hw.flags & IEEE80211_HW_RX_INCLUDES_FCS) present_fcs_len = FCS_LEN; - /* ensure hdr->frame_control is in skb head */ - if (!pskb_may_pull(origskb, 2)) { + /* ensure hdr->frame_control and vendor radiotap data are in skb head */ + if (!pskb_may_pull(origskb, 2 + rtap_vendor_space)) { dev_kfree_skb(origskb); return NULL; } if (!local->monitors) { - if (should_drop_frame(origskb, present_fcs_len)) { + if (should_drop_frame(origskb, present_fcs_len, + rtap_vendor_space)) { dev_kfree_skb(origskb); return NULL; } - return remove_monitor_info(local, origskb); + return remove_monitor_info(local, origskb, rtap_vendor_space); } /* room for the radiotap header based on driver features */ - needed_headroom = ieee80211_rx_radiotap_space(local, status); + rt_hdrlen = ieee80211_rx_radiotap_hdrlen(local, status, origskb); + needed_headroom = rt_hdrlen - rtap_vendor_space; - if (should_drop_frame(origskb, present_fcs_len)) { + if (should_drop_frame(origskb, present_fcs_len, rtap_vendor_space)) { /* only need to expand headroom if necessary */ skb = origskb; origskb = NULL; @@ -438,15 +500,15 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb, */ skb = skb_copy_expand(origskb, needed_headroom, 0, GFP_ATOMIC); - origskb = remove_monitor_info(local, origskb); + origskb = remove_monitor_info(local, origskb, + rtap_vendor_space); if (!skb) return origskb; } /* prepend radiotap information */ - ieee80211_add_rx_radiotap_header(local, skb, rate, needed_headroom, - true); + ieee80211_add_rx_radiotap_header(local, skb, rate, rt_hdrlen, true); skb_reset_mac_header(skb); skb->ip_summed = CHECKSUM_UNNECESSARY; @@ -985,7 +1047,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx, } static ieee80211_rx_result debug_noinline -ieee80211_rx_h_check(struct ieee80211_rx_data *rx) +ieee80211_rx_h_check_dup(struct ieee80211_rx_data *rx) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); @@ -994,10 +1056,16 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx) * Drop duplicate 802.11 retransmissions * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") */ - if (rx->skb->len >= 24 && rx->sta && - !ieee80211_is_ctl(hdr->frame_control) && - !ieee80211_is_qos_nullfunc(hdr->frame_control) && - !is_multicast_ether_addr(hdr->addr1)) { + + if (rx->skb->len < 24) + return RX_CONTINUE; + + if (ieee80211_is_ctl(hdr->frame_control) || + ieee80211_is_qos_nullfunc(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) + return RX_CONTINUE; + + if (rx->sta) { if (unlikely(ieee80211_has_retry(hdr->frame_control) && rx->sta->last_seq_ctrl[rx->seqno_idx] == hdr->seq_ctrl)) { @@ -1011,6 +1079,14 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx) } } + return RX_CONTINUE; +} + +static ieee80211_rx_result debug_noinline +ieee80211_rx_h_check(struct ieee80211_rx_data *rx) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; + if (unlikely(rx->skb->len < 16)) { I802_DEBUG_INC(rx->local->rx_handlers_drop_short); return RX_DROP_MONITOR; @@ -2257,6 +2333,27 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx) if (!ieee80211_frame_allowed(rx, fc)) return RX_DROP_MONITOR; + /* directly handle TDLS channel switch requests/responses */ + if (unlikely(((struct ethhdr *)rx->skb->data)->h_proto == + cpu_to_be16(ETH_P_TDLS))) { + struct ieee80211_tdls_data *tf = (void *)rx->skb->data; + + if (pskb_may_pull(rx->skb, + offsetof(struct ieee80211_tdls_data, u)) && + tf->payload_type == WLAN_TDLS_SNAP_RFTYPE && + tf->category == WLAN_CATEGORY_TDLS && + (tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST || + tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) { + rx->skb->pkt_type = IEEE80211_SDATA_QUEUE_TDLS_CHSW; + skb_queue_tail(&sdata->skb_queue, rx->skb); + ieee80211_queue_work(&rx->local->hw, &sdata->work); + if (rx->sta) + rx->sta->rx_packets++; + + return RX_QUEUED; + } + } + if (rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN && unlikely(port_control) && sdata->bss) { sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, @@ -2892,8 +2989,10 @@ static void ieee80211_rx_cooked_monitor(struct ieee80211_rx_data *rx, if (!local->cooked_mntrs) goto out_free_skb; + /* vendor data is long removed here */ + status->flag &= ~RX_FLAG_RADIOTAP_VENDOR_DATA; /* room for the radiotap header based on driver features */ - needed_headroom = ieee80211_rx_radiotap_space(local, status); + needed_headroom = ieee80211_rx_radiotap_hdrlen(local, status, skb); if (skb_headroom(skb) < needed_headroom && pskb_expand_head(skb, needed_headroom, 0, GFP_ATOMIC)) @@ -3046,6 +3145,7 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx) goto rxh_next; \ } while (0); + CALL_RXH(ieee80211_rx_h_check_dup) CALL_RXH(ieee80211_rx_h_check) ieee80211_rx_reorder_ampdu(rx, &reorder_release); diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index af0d094b2f2f..ae842678b629 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -184,9 +184,21 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb) return; if (ieee80211_is_probe_resp(mgmt->frame_control)) { - /* ignore ProbeResp to foreign address */ - if ((!sdata1 || !ether_addr_equal(mgmt->da, sdata1->vif.addr)) && - (!sdata2 || !ether_addr_equal(mgmt->da, sdata2->vif.addr))) + struct cfg80211_scan_request *scan_req; + struct cfg80211_sched_scan_request *sched_scan_req; + + scan_req = rcu_dereference(local->scan_req); + sched_scan_req = rcu_dereference(local->sched_scan_req); + + /* ignore ProbeResp to foreign address unless scanning + * with randomised address + */ + if (!(sdata1 && + (ether_addr_equal(mgmt->da, sdata1->vif.addr) || + scan_req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR)) && + !(sdata2 && + (ether_addr_equal(mgmt->da, sdata2->vif.addr) || + sched_scan_req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR))) return; elements = mgmt->u.probe_resp.variable; @@ -234,11 +246,14 @@ ieee80211_prepare_scan_chandef(struct cfg80211_chan_def *chandef, /* return false if no more work */ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) { - struct cfg80211_scan_request *req = local->scan_req; + struct cfg80211_scan_request *req; struct cfg80211_chan_def chandef; u8 bands_used = 0; int i, ielen, n_chans; + req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); + if (test_bit(SCAN_HW_CANCELLED, &local->scanning)) return false; @@ -281,6 +296,9 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) bands_used, req->rates, &chandef); local->hw_scan_req->req.ie_len = ielen; local->hw_scan_req->req.no_cck = req->no_cck; + ether_addr_copy(local->hw_scan_req->req.mac_addr, req->mac_addr); + ether_addr_copy(local->hw_scan_req->req.mac_addr_mask, + req->mac_addr_mask); return true; } @@ -290,6 +308,8 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) struct ieee80211_local *local = hw_to_local(hw); bool hw_scan = local->ops->hw_scan; bool was_scanning = local->scanning; + struct cfg80211_scan_request *scan_req; + struct ieee80211_sub_if_data *scan_sdata; lockdep_assert_held(&local->mtx); @@ -322,9 +342,15 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) kfree(local->hw_scan_req); local->hw_scan_req = NULL; - if (local->scan_req != local->int_scan_req) - cfg80211_scan_done(local->scan_req, aborted); - local->scan_req = NULL; + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); + + if (scan_req != local->int_scan_req) + cfg80211_scan_done(scan_req, aborted); + RCU_INIT_POINTER(local->scan_req, NULL); + + scan_sdata = rcu_dereference_protected(local->scan_sdata, + lockdep_is_held(&local->mtx)); RCU_INIT_POINTER(local->scan_sdata, NULL); local->scanning = 0; @@ -335,7 +361,7 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) if (!hw_scan) { ieee80211_configure_filter(local); - drv_sw_scan_complete(local); + drv_sw_scan_complete(local, scan_sdata); ieee80211_offchannel_return(local); } @@ -361,7 +387,8 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) } EXPORT_SYMBOL(ieee80211_scan_completed); -static int ieee80211_start_sw_scan(struct ieee80211_local *local) +static int ieee80211_start_sw_scan(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) { /* Software scan is not supported in multi-channel cases */ if (local->use_chanctx) @@ -380,7 +407,7 @@ static int ieee80211_start_sw_scan(struct ieee80211_local *local) * nullfunc frames and probe requests will be dropped in * ieee80211_tx_h_check_assoc(). */ - drv_sw_scan_start(local); + drv_sw_scan_start(local, sdata, local->scan_addr); local->leave_oper_channel_time = jiffies; local->next_scan_state = SCAN_DECISION; @@ -440,23 +467,26 @@ static void ieee80211_scan_state_send_probe(struct ieee80211_local *local, { int i; struct ieee80211_sub_if_data *sdata; + struct cfg80211_scan_request *scan_req; enum ieee80211_band band = local->hw.conf.chandef.chan->band; u32 tx_flags; + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); + tx_flags = IEEE80211_TX_INTFL_OFFCHAN_TX_OK; - if (local->scan_req->no_cck) + if (scan_req->no_cck) tx_flags |= IEEE80211_TX_CTL_NO_CCK_RATE; sdata = rcu_dereference_protected(local->scan_sdata, lockdep_is_held(&local->mtx)); - for (i = 0; i < local->scan_req->n_ssids; i++) + for (i = 0; i < scan_req->n_ssids; i++) ieee80211_send_probe_req( - sdata, NULL, - local->scan_req->ssids[i].ssid, - local->scan_req->ssids[i].ssid_len, - local->scan_req->ie, local->scan_req->ie_len, - local->scan_req->rates[band], false, + sdata, local->scan_addr, NULL, + scan_req->ssids[i].ssid, scan_req->ssids[i].ssid_len, + scan_req->ie, scan_req->ie_len, + scan_req->rates[band], false, tx_flags, local->hw.conf.chandef.chan, true); /* @@ -480,7 +510,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, if (!ieee80211_can_scan(local, sdata)) { /* wait for the work to finish/time out */ - local->scan_req = req; + rcu_assign_pointer(local->scan_req, req); rcu_assign_pointer(local->scan_sdata, sdata); return 0; } @@ -530,9 +560,16 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, */ } - local->scan_req = req; + rcu_assign_pointer(local->scan_req, req); rcu_assign_pointer(local->scan_sdata, sdata); + if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + get_random_mask_addr(local->scan_addr, + req->mac_addr, + req->mac_addr_mask); + else + memcpy(local->scan_addr, sdata->vif.addr, ETH_ALEN); + if (local->ops->hw_scan) { __set_bit(SCAN_HW_SCANNING, &local->scanning); } else if ((req->n_channels == 1) && @@ -549,7 +586,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, /* Notify driver scan is starting, keep order of operations * same as normal software scan, in case that matters. */ - drv_sw_scan_start(local); + drv_sw_scan_start(local, sdata, local->scan_addr); ieee80211_configure_filter(local); /* accept probe-responses */ @@ -558,7 +595,7 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, if ((req->channels[0]->flags & IEEE80211_CHAN_NO_IR) || - !local->scan_req->n_ssids) { + !req->n_ssids) { next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; } else { ieee80211_scan_state_send_probe(local, &next_delay); @@ -579,8 +616,9 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, if (local->ops->hw_scan) { WARN_ON(!ieee80211_prep_hw_scan(local)); rc = drv_hw_scan(local, sdata, local->hw_scan_req); - } else - rc = ieee80211_start_sw_scan(local); + } else { + rc = ieee80211_start_sw_scan(local, sdata); + } if (rc) { kfree(local->hw_scan_req); @@ -617,6 +655,7 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata; struct ieee80211_channel *next_chan; enum mac80211_scan_state next_scan_state; + struct cfg80211_scan_request *scan_req; /* * check if at least one STA interface is associated, @@ -641,7 +680,10 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local, } mutex_unlock(&local->iflist_mtx); - next_chan = local->scan_req->channels[local->scan_channel_idx]; + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); + + next_chan = scan_req->channels[local->scan_channel_idx]; /* * we're currently scanning a different channel, let's @@ -656,7 +698,7 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local, local->leave_oper_channel_time + HZ / 8); if (associated && !tx_empty) { - if (local->scan_req->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) + if (scan_req->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) next_scan_state = SCAN_ABORT; else next_scan_state = SCAN_SUSPEND; @@ -677,14 +719,18 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, int skip; struct ieee80211_channel *chan; enum nl80211_bss_scan_width oper_scan_width; + struct cfg80211_scan_request *scan_req; + + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); skip = 0; - chan = local->scan_req->channels[local->scan_channel_idx]; + chan = scan_req->channels[local->scan_channel_idx]; local->scan_chandef.chan = chan; local->scan_chandef.center_freq1 = chan->center_freq; local->scan_chandef.center_freq2 = 0; - switch (local->scan_req->scan_width) { + switch (scan_req->scan_width) { case NL80211_BSS_CHAN_WIDTH_5: local->scan_chandef.width = NL80211_CHAN_WIDTH_5; break; @@ -698,7 +744,7 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, oper_scan_width = cfg80211_chandef_to_scan_width( &local->_oper_chandef); if (chan == local->_oper_chandef.chan && - oper_scan_width == local->scan_req->scan_width) + oper_scan_width == scan_req->scan_width) local->scan_chandef = local->_oper_chandef; else local->scan_chandef.width = NL80211_CHAN_WIDTH_20_NOHT; @@ -727,8 +773,7 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, * * In any case, it is not necessary for a passive scan. */ - if (chan->flags & IEEE80211_CHAN_NO_IR || - !local->scan_req->n_ssids) { + if (chan->flags & IEEE80211_CHAN_NO_IR || !scan_req->n_ssids) { *next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; local->next_scan_state = SCAN_DECISION; return; @@ -777,6 +822,7 @@ void ieee80211_scan_work(struct work_struct *work) struct ieee80211_local *local = container_of(work, struct ieee80211_local, scan_work.work); struct ieee80211_sub_if_data *sdata; + struct cfg80211_scan_request *scan_req; unsigned long next_delay = 0; bool aborted; @@ -784,6 +830,8 @@ void ieee80211_scan_work(struct work_struct *work) sdata = rcu_dereference_protected(local->scan_sdata, lockdep_is_held(&local->mtx)); + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); /* When scanning on-channel, the first-callback means completed. */ if (test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning)) { @@ -796,20 +844,19 @@ void ieee80211_scan_work(struct work_struct *work) goto out_complete; } - if (!sdata || !local->scan_req) + if (!sdata || !scan_req) goto out; - if (local->scan_req && !local->scanning) { - struct cfg80211_scan_request *req = local->scan_req; + if (!local->scanning) { int rc; - local->scan_req = NULL; + RCU_INIT_POINTER(local->scan_req, NULL); RCU_INIT_POINTER(local->scan_sdata, NULL); - rc = __ieee80211_start_scan(sdata, req); + rc = __ieee80211_start_scan(sdata, scan_req); if (rc) { /* need to complete scan in cfg80211 */ - local->scan_req = req; + rcu_assign_pointer(local->scan_req, scan_req); aborted = true; goto out_complete; } else @@ -829,7 +876,7 @@ void ieee80211_scan_work(struct work_struct *work) switch (local->next_scan_state) { case SCAN_DECISION: /* if no more bands/channels left, complete scan */ - if (local->scan_channel_idx >= local->scan_req->n_channels) { + if (local->scan_channel_idx >= scan_req->n_channels) { aborted = false; goto out_complete; } @@ -1043,7 +1090,7 @@ int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, ret = drv_sched_scan_start(local, sdata, req, &sched_scan_ies); if (ret == 0) { rcu_assign_pointer(local->sched_scan_sdata, sdata); - local->sched_scan_req = req; + rcu_assign_pointer(local->sched_scan_req, req); } kfree(ie); @@ -1052,7 +1099,7 @@ out: if (ret) { /* Clean in case of failure after HW restart or upon resume. */ RCU_INIT_POINTER(local->sched_scan_sdata, NULL); - local->sched_scan_req = NULL; + RCU_INIT_POINTER(local->sched_scan_req, NULL); } return ret; @@ -1090,7 +1137,7 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata) } /* We don't want to restart sched scan anymore. */ - local->sched_scan_req = NULL; + RCU_INIT_POINTER(local->sched_scan_req, NULL); if (rcu_access_pointer(local->sched_scan_sdata)) { ret = drv_sched_scan_stop(local, sdata); @@ -1125,7 +1172,7 @@ void ieee80211_sched_scan_end(struct ieee80211_local *local) RCU_INIT_POINTER(local->sched_scan_sdata, NULL); /* If sched scan was aborted by the driver. */ - local->sched_scan_req = NULL; + RCU_INIT_POINTER(local->sched_scan_req, NULL); mutex_unlock(&local->mtx); diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index adc25371b171..a42f5b2b024d 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -351,6 +351,9 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, sta->sta_state = IEEE80211_STA_NONE; + /* Mark TID as unreserved */ + sta->reserved_tid = IEEE80211_TID_UNRESERVED; + ktime_get_ts(&uptime); sta->last_connected = uptime.tv_sec; ewma_init(&sta->avg_signal, 1024, 8); @@ -847,6 +850,15 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta) if (WARN_ON(ret)) return ret; + /* + * for TDLS peers, make sure to return to the base channel before + * removal. + */ + if (test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) { + drv_tdls_cancel_channel_switch(local, sdata, &sta->sta); + clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); + } + list_del_rcu(&sta->list); drv_sta_pre_rcu_remove(local, sta->sdata, sta); @@ -1249,7 +1261,8 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, return; } - ieee80211_xmit(sdata, skb, chanctx_conf->def.chan->band); + info->band = chanctx_conf->def.chan->band; + ieee80211_xmit(sdata, skb); rcu_read_unlock(); } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index bcda2ac7d844..4f052bb2a5ad 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -49,6 +49,9 @@ * packets. This means the link is enabled. * @WLAN_STA_TDLS_INITIATOR: We are the initiator of the TDLS link with this * station. + * @WLAN_STA_TDLS_CHAN_SWITCH: This TDLS peer supports TDLS channel-switching + * @WLAN_STA_TDLS_OFF_CHANNEL: The local STA is currently off-channel with this + * TDLS peer * @WLAN_STA_UAPSD: Station requested unscheduled SP while driver was * keeping station in power-save mode, reply when the driver * unblocks the station. @@ -78,6 +81,8 @@ enum ieee80211_sta_info_flags { WLAN_STA_TDLS_PEER, WLAN_STA_TDLS_PEER_AUTH, WLAN_STA_TDLS_INITIATOR, + WLAN_STA_TDLS_CHAN_SWITCH, + WLAN_STA_TDLS_OFF_CHANNEL, WLAN_STA_UAPSD, WLAN_STA_SP, WLAN_STA_4ADDR_EVENT, @@ -249,6 +254,9 @@ struct ieee80211_tx_latency_stat { u32 bin_count; }; +/* Value to indicate no TID reservation */ +#define IEEE80211_TID_UNRESERVED 0xff + /** * struct sta_info - STA information * @@ -337,6 +345,7 @@ struct ieee80211_tx_latency_stat { * AP only. * @cipher_scheme: optional cipher scheme for this station * @last_tdls_pkt_time: holds the time in jiffies of last TDLS pkt ACKed + * @reserved_tid: reserved TID (if any, otherwise IEEE80211_TID_UNRESERVED) */ struct sta_info { /* General information, mostly static */ @@ -454,6 +463,8 @@ struct sta_info { /* TDLS timeout data */ unsigned long last_tdls_pkt_time; + u8 reserved_tid; + /* keep last! */ struct ieee80211_sta sta; }; diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 9612d89fad56..71de2d3866cc 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -390,6 +390,46 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_local *local, } } +/* + * Handles the tx for TDLS teardown frames. + * If the frame wasn't ACKed by the peer - it will be re-sent through the AP + */ +static void ieee80211_tdls_td_tx_handle(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, u32 flags) +{ + struct sk_buff *teardown_skb; + struct sk_buff *orig_teardown_skb; + bool is_teardown = false; + + /* Get the teardown data we need and free the lock */ + spin_lock(&sdata->u.mgd.teardown_lock); + teardown_skb = sdata->u.mgd.teardown_skb; + orig_teardown_skb = sdata->u.mgd.orig_teardown_skb; + if ((skb == orig_teardown_skb) && teardown_skb) { + sdata->u.mgd.teardown_skb = NULL; + sdata->u.mgd.orig_teardown_skb = NULL; + is_teardown = true; + } + spin_unlock(&sdata->u.mgd.teardown_lock); + + if (is_teardown) { + /* This mechanism relies on being able to get ACKs */ + WARN_ON(!(local->hw.flags & + IEEE80211_HW_REPORTS_TX_ACK_STATUS)); + + /* Check if peer has ACKed */ + if (flags & IEEE80211_TX_STAT_ACK) { + dev_kfree_skb_any(teardown_skb); + } else { + tdls_dbg(sdata, + "TDLS Resending teardown through AP\n"); + + ieee80211_subif_start_xmit(teardown_skb, skb->dev); + } + } +} + static void ieee80211_report_used_skb(struct ieee80211_local *local, struct sk_buff *skb, bool dropped) { @@ -426,8 +466,19 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, if (!sdata) { skb->dev = NULL; } else if (info->flags & IEEE80211_TX_INTFL_MLME_CONN_TX) { - ieee80211_mgd_conn_tx_status(sdata, hdr->frame_control, - acked); + unsigned int hdr_size = + ieee80211_hdrlen(hdr->frame_control); + + /* Check to see if packet is a TDLS teardown packet */ + if (ieee80211_is_data(hdr->frame_control) && + (ieee80211_get_tdls_action(skb, hdr_size) == + WLAN_TDLS_TEARDOWN)) + ieee80211_tdls_td_tx_handle(local, sdata, skb, + info->flags); + else + ieee80211_mgd_conn_tx_status(sdata, + hdr->frame_control, + acked); } else if (ieee80211_is_nullfunc(hdr->frame_control) || ieee80211_is_qos_nullfunc(hdr->frame_control)) { cfg80211_probe_status(sdata->dev, hdr->addr1, diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index b4f368e2cb3b..55ddd77b865d 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -35,19 +35,101 @@ void ieee80211_tdls_peer_del_work(struct work_struct *wk) mutex_unlock(&local->mtx); } -static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb) +static void ieee80211_tdls_add_ext_capab(struct ieee80211_local *local, + struct sk_buff *skb) { u8 *pos = (void *)skb_put(skb, 7); + bool chan_switch = local->hw.wiphy->features & + NL80211_FEATURE_TDLS_CHANNEL_SWITCH; *pos++ = WLAN_EID_EXT_CAPABILITY; *pos++ = 5; /* len */ *pos++ = 0x0; *pos++ = 0x0; *pos++ = 0x0; - *pos++ = 0x0; + *pos++ = chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0; *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; } +static u8 +ieee80211_tdls_add_subband(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, u16 start, u16 end, + u16 spacing) +{ + u8 subband_cnt = 0, ch_cnt = 0; + struct ieee80211_channel *ch; + struct cfg80211_chan_def chandef; + int i, subband_start; + + for (i = start; i <= end; i += spacing) { + if (!ch_cnt) + subband_start = i; + + ch = ieee80211_get_channel(sdata->local->hw.wiphy, i); + if (ch) { + /* we will be active on the channel */ + u32 flags = IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_NO_IR; + cfg80211_chandef_create(&chandef, ch, + NL80211_CHAN_HT20); + if (cfg80211_chandef_usable(sdata->local->hw.wiphy, + &chandef, flags)) { + ch_cnt++; + continue; + } + } + + if (ch_cnt) { + u8 *pos = skb_put(skb, 2); + *pos++ = ieee80211_frequency_to_channel(subband_start); + *pos++ = ch_cnt; + + subband_cnt++; + ch_cnt = 0; + } + } + + return subband_cnt; +} + +static void +ieee80211_tdls_add_supp_channels(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + /* + * Add possible channels for TDLS. These are channels that are allowed + * to be active. + */ + u8 subband_cnt; + u8 *pos = skb_put(skb, 2); + + *pos++ = WLAN_EID_SUPPORTED_CHANNELS; + + /* + * 5GHz and 2GHz channels numbers can overlap. Ignore this for now, as + * this doesn't happen in real world scenarios. + */ + + /* 2GHz, with 5MHz spacing */ + subband_cnt = ieee80211_tdls_add_subband(sdata, skb, 2412, 2472, 5); + + /* 5GHz, with 20MHz spacing */ + subband_cnt += ieee80211_tdls_add_subband(sdata, skb, 5000, 5825, 20); + + /* length */ + *pos = 2 * subband_cnt; +} + +static void ieee80211_tdls_add_bss_coex_ie(struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 3); + + *pos++ = WLAN_EID_BSS_COEX_2040; + *pos++ = 1; /* len */ + + *pos++ = WLAN_BSS_COEX_INFORMATION_REQUEST; +} + static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata, u16 status_code) { @@ -190,6 +272,7 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata, ieee80211_add_srates_ie(sdata, skb, false, band); ieee80211_add_ext_srates_ie(sdata, skb, false, band); + ieee80211_tdls_add_supp_channels(sdata, skb); /* add any custom IEs that go before Extended Capabilities */ if (extra_ies_len) { @@ -209,7 +292,7 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata, offset = noffset; } - ieee80211_tdls_add_ext_capab(skb); + ieee80211_tdls_add_ext_capab(local, skb); /* add the QoS element if we support it */ if (local->hw.queues >= IEEE80211_NUM_ACS && @@ -271,6 +354,10 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata, rcu_read_unlock(); + if (ht_cap.ht_supported && + (ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ieee80211_tdls_add_bss_coex_ie(skb); + /* add any remaining IEs */ if (extra_ies_len) { noffset = extra_ies_len; @@ -362,11 +449,68 @@ ieee80211_tdls_add_setup_cfm_ies(struct ieee80211_sub_if_data *sdata, ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); } +static void +ieee80211_tdls_add_chan_switch_req_ies(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, const u8 *peer, + bool initiator, const u8 *extra_ies, + size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_tdls_data *tf; + size_t offset = 0, noffset; + u8 *pos; + + if (WARN_ON_ONCE(!chandef)) + return; + + tf = (void *)skb->data; + tf->u.chan_switch_req.target_channel = + ieee80211_frequency_to_channel(chandef->chan->center_freq); + tf->u.chan_switch_req.oper_class = oper_class; + + if (extra_ies_len) { + static const u8 before_lnkie[] = { + WLAN_EID_SECONDARY_CHANNEL_OFFSET, + }; + noffset = ieee80211_ie_split(extra_ies, extra_ies_len, + before_lnkie, + ARRAY_SIZE(before_lnkie), + offset); + pos = skb_put(skb, noffset - offset); + memcpy(pos, extra_ies + offset, noffset - offset); + offset = noffset; + } + + ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); + + /* add any remaining IEs */ + if (extra_ies_len) { + noffset = extra_ies_len; + pos = skb_put(skb, noffset - offset); + memcpy(pos, extra_ies + offset, noffset - offset); + } +} + +static void +ieee80211_tdls_add_chan_switch_resp_ies(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, const u8 *peer, + u16 status_code, bool initiator, + const u8 *extra_ies, + size_t extra_ies_len) +{ + if (status_code == 0) + ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); + + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); +} + static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, const u8 *peer, u8 action_code, u16 status_code, bool initiator, const u8 *extra_ies, - size_t extra_ies_len) + size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) { switch (action_code) { case WLAN_TDLS_SETUP_REQUEST: @@ -393,6 +537,18 @@ static void ieee80211_tdls_add_ies(struct ieee80211_sub_if_data *sdata, if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN) ieee80211_tdls_add_link_ie(sdata, skb, peer, initiator); break; + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: + ieee80211_tdls_add_chan_switch_req_ies(sdata, skb, peer, + initiator, extra_ies, + extra_ies_len, + oper_class, chandef); + break; + case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE: + ieee80211_tdls_add_chan_switch_resp_ies(sdata, skb, peer, + status_code, + initiator, extra_ies, + extra_ies_len); + break; } } @@ -459,6 +615,19 @@ ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, skb_put(skb, sizeof(tf->u.discover_req)); tf->u.discover_req.dialog_token = dialog_token; break; + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST; + + skb_put(skb, sizeof(tf->u.chan_switch_req)); + break; + case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE; + + skb_put(skb, sizeof(tf->u.chan_switch_resp)); + tf->u.chan_switch_resp.status_code = cpu_to_le16(status_code); + break; default: return -EINVAL; } @@ -502,32 +671,33 @@ ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int -ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u8 action_code, - u8 dialog_token, u16 status_code, - u32 peer_capability, bool initiator, - const u8 *extra_ies, size_t extra_ies_len) +static struct sk_buff * +ieee80211_tdls_build_mgmt_packet_data(struct ieee80211_sub_if_data *sdata, + const u8 *peer, u8 action_code, + u8 dialog_token, u16 status_code, + bool initiator, const u8 *extra_ies, + size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; - struct sk_buff *skb = NULL; - bool send_direct; - struct sta_info *sta; + struct sk_buff *skb; int ret; - skb = dev_alloc_skb(local->hw.extra_tx_headroom + - max(sizeof(struct ieee80211_mgmt), - sizeof(struct ieee80211_tdls_data)) + - 50 + /* supported rates */ - 7 + /* ext capab */ - 26 + /* max(WMM-info, WMM-param) */ - 2 + max(sizeof(struct ieee80211_ht_cap), - sizeof(struct ieee80211_ht_operation)) + - extra_ies_len + - sizeof(struct ieee80211_tdls_lnkie)); + skb = netdev_alloc_skb(sdata->dev, + local->hw.extra_tx_headroom + + max(sizeof(struct ieee80211_mgmt), + sizeof(struct ieee80211_tdls_data)) + + 50 + /* supported rates */ + 7 + /* ext capab */ + 26 + /* max(WMM-info, WMM-param) */ + 2 + max(sizeof(struct ieee80211_ht_cap), + sizeof(struct ieee80211_ht_operation)) + + 50 + /* supported channels */ + 3 + /* 40/20 BSS coex */ + extra_ies_len + + sizeof(struct ieee80211_tdls_lnkie)); if (!skb) - return -ENOMEM; + return NULL; skb_reserve(skb, local->hw.extra_tx_headroom); @@ -537,16 +707,18 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, case WLAN_TDLS_SETUP_CONFIRM: case WLAN_TDLS_TEARDOWN: case WLAN_TDLS_DISCOVERY_REQUEST: - ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer, + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: + case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE: + ret = ieee80211_prep_tdls_encap_data(local->hw.wiphy, + sdata->dev, peer, action_code, dialog_token, status_code, skb); - send_direct = false; break; case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: - ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code, + ret = ieee80211_prep_tdls_direct(local->hw.wiphy, sdata->dev, + peer, action_code, dialog_token, status_code, skb); - send_direct = true; break; default: ret = -ENOTSUPP; @@ -556,6 +728,30 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, if (ret < 0) goto fail; + ieee80211_tdls_add_ies(sdata, skb, peer, action_code, status_code, + initiator, extra_ies, extra_ies_len, oper_class, + chandef); + return skb; + +fail: + dev_kfree_skb(skb); + return NULL; +} + +static int +ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, + bool initiator, const u8 *extra_ies, + size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct sk_buff *skb = NULL; + struct sta_info *sta; + u32 flags = 0; + int ret = 0; + rcu_read_lock(); sta = sta_info_get(sdata, peer); @@ -586,6 +782,8 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, initiator = false; break; case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: + case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE: /* any value is ok */ break; default: @@ -600,9 +798,17 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, if (ret < 0) goto fail; - ieee80211_tdls_add_ies(sdata, skb, peer, action_code, status_code, - initiator, extra_ies, extra_ies_len); - if (send_direct) { + skb = ieee80211_tdls_build_mgmt_packet_data(sdata, peer, action_code, + dialog_token, status_code, + initiator, extra_ies, + extra_ies_len, oper_class, + chandef); + if (!skb) { + ret = -EINVAL; + goto fail; + } + + if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { ieee80211_tx_skb(sdata, skb); return 0; } @@ -623,9 +829,44 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, break; } + /* + * Set the WLAN_TDLS_TEARDOWN flag to indicate a teardown in progress. + * Later, if no ACK is returned from peer, we will re-send the teardown + * packet through the AP. + */ + if ((action_code == WLAN_TDLS_TEARDOWN) && + (sdata->local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) { + struct sta_info *sta = NULL; + bool try_resend; /* Should we keep skb for possible resend */ + + /* If not sending directly to peer - no point in keeping skb */ + rcu_read_lock(); + sta = sta_info_get(sdata, peer); + try_resend = sta && test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH); + rcu_read_unlock(); + + spin_lock_bh(&sdata->u.mgd.teardown_lock); + if (try_resend && !sdata->u.mgd.teardown_skb) { + /* Mark it as requiring TX status callback */ + flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | + IEEE80211_TX_INTFL_MLME_CONN_TX; + + /* + * skb is copied since mac80211 will later set + * properties that might not be the same as the AP, + * such as encryption, QoS, addresses, etc. + * + * No problem if skb_copy() fails, so no need to check. + */ + sdata->u.mgd.teardown_skb = skb_copy(skb, GFP_ATOMIC); + sdata->u.mgd.orig_teardown_skb = skb; + } + spin_unlock_bh(&sdata->u.mgd.teardown_lock); + } + /* disable bottom halves when entering the Tx path */ local_bh_disable(); - ret = ieee80211_subif_start_xmit(skb, dev); + __ieee80211_subif_start_xmit(skb, dev, flags); local_bh_enable(); return ret; @@ -676,7 +917,8 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code, dialog_token, status_code, peer_capability, initiator, - extra_ies, extra_ies_len); + extra_ies, extra_ies_len, 0, + NULL); if (ret < 0) goto exit; @@ -715,7 +957,8 @@ ieee80211_tdls_mgmt_teardown(struct wiphy *wiphy, struct net_device *dev, ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code, dialog_token, status_code, peer_capability, initiator, - extra_ies, extra_ies_len); + extra_ies, extra_ies_len, 0, + NULL); if (ret < 0) sdata_err(sdata, "Failed sending TDLS teardown packet %d\n", ret); @@ -785,7 +1028,7 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, status_code, peer_capability, initiator, extra_ies, - extra_ies_len); + extra_ies_len, 0, NULL); break; default: ret = -EOPNOTSUPP; @@ -888,3 +1131,480 @@ void ieee80211_tdls_oper_request(struct ieee80211_vif *vif, const u8 *peer, cfg80211_tdls_oper_request(sdata->dev, peer, oper, reason_code, gfp); } EXPORT_SYMBOL(ieee80211_tdls_oper_request); + +static void +iee80211_tdls_add_ch_switch_timing(u8 *buf, u16 switch_time, u16 switch_timeout) +{ + struct ieee80211_ch_switch_timing *ch_sw; + + *buf++ = WLAN_EID_CHAN_SWITCH_TIMING; + *buf++ = sizeof(struct ieee80211_ch_switch_timing); + + ch_sw = (void *)buf; + ch_sw->switch_time = cpu_to_le16(switch_time); + ch_sw->switch_timeout = cpu_to_le16(switch_timeout); +} + +/* find switch timing IE in SKB ready for Tx */ +static const u8 *ieee80211_tdls_find_sw_timing_ie(struct sk_buff *skb) +{ + struct ieee80211_tdls_data *tf; + const u8 *ie_start; + + /* + * Get the offset for the new location of the switch timing IE. + * The SKB network header will now point to the "payload_type" + * element of the TDLS data frame struct. + */ + tf = container_of(skb->data + skb_network_offset(skb), + struct ieee80211_tdls_data, payload_type); + ie_start = tf->u.chan_switch_req.variable; + return cfg80211_find_ie(WLAN_EID_CHAN_SWITCH_TIMING, ie_start, + skb->len - (ie_start - skb->data)); +} + +static struct sk_buff * +ieee80211_tdls_ch_sw_tmpl_get(struct sta_info *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + u32 *ch_sw_tm_ie_offset) +{ + struct ieee80211_sub_if_data *sdata = sta->sdata; + u8 extra_ies[2 + sizeof(struct ieee80211_sec_chan_offs_ie) + + 2 + sizeof(struct ieee80211_ch_switch_timing)]; + int extra_ies_len = 2 + sizeof(struct ieee80211_ch_switch_timing); + u8 *pos = extra_ies; + struct sk_buff *skb; + + /* + * if chandef points to a wide channel add a Secondary-Channel + * Offset information element + */ + if (chandef->width == NL80211_CHAN_WIDTH_40) { + struct ieee80211_sec_chan_offs_ie *sec_chan_ie; + bool ht40plus; + + *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; + *pos++ = sizeof(*sec_chan_ie); + sec_chan_ie = (void *)pos; + + ht40plus = cfg80211_get_chandef_type(chandef) == + NL80211_CHAN_HT40PLUS; + sec_chan_ie->sec_chan_offs = ht40plus ? + IEEE80211_HT_PARAM_CHA_SEC_ABOVE : + IEEE80211_HT_PARAM_CHA_SEC_BELOW; + pos += sizeof(*sec_chan_ie); + + extra_ies_len += 2 + sizeof(struct ieee80211_sec_chan_offs_ie); + } + + /* just set the values to 0, this is a template */ + iee80211_tdls_add_ch_switch_timing(pos, 0, 0); + + skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr, + WLAN_TDLS_CHANNEL_SWITCH_REQUEST, + 0, 0, !sta->sta.tdls_initiator, + extra_ies, extra_ies_len, + oper_class, chandef); + if (!skb) + return NULL; + + skb = ieee80211_build_data_template(sdata, skb, 0); + if (IS_ERR(skb)) { + tdls_dbg(sdata, "Failed building TDLS channel switch frame\n"); + return NULL; + } + + if (ch_sw_tm_ie_offset) { + const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb); + + if (!tm_ie) { + tdls_dbg(sdata, "No switch timing IE in TDLS switch\n"); + dev_kfree_skb_any(skb); + return NULL; + } + + *ch_sw_tm_ie_offset = tm_ie - skb->data; + } + + tdls_dbg(sdata, + "TDLS channel switch request template for %pM ch %d width %d\n", + sta->sta.addr, chandef->chan->center_freq, chandef->width); + return skb; +} + +int +ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, + const u8 *addr, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + struct sta_info *sta; + struct sk_buff *skb = NULL; + u32 ch_sw_tm_ie; + int ret; + + mutex_lock(&local->sta_mtx); + sta = sta_info_get(sdata, addr); + if (!sta) { + tdls_dbg(sdata, + "Invalid TDLS peer %pM for channel switch request\n", + addr); + ret = -ENOENT; + goto out; + } + + if (!test_sta_flag(sta, WLAN_STA_TDLS_CHAN_SWITCH)) { + tdls_dbg(sdata, "TDLS channel switch unsupported by %pM\n", + addr); + ret = -ENOTSUPP; + goto out; + } + + skb = ieee80211_tdls_ch_sw_tmpl_get(sta, oper_class, chandef, + &ch_sw_tm_ie); + if (!skb) { + ret = -ENOENT; + goto out; + } + + ret = drv_tdls_channel_switch(local, sdata, &sta->sta, oper_class, + chandef, skb, ch_sw_tm_ie); + if (!ret) + set_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); + +out: + mutex_unlock(&local->sta_mtx); + dev_kfree_skb_any(skb); + return ret; +} + +void +ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + struct sta_info *sta; + + mutex_lock(&local->sta_mtx); + sta = sta_info_get(sdata, addr); + if (!sta) { + tdls_dbg(sdata, + "Invalid TDLS peer %pM for channel switch cancel\n", + addr); + goto out; + } + + if (!test_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL)) { + tdls_dbg(sdata, "TDLS channel switch not initiated by %pM\n", + addr); + goto out; + } + + drv_tdls_cancel_channel_switch(local, sdata, &sta->sta); + clear_sta_flag(sta, WLAN_STA_TDLS_OFF_CHANNEL); + +out: + mutex_unlock(&local->sta_mtx); +} + +static struct sk_buff * +ieee80211_tdls_ch_sw_resp_tmpl_get(struct sta_info *sta, + u32 *ch_sw_tm_ie_offset) +{ + struct ieee80211_sub_if_data *sdata = sta->sdata; + struct sk_buff *skb; + u8 extra_ies[2 + sizeof(struct ieee80211_ch_switch_timing)]; + + /* initial timing are always zero in the template */ + iee80211_tdls_add_ch_switch_timing(extra_ies, 0, 0); + + skb = ieee80211_tdls_build_mgmt_packet_data(sdata, sta->sta.addr, + WLAN_TDLS_CHANNEL_SWITCH_RESPONSE, + 0, 0, !sta->sta.tdls_initiator, + extra_ies, sizeof(extra_ies), 0, NULL); + if (!skb) + return NULL; + + skb = ieee80211_build_data_template(sdata, skb, 0); + if (IS_ERR(skb)) { + tdls_dbg(sdata, + "Failed building TDLS channel switch resp frame\n"); + return NULL; + } + + if (ch_sw_tm_ie_offset) { + const u8 *tm_ie = ieee80211_tdls_find_sw_timing_ie(skb); + + if (!tm_ie) { + tdls_dbg(sdata, + "No switch timing IE in TDLS switch resp\n"); + dev_kfree_skb_any(skb); + return NULL; + } + + *ch_sw_tm_ie_offset = tm_ie - skb->data; + } + + tdls_dbg(sdata, "TDLS get channel switch response template for %pM\n", + sta->sta.addr); + return skb; +} + +static int +ieee80211_process_tdls_channel_switch_resp(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + struct ieee80211_local *local = sdata->local; + struct ieee802_11_elems elems; + struct sta_info *sta; + struct ieee80211_tdls_data *tf = (void *)skb->data; + bool local_initiator; + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + int baselen = offsetof(typeof(*tf), u.chan_switch_resp.variable); + struct ieee80211_tdls_ch_sw_params params = {}; + int ret; + + params.action_code = WLAN_TDLS_CHANNEL_SWITCH_RESPONSE; + params.timestamp = rx_status->device_timestamp; + + if (skb->len < baselen) { + tdls_dbg(sdata, "TDLS channel switch resp too short: %d\n", + skb->len); + return -EINVAL; + } + + mutex_lock(&local->sta_mtx); + sta = sta_info_get(sdata, tf->sa); + if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) { + tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n", + tf->sa); + ret = -EINVAL; + goto out; + } + + params.sta = &sta->sta; + params.status = le16_to_cpu(tf->u.chan_switch_resp.status_code); + if (params.status != 0) { + ret = 0; + goto call_drv; + } + + ieee802_11_parse_elems(tf->u.chan_switch_resp.variable, + skb->len - baselen, false, &elems); + if (elems.parse_error) { + tdls_dbg(sdata, "Invalid IEs in TDLS channel switch resp\n"); + ret = -EINVAL; + goto out; + } + + if (!elems.ch_sw_timing || !elems.lnk_id) { + tdls_dbg(sdata, "TDLS channel switch resp - missing IEs\n"); + ret = -EINVAL; + goto out; + } + + /* validate the initiator is set correctly */ + local_initiator = + !memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN); + if (local_initiator == sta->sta.tdls_initiator) { + tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n"); + ret = -EINVAL; + goto out; + } + + params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time); + params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout); + + params.tmpl_skb = + ieee80211_tdls_ch_sw_resp_tmpl_get(sta, ¶ms.ch_sw_tm_ie); + if (!params.tmpl_skb) { + ret = -ENOENT; + goto out; + } + +call_drv: + drv_tdls_recv_channel_switch(sdata->local, sdata, ¶ms); + + tdls_dbg(sdata, + "TDLS channel switch response received from %pM status %d\n", + tf->sa, params.status); + +out: + mutex_unlock(&local->sta_mtx); + dev_kfree_skb_any(params.tmpl_skb); + return ret; +} + +static int +ieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + struct ieee80211_local *local = sdata->local; + struct ieee802_11_elems elems; + struct cfg80211_chan_def chandef; + struct ieee80211_channel *chan; + enum nl80211_channel_type chan_type; + int freq; + u8 target_channel, oper_class; + bool local_initiator; + struct sta_info *sta; + enum ieee80211_band band; + struct ieee80211_tdls_data *tf = (void *)skb->data; + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + int baselen = offsetof(typeof(*tf), u.chan_switch_req.variable); + struct ieee80211_tdls_ch_sw_params params = {}; + int ret = 0; + + params.action_code = WLAN_TDLS_CHANNEL_SWITCH_REQUEST; + params.timestamp = rx_status->device_timestamp; + + if (skb->len < baselen) { + tdls_dbg(sdata, "TDLS channel switch req too short: %d\n", + skb->len); + return -EINVAL; + } + + target_channel = tf->u.chan_switch_req.target_channel; + oper_class = tf->u.chan_switch_req.oper_class; + + /* + * We can't easily infer the channel band. The operating class is + * ambiguous - there are multiple tables (US/Europe/JP/Global). The + * solution here is to treat channels with number >14 as 5GHz ones, + * and specifically check for the (oper_class, channel) combinations + * where this doesn't hold. These are thankfully unique according to + * IEEE802.11-2012. + * We consider only the 2GHz and 5GHz bands and 20MHz+ channels as + * valid here. + */ + if ((oper_class == 112 || oper_class == 2 || oper_class == 3 || + oper_class == 4 || oper_class == 5 || oper_class == 6) && + target_channel < 14) + band = IEEE80211_BAND_5GHZ; + else + band = target_channel < 14 ? IEEE80211_BAND_2GHZ : + IEEE80211_BAND_5GHZ; + + freq = ieee80211_channel_to_frequency(target_channel, band); + if (freq == 0) { + tdls_dbg(sdata, "Invalid channel in TDLS chan switch: %d\n", + target_channel); + return -EINVAL; + } + + chan = ieee80211_get_channel(sdata->local->hw.wiphy, freq); + if (!chan) { + tdls_dbg(sdata, + "Unsupported channel for TDLS chan switch: %d\n", + target_channel); + return -EINVAL; + } + + ieee802_11_parse_elems(tf->u.chan_switch_req.variable, + skb->len - baselen, false, &elems); + if (elems.parse_error) { + tdls_dbg(sdata, "Invalid IEs in TDLS channel switch req\n"); + return -EINVAL; + } + + if (!elems.ch_sw_timing || !elems.lnk_id) { + tdls_dbg(sdata, "TDLS channel switch req - missing IEs\n"); + return -EINVAL; + } + + mutex_lock(&local->sta_mtx); + sta = sta_info_get(sdata, tf->sa); + if (!sta || !test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH)) { + tdls_dbg(sdata, "TDLS chan switch from non-peer sta %pM\n", + tf->sa); + ret = -EINVAL; + goto out; + } + + params.sta = &sta->sta; + + /* validate the initiator is set correctly */ + local_initiator = + !memcmp(elems.lnk_id->init_sta, sdata->vif.addr, ETH_ALEN); + if (local_initiator == sta->sta.tdls_initiator) { + tdls_dbg(sdata, "TDLS chan switch invalid lnk-id initiator\n"); + ret = -EINVAL; + goto out; + } + + if (!sta->sta.ht_cap.ht_supported) { + chan_type = NL80211_CHAN_NO_HT; + } else if (!elems.sec_chan_offs) { + chan_type = NL80211_CHAN_HT20; + } else { + switch (elems.sec_chan_offs->sec_chan_offs) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + chan_type = NL80211_CHAN_HT40PLUS; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + chan_type = NL80211_CHAN_HT40MINUS; + break; + default: + chan_type = NL80211_CHAN_HT20; + break; + } + } + + cfg80211_chandef_create(&chandef, chan, chan_type); + params.chandef = &chandef; + + params.switch_time = le16_to_cpu(elems.ch_sw_timing->switch_time); + params.switch_timeout = le16_to_cpu(elems.ch_sw_timing->switch_timeout); + + params.tmpl_skb = + ieee80211_tdls_ch_sw_resp_tmpl_get(sta, + ¶ms.ch_sw_tm_ie); + if (!params.tmpl_skb) { + ret = -ENOENT; + goto out; + } + + drv_tdls_recv_channel_switch(sdata->local, sdata, ¶ms); + + tdls_dbg(sdata, + "TDLS ch switch request received from %pM ch %d width %d\n", + tf->sa, params.chandef->chan->center_freq, + params.chandef->width); +out: + mutex_unlock(&local->sta_mtx); + dev_kfree_skb_any(params.tmpl_skb); + return ret; +} + +void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + struct ieee80211_tdls_data *tf = (void *)skb->data; + struct wiphy *wiphy = sdata->local->hw.wiphy; + + /* make sure the driver supports it */ + if (!(wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH)) + return; + + /* we want to access the entire packet */ + if (skb_linearize(skb)) + return; + /* + * The packet/size was already validated by mac80211 Rx path, only look + * at the action type. + */ + switch (tf->action_code) { + case WLAN_TDLS_CHANNEL_SWITCH_REQUEST: + ieee80211_process_tdls_channel_switch_req(sdata, skb); + break; + case WLAN_TDLS_CHANNEL_SWITCH_RESPONSE: + ieee80211_process_tdls_channel_switch_resp(sdata, skb); + break; + default: + WARN_ON_ONCE(1); + return; + } +} diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h index 809a4983eb4a..85ccfbe863db 100644 --- a/net/mac80211/trace.h +++ b/net/mac80211/trace.h @@ -16,6 +16,7 @@ #define STA_ENTRY __array(char, sta_addr, ETH_ALEN) #define STA_ASSIGN (sta ? memcpy(__entry->sta_addr, sta->addr, ETH_ALEN) : memset(__entry->sta_addr, 0, ETH_ALEN)) +#define STA_NAMED_ASSIGN(s) memcpy(__entry->sta_addr, (s)->addr, ETH_ALEN) #define STA_PR_FMT " sta:%pM" #define STA_PR_ARG __entry->sta_addr @@ -595,14 +596,33 @@ DEFINE_EVENT(local_sdata_evt, drv_sched_scan_stop, TP_ARGS(local, sdata) ); -DEFINE_EVENT(local_only_evt, drv_sw_scan_start, - TP_PROTO(struct ieee80211_local *local), - TP_ARGS(local) +TRACE_EVENT(drv_sw_scan_start, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + const u8 *mac_addr), + + TP_ARGS(local, sdata, mac_addr), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + __array(char, mac_addr, ETH_ALEN) + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + memcpy(__entry->mac_addr, mac_addr, ETH_ALEN); + ), + + TP_printk(LOCAL_PR_FMT ", " VIF_PR_FMT ", addr:%pM", + LOCAL_PR_ARG, VIF_PR_ARG, __entry->mac_addr) ); -DEFINE_EVENT(local_only_evt, drv_sw_scan_complete, - TP_PROTO(struct ieee80211_local *local), - TP_ARGS(local) +DEFINE_EVENT(local_sdata_evt, drv_sw_scan_complete, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata) ); TRACE_EVENT(drv_get_stats, @@ -826,6 +846,13 @@ DEFINE_EVENT(sta_event, drv_sta_pre_rcu_remove, TP_ARGS(local, sdata, sta) ); +DEFINE_EVENT(sta_event, drv_sta_rate_tbl_update, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta), + TP_ARGS(local, sdata, sta) +); + TRACE_EVENT(drv_conf_tx, TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, @@ -2140,6 +2167,7 @@ TRACE_EVENT(drv_pre_channel_switch, VIF_ENTRY CHANDEF_ENTRY __field(u64, timestamp) + __field(u32, device_timestamp) __field(bool, block_tx) __field(u8, count) ), @@ -2149,6 +2177,7 @@ TRACE_EVENT(drv_pre_channel_switch, VIF_ASSIGN; CHANDEF_ASSIGN(&ch_switch->chandef) __entry->timestamp = ch_switch->timestamp; + __entry->device_timestamp = ch_switch->device_timestamp; __entry->block_tx = ch_switch->block_tx; __entry->count = ch_switch->count; ), @@ -2194,6 +2223,107 @@ TRACE_EVENT(drv_get_txpower, ) ); +TRACE_EVENT(drv_tdls_channel_switch, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef), + + TP_ARGS(local, sdata, sta, oper_class, chandef), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + STA_ENTRY + __field(u8, oper_class) + CHANDEF_ENTRY + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + STA_ASSIGN; + __entry->oper_class = oper_class; + CHANDEF_ASSIGN(chandef) + ), + + TP_printk( + LOCAL_PR_FMT VIF_PR_FMT " tdls channel switch to" + CHANDEF_PR_FMT " oper_class:%d " STA_PR_FMT, + LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG, __entry->oper_class, + STA_PR_ARG + ) +); + +TRACE_EVENT(drv_tdls_cancel_channel_switch, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta *sta), + + TP_ARGS(local, sdata, sta), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + STA_ENTRY + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + STA_ASSIGN; + ), + + TP_printk( + LOCAL_PR_FMT VIF_PR_FMT + " tdls cancel channel switch with " STA_PR_FMT, + LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG + ) +); + +TRACE_EVENT(drv_tdls_recv_channel_switch, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_tdls_ch_sw_params *params), + + TP_ARGS(local, sdata, params), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + __field(u8, action_code) + STA_ENTRY + CHANDEF_ENTRY + __field(u32, status) + __field(bool, peer_initiator) + __field(u32, timestamp) + __field(u16, switch_time) + __field(u16, switch_timeout) + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + STA_NAMED_ASSIGN(params->sta); + CHANDEF_ASSIGN(params->chandef) + __entry->peer_initiator = params->sta->tdls_initiator; + __entry->action_code = params->action_code; + __entry->status = params->status; + __entry->timestamp = params->timestamp; + __entry->switch_time = params->switch_time; + __entry->switch_timeout = params->switch_timeout; + ), + + TP_printk( + LOCAL_PR_FMT VIF_PR_FMT " received tdls channel switch packet" + " action:%d status:%d time:%d switch time:%d switch" + " timeout:%d initiator: %d chan:" CHANDEF_PR_FMT STA_PR_FMT, + LOCAL_PR_ARG, VIF_PR_ARG, __entry->action_code, __entry->status, + __entry->timestamp, __entry->switch_time, + __entry->switch_timeout, __entry->peer_initiator, + CHANDEF_PR_ARG, STA_PR_ARG + ) +); #ifdef CONFIG_MAC80211_MESSAGE_TRACING #undef TRACE_SYSTEM diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3ffd91f295a6..66ddbbeccd20 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1426,8 +1426,7 @@ EXPORT_SYMBOL(ieee80211_tx_prepare_skb); * Returns false if the frame couldn't be transmitted but was queued instead. */ static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, bool txpending, - enum ieee80211_band band) + struct sk_buff *skb, bool txpending) { struct ieee80211_local *local = sdata->local; struct ieee80211_tx_data tx; @@ -1452,8 +1451,6 @@ static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata, return true; } - info->band = band; - /* set up hw_queue value early */ if (!(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) || !(local->hw.flags & IEEE80211_HW_QUEUE_CONTROL)) @@ -1501,8 +1498,7 @@ static int ieee80211_skb_resize(struct ieee80211_sub_if_data *sdata, return 0; } -void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - enum ieee80211_band band) +void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) { struct ieee80211_local *local = sdata->local; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); @@ -1537,7 +1533,7 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, } ieee80211_set_qos_hdr(sdata, skb); - ieee80211_tx(sdata, skb, false, band); + ieee80211_tx(sdata, skb, false); } static bool ieee80211_parse_tx_radiotap(struct sk_buff *skb) @@ -1757,7 +1753,8 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, sdata->vif.type)) goto fail_rcu; - ieee80211_xmit(sdata, skb, chandef->chan->band); + info->band = chandef->chan->band; + ieee80211_xmit(sdata, skb); rcu_read_unlock(); return NETDEV_TX_OK; @@ -1787,23 +1784,26 @@ static void ieee80211_tx_latency_start_msrmnt(struct ieee80211_local *local, } /** - * ieee80211_subif_start_xmit - netif start_xmit function for Ethernet-type - * subinterfaces (wlan#, WDS, and VLAN interfaces) - * @skb: packet to be sent - * @dev: incoming interface + * ieee80211_build_hdr - build 802.11 header in the given frame + * @sdata: virtual interface to build the header for + * @skb: the skb to build the header in + * @info_flags: skb flags to set + * + * This function takes the skb with 802.3 header and reformats the header to + * the appropriate IEEE 802.11 header based on which interface the packet is + * being transmitted on. * - * Returns: NETDEV_TX_OK both on success and on failure. On failure skb will - * be freed. + * Note that this function also takes care of the TX status request and + * potential unsharing of the SKB - this needs to be interleaved with the + * header building. * - * This function takes in an Ethernet header and encapsulates it with suitable - * IEEE 802.11 header based on which interface the packet is coming in. The - * encapsulated packet will then be passed to master interface, wlan#.11, for - * transmission (through low-level driver). + * The function requires the read-side RCU lock held + * + * Returns: the (possibly reallocated) skb or an ERR_PTR() code */ -netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, - struct net_device *dev) +static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, u32 info_flags) { - struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; struct ieee80211_tx_info *info; int head_need; @@ -1819,25 +1819,17 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, bool wme_sta = false, authorized = false, tdls_auth = false; bool tdls_peer = false, tdls_setup_frame = false; bool multicast; - u32 info_flags = 0; u16 info_id = 0; struct ieee80211_chanctx_conf *chanctx_conf; struct ieee80211_sub_if_data *ap_sdata; enum ieee80211_band band; - - if (unlikely(skb->len < ETH_HLEN)) - goto fail; + int ret; /* convert Ethernet header to proper 802.11 header (based on * operation mode) */ ethertype = (skb->data[12] << 8) | skb->data[13]; fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA); - rcu_read_lock(); - - /* Measure frame arrival for Tx latency statistics calculation */ - ieee80211_tx_latency_start_msrmnt(local, skb); - switch (sdata->vif.type) { case NL80211_IFTYPE_AP_VLAN: sta = rcu_dereference(sdata->u.vlan.sta); @@ -1855,8 +1847,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, ap_sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); chanctx_conf = rcu_dereference(ap_sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } band = chanctx_conf->def.chan->band; if (sta) break; @@ -1864,8 +1858,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, case NL80211_IFTYPE_AP: if (sdata->vif.type == NL80211_IFTYPE_AP) chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS); /* DA BSSID SA */ memcpy(hdr.addr1, skb->data, ETH_ALEN); @@ -1952,8 +1948,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, } chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } band = chanctx_conf->def.chan->band; break; #endif @@ -1983,8 +1981,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, * of a link teardown after a TDLS sta is removed due to being * unreachable. */ - if (tdls_peer && !tdls_auth && !tdls_setup_frame) - goto fail_rcu; + if (tdls_peer && !tdls_auth && !tdls_setup_frame) { + ret = -EINVAL; + goto free; + } /* send direct packets to authorized TDLS peers */ if (tdls_peer && tdls_auth) { @@ -2012,8 +2012,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, hdrlen = 24; } chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } band = chanctx_conf->def.chan->band; break; case NL80211_IFTYPE_OCB: @@ -2023,8 +2025,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, eth_broadcast_addr(hdr.addr3); hdrlen = 24; chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } band = chanctx_conf->def.chan->band; break; case NL80211_IFTYPE_ADHOC: @@ -2034,12 +2038,15 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN); hdrlen = 24; chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf); - if (!chanctx_conf) - goto fail_rcu; + if (!chanctx_conf) { + ret = -ENOTCONN; + goto free; + } band = chanctx_conf->def.chan->band; break; default: - goto fail_rcu; + ret = -EINVAL; + goto free; } /* @@ -2077,12 +2084,13 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, !ether_addr_equal(sdata->vif.addr, skb->data + ETH_ALEN)))) { #ifdef CONFIG_MAC80211_VERBOSE_DEBUG net_info_ratelimited("%s: dropped frame to %pM (unauthorized port)\n", - dev->name, hdr.addr1); + sdata->name, hdr.addr1); #endif I802_DEBUG_INC(local->tx_handlers_drop_unauth_port); - goto fail_rcu; + ret = -EPERM; + goto free; } if (unlikely(!multicast && skb->sk && @@ -2119,8 +2127,10 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, skb = skb_clone(skb, GFP_ATOMIC); kfree_skb(tmp_skb); - if (!skb) - goto fail_rcu; + if (!skb) { + ret = -ENOMEM; + goto free; + } } hdr.frame_control = fc; @@ -2169,7 +2179,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, if (ieee80211_skb_resize(sdata, skb, head_need, true)) { ieee80211_free_txskb(&local->hw, skb); skb = NULL; - goto fail_rcu; + return ERR_PTR(-ENOMEM); } } @@ -2203,9 +2213,6 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, nh_pos += hdrlen; h_pos += hdrlen; - dev->stats.tx_packets++; - dev->stats.tx_bytes += skb->len; - /* Update skb pointers to various headers since this modified frame * is going to go through Linux networking code that may potentially * need things like pointer to IP header. */ @@ -2216,23 +2223,90 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, info = IEEE80211_SKB_CB(skb); memset(info, 0, sizeof(*info)); - dev->trans_start = jiffies; - info->flags = info_flags; info->ack_frame_id = info_id; + info->band = band; - ieee80211_xmit(sdata, skb, band); - rcu_read_unlock(); + return skb; + free: + kfree_skb(skb); + return ERR_PTR(ret); +} - return NETDEV_TX_OK; +void __ieee80211_subif_start_xmit(struct sk_buff *skb, + struct net_device *dev, + u32 info_flags) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + struct ieee80211_local *local = sdata->local; + + if (unlikely(skb->len < ETH_HLEN)) { + kfree_skb(skb); + return; + } + + rcu_read_lock(); + + /* Measure frame arrival for Tx latency statistics calculation */ + ieee80211_tx_latency_start_msrmnt(local, skb); + + skb = ieee80211_build_hdr(sdata, skb, info_flags); + if (IS_ERR(skb)) + goto out; + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + dev->trans_start = jiffies; - fail_rcu: + ieee80211_xmit(sdata, skb); + out: rcu_read_unlock(); - fail: - dev_kfree_skb(skb); +} + +/** + * ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs + * @skb: packet to be sent + * @dev: incoming interface + * + * On failure skb will be freed. + */ +netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + __ieee80211_subif_start_xmit(skb, dev, 0); return NETDEV_TX_OK; } +struct sk_buff * +ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, u32 info_flags) +{ + struct ieee80211_hdr *hdr; + struct ieee80211_tx_data tx = { + .local = sdata->local, + .sdata = sdata, + }; + + rcu_read_lock(); + + skb = ieee80211_build_hdr(sdata, skb, info_flags); + if (IS_ERR(skb)) + goto out; + + hdr = (void *)skb->data; + tx.sta = sta_info_get(sdata, hdr->addr1); + tx.skb = skb; + + if (ieee80211_tx_h_select_key(&tx) != TX_CONTINUE) { + rcu_read_unlock(); + kfree_skb(skb); + return ERR_PTR(-EINVAL); + } + +out: + rcu_read_unlock(); + return skb; +} /* * ieee80211_clear_tx_pending may not be called in a context where @@ -2272,8 +2346,8 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local, dev_kfree_skb(skb); return true; } - result = ieee80211_tx(sdata, skb, true, - chanctx_conf->def.chan->band); + info->band = chanctx_conf->def.chan->band; + result = ieee80211_tx(sdata, skb, true); } else { struct sk_buff_head skbs; @@ -2887,19 +2961,16 @@ struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw, EXPORT_SYMBOL(ieee80211_nullfunc_get); struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, + const u8 *src_addr, const u8 *ssid, size_t ssid_len, size_t tailroom) { - struct ieee80211_sub_if_data *sdata; - struct ieee80211_local *local; + struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_hdr_3addr *hdr; struct sk_buff *skb; size_t ie_ssid_len; u8 *pos; - sdata = vif_to_sdata(vif); - local = sdata->local; ie_ssid_len = 2 + ssid_len; skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*hdr) + @@ -2914,7 +2985,7 @@ struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw, hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_PROBE_REQ); eth_broadcast_addr(hdr->addr1); - memcpy(hdr->addr2, vif->addr, ETH_ALEN); + memcpy(hdr->addr2, src_addr, ETH_ALEN); eth_broadcast_addr(hdr->addr3); pos = skb_put(skb, ie_ssid_len); @@ -3033,6 +3104,97 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw, } EXPORT_SYMBOL(ieee80211_get_buffered_bc); +int ieee80211_reserve_tid(struct ieee80211_sta *pubsta, u8 tid) +{ + struct sta_info *sta = container_of(pubsta, struct sta_info, sta); + struct ieee80211_sub_if_data *sdata = sta->sdata; + struct ieee80211_local *local = sdata->local; + int ret; + u32 queues; + + lockdep_assert_held(&local->sta_mtx); + + /* only some cases are supported right now */ + switch (sdata->vif.type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + break; + default: + WARN_ON(1); + return -EINVAL; + } + + if (WARN_ON(tid >= IEEE80211_NUM_UPS)) + return -EINVAL; + + if (sta->reserved_tid == tid) { + ret = 0; + goto out; + } + + if (sta->reserved_tid != IEEE80211_TID_UNRESERVED) { + sdata_err(sdata, "TID reservation already active\n"); + ret = -EALREADY; + goto out; + } + + ieee80211_stop_vif_queues(sdata->local, sdata, + IEEE80211_QUEUE_STOP_REASON_RESERVE_TID); + + synchronize_net(); + + /* Tear down BA sessions so we stop aggregating on this TID */ + if (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION) { + set_sta_flag(sta, WLAN_STA_BLOCK_BA); + __ieee80211_stop_tx_ba_session(sta, tid, + AGG_STOP_LOCAL_REQUEST); + } + + queues = BIT(sdata->vif.hw_queue[ieee802_1d_to_ac[tid]]); + __ieee80211_flush_queues(local, sdata, queues); + + sta->reserved_tid = tid; + + ieee80211_wake_vif_queues(local, sdata, + IEEE80211_QUEUE_STOP_REASON_RESERVE_TID); + + if (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION) + clear_sta_flag(sta, WLAN_STA_BLOCK_BA); + + ret = 0; + out: + return ret; +} +EXPORT_SYMBOL(ieee80211_reserve_tid); + +void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid) +{ + struct sta_info *sta = container_of(pubsta, struct sta_info, sta); + struct ieee80211_sub_if_data *sdata = sta->sdata; + + lockdep_assert_held(&sdata->local->sta_mtx); + + /* only some cases are supported right now */ + switch (sdata->vif.type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + break; + default: + WARN_ON(1); + return; + } + + if (tid != sta->reserved_tid) { + sdata_err(sdata, "TID to unreserve (%d) isn't reserved\n", tid); + return; + } + + sta->reserved_tid = IEEE80211_TID_UNRESERVED; +} +EXPORT_SYMBOL(ieee80211_unreserve_tid); + void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, int tid, enum ieee80211_band band) @@ -3054,6 +3216,7 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, * requirements are that we do not come into tx with bhs on. */ local_bh_disable(); - ieee80211_xmit(sdata, skb, band); + IEEE80211_SKB_CB(skb)->band = band; + ieee80211_xmit(sdata, skb); local_bh_enable(); } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index f9319a5dca64..bb9664cb8831 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -576,15 +576,19 @@ ieee80211_get_vif_queues(struct ieee80211_local *local, return queues; } -void ieee80211_flush_queues(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata) +void __ieee80211_flush_queues(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + unsigned int queues) { - unsigned int queues; - if (!local->ops->flush) return; - queues = ieee80211_get_vif_queues(local, sdata); + /* + * If no queue was set, or if the HW doesn't support + * IEEE80211_HW_QUEUE_CONTROL - flush all queues + */ + if (!queues || !(local->hw.flags & IEEE80211_HW_QUEUE_CONTROL)) + queues = ieee80211_get_vif_queues(local, sdata); ieee80211_stop_queues_by_reason(&local->hw, queues, IEEE80211_QUEUE_STOP_REASON_FLUSH, @@ -597,6 +601,12 @@ void ieee80211_flush_queues(struct ieee80211_local *local, false); } +void ieee80211_flush_queues(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) +{ + __ieee80211_flush_queues(local, sdata, 0); +} + void ieee80211_stop_vif_queues(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, enum queue_stop_reason reason) @@ -831,6 +841,9 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action, case WLAN_EID_SECONDARY_CHANNEL_OFFSET: case WLAN_EID_WIDE_BW_CHANNEL_SWITCH: case WLAN_EID_CHAN_SWITCH_PARAM: + case WLAN_EID_EXT_CAPABILITY: + case WLAN_EID_CHAN_SWITCH_TIMING: + case WLAN_EID_LINK_ID: /* * not listing WLAN_EID_CHANNEL_SWITCH_WRAPPER -- it seems possible * that if the content gets bigger it might be needed more than once @@ -850,6 +863,24 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action, elem_parse_failed = false; switch (id) { + case WLAN_EID_LINK_ID: + if (elen + 2 != sizeof(struct ieee80211_tdls_lnkie)) { + elem_parse_failed = true; + break; + } + elems->lnk_id = (void *)(pos - 2); + break; + case WLAN_EID_CHAN_SWITCH_TIMING: + if (elen != sizeof(struct ieee80211_ch_switch_timing)) { + elem_parse_failed = true; + break; + } + elems->ch_sw_timing = (void *)pos; + break; + case WLAN_EID_EXT_CAPABILITY: + elems->ext_capab = pos; + elems->ext_capab_len = elen; + break; case WLAN_EID_SSID: elems->ssid = pos; elems->ssid_len = elen; @@ -1492,7 +1523,8 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, }; struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, - u8 *dst, u32 ratemask, + const u8 *src, const u8 *dst, + u32 ratemask, struct ieee80211_channel *chan, const u8 *ssid, size_t ssid_len, const u8 *ie, size_t ie_len, @@ -1517,8 +1549,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, else chandef.chan = chan; - skb = ieee80211_probereq_get(&local->hw, &sdata->vif, - ssid, ssid_len, 100 + ie_len); + skb = ieee80211_probereq_get(&local->hw, src, ssid, ssid_len, + 100 + ie_len); if (!skb) return NULL; @@ -1540,7 +1572,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, return skb; } -void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, +void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, + const u8 *src, const u8 *dst, const u8 *ssid, size_t ssid_len, const u8 *ie, size_t ie_len, u32 ratemask, bool directed, u32 tx_flags, @@ -1548,7 +1581,7 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, { struct sk_buff *skb; - skb = ieee80211_build_probe_req(sdata, dst, ratemask, channel, + skb = ieee80211_build_probe_req(sdata, src, dst, ratemask, channel, ssid, ssid_len, ie, ie_len, directed); if (skb) { @@ -1690,6 +1723,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) int res, i; bool reconfig_due_to_wowlan = false; struct ieee80211_sub_if_data *sched_scan_sdata; + struct cfg80211_sched_scan_request *sched_scan_req; bool sched_scan_stopped = false; #ifdef CONFIG_PM @@ -1980,13 +2014,15 @@ int ieee80211_reconfig(struct ieee80211_local *local) mutex_lock(&local->mtx); sched_scan_sdata = rcu_dereference_protected(local->sched_scan_sdata, lockdep_is_held(&local->mtx)); - if (sched_scan_sdata && local->sched_scan_req) + sched_scan_req = rcu_dereference_protected(local->sched_scan_req, + lockdep_is_held(&local->mtx)); + if (sched_scan_sdata && sched_scan_req) /* * Sched scan stopped, but we don't want to report it. Instead, * we're trying to reschedule. */ if (__ieee80211_request_sched_scan_start(sched_scan_sdata, - local->sched_scan_req)) + sched_scan_req)) sched_scan_stopped = true; mutex_unlock(&local->mtx); diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c index 671ce0d27a80..bc9e8fc48785 100644 --- a/net/mac80211/vht.c +++ b/net/mac80211/vht.c @@ -287,6 +287,8 @@ enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta) /* fall through */ case NL80211_CHAN_WIDTH_20_NOHT: case NL80211_CHAN_WIDTH_20: + bw = IEEE80211_STA_RX_BW_20; + break; case NL80211_CHAN_WIDTH_40: bw = sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ? IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20; diff --git a/net/mac80211/wme.c b/net/mac80211/wme.c index fdf52db95b33..9eb0aee9105b 100644 --- a/net/mac80211/wme.c +++ b/net/mac80211/wme.c @@ -53,6 +53,36 @@ static int wme_downgrade_ac(struct sk_buff *skb) } } +/** + * ieee80211_fix_reserved_tid - return the TID to use if this one is reserved + * @tid: the assumed-reserved TID + * + * Returns: the alternative TID to use, or 0 on error + */ +static inline u8 ieee80211_fix_reserved_tid(u8 tid) +{ + switch (tid) { + case 0: + return 3; + case 1: + return 2; + case 2: + return 1; + case 3: + return 0; + case 4: + return 5; + case 5: + return 4; + case 6: + return 7; + case 7: + return 6; + } + + return 0; +} + static u16 ieee80211_downgrade_queue(struct ieee80211_sub_if_data *sdata, struct sta_info *sta, struct sk_buff *skb) { @@ -77,6 +107,10 @@ static u16 ieee80211_downgrade_queue(struct ieee80211_sub_if_data *sdata, } } + /* Check to see if this is a reserved TID */ + if (sta && sta->reserved_tid == skb->priority) + skb->priority = ieee80211_fix_reserved_tid(skb->priority); + /* look up which queue to use for frames with this 1d tag */ return ieee802_1d_to_ac[skb->priority]; } @@ -143,6 +177,11 @@ u16 ieee80211_select_queue(struct ieee80211_sub_if_data *sdata, break; #endif case NL80211_IFTYPE_STATION: + /* might be a TDLS station */ + sta = sta_info_get(sdata, skb->data); + if (sta) + qos = sta->sta.wme; + ra = sdata->u.mgd.bssid; break; case NL80211_IFTYPE_ADHOC: diff --git a/net/wireless/core.c b/net/wireless/core.c index a4d27927aba2..4c2e501203d1 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -541,6 +541,10 @@ int wiphy_register(struct wiphy *wiphy) !wiphy->wowlan->tcp)) return -EINVAL; #endif + if (WARN_ON((wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) && + (!rdev->ops->tdls_channel_switch || + !rdev->ops->tdls_cancel_channel_switch))) + return -EINVAL; if (WARN_ON(wiphy->coalesce && (!wiphy->coalesce->n_rules || diff --git a/net/wireless/core.h b/net/wireless/core.h index 61ee664cf2bd..faa5b1609aae 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -111,6 +111,7 @@ cfg80211_rdev_free_wowlan(struct cfg80211_registered_device *rdev) rdev->wiphy.wowlan_config->tcp->sock) sock_release(rdev->wiphy.wowlan_config->tcp->sock); kfree(rdev->wiphy.wowlan_config->tcp); + kfree(rdev->wiphy.wowlan_config->nd_config); kfree(rdev->wiphy.wowlan_config); #endif } diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 1a31736914e5..6e4177701d86 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -209,7 +209,7 @@ cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info) } /* policy for the attributes */ -static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { +static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_WIPHY] = { .type = NLA_U32 }, [NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING, .len = 20-1 }, @@ -388,13 +388,14 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { [NL80211_ATTR_MAC_HINT] = { .len = ETH_ALEN }, [NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 }, [NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 }, - [NL80211_ATTR_IFACE_SOCKET_OWNER] = { .type = NLA_FLAG }, + [NL80211_ATTR_SOCKET_OWNER] = { .type = NLA_FLAG }, [NL80211_ATTR_CSA_C_OFFSETS_TX] = { .type = NLA_BINARY }, [NL80211_ATTR_USE_RRM] = { .type = NLA_FLAG }, [NL80211_ATTR_TSID] = { .type = NLA_U8 }, [NL80211_ATTR_USER_PRIO] = { .type = NLA_U8 }, [NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 }, [NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 }, + [NL80211_ATTR_MAC_MASK] = { .len = ETH_ALEN }, }; /* policy for the key attributes */ @@ -428,6 +429,7 @@ nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = { [NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG }, [NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG }, [NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED }, + [NL80211_WOWLAN_TRIG_NET_DETECT] = { .type = NLA_NESTED }, }; static const struct nla_policy @@ -1088,6 +1090,8 @@ static int nl80211_send_wowlan(struct sk_buff *msg, if (large && nl80211_send_wowlan_tcp_caps(rdev, msg)) return -ENOBUFS; + /* TODO: send wowlan net detect */ + nla_nest_end(msg, nl_wowlan); return 0; @@ -2341,12 +2345,16 @@ static int nl80211_send_chandef(struct sk_buff *msg, static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags, struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev) + struct wireless_dev *wdev, bool removal) { struct net_device *dev = wdev->netdev; + u8 cmd = NL80211_CMD_NEW_INTERFACE; void *hdr; - hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_INTERFACE); + if (removal) + cmd = NL80211_CMD_DEL_INTERFACE; + + hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); if (!hdr) return -1; @@ -2413,7 +2421,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback * } if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, - rdev, wdev) < 0) { + rdev, wdev, false) < 0) { goto out; } if_idx++; @@ -2441,7 +2449,7 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info) return -ENOMEM; if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0, - rdev, wdev) < 0) { + rdev, wdev, false) < 0) { nlmsg_free(msg); return -ENOBUFS; } @@ -2587,7 +2595,7 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct vif_params params; struct wireless_dev *wdev; - struct sk_buff *msg; + struct sk_buff *msg, *event; int err; enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED; u32 flags; @@ -2641,12 +2649,15 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) wdev = rdev_add_virtual_intf(rdev, nla_data(info->attrs[NL80211_ATTR_IFNAME]), type, err ? NULL : &flags, ¶ms); - if (IS_ERR(wdev)) { + if (WARN_ON(!wdev)) { + nlmsg_free(msg); + return -EPROTO; + } else if (IS_ERR(wdev)) { nlmsg_free(msg); return PTR_ERR(wdev); } - if (info->attrs[NL80211_ATTR_IFACE_SOCKET_OWNER]) + if (info->attrs[NL80211_ATTR_SOCKET_OWNER]) wdev->owner_nlportid = info->snd_portid; switch (type) { @@ -2682,11 +2693,25 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) } if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0, - rdev, wdev) < 0) { + rdev, wdev, false) < 0) { nlmsg_free(msg); return -ENOBUFS; } + event = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (event) { + if (nl80211_send_iface(event, 0, 0, 0, + rdev, wdev, false) < 0) { + nlmsg_free(event); + goto out; + } + + genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), + event, 0, NL80211_MCGRP_CONFIG, + GFP_KERNEL); + } + +out: return genlmsg_reply(msg, info); } @@ -2694,10 +2719,18 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct wireless_dev *wdev = info->user_ptr[1]; + struct sk_buff *msg; + int status; if (!rdev->ops->del_virtual_intf) return -EOPNOTSUPP; + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (msg && nl80211_send_iface(msg, 0, 0, 0, rdev, wdev, true) < 0) { + nlmsg_free(msg); + msg = NULL; + } + /* * If we remove a wireless device without a netdev then clear * user_ptr[1] so that nl80211_post_doit won't dereference it @@ -2708,7 +2741,15 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info) if (!wdev->netdev) info->user_ptr[1] = NULL; - return rdev_del_virtual_intf(rdev, wdev); + status = rdev_del_virtual_intf(rdev, wdev); + if (status >= 0 && msg) + genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), + msg, 0, NL80211_MCGRP_CONFIG, + GFP_KERNEL); + else + nlmsg_free(msg); + + return status; } static int nl80211_set_noack_map(struct sk_buff *skb, struct genl_info *info) @@ -4453,7 +4494,7 @@ static int nl80211_send_mpath(struct sk_buff *msg, u32 portid, u32 seq, void *hdr; struct nlattr *pinfoattr; - hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_STATION); + hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_MPATH); if (!hdr) return -1; @@ -5478,6 +5519,43 @@ static int validate_scan_freqs(struct nlattr *freqs) return n_channels; } +static int nl80211_parse_random_mac(struct nlattr **attrs, + u8 *mac_addr, u8 *mac_addr_mask) +{ + int i; + + if (!attrs[NL80211_ATTR_MAC] && !attrs[NL80211_ATTR_MAC_MASK]) { + memset(mac_addr, 0, ETH_ALEN); + memset(mac_addr_mask, 0, ETH_ALEN); + mac_addr[0] = 0x2; + mac_addr_mask[0] = 0x3; + + return 0; + } + + /* need both or none */ + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_MAC_MASK]) + return -EINVAL; + + memcpy(mac_addr, nla_data(attrs[NL80211_ATTR_MAC]), ETH_ALEN); + memcpy(mac_addr_mask, nla_data(attrs[NL80211_ATTR_MAC_MASK]), ETH_ALEN); + + /* don't allow or configure an mcast address */ + if (!is_multicast_ether_addr(mac_addr_mask) || + is_multicast_ether_addr(mac_addr)) + return -EINVAL; + + /* + * allow users to pass a MAC address that has bits set outside + * of the mask, but don't bother drivers with having to deal + * with such bits + */ + for (i = 0; i < ETH_ALEN; i++) + mac_addr[i] &= mac_addr_mask[i]; + + return 0; +} + static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -5655,6 +5733,25 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) err = -EOPNOTSUPP; goto out_free; } + + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + if (!(wiphy->features & + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR)) { + err = -EOPNOTSUPP; + goto out_free; + } + + if (wdev->current_bss) { + err = -EOPNOTSUPP; + goto out_free; + } + + err = nl80211_parse_random_mac(info->attrs, + request->mac_addr, + request->mac_addr_mask); + if (err) + goto out_free; + } } request->no_cck = @@ -5681,14 +5778,12 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) return err; } -static int nl80211_start_sched_scan(struct sk_buff *skb, - struct genl_info *info) +static struct cfg80211_sched_scan_request * +nl80211_parse_sched_scan(struct wiphy *wiphy, struct wireless_dev *wdev, + struct nlattr **attrs) { struct cfg80211_sched_scan_request *request; - struct cfg80211_registered_device *rdev = info->user_ptr[0]; - struct net_device *dev = info->user_ptr[1]; struct nlattr *attr; - struct wiphy *wiphy; int err, tmp, n_ssids = 0, n_match_sets = 0, n_channels, i; u32 interval; enum ieee80211_band band; @@ -5696,38 +5791,32 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, struct nlattr *tb[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1]; s32 default_match_rssi = NL80211_SCAN_RSSI_THOLD_OFF; - if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) || - !rdev->ops->sched_scan_start) - return -EOPNOTSUPP; - - if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE])) - return -EINVAL; + if (!is_valid_ie_attr(attrs[NL80211_ATTR_IE])) + return ERR_PTR(-EINVAL); - if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]) - return -EINVAL; + if (!attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]) + return ERR_PTR(-EINVAL); - interval = nla_get_u32(info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]); + interval = nla_get_u32(attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]); if (interval == 0) - return -EINVAL; - - wiphy = &rdev->wiphy; + return ERR_PTR(-EINVAL); - if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { + if (attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { n_channels = validate_scan_freqs( - info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]); + attrs[NL80211_ATTR_SCAN_FREQUENCIES]); if (!n_channels) - return -EINVAL; + return ERR_PTR(-EINVAL); } else { n_channels = ieee80211_get_num_supported_channels(wiphy); } - if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) - nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], + if (attrs[NL80211_ATTR_SCAN_SSIDS]) + nla_for_each_nested(attr, attrs[NL80211_ATTR_SCAN_SSIDS], tmp) n_ssids++; if (n_ssids > wiphy->max_sched_scan_ssids) - return -EINVAL; + return ERR_PTR(-EINVAL); /* * First, count the number of 'real' matchsets. Due to an issue with @@ -5738,9 +5827,9 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, * older userspace that treated a matchset with only the RSSI as the * global RSSI for all other matchsets - if there are other matchsets. */ - if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) { + if (attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) { nla_for_each_nested(attr, - info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH], + attrs[NL80211_ATTR_SCHED_SCAN_MATCH], tmp) { struct nlattr *rssi; @@ -5748,7 +5837,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, nla_data(attr), nla_len(attr), nl80211_match_policy); if (err) - return err; + return ERR_PTR(err); /* add other standalone attributes here */ if (tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]) { n_match_sets++; @@ -5765,30 +5854,23 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, n_match_sets = 1; if (n_match_sets > wiphy->max_match_sets) - return -EINVAL; + return ERR_PTR(-EINVAL); - if (info->attrs[NL80211_ATTR_IE]) - ie_len = nla_len(info->attrs[NL80211_ATTR_IE]); + if (attrs[NL80211_ATTR_IE]) + ie_len = nla_len(attrs[NL80211_ATTR_IE]); else ie_len = 0; if (ie_len > wiphy->max_sched_scan_ie_len) - return -EINVAL; - - if (rdev->sched_scan_req) { - err = -EINPROGRESS; - goto out; - } + return ERR_PTR(-EINVAL); request = kzalloc(sizeof(*request) + sizeof(*request->ssids) * n_ssids + sizeof(*request->match_sets) * n_match_sets + sizeof(*request->channels) * n_channels + ie_len, GFP_KERNEL); - if (!request) { - err = -ENOMEM; - goto out; - } + if (!request) + return ERR_PTR(-ENOMEM); if (n_ssids) request->ssids = (void *)&request->channels[n_channels]; @@ -5813,10 +5895,10 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, request->n_match_sets = n_match_sets; i = 0; - if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { + if (attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { /* user specified, bail out if channel not found */ nla_for_each_nested(attr, - info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], + attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) { struct ieee80211_channel *chan; @@ -5862,8 +5944,8 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, request->n_channels = i; i = 0; - if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) { - nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], + if (attrs[NL80211_ATTR_SCAN_SSIDS]) { + nla_for_each_nested(attr, attrs[NL80211_ATTR_SCAN_SSIDS], tmp) { if (nla_len(attr) > IEEE80211_MAX_SSID_LEN) { err = -EINVAL; @@ -5877,9 +5959,9 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, } i = 0; - if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) { + if (attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) { nla_for_each_nested(attr, - info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH], + attrs[NL80211_ATTR_SCHED_SCAN_MATCH], tmp) { struct nlattr *ssid, *rssi; @@ -5934,36 +6016,88 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, if (ie_len) { request->ie_len = ie_len; memcpy((void *)request->ie, - nla_data(info->attrs[NL80211_ATTR_IE]), + nla_data(attrs[NL80211_ATTR_IE]), request->ie_len); } - if (info->attrs[NL80211_ATTR_SCAN_FLAGS]) { + if (attrs[NL80211_ATTR_SCAN_FLAGS]) { request->flags = nla_get_u32( - info->attrs[NL80211_ATTR_SCAN_FLAGS]); + attrs[NL80211_ATTR_SCAN_FLAGS]); if ((request->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) && !(wiphy->features & NL80211_FEATURE_LOW_PRIORITY_SCAN)) { err = -EOPNOTSUPP; goto out_free; } + + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + u32 flg = NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR; + + if (!wdev) /* must be net-detect */ + flg = NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + if (!(wiphy->features & flg)) { + err = -EOPNOTSUPP; + goto out_free; + } + + if (wdev && wdev->current_bss) { + err = -EOPNOTSUPP; + goto out_free; + } + + err = nl80211_parse_random_mac(attrs, request->mac_addr, + request->mac_addr_mask); + if (err) + goto out_free; + } } - request->dev = dev; - request->wiphy = &rdev->wiphy; request->interval = interval; request->scan_start = jiffies; - err = rdev_sched_scan_start(rdev, dev, request); - if (!err) { - rdev->sched_scan_req = request; - nl80211_send_sched_scan(rdev, dev, - NL80211_CMD_START_SCHED_SCAN); - goto out; - } + return request; out_free: kfree(request); -out: + return ERR_PTR(err); +} + +static int nl80211_start_sched_scan(struct sk_buff *skb, + struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct net_device *dev = info->user_ptr[1]; + struct wireless_dev *wdev = dev->ieee80211_ptr; + int err; + + if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) || + !rdev->ops->sched_scan_start) + return -EOPNOTSUPP; + + if (rdev->sched_scan_req) + return -EINPROGRESS; + + rdev->sched_scan_req = nl80211_parse_sched_scan(&rdev->wiphy, wdev, + info->attrs); + err = PTR_ERR_OR_ZERO(rdev->sched_scan_req); + if (err) + goto out_err; + + err = rdev_sched_scan_start(rdev, dev, rdev->sched_scan_req); + if (err) + goto out_free; + + rdev->sched_scan_req->dev = dev; + rdev->sched_scan_req->wiphy = &rdev->wiphy; + + nl80211_send_sched_scan(rdev, dev, + NL80211_CMD_START_SCHED_SCAN); + return 0; + +out_free: + kfree(rdev->sched_scan_req); +out_err: + rdev->sched_scan_req = NULL; return err; } @@ -8681,6 +8815,39 @@ static int nl80211_parse_wowlan_tcp(struct cfg80211_registered_device *rdev, return 0; } +static int nl80211_parse_wowlan_nd(struct cfg80211_registered_device *rdev, + const struct wiphy_wowlan_support *wowlan, + struct nlattr *attr, + struct cfg80211_wowlan *trig) +{ + struct nlattr **tb; + int err; + + tb = kzalloc(NUM_NL80211_ATTR * sizeof(*tb), GFP_KERNEL); + if (!tb) + return -ENOMEM; + + if (!(wowlan->flags & WIPHY_WOWLAN_NET_DETECT)) { + err = -EOPNOTSUPP; + goto out; + } + + err = nla_parse(tb, NL80211_ATTR_MAX, + nla_data(attr), nla_len(attr), + nl80211_policy); + if (err) + goto out; + + trig->nd_config = nl80211_parse_sched_scan(&rdev->wiphy, NULL, tb); + err = PTR_ERR_OR_ZERO(trig->nd_config); + if (err) + trig->nd_config = NULL; + +out: + kfree(tb); + return err; +} + static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -8826,6 +8993,14 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info) goto error; } + if (tb[NL80211_WOWLAN_TRIG_NET_DETECT]) { + err = nl80211_parse_wowlan_nd( + rdev, wowlan, tb[NL80211_WOWLAN_TRIG_NET_DETECT], + &new_triggers); + if (err) + goto error; + } + ntrig = kmemdup(&new_triggers, sizeof(new_triggers), GFP_KERNEL); if (!ntrig) { err = -ENOMEM; @@ -9658,6 +9833,98 @@ static int nl80211_del_tx_ts(struct sk_buff *skb, struct genl_info *info) return err; } +static int nl80211_tdls_channel_switch(struct sk_buff *skb, + struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct net_device *dev = info->user_ptr[1]; + struct wireless_dev *wdev = dev->ieee80211_ptr; + struct cfg80211_chan_def chandef = {}; + const u8 *addr; + u8 oper_class; + int err; + + if (!rdev->ops->tdls_channel_switch || + !(rdev->wiphy.features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH)) + return -EOPNOTSUPP; + + switch (dev->ieee80211_ptr->iftype) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + break; + default: + return -EOPNOTSUPP; + } + + if (!info->attrs[NL80211_ATTR_MAC] || + !info->attrs[NL80211_ATTR_OPER_CLASS]) + return -EINVAL; + + err = nl80211_parse_chandef(rdev, info, &chandef); + if (err) + return err; + + /* + * Don't allow wide channels on the 2.4Ghz band, as per IEEE802.11-2012 + * section 10.22.6.2.1. Disallow 5/10Mhz channels as well for now, the + * specification is not defined for them. + */ + if (chandef.chan->band == IEEE80211_BAND_2GHZ && + chandef.width != NL80211_CHAN_WIDTH_20_NOHT && + chandef.width != NL80211_CHAN_WIDTH_20) + return -EINVAL; + + /* we will be active on the TDLS link */ + if (!cfg80211_reg_can_beacon(&rdev->wiphy, &chandef, wdev->iftype)) + return -EINVAL; + + /* don't allow switching to DFS channels */ + if (cfg80211_chandef_dfs_required(wdev->wiphy, &chandef, wdev->iftype)) + return -EINVAL; + + addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + oper_class = nla_get_u8(info->attrs[NL80211_ATTR_OPER_CLASS]); + + wdev_lock(wdev); + err = rdev_tdls_channel_switch(rdev, dev, addr, oper_class, &chandef); + wdev_unlock(wdev); + + return err; +} + +static int nl80211_tdls_cancel_channel_switch(struct sk_buff *skb, + struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct net_device *dev = info->user_ptr[1]; + struct wireless_dev *wdev = dev->ieee80211_ptr; + const u8 *addr; + + if (!rdev->ops->tdls_channel_switch || + !rdev->ops->tdls_cancel_channel_switch || + !(rdev->wiphy.features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH)) + return -EOPNOTSUPP; + + switch (dev->ieee80211_ptr->iftype) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + break; + default: + return -EOPNOTSUPP; + } + + if (!info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + + addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + + wdev_lock(wdev); + rdev_tdls_cancel_channel_switch(rdev, dev, addr); + wdev_unlock(wdev); + + return 0; +} + #define NL80211_FLAG_NEED_WIPHY 0x01 #define NL80211_FLAG_NEED_NETDEV 0x02 #define NL80211_FLAG_NEED_RTNL 0x04 @@ -10456,6 +10723,22 @@ static const struct genl_ops nl80211_ops[] = { .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, + { + .cmd = NL80211_CMD_TDLS_CHANNEL_SWITCH, + .doit = nl80211_tdls_channel_switch, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | + NL80211_FLAG_NEED_RTNL, + }, + { + .cmd = NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH, + .doit = nl80211_tdls_cancel_channel_switch, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | + NL80211_FLAG_NEED_RTNL, + }, }; /* notification functions */ @@ -11653,7 +11936,9 @@ EXPORT_SYMBOL(cfg80211_pmksa_candidate_notify); static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev, struct net_device *netdev, struct cfg80211_chan_def *chandef, - gfp_t gfp) + gfp_t gfp, + enum nl80211_commands notif, + u8 count) { struct sk_buff *msg; void *hdr; @@ -11662,7 +11947,7 @@ static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev, if (!msg) return; - hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_CH_SWITCH_NOTIFY); + hdr = nl80211hdr_put(msg, 0, 0, 0, notif); if (!hdr) { nlmsg_free(msg); return; @@ -11674,6 +11959,10 @@ static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev, if (nl80211_send_chandef(msg, chandef)) goto nla_put_failure; + if ((notif == NL80211_CMD_CH_SWITCH_STARTED_NOTIFY) && + (nla_put_u32(msg, NL80211_ATTR_CH_SWITCH_COUNT, count))) + goto nla_put_failure; + genlmsg_end(msg, hdr); genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0, @@ -11696,18 +11985,28 @@ void cfg80211_ch_switch_notify(struct net_device *dev, trace_cfg80211_ch_switch_notify(dev, chandef); - if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP && - wdev->iftype != NL80211_IFTYPE_P2P_GO && - wdev->iftype != NL80211_IFTYPE_ADHOC && - wdev->iftype != NL80211_IFTYPE_MESH_POINT)) - return; - wdev->chandef = *chandef; wdev->preset_chandef = *chandef; - nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL); + nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL, + NL80211_CMD_CH_SWITCH_NOTIFY, 0); } EXPORT_SYMBOL(cfg80211_ch_switch_notify); +void cfg80211_ch_switch_started_notify(struct net_device *dev, + struct cfg80211_chan_def *chandef, + u8 count) +{ + struct wireless_dev *wdev = dev->ieee80211_ptr; + struct wiphy *wiphy = wdev->wiphy; + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + + trace_cfg80211_ch_switch_started_notify(dev, chandef); + + nl80211_ch_switch_notify(rdev, dev, chandef, GFP_KERNEL, + NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, count); +} +EXPORT_SYMBOL(cfg80211_ch_switch_started_notify); + void cfg80211_cqm_txe_notify(struct net_device *dev, const u8 *peer, u32 num_packets, u32 rate, u32 intvl, gfp_t gfp) @@ -11944,6 +12243,67 @@ void cfg80211_report_obss_beacon(struct wiphy *wiphy, EXPORT_SYMBOL(cfg80211_report_obss_beacon); #ifdef CONFIG_PM +static int cfg80211_net_detect_results(struct sk_buff *msg, + struct cfg80211_wowlan_wakeup *wakeup) +{ + struct cfg80211_wowlan_nd_info *nd = wakeup->net_detect; + struct nlattr *nl_results, *nl_match, *nl_freqs; + int i, j; + + nl_results = nla_nest_start( + msg, NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS); + if (!nl_results) + return -EMSGSIZE; + + for (i = 0; i < nd->n_matches; i++) { + struct cfg80211_wowlan_nd_match *match = nd->matches[i]; + + nl_match = nla_nest_start(msg, i); + if (!nl_match) + break; + + /* The SSID attribute is optional in nl80211, but for + * simplicity reasons it's always present in the + * cfg80211 structure. If a driver can't pass the + * SSID, that needs to be changed. A zero length SSID + * is still a valid SSID (wildcard), so it cannot be + * used for this purpose. + */ + if (nla_put(msg, NL80211_ATTR_SSID, match->ssid.ssid_len, + match->ssid.ssid)) { + nla_nest_cancel(msg, nl_match); + goto out; + } + + if (match->n_channels) { + nl_freqs = nla_nest_start( + msg, NL80211_ATTR_SCAN_FREQUENCIES); + if (!nl_freqs) { + nla_nest_cancel(msg, nl_match); + goto out; + } + + for (j = 0; j < match->n_channels; j++) { + if (nla_put_u32(msg, + NL80211_ATTR_WIPHY_FREQ, + match->channels[j])) { + nla_nest_cancel(msg, nl_freqs); + nla_nest_cancel(msg, nl_match); + goto out; + } + } + + nla_nest_end(msg, nl_freqs); + } + + nla_nest_end(msg, nl_match); + } + +out: + nla_nest_end(msg, nl_results); + return 0; +} + void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev, struct cfg80211_wowlan_wakeup *wakeup, gfp_t gfp) @@ -12038,6 +12398,10 @@ void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev, goto free_msg; } + if (wakeup->net_detect && + cfg80211_net_detect_results(msg, wakeup)) + goto free_msg; + nla_nest_end(msg, reasons); } diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 1b3864cd50ca..35cfb7134bdb 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -993,4 +993,28 @@ rdev_del_tx_ts(struct cfg80211_registered_device *rdev, return ret; } +static inline int +rdev_tdls_channel_switch(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *addr, + u8 oper_class, struct cfg80211_chan_def *chandef) +{ + int ret; + + trace_rdev_tdls_channel_switch(&rdev->wiphy, dev, addr, oper_class, + chandef); + ret = rdev->ops->tdls_channel_switch(&rdev->wiphy, dev, addr, + oper_class, chandef); + trace_rdev_return_int(&rdev->wiphy, ret); + return ret; +} + +static inline void +rdev_tdls_cancel_channel_switch(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *addr) +{ + trace_rdev_tdls_cancel_channel_switch(&rdev->wiphy, dev, addr); + rdev->ops->tdls_cancel_channel_switch(&rdev->wiphy, dev, addr); + trace_rdev_return_void(&rdev->wiphy); +} + #endif /* __CFG80211_RDEV_OPS */ diff --git a/net/wireless/reg.c b/net/wireless/reg.c index b725a31a4751..32d8310b0f85 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -573,8 +573,9 @@ static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) return get_cfg80211_regdom(); } -unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, - const struct ieee80211_reg_rule *rule) +static unsigned int +reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, + const struct ieee80211_reg_rule *rule) { const struct ieee80211_freq_range *freq_range = &rule->freq_range; const struct ieee80211_freq_range *freq_range_tmp; @@ -622,6 +623,27 @@ unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, return end_freq - start_freq; } +unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, + const struct ieee80211_reg_rule *rule) +{ + unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); + + if (rule->flags & NL80211_RRF_NO_160MHZ) + bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); + if (rule->flags & NL80211_RRF_NO_80MHZ) + bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); + + /* + * HT40+/HT40- limits are handled per-channel. Only limit BW if both + * are not allowed. + */ + if (rule->flags & NL80211_RRF_NO_HT40MINUS && + rule->flags & NL80211_RRF_NO_HT40PLUS) + bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); + + return bw; +} + /* Sanity check on a regulatory rule */ static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) { @@ -946,6 +968,16 @@ static u32 map_regdom_flags(u32 rd_flags) channel_flags |= IEEE80211_CHAN_NO_OFDM; if (rd_flags & NL80211_RRF_NO_OUTDOOR) channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; + if (rd_flags & NL80211_RRF_GO_CONCURRENT) + channel_flags |= IEEE80211_CHAN_GO_CONCURRENT; + if (rd_flags & NL80211_RRF_NO_HT40MINUS) + channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; + if (rd_flags & NL80211_RRF_NO_HT40PLUS) + channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; + if (rd_flags & NL80211_RRF_NO_80MHZ) + channel_flags |= IEEE80211_CHAN_NO_80MHZ; + if (rd_flags & NL80211_RRF_NO_160MHZ) + channel_flags |= IEEE80211_CHAN_NO_160MHZ; return channel_flags; } @@ -1565,10 +1597,23 @@ static void handle_channel_custom(struct wiphy *wiphy, if (max_bandwidth_khz < MHZ_TO_KHZ(160)) bw_flags |= IEEE80211_CHAN_NO_160MHZ; + chan->dfs_state_entered = jiffies; + chan->dfs_state = NL80211_DFS_USABLE; + + chan->beacon_found = false; chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); chan->max_reg_power = chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp); + + if (chan->flags & IEEE80211_CHAN_RADAR) { + if (reg_rule->dfs_cac_ms) + chan->dfs_cac_ms = reg_rule->dfs_cac_ms; + else + chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; + } + + chan->max_power = chan->max_reg_power; } static void handle_band_custom(struct wiphy *wiphy, diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 277a85df910e..ad38910f7036 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2032,6 +2032,48 @@ TRACE_EVENT(rdev_del_tx_ts, WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(peer), __entry->tsid) ); +TRACE_EVENT(rdev_tdls_channel_switch, + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, + const u8 *addr, u8 oper_class, + struct cfg80211_chan_def *chandef), + TP_ARGS(wiphy, netdev, addr, oper_class, chandef), + TP_STRUCT__entry( + WIPHY_ENTRY + NETDEV_ENTRY + MAC_ENTRY(addr) + __field(u8, oper_class) + CHAN_DEF_ENTRY + ), + TP_fast_assign( + WIPHY_ASSIGN; + NETDEV_ASSIGN; + MAC_ASSIGN(addr, addr); + CHAN_DEF_ASSIGN(chandef); + ), + TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT + " oper class %d, " CHAN_DEF_PR_FMT, + WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr), + __entry->oper_class, CHAN_DEF_PR_ARG) +); + +TRACE_EVENT(rdev_tdls_cancel_channel_switch, + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, + const u8 *addr), + TP_ARGS(wiphy, netdev, addr), + TP_STRUCT__entry( + WIPHY_ENTRY + NETDEV_ENTRY + MAC_ENTRY(addr) + ), + TP_fast_assign( + WIPHY_ASSIGN; + NETDEV_ASSIGN; + MAC_ASSIGN(addr, addr); + ), + TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT, + WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr)) +); + /************************************************************* * cfg80211 exported functions traces * *************************************************************/ @@ -2355,6 +2397,22 @@ TRACE_EVENT(cfg80211_ch_switch_notify, NETDEV_PR_ARG, CHAN_DEF_PR_ARG) ); +TRACE_EVENT(cfg80211_ch_switch_started_notify, + TP_PROTO(struct net_device *netdev, + struct cfg80211_chan_def *chandef), + TP_ARGS(netdev, chandef), + TP_STRUCT__entry( + NETDEV_ENTRY + CHAN_DEF_ENTRY + ), + TP_fast_assign( + NETDEV_ASSIGN; + CHAN_DEF_ASSIGN(chandef); + ), + TP_printk(NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT, + NETDEV_PR_ARG, CHAN_DEF_PR_ARG) +); + TRACE_EVENT(cfg80211_radar_event, TP_PROTO(struct wiphy *wiphy, struct cfg80211_chan_def *chandef), TP_ARGS(wiphy, chandef), |