diff options
Diffstat (limited to 'drivers/net/wireless/mwifiex/tdls.c')
-rw-r--r-- | drivers/net/wireless/mwifiex/tdls.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/drivers/net/wireless/mwifiex/tdls.c b/drivers/net/wireless/mwifiex/tdls.c new file mode 100644 index 000000000000..73cd444a6d43 --- /dev/null +++ b/drivers/net/wireless/mwifiex/tdls.c @@ -0,0 +1,423 @@ +/* Marvell Wireless LAN device driver: TDLS handling + * + * Copyright (C) 2014, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available on the worldwide web at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" + +/* This function appends rate TLV to scan config command. */ +static int +mwifiex_tdls_append_rates_ie(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + u8 rates[MWIFIEX_SUPPORTED_RATES], *pos; + u16 rates_size, supp_rates_size, ext_rates_size; + + memset(rates, 0, sizeof(rates)); + rates_size = mwifiex_get_supported_rates(priv, rates); + + supp_rates_size = min_t(u16, rates_size, MWIFIEX_TDLS_SUPPORTED_RATES); + + if (skb_tailroom(skb) < rates_size + 4) { + dev_err(priv->adapter->dev, + "Insuffient space while adding rates\n"); + return -ENOMEM; + } + + pos = skb_put(skb, supp_rates_size + 2); + *pos++ = WLAN_EID_SUPP_RATES; + *pos++ = supp_rates_size; + memcpy(pos, rates, supp_rates_size); + + if (rates_size > MWIFIEX_TDLS_SUPPORTED_RATES) { + ext_rates_size = rates_size - MWIFIEX_TDLS_SUPPORTED_RATES; + pos = skb_put(skb, ext_rates_size + 2); + *pos++ = WLAN_EID_EXT_SUPP_RATES; + *pos++ = ext_rates_size; + memcpy(pos, rates + MWIFIEX_TDLS_SUPPORTED_RATES, + ext_rates_size); + } + + return 0; +} + +static void mwifiex_tdls_add_ext_capab(struct sk_buff *skb) +{ + struct ieee_types_extcap *extcap; + + extcap = (void *)skb_put(skb, sizeof(struct ieee_types_extcap)); + extcap->ieee_hdr.element_id = WLAN_EID_EXT_CAPABILITY; + extcap->ieee_hdr.len = 8; + memset(extcap->ext_capab, 0, 8); + extcap->ext_capab[4] |= WLAN_EXT_CAPA5_TDLS_ENABLED; +} + +static void mwifiex_tdls_add_qos_capab(struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 3); + + *pos++ = WLAN_EID_QOS_CAPA; + *pos++ = 1; + *pos++ = MWIFIEX_TDLS_DEF_QOS_CAPAB; +} + +static int mwifiex_prep_tdls_encap_data(struct mwifiex_private *priv, + u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_tdls_data *tf; + int ret; + u16 capab; + struct ieee80211_ht_cap *ht_cap; + u8 radio, *pos; + + capab = priv->curr_bss_params.bss_descriptor.cap_info_bitmap; + + tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u)); + memcpy(tf->da, peer, ETH_ALEN); + memcpy(tf->sa, priv->curr_addr, ETH_ALEN); + tf->ether_type = cpu_to_be16(ETH_P_TDLS); + tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_REQUEST; + skb_put(skb, sizeof(tf->u.setup_req)); + tf->u.setup_req.dialog_token = dialog_token; + tf->u.setup_req.capability = cpu_to_le16(capab); + ret = mwifiex_tdls_append_rates_ie(priv, skb); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + pos = (void *)skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2); + *pos++ = WLAN_EID_HT_CAPABILITY; + *pos++ = sizeof(struct ieee80211_ht_cap); + ht_cap = (void *)pos; + radio = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + ret = mwifiex_fill_cap_info(priv, radio, ht_cap); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + mwifiex_tdls_add_ext_capab(skb); + mwifiex_tdls_add_qos_capab(skb); + break; + + case WLAN_TDLS_SETUP_RESPONSE: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_RESPONSE; + skb_put(skb, sizeof(tf->u.setup_resp)); + tf->u.setup_resp.status_code = cpu_to_le16(status_code); + tf->u.setup_resp.dialog_token = dialog_token; + tf->u.setup_resp.capability = cpu_to_le16(capab); + ret = mwifiex_tdls_append_rates_ie(priv, skb); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + pos = (void *)skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2); + *pos++ = WLAN_EID_HT_CAPABILITY; + *pos++ = sizeof(struct ieee80211_ht_cap); + ht_cap = (void *)pos; + radio = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + ret = mwifiex_fill_cap_info(priv, radio, ht_cap); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + mwifiex_tdls_add_ext_capab(skb); + mwifiex_tdls_add_qos_capab(skb); + break; + + case WLAN_TDLS_SETUP_CONFIRM: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_SETUP_CONFIRM; + skb_put(skb, sizeof(tf->u.setup_cfm)); + tf->u.setup_cfm.status_code = cpu_to_le16(status_code); + tf->u.setup_cfm.dialog_token = dialog_token; + break; + + case WLAN_TDLS_TEARDOWN: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_TEARDOWN; + skb_put(skb, sizeof(tf->u.teardown)); + tf->u.teardown.reason_code = cpu_to_le16(status_code); + break; + + case WLAN_TDLS_DISCOVERY_REQUEST: + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST; + skb_put(skb, sizeof(tf->u.discover_req)); + tf->u.discover_req.dialog_token = dialog_token; + break; + default: + dev_err(priv->adapter->dev, "Unknown TDLS frame type.\n"); + return -EINVAL; + } + + return 0; +} + +static void +mwifiex_tdls_add_link_ie(struct sk_buff *skb, u8 *src_addr, u8 *peer, u8 *bssid) +{ + struct ieee80211_tdls_lnkie *lnkid; + + lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); + lnkid->ie_type = WLAN_EID_LINK_ID; + lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - + sizeof(struct ieee_types_header); + + memcpy(lnkid->bssid, bssid, ETH_ALEN); + memcpy(lnkid->init_sta, src_addr, ETH_ALEN); + memcpy(lnkid->resp_sta, peer, ETH_ALEN); +} + +int mwifiex_send_tdls_data_frame(struct mwifiex_private *priv, + u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, const u8 *extra_ies, + size_t extra_ies_len) +{ + struct sk_buff *skb; + struct mwifiex_txinfo *tx_info; + struct timeval tv; + int ret; + u16 skb_len; + + skb_len = MWIFIEX_MIN_DATA_HEADER_LEN + + max(sizeof(struct ieee80211_mgmt), + sizeof(struct ieee80211_tdls_data)) + + MWIFIEX_MGMT_FRAME_HEADER_SIZE + + MWIFIEX_SUPPORTED_RATES + + 3 + /* Qos Info */ + sizeof(struct ieee_types_extcap) + + sizeof(struct ieee80211_ht_cap) + + sizeof(struct ieee_types_bss_co_2040) + + sizeof(struct ieee80211_ht_operation) + + sizeof(struct ieee80211_tdls_lnkie) + + extra_ies_len; + + skb = dev_alloc_skb(skb_len); + if (!skb) { + dev_err(priv->adapter->dev, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + skb_reserve(skb, MWIFIEX_MIN_DATA_HEADER_LEN); + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_CONFIRM: + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + ret = mwifiex_prep_tdls_encap_data(priv, peer, action_code, + dialog_token, status_code, + skb); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, + extra_ies_len); + mwifiex_tdls_add_link_ie(skb, priv->curr_addr, peer, + priv->cfg_bssid); + break; + case WLAN_TDLS_SETUP_RESPONSE: + ret = mwifiex_prep_tdls_encap_data(priv, peer, action_code, + dialog_token, status_code, + skb); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, + extra_ies_len); + mwifiex_tdls_add_link_ie(skb, peer, priv->curr_addr, + priv->cfg_bssid); + break; + } + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + skb->priority = MWIFIEX_PRIO_BK; + break; + default: + skb->priority = MWIFIEX_PRIO_VI; + break; + } + + tx_info = MWIFIEX_SKB_TXCB(skb); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + + do_gettimeofday(&tv); + skb->tstamp = timeval_to_ktime(tv); + mwifiex_queue_tx_pkt(priv, skb); + + return 0; +} + +static int +mwifiex_construct_tdls_action_frame(struct mwifiex_private *priv, u8 *peer, + u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt; + u8 bc_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + int ret; + u16 capab; + struct ieee80211_ht_cap *ht_cap; + u8 radio, *pos; + + capab = priv->curr_bss_params.bss_descriptor.cap_info_bitmap; + + mgmt = (void *)skb_put(skb, offsetof(struct ieee80211_mgmt, u)); + + memset(mgmt, 0, 24); + memcpy(mgmt->da, peer, ETH_ALEN); + memcpy(mgmt->sa, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->bssid, priv->cfg_bssid, ETH_ALEN); + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + + /* add address 4 */ + pos = skb_put(skb, ETH_ALEN); + + switch (action_code) { + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + skb_put(skb, sizeof(mgmt->u.action.u.tdls_discover_resp) + 1); + mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; + mgmt->u.action.u.tdls_discover_resp.action_code = + WLAN_PUB_ACTION_TDLS_DISCOVER_RES; + mgmt->u.action.u.tdls_discover_resp.dialog_token = + dialog_token; + mgmt->u.action.u.tdls_discover_resp.capability = + cpu_to_le16(capab); + /* move back for addr4 */ + memmove(pos + ETH_ALEN, &mgmt->u.action.category, + sizeof(mgmt->u.action.u.tdls_discover_resp)); + /* init address 4 */ + memcpy(pos, bc_addr, ETH_ALEN); + + ret = mwifiex_tdls_append_rates_ie(priv, skb); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + pos = (void *)skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2); + *pos++ = WLAN_EID_HT_CAPABILITY; + *pos++ = sizeof(struct ieee80211_ht_cap); + ht_cap = (void *)pos; + radio = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + ret = mwifiex_fill_cap_info(priv, radio, ht_cap); + if (ret) { + dev_kfree_skb_any(skb); + return ret; + } + + mwifiex_tdls_add_ext_capab(skb); + mwifiex_tdls_add_qos_capab(skb); + break; + default: + dev_err(priv->adapter->dev, "Unknown TDLS action frame type\n"); + return -EINVAL; + } + + return 0; +} + +int mwifiex_send_tdls_action_frame(struct mwifiex_private *priv, + u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, const u8 *extra_ies, + size_t extra_ies_len) +{ + struct sk_buff *skb; + struct mwifiex_txinfo *tx_info; + struct timeval tv; + u8 *pos; + u32 pkt_type, tx_control; + u16 pkt_len, skb_len; + + skb_len = MWIFIEX_MIN_DATA_HEADER_LEN + + max(sizeof(struct ieee80211_mgmt), + sizeof(struct ieee80211_tdls_data)) + + MWIFIEX_MGMT_FRAME_HEADER_SIZE + + MWIFIEX_SUPPORTED_RATES + + sizeof(struct ieee_types_extcap) + + sizeof(struct ieee80211_ht_cap) + + sizeof(struct ieee_types_bss_co_2040) + + sizeof(struct ieee80211_ht_operation) + + sizeof(struct ieee80211_tdls_lnkie) + + extra_ies_len + + 3 + /* Qos Info */ + ETH_ALEN; /* Address4 */ + + skb = dev_alloc_skb(skb_len); + if (!skb) { + dev_err(priv->adapter->dev, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + skb_reserve(skb, MWIFIEX_MIN_DATA_HEADER_LEN); + + pkt_type = PKT_TYPE_MGMT; + tx_control = 0; + pos = skb_put(skb, MWIFIEX_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memset(pos, 0, MWIFIEX_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memcpy(pos, &pkt_type, sizeof(pkt_type)); + memcpy(pos + sizeof(pkt_type), &tx_control, sizeof(tx_control)); + + if (mwifiex_construct_tdls_action_frame(priv, peer, action_code, + dialog_token, status_code, + skb)) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); + + /* the TDLS link IE is always added last we are the responder */ + + mwifiex_tdls_add_link_ie(skb, peer, priv->curr_addr, + priv->cfg_bssid); + + skb->priority = MWIFIEX_PRIO_VI; + + tx_info = MWIFIEX_SKB_TXCB(skb); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= MWIFIEX_BUF_FLAG_TDLS_PKT; + + pkt_len = skb->len - MWIFIEX_MGMT_FRAME_HEADER_SIZE - sizeof(pkt_len); + memcpy(skb->data + MWIFIEX_MGMT_FRAME_HEADER_SIZE, &pkt_len, + sizeof(pkt_len)); + do_gettimeofday(&tv); + skb->tstamp = timeval_to_ktime(tv); + mwifiex_queue_tx_pkt(priv, skb); + + return 0; +} |