783 lines
22 KiB
C
783 lines
22 KiB
C
|
/**
|
||
|
******************************************************************************
|
||
|
*
|
||
|
* @file rwnx_tx.c
|
||
|
*
|
||
|
* Copyright (C) RivieraWaves 2012-2019
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* INCLUDE FILES
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
#include "rwnx_tdls.h"
|
||
|
#include "rwnx_compat.h"
|
||
|
|
||
|
/**
|
||
|
* FUNCTION DEFINITIONS
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
static u16
|
||
|
rwnx_get_tdls_sta_capab(struct rwnx_vif *rwnx_vif, u16 status_code)
|
||
|
{
|
||
|
u16 capab = 0;
|
||
|
|
||
|
/* The capability will be 0 when sending a failure code */
|
||
|
if (status_code != 0)
|
||
|
return capab;
|
||
|
|
||
|
if (rwnx_vif->sta.ap->band != NL80211_BAND_2GHZ)
|
||
|
return capab;
|
||
|
|
||
|
capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME;
|
||
|
capab |= WLAN_CAPABILITY_SHORT_PREAMBLE;
|
||
|
|
||
|
return capab;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
rwnx_tdls_prepare_encap_data(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
const u8 *peer, u8 action_code, u8 dialog_token,
|
||
|
u16 status_code, struct sk_buff *skb)
|
||
|
{
|
||
|
struct ieee80211_tdls_data *tf;
|
||
|
tf = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_data) - sizeof(tf->u));
|
||
|
|
||
|
// set eth header
|
||
|
memcpy(tf->da, peer, ETH_ALEN);
|
||
|
memcpy(tf->sa, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
|
||
|
tf->ether_type = cpu_to_be16(ETH_P_TDLS);
|
||
|
|
||
|
// set common TDLS info
|
||
|
tf->payload_type = WLAN_TDLS_SNAP_RFTYPE;
|
||
|
tf->category = WLAN_CATEGORY_TDLS;
|
||
|
tf->action_code = action_code;
|
||
|
|
||
|
// set action specific TDLS info
|
||
|
switch (action_code) {
|
||
|
case 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(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
|
||
|
break;
|
||
|
|
||
|
case 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(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
|
||
|
break;
|
||
|
|
||
|
case 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:
|
||
|
skb_put(skb, sizeof(tf->u.teardown));
|
||
|
tf->u.teardown.reason_code = cpu_to_le16(status_code);
|
||
|
break;
|
||
|
|
||
|
case WLAN_TDLS_DISCOVERY_REQUEST:
|
||
|
skb_put(skb, sizeof(tf->u.discover_req));
|
||
|
tf->u.discover_req.dialog_token = dialog_token;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
rwnx_prep_tdls_direct(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
const u8 *peer, u8 action_code, u8 dialog_token,
|
||
|
u16 status_code, struct sk_buff *skb)
|
||
|
{
|
||
|
struct ieee80211_mgmt *mgmt;
|
||
|
|
||
|
mgmt = (void *)skb_put(skb, 24);
|
||
|
memset(mgmt, 0, 24);
|
||
|
memcpy(mgmt->da, peer, ETH_ALEN);
|
||
|
memcpy(mgmt->sa, rwnx_hw->wiphy->perm_addr, ETH_ALEN);
|
||
|
memcpy(mgmt->bssid, rwnx_vif->sta.ap->mac_addr, ETH_ALEN);
|
||
|
|
||
|
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
||
|
IEEE80211_STYPE_ACTION);
|
||
|
|
||
|
switch (action_code) {
|
||
|
case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
|
||
|
skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp));
|
||
|
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(rwnx_get_tdls_sta_capab(rwnx_vif, status_code));
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
rwnx_add_srates_ie(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
|
||
|
{
|
||
|
u8 i, rates, *pos;
|
||
|
int rate;
|
||
|
struct ieee80211_supported_band *rwnx_band_2GHz = rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
|
||
|
|
||
|
rates = 8;
|
||
|
|
||
|
if (skb_tailroom(skb) < rates + 2)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
pos = skb_put(skb, rates + 2);
|
||
|
*pos++ = WLAN_EID_SUPP_RATES;
|
||
|
*pos++ = rates;
|
||
|
for (i = 0; i < rates; i++) {
|
||
|
rate = rwnx_band_2GHz->bitrates[i].bitrate;
|
||
|
rate = DIV_ROUND_UP(rate, 5);
|
||
|
*pos++ = (u8)rate;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
rwnx_add_ext_srates_ie(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
|
||
|
{
|
||
|
u8 i, exrates, *pos;
|
||
|
int rate;
|
||
|
struct ieee80211_supported_band *rwnx_band_2GHz = rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
|
||
|
|
||
|
exrates = rwnx_band_2GHz->n_bitrates - 8;
|
||
|
|
||
|
if (skb_tailroom(skb) < exrates + 2)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
pos = skb_put(skb, exrates + 2);
|
||
|
*pos++ = WLAN_EID_EXT_SUPP_RATES;
|
||
|
*pos++ = exrates;
|
||
|
for (i = 8; i < (8+exrates); i++) {
|
||
|
rate = rwnx_band_2GHz->bitrates[i].bitrate;
|
||
|
rate = DIV_ROUND_UP(rate, 5);
|
||
|
*pos++ = (u8)rate;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_supp_channels(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
|
||
|
{
|
||
|
/*
|
||
|
* Add possible channels for TDLS. These are channels that are allowed
|
||
|
* to be active.
|
||
|
*/
|
||
|
u8 subband_cnt = 0;
|
||
|
u8 *pos_subband;
|
||
|
u8 *pos = skb_put(skb, 2);
|
||
|
struct ieee80211_supported_band *rwnx_band_2GHz = rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
|
||
|
struct ieee80211_supported_band *rwnx_band_5GHz = rwnx_hw->wiphy->bands[NL80211_BAND_5GHZ];
|
||
|
|
||
|
*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 */
|
||
|
pos_subband = skb_put(skb, 2);
|
||
|
if (rwnx_band_2GHz->n_channels > 0) {
|
||
|
*pos_subband++ = ieee80211_frequency_to_channel(rwnx_band_2GHz->channels[0].center_freq);
|
||
|
*pos_subband++ = rwnx_band_2GHz->n_channels;
|
||
|
subband_cnt++;
|
||
|
}
|
||
|
|
||
|
/* 5GHz, with 20MHz spacing */
|
||
|
pos_subband = skb_put(skb, 2);
|
||
|
if (rwnx_hw->band_5g_support) {
|
||
|
if (rwnx_band_5GHz->n_channels > 0) {
|
||
|
*pos_subband++ = ieee80211_frequency_to_channel(rwnx_band_5GHz->channels[0].center_freq);
|
||
|
*pos_subband++ = rwnx_band_5GHz->n_channels;
|
||
|
subband_cnt++;
|
||
|
}
|
||
|
}
|
||
|
/* length */
|
||
|
*pos = 2 * subband_cnt;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_ext_capab(struct rwnx_hw *rwnx_hw, struct sk_buff *skb)
|
||
|
{
|
||
|
u8 *pos = (void *)skb_put(skb, 7);
|
||
|
bool chan_switch = rwnx_hw->wiphy->features &
|
||
|
NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
|
||
|
|
||
|
*pos++ = WLAN_EID_EXT_CAPABILITY;
|
||
|
*pos++ = 5; /* len */
|
||
|
*pos++ = 0x0;
|
||
|
*pos++ = 0x0;
|
||
|
*pos++ = 0x0;
|
||
|
*pos++ = WLAN_EXT_CAPA4_TDLS_BUFFER_STA |
|
||
|
(chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0);
|
||
|
*pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_add_wmm_info_ie(struct sk_buff *skb, u8 qosinfo)
|
||
|
{
|
||
|
u8 *pos = (void *)skb_put(skb, 9);
|
||
|
|
||
|
*pos++ = WLAN_EID_VENDOR_SPECIFIC;
|
||
|
*pos++ = 7; /* len */
|
||
|
*pos++ = 0x00; /* Microsoft OUI 00:50:F2 */
|
||
|
*pos++ = 0x50;
|
||
|
*pos++ = 0xf2;
|
||
|
*pos++ = 2; /* WME */
|
||
|
*pos++ = 0; /* WME info */
|
||
|
*pos++ = 1; /* WME ver */
|
||
|
*pos++ = qosinfo; /* U-APSD no in use */
|
||
|
}
|
||
|
|
||
|
/* translate numbering in the WMM parameter IE to the mac80211 notation */
|
||
|
static u8 rwnx_ac_from_wmm(int ac)
|
||
|
{
|
||
|
switch (ac) {
|
||
|
default:
|
||
|
WARN_ON_ONCE(1);
|
||
|
case 0:
|
||
|
return AC_BE;
|
||
|
case 1:
|
||
|
return AC_BK;
|
||
|
case 2:
|
||
|
return AC_VI;
|
||
|
case 3:
|
||
|
return AC_VO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_add_wmm_param_ie(struct sk_buff *skb, u8 acm_bits, u32 *ac_params)
|
||
|
{
|
||
|
struct ieee80211_wmm_param_ie *wmm;
|
||
|
int i, j;
|
||
|
u8 cw_min, cw_max;
|
||
|
bool acm;
|
||
|
|
||
|
wmm = (void *)skb_put(skb, sizeof(struct ieee80211_wmm_param_ie));
|
||
|
memset(wmm, 0, sizeof(*wmm));
|
||
|
|
||
|
wmm->element_id = WLAN_EID_VENDOR_SPECIFIC;
|
||
|
wmm->len = sizeof(*wmm) - 2;
|
||
|
|
||
|
wmm->oui[0] = 0x00; /* Microsoft OUI 00:50:F2 */
|
||
|
wmm->oui[1] = 0x50;
|
||
|
wmm->oui[2] = 0xf2;
|
||
|
wmm->oui_type = 2; /* WME */
|
||
|
wmm->oui_subtype = 1; /* WME param */
|
||
|
wmm->version = 1; /* WME ver */
|
||
|
wmm->qos_info = 0; /* U-APSD not in use */
|
||
|
|
||
|
/*
|
||
|
* Use the EDCA parameters defined for the BSS, or default if the AP
|
||
|
* doesn't support it, as mandated by 802.11-2012 section 10.22.4
|
||
|
*/
|
||
|
for (i = 0; i < AC_MAX; i++) {
|
||
|
j = rwnx_ac_from_wmm(i);
|
||
|
cw_min = (ac_params[j] & 0xF0) >> 4;
|
||
|
cw_max = (ac_params[j] & 0xF00) >> 8;
|
||
|
acm = (acm_bits & (1 << j)) != 0;
|
||
|
|
||
|
wmm->ac[i].aci_aifsn = (i << 5) | (acm << 4) | (ac_params[j] & 0xF);
|
||
|
wmm->ac[i].cw = (cw_max << 4) | cw_min;
|
||
|
wmm->ac[i].txop_limit = (ac_params[j] & 0x0FFFF000) >> 12;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_oper_classes(struct rwnx_vif *rwnx_vif, struct sk_buff *skb)
|
||
|
{
|
||
|
u8 *pos;
|
||
|
u8 op_class;
|
||
|
struct cfg80211_chan_def chan_def;
|
||
|
struct ieee80211_channel chan;
|
||
|
|
||
|
chan.band = rwnx_vif->sta.ap->band;
|
||
|
chan.center_freq = rwnx_vif->sta.ap->center_freq;
|
||
|
chan_def.chan = &chan;
|
||
|
chan_def.width = rwnx_vif->sta.ap->width;
|
||
|
chan_def.center_freq1 = rwnx_vif->sta.ap->center_freq1;
|
||
|
chan_def.center_freq2 = rwnx_vif->sta.ap->center_freq2;
|
||
|
|
||
|
if (!rwnx_ieee80211_chandef_to_operating_class(&chan_def, &op_class))
|
||
|
return;
|
||
|
|
||
|
pos = skb_put(skb, 4);
|
||
|
*pos++ = WLAN_EID_SUPPORTED_REGULATORY_CLASSES;
|
||
|
*pos++ = 2; /* len */
|
||
|
|
||
|
// current op class
|
||
|
*pos++ = op_class;
|
||
|
*pos++ = op_class; /* give current operating class as alternate too */
|
||
|
|
||
|
// need to add 5GHz classes?
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_ie_build_ht_cap(struct sk_buff *skb, struct ieee80211_sta_ht_cap *ht_cap,
|
||
|
u16 cap)
|
||
|
{
|
||
|
u8 *pos;
|
||
|
__le16 tmp;
|
||
|
|
||
|
pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2);
|
||
|
*pos++ = WLAN_EID_HT_CAPABILITY;
|
||
|
*pos++ = sizeof(struct ieee80211_ht_cap);
|
||
|
memset(pos, 0, sizeof(struct ieee80211_ht_cap));
|
||
|
|
||
|
/* capability flags */
|
||
|
tmp = cpu_to_le16(cap);
|
||
|
memcpy(pos, &tmp, sizeof(u16));
|
||
|
pos += sizeof(u16);
|
||
|
|
||
|
/* AMPDU parameters */
|
||
|
*pos++ = ht_cap->ampdu_factor |
|
||
|
(ht_cap->ampdu_density <<
|
||
|
IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT);
|
||
|
|
||
|
/* MCS set */
|
||
|
memcpy(pos, &ht_cap->mcs, sizeof(ht_cap->mcs));
|
||
|
pos += sizeof(ht_cap->mcs);
|
||
|
|
||
|
/* extended capabilities */
|
||
|
pos += sizeof(__le16);
|
||
|
|
||
|
/* BF capabilities */
|
||
|
pos += sizeof(__le32);
|
||
|
|
||
|
/* antenna selection */
|
||
|
pos += sizeof(u8);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_ie_build_vht_cap(struct sk_buff *skb, struct ieee80211_sta_vht_cap *vht_cap,
|
||
|
u32 cap)
|
||
|
{
|
||
|
u8 *pos;
|
||
|
__le32 tmp;
|
||
|
|
||
|
pos = skb_put(skb, 14);
|
||
|
|
||
|
*pos++ = WLAN_EID_VHT_CAPABILITY;
|
||
|
*pos++ = sizeof(struct ieee80211_vht_cap);
|
||
|
memset(pos, 0, sizeof(struct ieee80211_vht_cap));
|
||
|
|
||
|
/* capability flags */
|
||
|
tmp = cpu_to_le32(cap);
|
||
|
memcpy(pos, &tmp, sizeof(u32));
|
||
|
pos += sizeof(u32);
|
||
|
|
||
|
/* VHT MCS set */
|
||
|
memcpy(pos, &vht_cap->vht_mcs, sizeof(vht_cap->vht_mcs));
|
||
|
pos += sizeof(vht_cap->vht_mcs);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_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 void
|
||
|
rwnx_tdls_add_link_ie(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
struct sk_buff *skb, const u8 *peer,
|
||
|
bool initiator)
|
||
|
{
|
||
|
struct ieee80211_tdls_lnkie *lnkid;
|
||
|
const u8 *init_addr, *rsp_addr;
|
||
|
|
||
|
if (initiator) {
|
||
|
init_addr = rwnx_hw->wiphy->perm_addr;
|
||
|
rsp_addr = peer;
|
||
|
} else {
|
||
|
init_addr = peer;
|
||
|
rsp_addr = rwnx_hw->wiphy->perm_addr;
|
||
|
}
|
||
|
|
||
|
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) - 2;
|
||
|
|
||
|
memcpy(lnkid->bssid, rwnx_vif->sta.ap->mac_addr, ETH_ALEN);
|
||
|
memcpy(lnkid->init_sta, init_addr, ETH_ALEN);
|
||
|
memcpy(lnkid->resp_sta, rsp_addr, ETH_ALEN);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_aid_ie(struct rwnx_vif *rwnx_vif, struct sk_buff *skb)
|
||
|
{
|
||
|
u8 *pos = (void *)skb_put(skb, 4);
|
||
|
|
||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
|
||
|
*pos++ = WLAN_EID_AID;
|
||
|
#else
|
||
|
*pos++ = 197;
|
||
|
#endif
|
||
|
*pos++ = 2; /* len */
|
||
|
*pos++ = rwnx_vif->sta.ap->aid;
|
||
|
}
|
||
|
|
||
|
static u8 *
|
||
|
rwnx_ie_build_ht_oper(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
u8 *pos, struct ieee80211_sta_ht_cap *ht_cap,
|
||
|
u16 prot_mode)
|
||
|
{
|
||
|
struct ieee80211_ht_operation *ht_oper;
|
||
|
/* Build HT Information */
|
||
|
*pos++ = WLAN_EID_HT_OPERATION;
|
||
|
*pos++ = sizeof(struct ieee80211_ht_operation);
|
||
|
ht_oper = (struct ieee80211_ht_operation *)pos;
|
||
|
ht_oper->primary_chan = ieee80211_frequency_to_channel(
|
||
|
rwnx_vif->sta.ap->center_freq);
|
||
|
switch (rwnx_vif->sta.ap->width) {
|
||
|
case NL80211_CHAN_WIDTH_160:
|
||
|
case NL80211_CHAN_WIDTH_80P80:
|
||
|
case NL80211_CHAN_WIDTH_80:
|
||
|
case NL80211_CHAN_WIDTH_40:
|
||
|
if (rwnx_vif->sta.ap->center_freq1 > rwnx_vif->sta.ap->center_freq)
|
||
|
ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
|
||
|
else
|
||
|
ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
|
||
|
break;
|
||
|
default:
|
||
|
ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE;
|
||
|
break;
|
||
|
}
|
||
|
if (ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 &&
|
||
|
rwnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20_NOHT &&
|
||
|
rwnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20)
|
||
|
ht_oper->ht_param |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY;
|
||
|
|
||
|
ht_oper->operation_mode = cpu_to_le16(prot_mode);
|
||
|
ht_oper->stbc_param = 0x0000;
|
||
|
|
||
|
/* It seems that Basic MCS set and Supported MCS set
|
||
|
are identical for the first 10 bytes */
|
||
|
memset(&ht_oper->basic_set, 0, 16);
|
||
|
memcpy(&ht_oper->basic_set, &ht_cap->mcs, 10);
|
||
|
|
||
|
return pos + sizeof(struct ieee80211_ht_operation);
|
||
|
}
|
||
|
|
||
|
static u8 *
|
||
|
rwnx_ie_build_vht_oper(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
u8 *pos, struct ieee80211_sta_ht_cap *ht_cap,
|
||
|
u16 prot_mode)
|
||
|
{
|
||
|
struct ieee80211_vht_operation *vht_oper;
|
||
|
/* Build HT Information */
|
||
|
*pos++ = WLAN_EID_VHT_OPERATION;
|
||
|
*pos++ = sizeof(struct ieee80211_vht_operation);
|
||
|
vht_oper = (struct ieee80211_vht_operation *)pos;
|
||
|
|
||
|
switch (rwnx_vif->sta.ap->width) {
|
||
|
case NL80211_CHAN_WIDTH_80:
|
||
|
vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ; // Channel Width
|
||
|
CCFS0(vht_oper) =
|
||
|
ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq); // Channel Center Frequency Segment 0
|
||
|
CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.)
|
||
|
break;
|
||
|
case NL80211_CHAN_WIDTH_160:
|
||
|
vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_160MHZ; // Channel Width
|
||
|
CCFS0(vht_oper) =
|
||
|
ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq); // Channel Center Frequency Segment 0
|
||
|
CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.)
|
||
|
break;
|
||
|
case NL80211_CHAN_WIDTH_80P80:
|
||
|
vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80P80MHZ; // Channel Width
|
||
|
CCFS0(vht_oper) =
|
||
|
ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq1); // Channel Center Frequency Segment 0
|
||
|
CCFS1(vht_oper) =
|
||
|
ieee80211_frequency_to_channel(rwnx_vif->sta.ap->center_freq2); // Channel Center Frequency Segment 1
|
||
|
break;
|
||
|
default:
|
||
|
vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_USE_HT;
|
||
|
CCFS0(vht_oper) = 0;
|
||
|
CCFS1(vht_oper) = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
vht_oper->basic_mcs_set = cpu_to_le16(rwnx_hw->mod_params->mcs_map);
|
||
|
|
||
|
return pos + sizeof(struct ieee80211_vht_operation);
|
||
|
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_setup_start_ies(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
struct sk_buff *skb, const u8 *peer,
|
||
|
u8 action_code, bool initiator,
|
||
|
const u8 *extra_ies, size_t extra_ies_len)
|
||
|
{
|
||
|
enum nl80211_band band = rwnx_vif->sta.ap->band;
|
||
|
struct ieee80211_supported_band *sband;
|
||
|
struct ieee80211_sta_ht_cap ht_cap;
|
||
|
struct ieee80211_sta_vht_cap vht_cap;
|
||
|
size_t offset = 0, noffset;
|
||
|
u8 *pos;
|
||
|
|
||
|
rcu_read_lock();
|
||
|
|
||
|
rwnx_add_srates_ie(rwnx_hw, skb);
|
||
|
rwnx_add_ext_srates_ie(rwnx_hw, skb);
|
||
|
rwnx_tdls_add_supp_channels(rwnx_hw, skb);
|
||
|
rwnx_tdls_add_ext_capab(rwnx_hw, skb);
|
||
|
|
||
|
/* add the QoS element if we support it */
|
||
|
if (/*local->hw.queues >= IEEE80211_NUM_ACS &&*/
|
||
|
action_code != WLAN_PUB_ACTION_TDLS_DISCOVER_RES)
|
||
|
rwnx_add_wmm_info_ie(skb, 0); /* no U-APSD */
|
||
|
|
||
|
rwnx_tdls_add_oper_classes(rwnx_vif, skb);
|
||
|
|
||
|
/*
|
||
|
* with TDLS we can switch channels, and HT-caps are not necessarily
|
||
|
* the same on all bands. The specification limits the setup to a
|
||
|
* single HT-cap, so use the current band for now.
|
||
|
*/
|
||
|
sband = rwnx_hw->wiphy->bands[band];
|
||
|
memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
|
||
|
if (((action_code == WLAN_TDLS_SETUP_REQUEST) ||
|
||
|
(action_code == WLAN_TDLS_SETUP_RESPONSE) ||
|
||
|
(action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES)) &&
|
||
|
ht_cap.ht_supported /* (!sta || sta->sta.ht_cap.ht_supported)*/) {
|
||
|
rwnx_ie_build_ht_cap(skb, &ht_cap, ht_cap.cap);
|
||
|
}
|
||
|
|
||
|
if (ht_cap.ht_supported &&
|
||
|
(ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40))
|
||
|
rwnx_tdls_add_bss_coex_ie(skb);
|
||
|
|
||
|
rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
|
||
|
|
||
|
memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
|
||
|
if (vht_cap.vht_supported) {
|
||
|
rwnx_tdls_add_aid_ie(rwnx_vif, skb);
|
||
|
rwnx_ie_build_vht_cap(skb, &vht_cap, vht_cap.cap);
|
||
|
// Operating mode Notification (optional)
|
||
|
}
|
||
|
|
||
|
/* 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);
|
||
|
}
|
||
|
|
||
|
rcu_read_unlock();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_setup_cfm_ies(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
struct sk_buff *skb, const u8 *peer, bool initiator,
|
||
|
const u8 *extra_ies, size_t extra_ies_len)
|
||
|
{
|
||
|
struct ieee80211_supported_band *sband;
|
||
|
enum nl80211_band band = rwnx_vif->sta.ap->band;
|
||
|
struct ieee80211_sta_ht_cap ht_cap;
|
||
|
struct ieee80211_sta_vht_cap vht_cap;
|
||
|
|
||
|
size_t offset = 0, noffset;
|
||
|
struct rwnx_sta *sta, *ap_sta;
|
||
|
u8 *pos;
|
||
|
|
||
|
rcu_read_lock();
|
||
|
|
||
|
sta = rwnx_get_sta(rwnx_hw, peer);
|
||
|
ap_sta = rwnx_vif->sta.ap;
|
||
|
if (WARN_ON_ONCE(!sta || !ap_sta)) {
|
||
|
rcu_read_unlock();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* add the QoS param IE if both the peer and we support it */
|
||
|
if (sta->qos)
|
||
|
rwnx_add_wmm_param_ie(skb, ap_sta->acm, ap_sta->ac_param);
|
||
|
|
||
|
/* if HT support is only added in TDLS, we need an HT-operation IE */
|
||
|
sband = rwnx_hw->wiphy->bands[band];
|
||
|
memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap));
|
||
|
if (ht_cap.ht_supported && !ap_sta->ht && sta->ht) {
|
||
|
pos = skb_put(skb, 2 + sizeof(struct ieee80211_ht_operation));
|
||
|
/* send an empty HT operation IE */
|
||
|
rwnx_ie_build_ht_oper(rwnx_hw, rwnx_vif, pos, &ht_cap, 0);
|
||
|
}
|
||
|
|
||
|
rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
|
||
|
|
||
|
memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap));
|
||
|
if (vht_cap.vht_supported && !ap_sta->vht && sta->vht) {
|
||
|
pos = skb_put(skb, 2 + sizeof(struct ieee80211_vht_operation));
|
||
|
rwnx_ie_build_vht_oper(rwnx_hw, rwnx_vif, pos, &ht_cap, 0);
|
||
|
// Operating mode Notification (optional)
|
||
|
}
|
||
|
|
||
|
/* 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);
|
||
|
}
|
||
|
|
||
|
rcu_read_unlock();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
rwnx_tdls_add_ies(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
struct sk_buff *skb, const u8 *peer,
|
||
|
u8 action_code, u16 status_code,
|
||
|
bool initiator, const u8 *extra_ies,
|
||
|
size_t extra_ies_len, u8 oper_class,
|
||
|
struct cfg80211_chan_def *chandef)
|
||
|
{
|
||
|
switch (action_code) {
|
||
|
case WLAN_TDLS_SETUP_REQUEST:
|
||
|
case WLAN_TDLS_SETUP_RESPONSE:
|
||
|
case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
|
||
|
if (status_code == 0)
|
||
|
rwnx_tdls_add_setup_start_ies(rwnx_hw, rwnx_vif, skb, peer, action_code,
|
||
|
initiator, extra_ies, extra_ies_len);
|
||
|
break;
|
||
|
case WLAN_TDLS_SETUP_CONFIRM:
|
||
|
if (status_code == 0)
|
||
|
rwnx_tdls_add_setup_cfm_ies(rwnx_hw, rwnx_vif, skb, peer, initiator,
|
||
|
extra_ies, extra_ies_len);
|
||
|
break;
|
||
|
|
||
|
case WLAN_TDLS_TEARDOWN:
|
||
|
case WLAN_TDLS_DISCOVERY_REQUEST:
|
||
|
if (extra_ies_len)
|
||
|
memcpy(skb_put(skb, extra_ies_len), extra_ies,
|
||
|
extra_ies_len);
|
||
|
if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN)
|
||
|
rwnx_tdls_add_link_ie(rwnx_hw, rwnx_vif, skb, peer, initiator);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
rwnx_tdls_send_mgmt_packet_data(struct rwnx_hw *rwnx_hw, struct rwnx_vif *rwnx_vif,
|
||
|
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 sk_buff *skb;
|
||
|
int ret = 0;
|
||
|
struct ieee80211_supported_band *rwnx_band_2GHz = rwnx_hw->wiphy->bands[NL80211_BAND_2GHZ];
|
||
|
struct ieee80211_supported_band *rwnx_band_5GHz = rwnx_hw->wiphy->bands[NL80211_BAND_5GHZ];
|
||
|
int channels = rwnx_band_2GHz->n_channels;
|
||
|
|
||
|
if (rwnx_hw->band_5g_support)
|
||
|
channels += rwnx_band_5GHz->n_channels;
|
||
|
|
||
|
skb = netdev_alloc_skb(rwnx_vif->ndev,
|
||
|
sizeof(struct ieee80211_tdls_data) + // ethhdr + TDLS info
|
||
|
10 + /* supported rates */
|
||
|
6 + /* extended supported rates */
|
||
|
(2 + channels) + /* supported channels */
|
||
|
sizeof(struct ieee_types_extcap) +
|
||
|
sizeof(struct ieee80211_wmm_param_ie) +
|
||
|
4 + /* oper classes */
|
||
|
28 + //sizeof(struct ieee80211_ht_cap) +
|
||
|
sizeof(struct ieee_types_bss_co_2040) +
|
||
|
sizeof(struct ieee80211_tdls_lnkie) +
|
||
|
(2 + sizeof(struct ieee80211_vht_cap)) +
|
||
|
4 + /*AID*/
|
||
|
(2 + sizeof(struct ieee80211_ht_operation)) +
|
||
|
extra_ies_len);
|
||
|
|
||
|
if (!skb)
|
||
|
return 0;
|
||
|
|
||
|
switch (action_code) {
|
||
|
case WLAN_TDLS_SETUP_REQUEST:
|
||
|
case WLAN_TDLS_SETUP_RESPONSE:
|
||
|
case WLAN_TDLS_SETUP_CONFIRM:
|
||
|
case WLAN_TDLS_TEARDOWN:
|
||
|
case WLAN_TDLS_DISCOVERY_REQUEST:
|
||
|
ret = rwnx_tdls_prepare_encap_data(rwnx_hw, rwnx_vif, peer, action_code,
|
||
|
dialog_token, status_code, skb);
|
||
|
break;
|
||
|
|
||
|
case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
|
||
|
ret = rwnx_prep_tdls_direct(rwnx_hw, rwnx_vif, peer, action_code,
|
||
|
dialog_token, status_code, skb);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
ret = -ENOTSUPP;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (ret < 0)
|
||
|
goto fail;
|
||
|
|
||
|
rwnx_tdls_add_ies(rwnx_hw, rwnx_vif, skb, peer, action_code, status_code,
|
||
|
initiator, extra_ies, extra_ies_len, oper_class, chandef);
|
||
|
|
||
|
if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
|
||
|
u64 cookie;
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
|
||
|
struct cfg80211_mgmt_tx_params params;
|
||
|
|
||
|
params.len = skb->len;
|
||
|
params.buf = skb->data;
|
||
|
ret = rwnx_start_mgmt_xmit(rwnx_vif, NULL, ¶ms, false, &cookie);
|
||
|
#else
|
||
|
ret = rwnx_start_mgmt_xmit(rwnx_vif, NULL, NULL, false, 0, skb->data, skb->len, false, false, &cookie);
|
||
|
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
switch (action_code) {
|
||
|
case WLAN_TDLS_SETUP_REQUEST:
|
||
|
case WLAN_TDLS_SETUP_RESPONSE:
|
||
|
case WLAN_TDLS_SETUP_CONFIRM:
|
||
|
skb->priority = 2;
|
||
|
break;
|
||
|
default:
|
||
|
skb->priority = 5;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret = rwnx_select_txq(rwnx_vif, skb);
|
||
|
ret = rwnx_start_xmit(skb, rwnx_vif->ndev);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
fail:
|
||
|
dev_kfree_skb(skb);
|
||
|
return ret;
|
||
|
}
|