blob: 78d9966a3957b2b934fe6b21de5cade494ec2839 [file] [log] [blame]
/*
* Copyright (c) 2010 Broadcom Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Toplevel file. Relies on dhd_linux.c to send commands to the dongle. */
#include <linux/kernel.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <net/cfg80211.h>
#include <net/netlink.h>
#include <brcmu_utils.h>
#include <defs.h>
#include <brcmu_wifi.h>
#include "core.h"
#include "debug.h"
#include "tracepoint.h"
#include "fwil_types.h"
#include "p2p.h"
#include "btcoex.h"
#include "cfg80211.h"
#include "feature.h"
#include "fwil.h"
#include "proto.h"
#include "vendor.h"
#include "bus.h"
#include "common.h"
#define BRCMF_SCAN_IE_LEN_MAX 2048
#define BRCMF_PNO_VERSION 2
#define BRCMF_PNO_TIME 30
#define BRCMF_PNO_REPEAT 4
#define BRCMF_PNO_FREQ_EXPO_MAX 3
#define BRCMF_PNO_MAX_PFN_COUNT 16
#define BRCMF_PNO_ENABLE_ADAPTSCAN_BIT 6
#define BRCMF_PNO_HIDDEN_BIT 2
#define BRCMF_PNO_WPA_AUTH_ANY 0xFFFFFFFF
#define BRCMF_PNO_SCAN_COMPLETE 1
#define BRCMF_PNO_SCAN_INCOMPLETE 0
#define WPA_OUI "\x00\x50\xF2" /* WPA OUI */
#define WPA_OUI_TYPE 1
#define RSN_OUI "\x00\x0F\xAC" /* RSN OUI */
#define WME_OUI_TYPE 2
#define WPS_OUI_TYPE 4
#define VS_IE_FIXED_HDR_LEN 6
#define WPA_IE_VERSION_LEN 2
#define WPA_IE_MIN_OUI_LEN 4
#define WPA_IE_SUITE_COUNT_LEN 2
#define WPA_CIPHER_NONE 0 /* None */
#define WPA_CIPHER_WEP_40 1 /* WEP (40-bit) */
#define WPA_CIPHER_TKIP 2 /* TKIP: default for WPA */
#define WPA_CIPHER_AES_CCM 4 /* AES (CCM) */
#define WPA_CIPHER_WEP_104 5 /* WEP (104-bit) */
#define RSN_AKM_NONE 0 /* None (IBSS) */
#define RSN_AKM_UNSPECIFIED 1 /* Over 802.1x */
#define RSN_AKM_PSK 2 /* Pre-shared Key */
#define RSN_AKM_SHA256_1X 5 /* SHA256, 802.1X */
#define RSN_AKM_SHA256_PSK 6 /* SHA256, Pre-shared Key */
#define RSN_CAP_LEN 2 /* Length of RSN capabilities */
#define RSN_CAP_PTK_REPLAY_CNTR_MASK (BIT(2) | BIT(3))
#define RSN_CAP_MFPR_MASK BIT(6)
#define RSN_CAP_MFPC_MASK BIT(7)
#define RSN_PMKID_COUNT_LEN 2
#define VNDR_IE_CMD_LEN 4 /* length of the set command
* string :"add", "del" (+ NUL)
*/
#define VNDR_IE_COUNT_OFFSET 4
#define VNDR_IE_PKTFLAG_OFFSET 8
#define VNDR_IE_VSIE_OFFSET 12
#define VNDR_IE_HDR_SIZE 12
#define VNDR_IE_PARSE_LIMIT 5
#define DOT11_MGMT_HDR_LEN 24 /* d11 management header len */
#define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */
#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320
#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400
#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20
#define BRCMF_SCAN_CHANNEL_TIME 40
#define BRCMF_SCAN_UNASSOC_TIME 40
#define BRCMF_SCAN_PASSIVE_TIME 120
#define BRCMF_ND_INFO_TIMEOUT msecs_to_jiffies(2000)
#define BRCMF_ASSOC_PARAMS_FIXED_SIZE \
(sizeof(struct brcmf_assoc_params_le) - sizeof(u16))
static bool check_vif_up(struct brcmf_cfg80211_vif *vif)
{
if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) {
brcmf_dbg(INFO, "device is not ready : status (%lu)\n",
vif->sme_state);
return false;
}
return true;
}
#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2)
#define RATETAB_ENT(_rateid, _flags) \
{ \
.bitrate = RATE_TO_BASE100KBPS(_rateid), \
.hw_value = (_rateid), \
.flags = (_flags), \
}
static struct ieee80211_rate __wl_rates[] = {
RATETAB_ENT(BRCM_RATE_1M, 0),
RATETAB_ENT(BRCM_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(BRCM_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(BRCM_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(BRCM_RATE_6M, 0),
RATETAB_ENT(BRCM_RATE_9M, 0),
RATETAB_ENT(BRCM_RATE_12M, 0),
RATETAB_ENT(BRCM_RATE_18M, 0),
RATETAB_ENT(BRCM_RATE_24M, 0),
RATETAB_ENT(BRCM_RATE_36M, 0),
RATETAB_ENT(BRCM_RATE_48M, 0),
RATETAB_ENT(BRCM_RATE_54M, 0),
};
#define wl_g_rates (__wl_rates + 0)
#define wl_g_rates_size ARRAY_SIZE(__wl_rates)
#define wl_a_rates (__wl_rates + 4)
#define wl_a_rates_size (wl_g_rates_size - 4)
#define CHAN2G(_channel, _freq) { \
.band = NL80211_BAND_2GHZ, \
.center_freq = (_freq), \
.hw_value = (_channel), \
.flags = IEEE80211_CHAN_DISABLED, \
.max_antenna_gain = 0, \
.max_power = 30, \
}
#define CHAN5G(_channel) { \
.band = NL80211_BAND_5GHZ, \
.center_freq = 5000 + (5 * (_channel)), \
.hw_value = (_channel), \
.flags = IEEE80211_CHAN_DISABLED, \
.max_antenna_gain = 0, \
.max_power = 30, \
}
static struct ieee80211_channel __wl_2ghz_channels[] = {
CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427),
CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447),
CHAN2G(9, 2452), CHAN2G(10, 2457), CHAN2G(11, 2462), CHAN2G(12, 2467),
CHAN2G(13, 2472), CHAN2G(14, 2484)
};
static struct ieee80211_channel __wl_5ghz_channels[] = {
CHAN5G(34), CHAN5G(36), CHAN5G(38), CHAN5G(40), CHAN5G(42),
CHAN5G(44), CHAN5G(46), CHAN5G(48), CHAN5G(52), CHAN5G(56),
CHAN5G(60), CHAN5G(64), CHAN5G(100), CHAN5G(104), CHAN5G(108),
CHAN5G(112), CHAN5G(116), CHAN5G(120), CHAN5G(124), CHAN5G(128),
CHAN5G(132), CHAN5G(136), CHAN5G(140), CHAN5G(144), CHAN5G(149),
CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165)
};
/* Band templates duplicated per wiphy. The channel info
* above is added to the band during setup.
*/
static const struct ieee80211_supported_band __wl_band_2ghz = {
.band = NL80211_BAND_2GHZ,
.bitrates = wl_g_rates,
.n_bitrates = wl_g_rates_size,
};
static const struct ieee80211_supported_band __wl_band_5ghz = {
.band = NL80211_BAND_5GHZ,
.bitrates = wl_a_rates,
.n_bitrates = wl_a_rates_size,
};
/* This is to override regulatory domains defined in cfg80211 module (reg.c)
* By default world regulatory domain defined in reg.c puts the flags
* NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165).
* With respect to these flags, wpa_supplicant doesn't * start p2p
* operations on 5GHz channels. All the changes in world regulatory
* domain are to be done here.
*/
static const struct ieee80211_regdomain brcmf_regdom = {
.n_reg_rules = 4,
.alpha2 = "99",
.reg_rules = {
/* IEEE 802.11b/g, channels 1..11 */
REG_RULE(2412-10, 2472+10, 40, 6, 20, 0),
/* If any */
/* IEEE 802.11 channel 14 - Only JP enables
* this and for 802.11b only
*/
REG_RULE(2484-10, 2484+10, 20, 6, 20, 0),
/* IEEE 802.11a, channel 36..64 */
REG_RULE(5150-10, 5350+10, 80, 6, 20, 0),
/* IEEE 802.11a, channel 100..165 */
REG_RULE(5470-10, 5850+10, 80, 6, 20, 0), }
};
/* Note: brcmf_cipher_suites is an array of int defining which cipher suites
* are supported. A pointer to this array and the number of entries is passed
* on to upper layers. AES_CMAC defines whether or not the driver supports MFP.
* So the cipher suite AES_CMAC has to be the last one in the array, and when
* device does not support MFP then the number of suites will be decreased by 1
*/
static const u32 brcmf_cipher_suites[] = {
WLAN_CIPHER_SUITE_WEP40,
WLAN_CIPHER_SUITE_WEP104,
WLAN_CIPHER_SUITE_TKIP,
WLAN_CIPHER_SUITE_CCMP,
/* Keep as last entry: */
WLAN_CIPHER_SUITE_AES_CMAC
};
/* Vendor specific ie. id = 221, oui and type defines exact ie */
struct brcmf_vs_tlv {
u8 id;
u8 len;
u8 oui[3];
u8 oui_type;
};
struct parsed_vndr_ie_info {
u8 *ie_ptr;
u32 ie_len; /* total length including id & length field */
struct brcmf_vs_tlv vndrie;
};
struct parsed_vndr_ies {
u32 count;
struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
};
static u8 nl80211_band_to_fwil(enum nl80211_band band)
{
switch (band) {
case NL80211_BAND_2GHZ:
return WLC_BAND_2G;
case NL80211_BAND_5GHZ:
return WLC_BAND_5G;
default:
WARN_ON(1);
break;
}
return 0;
}
static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
struct cfg80211_chan_def *ch)
{
struct brcmu_chan ch_inf;
s32 primary_offset;
brcmf_dbg(TRACE, "chandef: control %d center %d width %d\n",
ch->chan->center_freq, ch->center_freq1, ch->width);
ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq1);
primary_offset = ch->chan->center_freq - ch->center_freq1;
switch (ch->width) {
case NL80211_CHAN_WIDTH_20:
case NL80211_CHAN_WIDTH_20_NOHT:
ch_inf.bw = BRCMU_CHAN_BW_20;
WARN_ON(primary_offset != 0);
break;
case NL80211_CHAN_WIDTH_40:
ch_inf.bw = BRCMU_CHAN_BW_40;
if (primary_offset > 0)
ch_inf.sb = BRCMU_CHAN_SB_U;
else
ch_inf.sb = BRCMU_CHAN_SB_L;
break;
case NL80211_CHAN_WIDTH_80:
ch_inf.bw = BRCMU_CHAN_BW_80;
if (primary_offset == -30)
ch_inf.sb = BRCMU_CHAN_SB_LL;
else if (primary_offset == -10)
ch_inf.sb = BRCMU_CHAN_SB_LU;
else if (primary_offset == 10)
ch_inf.sb = BRCMU_CHAN_SB_UL;
else
ch_inf.sb = BRCMU_CHAN_SB_UU;
break;
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_160:
case NL80211_CHAN_WIDTH_5:
case NL80211_CHAN_WIDTH_10:
default:
WARN_ON_ONCE(1);
}
switch (ch->chan->band) {
case NL80211_BAND_2GHZ:
ch_inf.band = BRCMU_CHAN_BAND_2G;
break;
case NL80211_BAND_5GHZ:
ch_inf.band = BRCMU_CHAN_BAND_5G;
break;
case NL80211_BAND_60GHZ:
default:
WARN_ON_ONCE(1);
}
d11inf->encchspec(&ch_inf);
return ch_inf.chspec;
}
u16 channel_to_chanspec(struct brcmu_d11inf *d11inf,
struct ieee80211_channel *ch)
{
struct brcmu_chan ch_inf;
ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq);
ch_inf.bw = BRCMU_CHAN_BW_20;
d11inf->encchspec(&ch_inf);
return ch_inf.chspec;
}
/* Traverse a string of 1-byte tag/1-byte length/variable-length value
* triples, returning a pointer to the substring whose first element
* matches tag
*/
const struct brcmf_tlv *
brcmf_parse_tlvs(const void *buf, int buflen, uint key)
{
const struct brcmf_tlv *elt = buf;
int totlen = buflen;
/* find tagged parameter */
while (totlen >= TLV_HDR_LEN) {
int len = elt->len;
/* validate remaining totlen */
if ((elt->id == key) && (totlen >= (len + TLV_HDR_LEN)))
return elt;
elt = (struct brcmf_tlv *)((u8 *)elt + (len + TLV_HDR_LEN));
totlen -= (len + TLV_HDR_LEN);
}
return NULL;
}
/* Is any of the tlvs the expected entry? If
* not update the tlvs buffer pointer/length.
*/
static bool
brcmf_tlv_has_ie(const u8 *ie, const u8 **tlvs, u32 *tlvs_len,
const u8 *oui, u32 oui_len, u8 type)
{
/* If the contents match the OUI and the type */
if (ie[TLV_LEN_OFF] >= oui_len + 1 &&
!memcmp(&ie[TLV_BODY_OFF], oui, oui_len) &&
type == ie[TLV_BODY_OFF + oui_len]) {
return true;
}
if (tlvs == NULL)
return false;
/* point to the next ie */
ie += ie[TLV_LEN_OFF] + TLV_HDR_LEN;
/* calculate the length of the rest of the buffer */
*tlvs_len -= (int)(ie - *tlvs);
/* update the pointer to the start of the buffer */
*tlvs = ie;
return false;
}
static struct brcmf_vs_tlv *
brcmf_find_wpaie(const u8 *parse, u32 len)
{
const struct brcmf_tlv *ie;
while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) {
if (brcmf_tlv_has_ie((const u8 *)ie, &parse, &len,
WPA_OUI, TLV_OUI_LEN, WPA_OUI_TYPE))
return (struct brcmf_vs_tlv *)ie;
}
return NULL;
}
static struct brcmf_vs_tlv *
brcmf_find_wpsie(const u8 *parse, u32 len)
{
const struct brcmf_tlv *ie;
while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) {
if (brcmf_tlv_has_ie((u8 *)ie, &parse, &len,
WPA_OUI, TLV_OUI_LEN, WPS_OUI_TYPE))
return (struct brcmf_vs_tlv *)ie;
}
return NULL;
}
static int brcmf_vif_change_validate(struct brcmf_cfg80211_info *cfg,
struct brcmf_cfg80211_vif *vif,
enum nl80211_iftype new_type)
{
int iftype_num[NUM_NL80211_IFTYPES];
struct brcmf_cfg80211_vif *pos;
bool check_combos = false;
int ret = 0;
memset(&iftype_num[0], 0, sizeof(iftype_num));
list_for_each_entry(pos, &cfg->vif_list, list)
if (pos == vif) {
iftype_num[new_type]++;
} else {
/* concurrent interfaces so need check combinations */
check_combos = true;
iftype_num[pos->wdev.iftype]++;
}
if (check_combos)
ret = cfg80211_check_combinations(cfg->wiphy, 1, 0, iftype_num);
return ret;
}
static int brcmf_vif_add_validate(struct brcmf_cfg80211_info *cfg,
enum nl80211_iftype new_type)
{
int iftype_num[NUM_NL80211_IFTYPES];
struct brcmf_cfg80211_vif *pos;
memset(&iftype_num[0], 0, sizeof(iftype_num));
list_for_each_entry(pos, &cfg->vif_list, list)
iftype_num[pos->wdev.iftype]++;
iftype_num[new_type]++;
return cfg80211_check_combinations(cfg->wiphy, 1, 0, iftype_num);
}
static void convert_key_from_CPU(struct brcmf_wsec_key *key,
struct brcmf_wsec_key_le *key_le)
{
key_le->index = cpu_to_le32(key->index);
key_le->len = cpu_to_le32(key->len);
key_le->algo = cpu_to_le32(key->algo);
key_le->flags = cpu_to_le32(key->flags);
key_le->rxiv.hi = cpu_to_le32(key->rxiv.hi);
key_le->rxiv.lo = cpu_to_le16(key->rxiv.lo);
key_le->iv_initialized = cpu_to_le32(key->iv_initialized);
memcpy(key_le->data, key->data, sizeof(key->data));
memcpy(key_le->ea, key->ea, sizeof(key->ea));
}
static int
send_key_to_dongle(struct brcmf_if *ifp, struct brcmf_wsec_key *key)
{
int err;
struct brcmf_wsec_key_le key_le;
convert_key_from_CPU(key, &key_le);
brcmf_netdev_wait_pend8021x(ifp);
err = brcmf_fil_bsscfg_data_set(ifp, "wsec_key", &key_le,
sizeof(key_le));
if (err)
brcmf_err("wsec_key error (%d)\n", err);
return err;
}
static s32
brcmf_configure_arp_nd_offload(struct brcmf_if *ifp, bool enable)
{
s32 err;
u32 mode;
if (enable)
mode = BRCMF_ARP_OL_AGENT | BRCMF_ARP_OL_PEER_AUTO_REPLY;
else
mode = 0;
/* Try to set and enable ARP offload feature, this may fail, then it */
/* is simply not supported and err 0 will be returned */
err = brcmf_fil_iovar_int_set(ifp, "arp_ol", mode);
if (err) {
brcmf_dbg(TRACE, "failed to set ARP offload mode to 0x%x, err = %d\n",
mode, err);
err = 0;
} else {
err = brcmf_fil_iovar_int_set(ifp, "arpoe", enable);
if (err) {
brcmf_dbg(TRACE, "failed to configure (%d) ARP offload err = %d\n",
enable, err);
err = 0;
} else
brcmf_dbg(TRACE, "successfully configured (%d) ARP offload to 0x%x\n",
enable, mode);
}
err = brcmf_fil_iovar_int_set(ifp, "ndoe", enable);
if (err) {
brcmf_dbg(TRACE, "failed to configure (%d) ND offload err = %d\n",
enable, err);
err = 0;
} else
brcmf_dbg(TRACE, "successfully configured (%d) ND offload to 0x%x\n",
enable, mode);
return err;
}
static void
brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev)
{
struct brcmf_cfg80211_vif *vif;
struct brcmf_if *ifp;
vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev);
ifp = vif->ifp;
if ((wdev->iftype == NL80211_IFTYPE_ADHOC) ||
(wdev->iftype == NL80211_IFTYPE_AP) ||
(wdev->iftype == NL80211_IFTYPE_P2P_GO))
brcmf_proto_configure_addr_mode(ifp->drvr, ifp->ifidx,
ADDR_DIRECT);
else
brcmf_proto_configure_addr_mode(ifp->drvr, ifp->ifidx,
ADDR_INDIRECT);
}
static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr)
{
int bsscfgidx;
for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) {
/* bsscfgidx 1 is reserved for legacy P2P */
if (bsscfgidx == 1)
continue;
if (!drvr->iflist[bsscfgidx])
return bsscfgidx;
}
return -ENOMEM;
}
static int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp)
{
struct brcmf_mbss_ssid_le mbss_ssid_le;
int bsscfgidx;
int err;
memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le));
bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr);
if (bsscfgidx < 0)
return bsscfgidx;
mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx);
mbss_ssid_le.SSID_len = cpu_to_le32(5);
sprintf(mbss_ssid_le.SSID, "ssid%d" , bsscfgidx);
err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le,
sizeof(mbss_ssid_le));
if (err < 0)
brcmf_err("setting ssid failed %d\n", err);
return err;
}
/**
* brcmf_ap_add_vif() - create a new AP virtual interface for multiple BSS
*
* @wiphy: wiphy device of new interface.
* @name: name of the new interface.
* @flags: not used.
* @params: contains mac address for AP device.
*/
static
struct wireless_dev *brcmf_ap_add_vif(struct wiphy *wiphy, const char *name,
u32 *flags, struct vif_params *params)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
struct brcmf_cfg80211_vif *vif;
int err;
if (brcmf_cfg80211_vif_event_armed(cfg))
return ERR_PTR(-EBUSY);
brcmf_dbg(INFO, "Adding vif \"%s\"\n", name);
vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_AP);
if (IS_ERR(vif))
return (struct wireless_dev *)vif;
brcmf_cfg80211_arm_vif_event(cfg, vif);
err = brcmf_cfg80211_request_ap_if(ifp);
if (err) {
brcmf_cfg80211_arm_vif_event(cfg, NULL);
goto fail;
}
/* wait for firmware event */
err = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_ADD,
BRCMF_VIF_EVENT_TIMEOUT);
brcmf_cfg80211_arm_vif_event(cfg, NULL);
if (!err) {
brcmf_err("timeout occurred\n");
err = -EIO;
goto fail;
}
/* interface created in firmware */
ifp = vif->ifp;
if (!ifp) {
brcmf_err("no if pointer provided\n");
err = -ENOENT;
goto fail;
}
strncpy(ifp->ndev->name, name, sizeof(ifp->ndev->name) - 1);
err = brcmf_net_attach(ifp, true);
if (err) {
brcmf_err("Registering netdevice failed\n");
goto fail;
}
return &ifp->vif->wdev;
fail:
brcmf_free_vif(vif);
return ERR_PTR(err);
}
static bool brcmf_is_apmode(struct brcmf_cfg80211_vif *vif)
{
enum nl80211_iftype iftype;
iftype = vif->wdev.iftype;
return iftype == NL80211_IFTYPE_AP || iftype == NL80211_IFTYPE_P2P_GO;
}
static bool brcmf_is_ibssmode(struct brcmf_cfg80211_vif *vif)
{
return vif->wdev.iftype == NL80211_IFTYPE_ADHOC;
}
static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy,
const char *name,
unsigned char name_assign_type,
enum nl80211_iftype type,
u32 *flags,
struct vif_params *params)
{
struct wireless_dev *wdev;
int err;
brcmf_dbg(TRACE, "enter: %s type %d\n", name, type);
err = brcmf_vif_add_validate(wiphy_to_cfg(wiphy), type);
if (err) {
brcmf_err("iface validation failed: err=%d\n", err);
return ERR_PTR(err);
}
switch (type) {
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_MESH_POINT:
return ERR_PTR(-EOPNOTSUPP);
case NL80211_IFTYPE_AP:
wdev = brcmf_ap_add_vif(wiphy, name, flags, params);
break;
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
wdev = brcmf_p2p_add_vif(wiphy, name, name_assign_type, type, flags, params);
break;
case NL80211_IFTYPE_UNSPECIFIED:
default:
return ERR_PTR(-EINVAL);
}
if (IS_ERR(wdev))
brcmf_err("add iface %s type %d failed: err=%d\n",
name, type, (int)PTR_ERR(wdev));
else
brcmf_cfg80211_update_proto_addr_mode(wdev);
return wdev;
}
static void brcmf_scan_config_mpc(struct brcmf_if *ifp, int mpc)
{
if (brcmf_feat_is_quirk_enabled(ifp, BRCMF_FEAT_QUIRK_NEED_MPC))
brcmf_set_mpc(ifp, mpc);
}
void brcmf_set_mpc(struct brcmf_if *ifp, int mpc)
{
s32 err = 0;
if (check_vif_up(ifp->vif)) {
err = brcmf_fil_iovar_int_set(ifp, "mpc", mpc);
if (err) {
brcmf_err("fail to set mpc\n");
return;
}
brcmf_dbg(INFO, "MPC : %d\n", mpc);
}
}
s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
struct brcmf_if *ifp, bool aborted,
bool fw_abort)
{
struct brcmf_scan_params_le params_le;
struct cfg80211_scan_request *scan_request;
s32 err = 0;
brcmf_dbg(SCAN, "Enter\n");
/* clear scan request, because the FW abort can cause a second call */
/* to this functon and might cause a double cfg80211_scan_done */
scan_request = cfg->scan_request;
cfg->scan_request = NULL;
if (timer_pending(&cfg->escan_timeout))
del_timer_sync(&cfg->escan_timeout);
if (fw_abort) {
/* Do a scan abort to stop the driver's scan engine */
brcmf_dbg(SCAN, "ABORT scan in firmware\n");
memset(&params_le, 0, sizeof(params_le));
eth_broadcast_addr(params_le.bssid);
params_le.bss_type = DOT11_BSSTYPE_ANY;
params_le.scan_type = 0;
params_le.channel_num = cpu_to_le32(1);
params_le.nprobes = cpu_to_le32(1);
params_le.active_time = cpu_to_le32(-1);
params_le.passive_time = cpu_to_le32(-1);
params_le.home_time = cpu_to_le32(-1);
/* Scan is aborted by setting channel_list[0] to -1 */
params_le.channel_list[0] = cpu_to_le16(-1);
/* E-Scan (or anyother type) can be aborted by SCAN */
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN,
&params_le, sizeof(params_le));
if (err)
brcmf_err("Scan abort failed\n");
}
brcmf_scan_config_mpc(ifp, 1);
/*
* e-scan can be initiated by scheduled scan
* which takes precedence.
*/
if (cfg->sched_escan) {
brcmf_dbg(SCAN, "scheduled scan completed\n");
cfg->sched_escan = false;
if (!aborted)
cfg80211_sched_scan_results(cfg_to_wiphy(cfg));
} else if (scan_request) {
struct cfg80211_scan_info info = {
.aborted = aborted,
};
brcmf_dbg(SCAN, "ESCAN Completed scan: %s\n",
aborted ? "Aborted" : "Done");
cfg80211_scan_done(scan_request, &info);
}
if (!test_and_clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status))
brcmf_dbg(SCAN, "Scan complete, probably P2P scan\n");
return err;
}
static int brcmf_cfg80211_del_ap_iface(struct wiphy *wiphy,
struct wireless_dev *wdev)
{
struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy);
struct net_device *ndev = wdev->netdev;
struct brcmf_if *ifp = netdev_priv(ndev);
int ret;
int err;
brcmf_cfg80211_arm_vif_event(cfg, ifp->vif);
err = brcmf_fil_bsscfg_data_set(ifp, "interface_remove", NULL, 0);
if (err) {
brcmf_err("interface_remove failed %d\n", err);
goto err_unarm;
}
/* wait for firmware event */
ret = brcmf_cfg80211_wait_vif_event(cfg, BRCMF_E_IF_DEL,
BRCMF_VIF_EVENT_TIMEOUT);
if (!ret) {
brcmf_err("timeout occurred\n");
err = -EIO;
goto err_unarm;
}
brcmf_remove_interface(ifp, true);
err_unarm:
brcmf_cfg80211_arm_vif_event(cfg, NULL);
return err;
}
static
int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
{
struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy);
struct net_device *ndev = wdev->netdev;
if (ndev && ndev == cfg_to_ndev(cfg))
return -ENOTSUPP;
/* vif event pending in firmware */
if (brcmf_cfg80211_vif_event_armed(cfg))
return -EBUSY;
if (ndev) {
if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status) &&
cfg->escan_info.ifp == netdev_priv(ndev))
brcmf_notify_escan_complete(cfg, netdev_priv(ndev),
true, true);
brcmf_fil_iovar_int_set(netdev_priv(ndev), "mpc", 1);
}
switch (wdev->iftype) {
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_MESH_POINT:
return -EOPNOTSUPP;
case NL80211_IFTYPE_AP:
return brcmf_cfg80211_del_ap_iface(wiphy, wdev);
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
return brcmf_p2p_del_vif(wiphy, wdev);
case NL80211_IFTYPE_UNSPECIFIED:
default:
return -EINVAL;
}
return -EOPNOTSUPP;
}
static s32
brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
enum nl80211_iftype type, u32 *flags,
struct vif_params *params)
{
struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy);
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_cfg80211_vif *vif = ifp->vif;
s32 infra = 0;
s32 ap = 0;
s32 err = 0;
brcmf_dbg(TRACE, "Enter, bsscfgidx=%d, type=%d\n", ifp->bsscfgidx,
type);
/* WAR: There are a number of p2p interface related problems which
* need to be handled initially (before doing the validate).
* wpa_supplicant tends to do iface changes on p2p device/client/go
* which are not always possible/allowed. However we need to return
* OK otherwise the wpa_supplicant wont start. The situation differs
* on configuration and setup (p2pon=1 module param). The first check
* is to see if the request is a change to station for p2p iface.
*/
if ((type == NL80211_IFTYPE_STATION) &&
((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) ||
(vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) ||
(vif->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE))) {
brcmf_dbg(TRACE, "Ignoring cmd for p2p if\n");
/* Now depending on whether module param p2pon=1 was used the
* response needs to be either 0 or EOPNOTSUPP. The reason is
* that if p2pon=1 is used, but a newer supplicant is used then
* we should return an error, as this combination wont work.
* In other situations 0 is returned and supplicant will start
* normally. It will give a trace in cfg80211, but it is the
* only way to get it working. Unfortunately this will result
* in situation where we wont support new supplicant in
* combination with module param p2pon=1, but that is the way
* it is. If the user tries this then unloading of driver might
* fail/lock.
*/
if (cfg->p2p.p2pdev_dynamically)
return -EOPNOTSUPP;
else
return 0;
}
err = brcmf_vif_change_validate(wiphy_to_cfg(wiphy), vif, type);
if (err) {
brcmf_err("iface validation failed: err=%d\n", err);
return err;
}
switch (type) {
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_WDS:
brcmf_err("type (%d) : currently we do not support this type\n",
type);
return -EOPNOTSUPP;
case NL80211_IFTYPE_ADHOC:
infra = 0;
break;
case NL80211_IFTYPE_STATION:
infra = 1;
break;
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
ap = 1;
break;
default:
err = -EINVAL;
goto done;
}
if (ap) {
if (type == NL80211_IFTYPE_P2P_GO) {
brcmf_dbg(INFO, "IF Type = P2P GO\n");
err = brcmf_p2p_ifchange(cfg, BRCMF_FIL_P2P_IF_GO);
}
if (!err) {
brcmf_dbg(INFO, "IF Type = AP\n");
}
} else {
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, infra);
if (err) {
brcmf_err("WLC_SET_INFRA error (%d)\n", err);
err = -EAGAIN;
goto done;
}
brcmf_dbg(INFO, "IF Type = %s\n", brcmf_is_ibssmode(vif) ?
"Adhoc" : "Infra");
}
ndev->ieee80211_ptr->iftype = type;
brcmf_cfg80211_update_proto_addr_mode(&vif->wdev);
done:
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
struct brcmf_scan_params_le *params_le,
struct cfg80211_scan_request *request)
{
u32 n_ssids;
u32 n_channels;
s32 i;
s32 offset;
u16 chanspec;
char *ptr;
struct brcmf_ssid_le ssid_le;
eth_broadcast_addr(params_le->bssid);
params_le->bss_type = DOT11_BSSTYPE_ANY;
params_le->scan_type = 0;
params_le->channel_num = 0;
params_le->nprobes = cpu_to_le32(-1);
params_le->active_time = cpu_to_le32(-1);
params_le->passive_time = cpu_to_le32(-1);
params_le->home_time = cpu_to_le32(-1);
memset(&params_le->ssid_le, 0, sizeof(params_le->ssid_le));
/* if request is null exit so it will be all channel broadcast scan */
if (!request)
return;
n_ssids = request->n_ssids;
n_channels = request->n_channels;
/* Copy channel array if applicable */
brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
n_channels);
if (n_channels > 0) {
for (i = 0; i < n_channels; i++) {
chanspec = channel_to_chanspec(&cfg->d11inf,
request->channels[i]);
brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n",
request->channels[i]->hw_value, chanspec);
params_le->channel_list[i] = cpu_to_le16(chanspec);
}
} else {
brcmf_dbg(SCAN, "Scanning all channels\n");
}
/* Copy ssid array if applicable */
brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
if (n_ssids > 0) {
offset = offsetof(struct brcmf_scan_params_le, channel_list) +
n_channels * sizeof(u16);
offset = roundup(offset, sizeof(u32));
ptr = (char *)params_le + offset;
for (i = 0; i < n_ssids; i++) {
memset(&ssid_le, 0, sizeof(ssid_le));
ssid_le.SSID_len =
cpu_to_le32(request->ssids[i].ssid_len);
memcpy(ssid_le.SSID, request->ssids[i].ssid,
request->ssids[i].ssid_len);
if (!ssid_le.SSID_len)
brcmf_dbg(SCAN, "%d: Broadcast scan\n", i);
else
brcmf_dbg(SCAN, "%d: scan for %s size =%d\n",
i, ssid_le.SSID, ssid_le.SSID_len);
memcpy(ptr, &ssid_le, sizeof(ssid_le));
ptr += sizeof(ssid_le);
}
} else {
brcmf_dbg(SCAN, "Broadcast scan %p\n", request->ssids);
if ((request->ssids) && request->ssids->ssid_len) {
brcmf_dbg(SCAN, "SSID %s len=%d\n",
params_le->ssid_le.SSID,
request->ssids->ssid_len);
params_le->ssid_le.SSID_len =
cpu_to_le32(request->ssids->ssid_len);
memcpy(&params_le->ssid_le.SSID, request->ssids->ssid,
request->ssids->ssid_len);
}
}
/* Adding mask to channel numbers */
params_le->channel_num =
cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
(n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
}
static s32
brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
struct cfg80211_scan_request *request)
{
s32 params_size = BRCMF_SCAN_PARAMS_FIXED_SIZE +
offsetof(struct brcmf_escan_params_le, params_le);
struct brcmf_escan_params_le *params;
s32 err = 0;
brcmf_dbg(SCAN, "E-SCAN START\n");
if (request != NULL) {
/* Allocate space for populating ssids in struct */
params_size += sizeof(u32) * ((request->n_channels + 1) / 2);
/* Allocate space for populating ssids in struct */
params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids;
}
params = kzalloc(params_size, GFP_KERNEL);
if (!params) {
err = -ENOMEM;
goto exit;
}
BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN);
brcmf_escan_prep(cfg, &params->params_le, request);
params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION);
params->action = cpu_to_le16(WL_ESCAN_ACTION_START);
params->sync_id = cpu_to_le16(0x1234);
err = brcmf_fil_iovar_data_set(ifp, "escan", params, params_size);
if (err) {
if (err == -EBUSY)
brcmf_dbg(INFO, "system busy : escan canceled\n");
else
brcmf_err("error (%d)\n", err);
}
kfree(params);
exit:
return err;
}
static s32
brcmf_do_escan(struct brcmf_cfg80211_info *cfg, struct wiphy *wiphy,
struct brcmf_if *ifp, struct cfg80211_scan_request *request)
{
s32 err;
u32 passive_scan;
struct brcmf_scan_results *results;
struct escan_info *escan = &cfg->escan_info;
brcmf_dbg(SCAN, "Enter\n");
escan->ifp = ifp;
escan->wiphy = wiphy;
escan->escan_state = WL_ESCAN_STATE_SCANNING;
passive_scan = cfg->active_scan ? 0 : 1;
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PASSIVE_SCAN,
passive_scan);
if (err) {
brcmf_err("error (%d)\n", err);
return err;
}
brcmf_scan_config_mpc(ifp, 0);
results = (struct brcmf_scan_results *)cfg->escan_info.escan_buf;
results->version = 0;
results->count = 0;
results->buflen = WL_ESCAN_RESULTS_FIXED_SIZE;
err = escan->run(cfg, ifp, request);
if (err)
brcmf_scan_config_mpc(ifp, 1);
return err;
}
static s32
brcmf_cfg80211_escan(struct wiphy *wiphy, struct brcmf_cfg80211_vif *vif,
struct cfg80211_scan_request *request,
struct cfg80211_ssid *this_ssid)
{
struct brcmf_if *ifp = vif->ifp;
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct cfg80211_ssid *ssids;
u32 passive_scan;
bool escan_req;
bool spec_scan;
s32 err;
struct brcmf_ssid_le ssid_le;
u32 SSID_len;
brcmf_dbg(SCAN, "START ESCAN\n");
if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) {
brcmf_err("Scanning already: status (%lu)\n", cfg->scan_status);
return -EAGAIN;
}
if (test_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status)) {
brcmf_err("Scanning being aborted: status (%lu)\n",
cfg->scan_status);
return -EAGAIN;
}
if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) {
brcmf_err("Scanning suppressed: status (%lu)\n",
cfg->scan_status);
return -EAGAIN;
}
if (test_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state)) {
brcmf_err("Connecting: status (%lu)\n", ifp->vif->sme_state);
return -EAGAIN;
}
/* If scan req comes for p2p0, send it over primary I/F */
if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif)
vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif;
escan_req = false;
if (request) {
/* scan bss */
ssids = request->ssids;
escan_req = true;
} else {
/* scan in ibss */
/* we don't do escan in ibss */
ssids = this_ssid;
}
cfg->scan_request = request;
set_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status);
if (escan_req) {
cfg->escan_info.run = brcmf_run_escan;
err = brcmf_p2p_scan_prep(wiphy, request, vif);
if (err)
goto scan_out;
err = brcmf_do_escan(cfg, wiphy, vif->ifp, request);
if (err)
goto scan_out;
} else {
brcmf_dbg(SCAN, "ssid \"%s\", ssid_len (%d)\n",
ssids->ssid, ssids->ssid_len);
memset(&ssid_le, 0, sizeof(ssid_le));
SSID_len = min_t(u8, sizeof(ssid_le.SSID), ssids->ssid_len);
ssid_le.SSID_len = cpu_to_le32(0);
spec_scan = false;
if (SSID_len) {
memcpy(ssid_le.SSID, ssids->ssid, SSID_len);
ssid_le.SSID_len = cpu_to_le32(SSID_len);
spec_scan = true;
} else
brcmf_dbg(SCAN, "Broadcast scan\n");
passive_scan = cfg->active_scan ? 0 : 1;
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PASSIVE_SCAN,
passive_scan);
if (err) {
brcmf_err("WLC_SET_PASSIVE_SCAN error (%d)\n", err);
goto scan_out;
}
brcmf_scan_config_mpc(ifp, 0);
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, &ssid_le,
sizeof(ssid_le));
if (err) {
if (err == -EBUSY)
brcmf_dbg(INFO, "BUSY: scan for \"%s\" canceled\n",
ssid_le.SSID);
else
brcmf_err("WLC_SCAN error (%d)\n", err);
brcmf_scan_config_mpc(ifp, 1);
goto scan_out;
}
}
/* Arm scan timeout timer */
mod_timer(&cfg->escan_timeout, jiffies +
BRCMF_ESCAN_TIMER_INTERVAL_MS * HZ / 1000);
return 0;
scan_out:
clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status);
cfg->scan_request = NULL;
return err;
}
static s32
brcmf_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request)
{
struct brcmf_cfg80211_vif *vif;
s32 err = 0;
brcmf_dbg(TRACE, "Enter\n");
vif = container_of(request->wdev, struct brcmf_cfg80211_vif, wdev);
if (!check_vif_up(vif))
return -EIO;
err = brcmf_cfg80211_escan(wiphy, vif, request, NULL);
if (err)
brcmf_err("scan error (%d)\n", err);
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32 brcmf_set_rts(struct net_device *ndev, u32 rts_threshold)
{
s32 err = 0;
err = brcmf_fil_iovar_int_set(netdev_priv(ndev), "rtsthresh",
rts_threshold);
if (err)
brcmf_err("Error (%d)\n", err);
return err;
}
static s32 brcmf_set_frag(struct net_device *ndev, u32 frag_threshold)
{
s32 err = 0;
err = brcmf_fil_iovar_int_set(netdev_priv(ndev), "fragthresh",
frag_threshold);
if (err)
brcmf_err("Error (%d)\n", err);
return err;
}
static s32 brcmf_set_retry(struct net_device *ndev, u32 retry, bool l)
{
s32 err = 0;
u32 cmd = (l ? BRCMF_C_SET_LRL : BRCMF_C_SET_SRL);
err = brcmf_fil_cmd_int_set(netdev_priv(ndev), cmd, retry);
if (err) {
brcmf_err("cmd (%d) , error (%d)\n", cmd, err);
return err;
}
return err;
}
static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct net_device *ndev = cfg_to_ndev(cfg);
struct brcmf_if *ifp = netdev_priv(ndev);
s32 err = 0;
brcmf_dbg(TRACE, "Enter\n");
if (!check_vif_up(ifp->vif))
return -EIO;
if (changed & WIPHY_PARAM_RTS_THRESHOLD &&
(cfg->conf->rts_threshold != wiphy->rts_threshold)) {
cfg->conf->rts_threshold = wiphy->rts_threshold;
err = brcmf_set_rts(ndev, cfg->conf->rts_threshold);
if (!err)
goto done;
}
if (changed & WIPHY_PARAM_FRAG_THRESHOLD &&
(cfg->conf->frag_threshold != wiphy->frag_threshold)) {
cfg->conf->frag_threshold = wiphy->frag_threshold;
err = brcmf_set_frag(ndev, cfg->conf->frag_threshold);
if (!err)
goto done;
}
if (changed & WIPHY_PARAM_RETRY_LONG
&& (cfg->conf->retry_long != wiphy->retry_long)) {
cfg->conf->retry_long = wiphy->retry_long;
err = brcmf_set_retry(ndev, cfg->conf->retry_long, true);
if (!err)
goto done;
}
if (changed & WIPHY_PARAM_RETRY_SHORT
&& (cfg->conf->retry_short != wiphy->retry_short)) {
cfg->conf->retry_short = wiphy->retry_short;
err = brcmf_set_retry(ndev, cfg->conf->retry_short, false);
if (!err)
goto done;
}
done:
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static void brcmf_init_prof(struct brcmf_cfg80211_profile *prof)
{
memset(prof, 0, sizeof(*prof));
}
static u16 brcmf_map_fw_linkdown_reason(const struct brcmf_event_msg *e)
{
u16 reason;
switch (e->event_code) {
case BRCMF_E_DEAUTH:
case BRCMF_E_DEAUTH_IND:
case BRCMF_E_DISASSOC_IND:
reason = e->reason;
break;
case BRCMF_E_LINK:
default:
reason = 0;
break;
}
return reason;
}
static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy);
s32 err = 0;
brcmf_dbg(TRACE, "Enter\n");
if (test_and_clear_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state)) {
brcmf_dbg(INFO, "Call WLC_DISASSOC to stop excess roaming\n ");
err = brcmf_fil_cmd_data_set(vif->ifp,
BRCMF_C_DISASSOC, NULL, 0);
if (err) {
brcmf_err("WLC_DISASSOC failed (%d)\n", err);
}
if ((vif->wdev.iftype == NL80211_IFTYPE_STATION) ||
(vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT))
cfg80211_disconnected(vif->wdev.netdev, reason, NULL, 0,
true, GFP_KERNEL);
}
clear_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state);
clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status);
brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0);
brcmf_dbg(TRACE, "Exit\n");
}
static s32
brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev,
struct cfg80211_ibss_params *params)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
struct brcmf_join_params join_params;
size_t join_params_size = 0;
s32 err = 0;
s32 wsec = 0;
s32 bcnprd;
u16 chanspec;
u32 ssid_len;
brcmf_dbg(TRACE, "Enter\n");
if (!check_vif_up(ifp->vif))
return -EIO;
if (params->ssid)
brcmf_dbg(CONN, "SSID: %s\n", params->ssid);
else {
brcmf_dbg(CONN, "SSID: NULL, Not supported\n");
return -EOPNOTSUPP;
}
set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
if (params->bssid)
brcmf_dbg(CONN, "BSSID: %pM\n", params->bssid);
else
brcmf_dbg(CONN, "No BSSID specified\n");
if (params->chandef.chan)
brcmf_dbg(CONN, "channel: %d\n",
params->chandef.chan->center_freq);
else
brcmf_dbg(CONN, "no channel specified\n");
if (params->channel_fixed)
brcmf_dbg(CONN, "fixed channel required\n");
else
brcmf_dbg(CONN, "no fixed channel required\n");
if (params->ie && params->ie_len)
brcmf_dbg(CONN, "ie len: %d\n", params->ie_len);
else
brcmf_dbg(CONN, "no ie specified\n");
if (params->beacon_interval)
brcmf_dbg(CONN, "beacon interval: %d\n",
params->beacon_interval);
else
brcmf_dbg(CONN, "no beacon interval specified\n");
if (params->basic_rates)
brcmf_dbg(CONN, "basic rates: %08X\n", params->basic_rates);
else
brcmf_dbg(CONN, "no basic rates specified\n");
if (params->privacy)
brcmf_dbg(CONN, "privacy required\n");
else
brcmf_dbg(CONN, "no privacy required\n");
/* Configure Privacy for starter */
if (params->privacy)
wsec |= WEP_ENABLED;
err = brcmf_fil_iovar_int_set(ifp, "wsec", wsec);
if (err) {
brcmf_err("wsec failed (%d)\n", err);
goto done;
}
/* Configure Beacon Interval for starter */
if (params->beacon_interval)
bcnprd = params->beacon_interval;
else
bcnprd = 100;
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_BCNPRD, bcnprd);
if (err) {
brcmf_err("WLC_SET_BCNPRD failed (%d)\n", err);
goto done;
}
/* Configure required join parameter */
memset(&join_params, 0, sizeof(struct brcmf_join_params));
/* SSID */
ssid_len = min_t(u32, params->ssid_len, IEEE80211_MAX_SSID_LEN);
memcpy(join_params.ssid_le.SSID, params->ssid, ssid_len);
join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len);
join_params_size = sizeof(join_params.ssid_le);
/* BSSID */
if (params->bssid) {
memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN);
join_params_size += BRCMF_ASSOC_PARAMS_FIXED_SIZE;
memcpy(profile->bssid, params->bssid, ETH_ALEN);
} else {
eth_broadcast_addr(join_params.params_le.bssid);
eth_zero_addr(profile->bssid);
}
/* Channel */
if (params->chandef.chan) {
u32 target_channel;
cfg->channel =
ieee80211_frequency_to_channel(
params->chandef.chan->center_freq);
if (params->channel_fixed) {
/* adding chanspec */
chanspec = chandef_to_chanspec(&cfg->d11inf,
&params->chandef);
join_params.params_le.chanspec_list[0] =
cpu_to_le16(chanspec);
join_params.params_le.chanspec_num = cpu_to_le32(1);
join_params_size += sizeof(join_params.params_le);
}
/* set channel for starter */
target_channel = cfg->channel;
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_CHANNEL,
target_channel);
if (err) {
brcmf_err("WLC_SET_CHANNEL failed (%d)\n", err);
goto done;
}
} else
cfg->channel = 0;
cfg->ibss_starter = false;
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID,
&join_params, join_params_size);
if (err) {
brcmf_err("WLC_SET_SSID failed (%d)\n", err);
goto done;
}
done:
if (err)
clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *ndev)
{
struct brcmf_if *ifp = netdev_priv(ndev);
brcmf_dbg(TRACE, "Enter\n");
if (!check_vif_up(ifp->vif)) {
/* When driver is being unloaded, it can end up here. If an
* error is returned then later on a debug trace in the wireless
* core module will be printed. To avoid this 0 is returned.
*/
return 0;
}
brcmf_link_down(ifp->vif, WLAN_REASON_DEAUTH_LEAVING);
brcmf_net_setcarrier(ifp, false);
brcmf_dbg(TRACE, "Exit\n");
return 0;
}
static s32 brcmf_set_wpa_version(struct net_device *ndev,
struct cfg80211_connect_params *sme)
{
struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev);
struct brcmf_cfg80211_security *sec;
s32 val = 0;
s32 err = 0;
if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1)
val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED;
else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2)
val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
else
val = WPA_AUTH_DISABLED;
brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val);
if (err) {
brcmf_err("set wpa_auth failed (%d)\n", err);
return err;
}
sec = &profile->sec;
sec->wpa_versions = sme->crypto.wpa_versions;
return err;
}
static s32 brcmf_set_auth_type(struct net_device *ndev,
struct cfg80211_connect_params *sme)
{
struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev);
struct brcmf_cfg80211_security *sec;
s32 val = 0;
s32 err = 0;
switch (sme->auth_type) {
case NL80211_AUTHTYPE_OPEN_SYSTEM:
val = 0;
brcmf_dbg(CONN, "open system\n");
break;
case NL80211_AUTHTYPE_SHARED_KEY:
val = 1;
brcmf_dbg(CONN, "shared key\n");
break;
default:
val = 2;
brcmf_dbg(CONN, "automatic, auth type (%d)\n", sme->auth_type);
break;
}
err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "auth", val);
if (err) {
brcmf_err("set auth failed (%d)\n", err);
return err;
}
sec = &profile->sec;
sec->auth_type = sme->auth_type;
return err;
}
static s32
brcmf_set_wsec_mode(struct net_device *ndev,
struct cfg80211_connect_params *sme)
{
struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev);
struct brcmf_cfg80211_security *sec;
s32 pval = 0;
s32 gval = 0;
s32 wsec;
s32 err = 0;
if (sme->crypto.n_ciphers_pairwise) {
switch (sme->crypto.ciphers_pairwise[0]) {
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
pval = WEP_ENABLED;
break;
case WLAN_CIPHER_SUITE_TKIP:
pval = TKIP_ENABLED;
break;
case WLAN_CIPHER_SUITE_CCMP:
pval = AES_ENABLED;
break;
case WLAN_CIPHER_SUITE_AES_CMAC:
pval = AES_ENABLED;
break;
default:
brcmf_err("invalid cipher pairwise (%d)\n",
sme->crypto.ciphers_pairwise[0]);
return -EINVAL;
}
}
if (sme->crypto.cipher_group) {
switch (sme->crypto.cipher_group) {
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
gval = WEP_ENABLED;
break;
case WLAN_CIPHER_SUITE_TKIP:
gval = TKIP_ENABLED;
break;
case WLAN_CIPHER_SUITE_CCMP:
gval = AES_ENABLED;
break;
case WLAN_CIPHER_SUITE_AES_CMAC:
gval = AES_ENABLED;
break;
default:
brcmf_err("invalid cipher group (%d)\n",
sme->crypto.cipher_group);
return -EINVAL;
}
}
brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval);
/* In case of privacy, but no security and WPS then simulate */
/* setting AES. WPS-2.0 allows no security */
if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval &&
sme->privacy)
pval = AES_ENABLED;
wsec = pval | gval;
err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wsec", wsec);
if (err) {
brcmf_err("error (%d)\n", err);
return err;
}
sec = &profile->sec;
sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0];
sec->cipher_group = sme->crypto.cipher_group;
return err;
}
static s32
brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
{
struct brcmf_if *ifp = netdev_priv(ndev);
s32 val;
s32 err;
const struct brcmf_tlv *rsn_ie;
const u8 *ie;
u32 ie_len;
u32 offset;
u16 rsn_cap;
u32 mfp;
u16 count;
if (!sme->crypto.n_akm_suites)
return 0;
err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "wpa_auth", &val);
if (err) {
brcmf_err("could not get wpa_auth (%d)\n", err);
return err;
}
if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) {
switch (sme->crypto.akm_suites[0]) {
case WLAN_AKM_SUITE_8021X:
val = WPA_AUTH_UNSPECIFIED;
break;
case WLAN_AKM_SUITE_PSK:
val = WPA_AUTH_PSK;
break;
default:
brcmf_err("invalid cipher group (%d)\n",
sme->crypto.cipher_group);
return -EINVAL;
}
} else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) {
switch (sme->crypto.akm_suites[0]) {
case WLAN_AKM_SUITE_8021X:
val = WPA2_AUTH_UNSPECIFIED;
break;
case WLAN_AKM_SUITE_8021X_SHA256:
val = WPA2_AUTH_1X_SHA256;
break;
case WLAN_AKM_SUITE_PSK_SHA256:
val = WPA2_AUTH_PSK_SHA256;
break;
case WLAN_AKM_SUITE_PSK:
val = WPA2_AUTH_PSK;
break;
default:
brcmf_err("invalid cipher group (%d)\n",
sme->crypto.cipher_group);
return -EINVAL;
}
}
if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP))
goto skip_mfp_config;
/* The MFP mode (1 or 2) needs to be determined, parse IEs. The
* IE will not be verified, just a quick search for MFP config
*/
rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie, sme->ie_len,
WLAN_EID_RSN);
if (!rsn_ie)
goto skip_mfp_config;
ie = (const u8 *)rsn_ie;
ie_len = rsn_ie->len + TLV_HDR_LEN;
/* Skip unicast suite */
offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN;
if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len)
goto skip_mfp_config;
/* Skip multicast suite */
count = ie[offset] + (ie[offset + 1] << 8);
offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN);
if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len)
goto skip_mfp_config;
/* Skip auth key management suite(s) */
count = ie[offset] + (ie[offset + 1] << 8);
offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN);
if (offset + WPA_IE_SUITE_COUNT_LEN > ie_len)
goto skip_mfp_config;
/* Ready to read capabilities */
mfp = BRCMF_MFP_NONE;
rsn_cap = ie[offset] + (ie[offset + 1] << 8);
if (rsn_cap & RSN_CAP_MFPR_MASK)
mfp = BRCMF_MFP_REQUIRED;
else if (rsn_cap & RSN_CAP_MFPC_MASK)
mfp = BRCMF_MFP_CAPABLE;
brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp);
skip_mfp_config:
brcmf_dbg(CONN, "setting wpa_auth to %d\n", val);
err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val);
if (err) {
brcmf_err("could not set wpa_auth (%d)\n", err);
return err;
}
return err;
}
static s32
brcmf_set_sharedkey(struct net_device *ndev,
struct cfg80211_connect_params *sme)
{
struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev);
struct brcmf_cfg80211_security *sec;
struct brcmf_wsec_key key;
s32 val;
s32 err = 0;
brcmf_dbg(CONN, "key len (%d)\n", sme->key_len);
if (sme->key_len == 0)
return 0;
sec = &profile->sec;
brcmf_dbg(CONN, "wpa_versions 0x%x cipher_pairwise 0x%x\n",
sec->wpa_versions, sec->cipher_pairwise);
if (sec->wpa_versions & (NL80211_WPA_VERSION_1 | NL80211_WPA_VERSION_2))
return 0;
if (!(sec->cipher_pairwise &
(WLAN_CIPHER_SUITE_WEP40 | WLAN_CIPHER_SUITE_WEP104)))
return 0;
memset(&key, 0, sizeof(key));
key.len = (u32) sme->key_len;
key.index = (u32) sme->key_idx;
if (key.len > sizeof(key.data)) {
brcmf_err("Too long key length (%u)\n", key.len);
return -EINVAL;
}
memcpy(key.data, sme->key, key.len);
key.flags = BRCMF_PRIMARY_KEY;
switch (sec->cipher_pairwise) {
case WLAN_CIPHER_SUITE_WEP40:
key.algo = CRYPTO_ALGO_WEP1;
break;
case WLAN_CIPHER_SUITE_WEP104:
key.algo = CRYPTO_ALGO_WEP128;
break;
default:
brcmf_err("Invalid algorithm (%d)\n",
sme->crypto.ciphers_pairwise[0]);
return -EINVAL;
}
/* Set the new key/index */
brcmf_dbg(CONN, "key length (%d) key index (%d) algo (%d)\n",
key.len, key.index, key.algo);
brcmf_dbg(CONN, "key \"%s\"\n", key.data);
err = send_key_to_dongle(netdev_priv(ndev), &key);
if (err)
return err;
if (sec->auth_type == NL80211_AUTHTYPE_SHARED_KEY) {
brcmf_dbg(CONN, "set auth_type to shared key\n");
val = WL_AUTH_SHARED_KEY; /* shared key */
err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "auth", val);
if (err)
brcmf_err("set auth failed (%d)\n", err);
}
return err;
}
static
enum nl80211_auth_type brcmf_war_auth_type(struct brcmf_if *ifp,
enum nl80211_auth_type type)
{
if (type == NL80211_AUTHTYPE_AUTOMATIC &&
brcmf_feat_is_quirk_enabled(ifp, BRCMF_FEAT_QUIRK_AUTO_AUTH)) {
brcmf_dbg(CONN, "WAR: use OPEN instead of AUTO\n");
type = NL80211_AUTHTYPE_OPEN_SYSTEM;
}
return type;
}
static void brcmf_set_join_pref(struct brcmf_if *ifp,
struct cfg80211_bss_selection *bss_select)
{
struct brcmf_join_pref_params join_pref_params[2];
enum nl80211_band band;
int err, i = 0;
join_pref_params[i].len = 2;
join_pref_params[i].rssi_gain = 0;
if (bss_select->behaviour != NL80211_BSS_SELECT_ATTR_BAND_PREF)
brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_ASSOC_PREFER, WLC_BAND_AUTO);
switch (bss_select->behaviour) {
case __NL80211_BSS_SELECT_ATTR_INVALID:
brcmf_c_set_joinpref_default(ifp);
return;
case NL80211_BSS_SELECT_ATTR_BAND_PREF:
join_pref_params[i].type = BRCMF_JOIN_PREF_BAND;
band = bss_select->param.band_pref;
join_pref_params[i].band = nl80211_band_to_fwil(band);
i++;
break;
case NL80211_BSS_SELECT_ATTR_RSSI_ADJUST:
join_pref_params[i].type = BRCMF_JOIN_PREF_RSSI_DELTA;
band = bss_select->param.adjust.band;
join_pref_params[i].band = nl80211_band_to_fwil(band);
join_pref_params[i].rssi_gain = bss_select->param.adjust.delta;
i++;
break;
case NL80211_BSS_SELECT_ATTR_RSSI:
default:
break;
}
join_pref_params[i].type = BRCMF_JOIN_PREF_RSSI;
join_pref_params[i].len = 2;
join_pref_params[i].rssi_gain = 0;
join_pref_params[i].band = 0;
err = brcmf_fil_iovar_data_set(ifp, "join_pref", join_pref_params,
sizeof(join_pref_params));
if (err)
brcmf_err("Set join_pref error (%d)\n", err);
}
static s32
brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
struct cfg80211_connect_params *sme)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct brcmf_if *ifp = netdev_priv(ndev);
struct ieee80211_channel *chan = sme->channel;
struct brcmf_join_params join_params;
size_t join_params_size;
const struct brcmf_tlv *rsn_ie;
const struct brcmf_vs_tlv *wpa_ie;
const void *ie;
u32 ie_len;
struct brcmf_ext_join_params_le *ext_join_params;
u16 chanspec;
s32 err = 0;
u32 ssid_len;
brcmf_dbg(TRACE, "Enter\n");
if (!check_vif_up(ifp->vif))
return -EIO;
if (!sme->ssid) {
brcmf_err("Invalid ssid\n");
return -EOPNOTSUPP;
}
if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) {
/* A normal (non P2P) connection request setup. */
ie = NULL;
ie_len = 0;
/* find the WPA_IE */
wpa_ie = brcmf_find_wpaie((u8 *)sme->ie, sme->ie_len);
if (wpa_ie) {
ie = wpa_ie;
ie_len = wpa_ie->len + TLV_HDR_LEN;
} else {
/* find the RSN_IE */
rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie,
sme->ie_len,
WLAN_EID_RSN);
if (rsn_ie) {
ie = rsn_ie;
ie_len = rsn_ie->len + TLV_HDR_LEN;
}
}
brcmf_fil_iovar_data_set(ifp, "wpaie", ie, ie_len);
}
err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG,
sme->ie, sme->ie_len);
if (err)
brcmf_err("Set Assoc REQ IE Failed\n");
else
brcmf_dbg(TRACE, "Applied Vndr IEs for Assoc request\n");
set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
if (chan) {
cfg->channel =
ieee80211_frequency_to_channel(chan->center_freq);
chanspec = channel_to_chanspec(&cfg->d11inf, chan);
brcmf_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n",
cfg->channel, chan->center_freq, chanspec);
} else {
cfg->channel = 0;
chanspec = 0;
}
brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len);
err = brcmf_set_wpa_version(ndev, sme);
if (err) {
brcmf_err("wl_set_wpa_version failed (%d)\n", err);
goto done;
}
sme->auth_type = brcmf_war_auth_type(ifp, sme->auth_type);
err = brcmf_set_auth_type(ndev, sme);
if (err) {
brcmf_err("wl_set_auth_type failed (%d)\n", err);
goto done;
}
err = brcmf_set_wsec_mode(ndev, sme);
if (err) {
brcmf_err("wl_set_set_cipher failed (%d)\n", err);
goto done;
}
err = brcmf_set_key_mgmt(ndev, sme);
if (err) {
brcmf_err("wl_set_key_mgmt failed (%d)\n", err);
goto done;
}
err = brcmf_set_sharedkey(ndev, sme);
if (err) {
brcmf_err("brcmf_set_sharedkey failed (%d)\n", err);
goto done;
}
/* Join with specific BSSID and cached SSID
* If SSID is zero join based on BSSID only
*/
join_params_size = offsetof(struct brcmf_ext_join_params_le, assoc_le) +
offsetof(struct brcmf_assoc_params_le, chanspec_list);
if (cfg->channel)
join_params_size += sizeof(u16);
ext_join_params = kzalloc(join_params_size, GFP_KERNEL);
if (ext_join_params == NULL) {
err = -ENOMEM;
goto done;
}
ssid_len = min_t(u32, sme->ssid_len, IEEE80211_MAX_SSID_LEN);
ext_join_params->ssid_le.SSID_len = cpu_to_le32(ssid_len);
memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, ssid_len);
if (ssid_len < IEEE80211_MAX_SSID_LEN)
brcmf_dbg(CONN, "SSID \"%s\", len (%d)\n",
ext_join_params->ssid_le.SSID, ssid_len);
/* Set up join scan parameters */
ext_join_params->scan_le.scan_type = -1;
ext_join_params->scan_le.home_time = cpu_to_le32(-1);
if (sme->bssid)
memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN);
else
eth_broadcast_addr(ext_join_params->assoc_le.bssid);
if (cfg->channel) {
ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1);
ext_join_params->assoc_le.chanspec_list[0] =
cpu_to_le16(chanspec);
/* Increase dwell time to receive probe response or detect
* beacon from target AP at a noisy air only during connect
* command.
*/
ext_join_params->scan_le.active_time =
cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS);
ext_join_params->scan_le.passive_time =
cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS);
/* To sync with presence period of VSDB GO send probe request
* more frequently. Probe request will be stopped when it gets
* probe response from target AP/GO.
*/
ext_join_params->scan_le.nprobes =
cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS /
BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS);
} else {
ext_join_params->scan_le.active_time = cpu_to_le32(-1);
ext_join_params->scan_le.passive_time = cpu_to_le32(-1);
ext_join_params->scan_le.nprobes = cpu_to_le32(-1);
}
brcmf_set_join_pref(ifp, &sme->bss_select);
err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params,
join_params_size);
kfree(ext_join_params);
if (!err)
/* This is it. join command worked, we are done */
goto done;
/* join command failed, fallback to set ssid */
memset(&join_params, 0, sizeof(join_params));
join_params_size = sizeof(join_params.ssid_le);
memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid_len);
join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len);
if (sme->bssid)
memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN);
else
eth_broadcast_addr(join_params.params_le.bssid);
if (cfg->channel) {
join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec);
join_params.params_le.chanspec_num = cpu_to_le32(1);
join_params_size += sizeof(join_params.params_le);
}
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID,
&join_params, join_params_size);
if (err)
brcmf_err("BRCMF_C_SET_SSID failed (%d)\n", err);
done:
if (err)
clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev,
u16 reason_code)
{
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
struct brcmf_scb_val_le scbval;
s32 err = 0;
brcmf_dbg(TRACE, "Enter. Reason code = %d\n", reason_code);
if (!check_vif_up(ifp->vif))
return -EIO;
clear_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state);
clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
cfg80211_disconnected(ndev, reason_code, NULL, 0, true, GFP_KERNEL);
memcpy(&scbval.ea, &profile->bssid, ETH_ALEN);
scbval.val = cpu_to_le32(reason_code);
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_DISASSOC,
&scbval, sizeof(scbval));
if (err)
brcmf_err("error (%d)\n", err);
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
enum nl80211_tx_power_setting type, s32 mbm)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct net_device *ndev = cfg_to_ndev(cfg);
struct brcmf_if *ifp = netdev_priv(ndev);
s32 err;
s32 disable;
u32 qdbm = 127;
brcmf_dbg(TRACE, "Enter %d %d\n", type, mbm);
if (!check_vif_up(ifp->vif))
return -EIO;
switch (type) {
case NL80211_TX_POWER_AUTOMATIC:
break;
case NL80211_TX_POWER_LIMITED:
case NL80211_TX_POWER_FIXED:
if (mbm < 0) {
brcmf_err("TX_POWER_FIXED - dbm is negative\n");
err = -EINVAL;
goto done;
}
qdbm = MBM_TO_DBM(4 * mbm);
if (qdbm > 127)
qdbm = 127;
qdbm |= WL_TXPWR_OVERRIDE;
break;
default:
brcmf_err("Unsupported type %d\n", type);
err = -EINVAL;
goto done;
}
/* Make sure radio is off or on as far as software is concerned */
disable = WL_RADIO_SW_DISABLE << 16;
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_RADIO, disable);
if (err)
brcmf_err("WLC_SET_RADIO error (%d)\n", err);
err = brcmf_fil_iovar_int_set(ifp, "qtxpower", qdbm);
if (err)
brcmf_err("qtxpower error (%d)\n", err);
done:
brcmf_dbg(TRACE, "Exit %d (qdbm)\n", qdbm & ~WL_TXPWR_OVERRIDE);
return err;
}
static s32
brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
s32 *dbm)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct net_device *ndev = cfg_to_ndev(cfg);
struct brcmf_if *ifp = netdev_priv(ndev);
s32 qdbm = 0;
s32 err;
brcmf_dbg(TRACE, "Enter\n");
if (!check_vif_up(ifp->vif))
return -EIO;
err = brcmf_fil_iovar_int_get(ifp, "qtxpower", &qdbm);
if (err) {
brcmf_err("error (%d)\n", err);
goto done;
}
*dbm = (qdbm & ~WL_TXPWR_OVERRIDE) / 4;
done:
brcmf_dbg(TRACE, "Exit (0x%x %d)\n", qdbm, *dbm);
return err;
}
static s32
brcmf_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev,
u8 key_idx, bool unicast, bool multicast)
{
struct brcmf_if *ifp = netdev_priv(ndev);
u32 index;
u32 wsec;
s32 err = 0;
brcmf_dbg(TRACE, "Enter\n");
brcmf_dbg(CONN, "key index (%d)\n", key_idx);
if (!check_vif_up(ifp->vif))
return -EIO;
err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec);
if (err) {
brcmf_err("WLC_GET_WSEC error (%d)\n", err);
goto done;
}
if (wsec & WEP_ENABLED) {
/* Just select a new current key */
index = key_idx;
err = brcmf_fil_cmd_int_set(ifp,
BRCMF_C_SET_KEY_PRIMARY, index);
if (err)
brcmf_err("error (%d)\n", err);
}
done:
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev,
u8 key_idx, bool pairwise, const u8 *mac_addr)
{
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_wsec_key *key;
s32 err;
brcmf_dbg(TRACE, "Enter\n");
brcmf_dbg(CONN, "key index (%d)\n", key_idx);
if (!check_vif_up(ifp->vif))
return -EIO;
if (key_idx >= BRCMF_MAX_DEFAULT_KEYS) {
/* we ignore this key index in this case */
return -EINVAL;
}
key = &ifp->vif->profile.key[key_idx];
if (key->algo == CRYPTO_ALGO_OFF) {
brcmf_dbg(CONN, "Ignore clearing of (never configured) key\n");
return -EINVAL;
}
memset(key, 0, sizeof(*key));
key->index = (u32)key_idx;
key->flags = BRCMF_PRIMARY_KEY;
/* Clear the key/index */
err = send_key_to_dongle(ifp, key);
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
u8 key_idx, bool pairwise, const u8 *mac_addr,
struct key_params *params)
{
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_wsec_key *key;
s32 val;
s32 wsec;
s32 err;
u8 keybuf[8];
bool ext_key;
brcmf_dbg(TRACE, "Enter\n");
brcmf_dbg(CONN, "key index (%d)\n", key_idx);
if (!check_vif_up(ifp->vif))
return -EIO;
if (key_idx >= BRCMF_MAX_DEFAULT_KEYS) {
/* we ignore this key index in this case */
brcmf_err("invalid key index (%d)\n", key_idx);
return -EINVAL;
}
if (params->key_len == 0)
return brcmf_cfg80211_del_key(wiphy, ndev, key_idx, pairwise,
mac_addr);
if (params->key_len > sizeof(key->data)) {
brcmf_err("Too long key length (%u)\n", params->key_len);
return -EINVAL;
}
ext_key = false;
if (mac_addr && (params->cipher != WLAN_CIPHER_SUITE_WEP40) &&
(params->cipher != WLAN_CIPHER_SUITE_WEP104)) {
brcmf_dbg(TRACE, "Ext key, mac %pM", mac_addr);
ext_key = true;
}
key = &ifp->vif->profile.key[key_idx];
memset(key, 0, sizeof(*key));
if ((ext_key) && (!is_multicast_ether_addr(mac_addr)))
memcpy((char *)&key->ea, (void *)mac_addr, ETH_ALEN);
key->len = params->key_len;
key->index = key_idx;
memcpy(key->data, params->key, key->len);
if (!ext_key)
key->flags = BRCMF_PRIMARY_KEY;
switch (params->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
key->algo = CRYPTO_ALGO_WEP1;
val = WEP_ENABLED;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n");
break;
case WLAN_CIPHER_SUITE_WEP104:
key->algo = CRYPTO_ALGO_WEP128;
val = WEP_ENABLED;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n");
break;
case WLAN_CIPHER_SUITE_TKIP:
if (!brcmf_is_apmode(ifp->vif)) {
brcmf_dbg(CONN, "Swapping RX/TX MIC key\n");
memcpy(keybuf, &key->data[24], sizeof(keybuf));
memcpy(&key->data[24], &key->data[16], sizeof(keybuf));
memcpy(&key->data[16], keybuf, sizeof(keybuf));
}
key->algo = CRYPTO_ALGO_TKIP;
val = TKIP_ENABLED;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n");
break;
case WLAN_CIPHER_SUITE_AES_CMAC:
key->algo = CRYPTO_ALGO_AES_CCM;
val = AES_ENABLED;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n");
break;
case WLAN_CIPHER_SUITE_CCMP:
key->algo = CRYPTO_ALGO_AES_CCM;
val = AES_ENABLED;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n");
break;
default:
brcmf_err("Invalid cipher (0x%x)\n", params->cipher);
err = -EINVAL;
goto done;
}
err = send_key_to_dongle(ifp, key);
if (ext_key || err)
goto done;
err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec);
if (err) {
brcmf_err("get wsec error (%d)\n", err);
goto done;
}
wsec |= val;
err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec);
if (err) {
brcmf_err("set wsec error (%d)\n", err);
goto done;
}
done:
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, u8 key_idx,
bool pairwise, const u8 *mac_addr, void *cookie,
void (*callback)(void *cookie,
struct key_params *params))
{
struct key_params params;
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
struct brcmf_cfg80211_security *sec;
s32 wsec;
s32 err = 0;
brcmf_dbg(TRACE, "Enter\n");
brcmf_dbg(CONN, "key index (%d)\n", key_idx);
if (!check_vif_up(ifp->vif))
return -EIO;
memset(&params, 0, sizeof(params));
err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec);
if (err) {
brcmf_err("WLC_GET_WSEC error (%d)\n", err);
/* Ignore this error, may happen during DISASSOC */
err = -EAGAIN;
goto done;
}
if (wsec & WEP_ENABLED) {
sec = &profile->sec;
if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) {
params.cipher = WLAN_CIPHER_SUITE_WEP40;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n");
} else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) {
params.cipher = WLAN_CIPHER_SUITE_WEP104;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n");
}
} else if (wsec & TKIP_ENABLED) {
params.cipher = WLAN_CIPHER_SUITE_TKIP;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n");
} else if (wsec & AES_ENABLED) {
params.cipher = WLAN_CIPHER_SUITE_AES_CMAC;
brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n");
} else {
brcmf_err("Invalid algo (0x%x)\n", wsec);
err = -EINVAL;
goto done;
}
callback(cookie, &params);
done:
brcmf_dbg(TRACE, "Exit\n");
return err;
}
static s32
brcmf_cfg80211_config_default_mgmt_key(struct wiphy *wiphy,
struct net_device *ndev, u8 key_idx)
{
struct brcmf_if *ifp = netdev_priv(ndev);
brcmf_dbg(TRACE, "Enter key_idx %d\n", key_idx);
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP))
return 0;
brcmf_dbg(INFO, "Not supported\n");
return -EOPNOTSUPP;
}
static void
brcmf_cfg80211_reconfigure_wep(struct brcmf_if *ifp)
{
s32 err;
u8 key_idx;
struct brcmf_wsec_key *key;
s32 wsec;
for (key_idx = 0; key_idx < BRCMF_MAX_DEFAULT_KEYS; key_idx++) {
key = &ifp->vif->profile.key[key_idx];
if ((key->algo == CRYPTO_ALGO_WEP1) ||
(key->algo == CRYPTO_ALGO_WEP128))
break;
}
if (key_idx == BRCMF_MAX_DEFAULT_KEYS)
return;
err = send_key_to_dongle(ifp, key);
if (err) {
brcmf_err("Setting WEP key failed (%d)\n", err);
return;
}
err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec);
if (err) {
brcmf_err("get wsec error (%d)\n", err);
return;
}
wsec |= WEP_ENABLED;
err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec);
if (err)
brcmf_err("set wsec error (%d)\n", err);
}
static void brcmf_convert_sta_flags(u32 fw_sta_flags, struct station_info *si)
{
struct nl80211_sta_flag_update *sfu;
brcmf_dbg(TRACE, "flags %08x\n", fw_sta_flags);
si->filled |= BIT(NL80211_STA_INFO_STA_FLAGS);
sfu = &si->sta_flags;
sfu->mask = BIT(NL80211_STA_FLAG_WME) |
BIT(NL80211_STA_FLAG_AUTHENTICATED) |
BIT(NL80211_STA_FLAG_ASSOCIATED) |
BIT(NL80211_STA_FLAG_AUTHORIZED);
if (fw_sta_flags & BRCMF_STA_WME)
sfu->set |= BIT(NL80211_STA_FLAG_WME);
if (fw_sta_flags & BRCMF_STA_AUTHE)
sfu->set |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
if (fw_sta_flags & BRCMF_STA_ASSOC)
sfu->set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
if (fw_sta_flags & BRCMF_STA_AUTHO)
sfu->set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
}
static void brcmf_fill_bss_param(struct brcmf_if *ifp, struct station_info *si)
{
struct {
__le32 len;
struct brcmf_bss_info_le bss_le;
} *buf;
u16 capability;
int err;
buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL);
if (!buf)
return;
buf->len = cpu_to_le32(WL_BSS_INFO_MAX);
err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, buf,
WL_BSS_INFO_MAX);
if (err) {
brcmf_err("Failed to get bss info (%d)\n", err);
goto out_kfree;
}
si->filled |= BIT(NL80211_STA_INFO_BSS_PARAM);
si->bss_param.beacon_interval = le16_to_cpu(buf->bss_le.beacon_period);
si->bss_param.dtim_period = buf->bss_le.dtim_period;
capability = le16_to_cpu(buf->bss_le.capability);
if (capability & IEEE80211_HT_STBC_PARAM_DUAL_CTS_PROT)
si->bss_param.flags |= BSS_PARAM_FLAGS_CTS_PROT;
if (capability & WLAN_CAPABILITY_SHORT_PREAMBLE)
si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE;
if (capability & WLAN_CAPABILITY_SHORT_SLOT_TIME)
si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
out_kfree:
kfree(buf);
}
static s32
brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp,
struct station_info *sinfo)
{
struct brcmf_scb_val_le scbval;
struct brcmf_pktcnt_le pktcnt;
s32 err;
u32 rate;
u32 rssi;
/* Get the current tx rate */
err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_RATE, &rate);
if (err < 0) {
brcmf_err("BRCMF_C_GET_RATE error (%d)\n", err);
return err;
}
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
sinfo->txrate.legacy = rate * 5;
memset(&scbval, 0, sizeof(scbval));
err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, &scbval,
sizeof(scbval));
if (err) {
brcmf_err("BRCMF_C_GET_RSSI error (%d)\n", err);
return err;
}
rssi = le32_to_cpu(scbval.val);
sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
sinfo->signal = rssi;
err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_GET_PKTCNTS, &pktcnt,
sizeof(pktcnt));
if (err) {
brcmf_err("BRCMF_C_GET_GET_PKTCNTS error (%d)\n", err);
return err;
}
sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS) |
BIT(NL80211_STA_INFO_RX_DROP_MISC) |
BIT(NL80211_STA_INFO_TX_PACKETS) |
BIT(NL80211_STA_INFO_TX_FAILED);
sinfo->rx_packets = le32_to_cpu(pktcnt.rx_good_pkt);
sinfo->rx_dropped_misc = le32_to_cpu(pktcnt.rx_bad_pkt);
sinfo->tx_packets = le32_to_cpu(pktcnt.tx_good_pkt);
sinfo->tx_failed = le32_to_cpu(pktcnt.tx_bad_pkt);
return 0;
}
static s32
brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
const u8 *mac, struct station_info *sinfo)
{
struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_scb_val_le scb_val;
s32 err = 0;
struct brcmf_sta_info_le sta_info_le;
u32 sta_flags;
u32 is_tdls_peer;
s32 total_rssi;
s32 count_rssi;
int rssi;
u32 i;
brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac);
if (!check_vif_up(ifp->vif))
return -EIO;
if (brcmf_is_ibssmode(ifp->vif))
return brcmf_cfg80211_get_station_ibss(ifp, sinfo);
memset(&sta_info_le, 0, sizeof(sta_info_le));
memcpy(&sta_info_le, mac, ETH_ALEN);
err = brcmf_fil_iovar_data_get(ifp, "tdls_sta_info",
&sta_info_le,
sizeof(sta_info_le));
is_tdls_peer = !err;
if (err) {
err = brcmf_fil_iovar_data_get(ifp, "sta_info",
&sta_info_le,
sizeof(sta_info_le));
if (err < 0) {
brcmf_err("GET STA INFO failed, %d\n", err);
goto done;
}
}
brcmf_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver));
sinfo->filled = BIT(NL80211_STA_INFO_INACTIVE_TIME);
sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000;
sta_flags = le32_to_cpu(sta_info_le.flags);
brcmf_convert_sta_flags(sta_flags, sinfo);
sinfo->sta_flags.mask |= BIT(NL80211_STA_FLAG_TDLS_PEER);
if (is_tdls_peer)
sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER);
else
sinfo->sta_flags.set &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
if (sta_flags & BRCMF_STA_ASSOC) {
sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME);
sinfo->connected_time = le32_to_cpu(sta_info_le.in);
brcmf_fill_bss_param(ifp, sinfo);
}
if (sta_flags & BRCMF_STA_SCBSTATS) {
sinfo->filled |= BIT(NL80211_STA_INFO_TX_FAILED);
sinfo->tx_failed = le32_to_cpu(sta_info_le.tx_failures);
sinfo->filled |= BIT(NL80211_STA_INFO_TX_PACKETS);
sinfo->tx_packets = le32_to_cpu(sta_info_le.tx_pkts);
sinfo->tx_packets += le32_to_cpu(sta_info_le.tx_mcast_pkts);
sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS);
sinfo->rx_packets = le32_to_cpu(sta_info_le.rx_ucast_pkts);
sinfo->rx_packets += le32_to_cpu(sta_info_le.rx_mcast_pkts);
if (sinfo->tx_packets) {
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
sinfo->txrate.legacy =
le32_to_cpu(sta_info_le.tx_rate) / 100;
}
if (sinfo->rx_packets) {
sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE);
sinfo->rxrate.legacy =
le32_to_cpu(sta_info_le.rx_rate) / 100;
}
if (le16_to_cpu(sta_info_le.ver) >= 4) {
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES);
sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes);
sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES);
sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes);
}
total_rssi = 0;
count_rssi = 0;
for (i = 0; i < BRCMF_ANT_MAX; i++) {
if (sta_info_le.rssi[i]) {
sinfo->chain_signal_avg[count_rssi] =
sta_info_le.rssi[i];
sinfo->chain_signal[count_rssi] =
sta_info_le.rssi[i];