android13/external/wifi_driver/aic8800/aic8800_fdrv/rwnx_tdls.c

783 lines
22 KiB
C
Raw Normal View History

2024-06-22 08:45:49 -04:00
/**
******************************************************************************
*
* @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, &params, 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;
}