mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-23 11:29:53 +07:00
3fd0202a0d
John W. Linville says: ==================== pull request: wireless-next 2014-07-25 Please pull this batch of updates intended for the 3.17 stream! For the mac80211 bits, Johannes says: "We have a lot of TDLS patches, among them a fix that should make hwsim tests happy again. The rest, this time, is mostly small fixes." For the Bluetooth bits, Gustavo says: "Some more patches for 3.17. The most important change here is the move of the 6lowpan code to net/6lowpan. It has been agreed with Davem that this change will go through the bluetooth tree. The rest are mostly clean up and fixes." and, "Here follows some more patches for 3.17. These are mostly fixes to what we've sent to you before for next merge window." For the iwlwifi bits, Emmanuel says: "I have the usual amount of BT Coex stuff. Arik continues to work on TDLS and Ariej contributes a few things for HS2.0. I added a few more things to the firmware debugging infrastructure. Eran fixes a small bug - pretty normal content." And for the Atheros bits, Kalle says: "For ath6kl me and Jessica added support for ar6004 hw3.0, our latest version of ar6004. For ath10k Janusz added a printout so that it's easier to check what ath10k kconfig options are enabled. He also added a debugfs file to configure maximum amsdu and ampdu values. Also we had few fixes as usual." On top of that is the usual large batch of various driver updates -- brcmfmac, mwifiex, the TI drivers, and wil6210 all get some action. Rafał has also been very busy with b43 and related updates. Also, I pulled the wireless tree into this in order to resolve a merge conflict... P.S. The change to fs/compat_ioctl.c reflects a name change in a Bluetooth header file... ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
2965 lines
83 KiB
C
2965 lines
83 KiB
C
/*
|
|
* Marvell Wireless LAN device driver: CFG80211
|
|
*
|
|
* Copyright (C) 2011-2014, Marvell International Ltd.
|
|
*
|
|
* This software file (the "File") is distributed by Marvell International
|
|
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
|
|
* (the "License"). You may use, redistribute and/or modify this File in
|
|
* accordance with the terms and conditions of the License, a copy of which
|
|
* is available by writing to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
|
|
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
|
|
*
|
|
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
|
|
* this warranty disclaimer.
|
|
*/
|
|
|
|
#include "cfg80211.h"
|
|
#include "main.h"
|
|
|
|
static char *reg_alpha2;
|
|
module_param(reg_alpha2, charp, 0);
|
|
|
|
static const struct ieee80211_iface_limit mwifiex_ap_sta_limits[] = {
|
|
{
|
|
.max = 2, .types = BIT(NL80211_IFTYPE_STATION) |
|
|
BIT(NL80211_IFTYPE_P2P_GO) |
|
|
BIT(NL80211_IFTYPE_P2P_CLIENT),
|
|
},
|
|
{
|
|
.max = 1, .types = BIT(NL80211_IFTYPE_AP),
|
|
},
|
|
};
|
|
|
|
static const struct ieee80211_iface_combination mwifiex_iface_comb_ap_sta = {
|
|
.limits = mwifiex_ap_sta_limits,
|
|
.num_different_channels = 1,
|
|
.n_limits = ARRAY_SIZE(mwifiex_ap_sta_limits),
|
|
.max_interfaces = MWIFIEX_MAX_BSS_NUM,
|
|
.beacon_int_infra_match = true,
|
|
};
|
|
|
|
/*
|
|
* This function maps the nl802.11 channel type into driver channel type.
|
|
*
|
|
* The mapping is as follows -
|
|
* NL80211_CHAN_NO_HT -> IEEE80211_HT_PARAM_CHA_SEC_NONE
|
|
* NL80211_CHAN_HT20 -> IEEE80211_HT_PARAM_CHA_SEC_NONE
|
|
* NL80211_CHAN_HT40PLUS -> IEEE80211_HT_PARAM_CHA_SEC_ABOVE
|
|
* NL80211_CHAN_HT40MINUS -> IEEE80211_HT_PARAM_CHA_SEC_BELOW
|
|
* Others -> IEEE80211_HT_PARAM_CHA_SEC_NONE
|
|
*/
|
|
u8 mwifiex_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type)
|
|
{
|
|
switch (chan_type) {
|
|
case NL80211_CHAN_NO_HT:
|
|
case NL80211_CHAN_HT20:
|
|
return IEEE80211_HT_PARAM_CHA_SEC_NONE;
|
|
case NL80211_CHAN_HT40PLUS:
|
|
return IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
|
|
case NL80211_CHAN_HT40MINUS:
|
|
return IEEE80211_HT_PARAM_CHA_SEC_BELOW;
|
|
default:
|
|
return IEEE80211_HT_PARAM_CHA_SEC_NONE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function checks whether WEP is set.
|
|
*/
|
|
static int
|
|
mwifiex_is_alg_wep(u32 cipher)
|
|
{
|
|
switch (cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function retrieves the private structure from kernel wiphy structure.
|
|
*/
|
|
static void *mwifiex_cfg80211_get_adapter(struct wiphy *wiphy)
|
|
{
|
|
return (void *) (*(unsigned long *) wiphy_priv(wiphy));
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to delete a network key.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_del_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool pairwise, const u8 *mac_addr)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev);
|
|
const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
const u8 *peer_mac = pairwise ? mac_addr : bc_mac;
|
|
|
|
if (mwifiex_set_encode(priv, NULL, NULL, 0, key_index, peer_mac, 1)) {
|
|
wiphy_err(wiphy, "deleting the crypto keys\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
wiphy_dbg(wiphy, "info: crypto keys deleted\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function forms an skb for management frame.
|
|
*/
|
|
static int
|
|
mwifiex_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len)
|
|
{
|
|
u8 addr[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
u16 pkt_len;
|
|
u32 tx_control = 0, pkt_type = PKT_TYPE_MGMT;
|
|
|
|
pkt_len = len + ETH_ALEN;
|
|
|
|
skb_reserve(skb, MWIFIEX_MIN_DATA_HEADER_LEN +
|
|
MWIFIEX_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len));
|
|
memcpy(skb_push(skb, sizeof(pkt_len)), &pkt_len, sizeof(pkt_len));
|
|
|
|
memcpy(skb_push(skb, sizeof(tx_control)),
|
|
&tx_control, sizeof(tx_control));
|
|
|
|
memcpy(skb_push(skb, sizeof(pkt_type)), &pkt_type, sizeof(pkt_type));
|
|
|
|
/* Add packet data and address4 */
|
|
memcpy(skb_put(skb, sizeof(struct ieee80211_hdr_3addr)), buf,
|
|
sizeof(struct ieee80211_hdr_3addr));
|
|
memcpy(skb_put(skb, ETH_ALEN), addr, ETH_ALEN);
|
|
memcpy(skb_put(skb, len - sizeof(struct ieee80211_hdr_3addr)),
|
|
buf + sizeof(struct ieee80211_hdr_3addr),
|
|
len - sizeof(struct ieee80211_hdr_3addr));
|
|
|
|
skb->priority = LOW_PRIO_TID;
|
|
__net_timestamp(skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to transmit a management frame.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct cfg80211_mgmt_tx_params *params, u64 *cookie)
|
|
{
|
|
const u8 *buf = params->buf;
|
|
size_t len = params->len;
|
|
struct sk_buff *skb;
|
|
u16 pkt_len;
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct mwifiex_txinfo *tx_info;
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
|
|
|
|
if (!buf || !len) {
|
|
wiphy_err(wiphy, "invalid buffer and length\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
mgmt = (const struct ieee80211_mgmt *)buf;
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA &&
|
|
ieee80211_is_probe_resp(mgmt->frame_control)) {
|
|
/* Since we support offload probe resp, we need to skip probe
|
|
* resp in AP or GO mode */
|
|
wiphy_dbg(wiphy,
|
|
"info: skip to send probe resp in AP or GO mode\n");
|
|
return 0;
|
|
}
|
|
|
|
pkt_len = len + ETH_ALEN;
|
|
skb = dev_alloc_skb(MWIFIEX_MIN_DATA_HEADER_LEN +
|
|
MWIFIEX_MGMT_FRAME_HEADER_SIZE +
|
|
pkt_len + sizeof(pkt_len));
|
|
|
|
if (!skb) {
|
|
wiphy_err(wiphy, "allocate skb failed for management frame\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tx_info = MWIFIEX_SKB_TXCB(skb);
|
|
memset(tx_info, 0, sizeof(*tx_info));
|
|
tx_info->bss_num = priv->bss_num;
|
|
tx_info->bss_type = priv->bss_type;
|
|
tx_info->pkt_len = pkt_len;
|
|
|
|
mwifiex_form_mgmt_frame(skb, buf, len);
|
|
mwifiex_queue_tx_pkt(priv, skb);
|
|
|
|
*cookie = prandom_u32() | 1;
|
|
cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, GFP_ATOMIC);
|
|
|
|
wiphy_dbg(wiphy, "info: management frame transmitted\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to register a mgmt frame.
|
|
*/
|
|
static void
|
|
mwifiex_cfg80211_mgmt_frame_register(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
u16 frame_type, bool reg)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
|
|
u32 mask;
|
|
|
|
if (reg)
|
|
mask = priv->mgmt_frame_mask | BIT(frame_type >> 4);
|
|
else
|
|
mask = priv->mgmt_frame_mask & ~BIT(frame_type >> 4);
|
|
|
|
if (mask != priv->mgmt_frame_mask) {
|
|
priv->mgmt_frame_mask = mask;
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_MGMT_FRAME_REG,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
&priv->mgmt_frame_mask, false);
|
|
wiphy_dbg(wiphy, "info: mgmt frame registered\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to remain on channel.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan,
|
|
unsigned int duration, u64 *cookie)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
|
|
int ret;
|
|
|
|
if (!chan || !cookie) {
|
|
wiphy_err(wiphy, "Invalid parameter for ROC\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv->roc_cfg.cookie) {
|
|
wiphy_dbg(wiphy, "info: ongoing ROC, cookie = 0x%llu\n",
|
|
priv->roc_cfg.cookie);
|
|
return -EBUSY;
|
|
}
|
|
|
|
ret = mwifiex_remain_on_chan_cfg(priv, HostCmd_ACT_GEN_SET, chan,
|
|
duration);
|
|
|
|
if (!ret) {
|
|
*cookie = prandom_u32() | 1;
|
|
priv->roc_cfg.cookie = *cookie;
|
|
priv->roc_cfg.chan = *chan;
|
|
|
|
cfg80211_ready_on_channel(wdev, *cookie, chan,
|
|
duration, GFP_ATOMIC);
|
|
|
|
wiphy_dbg(wiphy, "info: ROC, cookie = 0x%llx\n", *cookie);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to cancel remain on channel.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev, u64 cookie)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
|
|
int ret;
|
|
|
|
if (cookie != priv->roc_cfg.cookie)
|
|
return -ENOENT;
|
|
|
|
ret = mwifiex_remain_on_chan_cfg(priv, HostCmd_ACT_GEN_REMOVE,
|
|
&priv->roc_cfg.chan, 0);
|
|
|
|
if (!ret) {
|
|
cfg80211_remain_on_channel_expired(wdev, cookie,
|
|
&priv->roc_cfg.chan,
|
|
GFP_ATOMIC);
|
|
|
|
memset(&priv->roc_cfg, 0, sizeof(struct mwifiex_roc_cfg));
|
|
|
|
wiphy_dbg(wiphy, "info: cancel ROC, cookie = 0x%llx\n", cookie);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to set Tx power.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_set_tx_power(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
enum nl80211_tx_power_setting type,
|
|
int mbm)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv;
|
|
struct mwifiex_power_cfg power_cfg;
|
|
int dbm = MBM_TO_DBM(mbm);
|
|
|
|
if (type == NL80211_TX_POWER_FIXED) {
|
|
power_cfg.is_power_auto = 0;
|
|
power_cfg.power_level = dbm;
|
|
} else {
|
|
power_cfg.is_power_auto = 1;
|
|
}
|
|
|
|
priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY);
|
|
|
|
return mwifiex_set_tx_power(priv, &power_cfg);
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to set Power Save option.
|
|
*
|
|
* The timeout value, if provided, is currently ignored.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_set_power_mgmt(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
bool enabled, int timeout)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
u32 ps_mode;
|
|
|
|
if (timeout)
|
|
wiphy_dbg(wiphy,
|
|
"info: ignore timeout value for IEEE Power Save\n");
|
|
|
|
ps_mode = enabled;
|
|
|
|
return mwifiex_drv_set_power(priv, &ps_mode);
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to set the default network key.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool unicast,
|
|
bool multicast)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev);
|
|
|
|
/* Return if WEP key not configured */
|
|
if (!priv->sec_info.wep_enabled)
|
|
return 0;
|
|
|
|
if (priv->bss_type == MWIFIEX_BSS_TYPE_UAP) {
|
|
priv->wep_key_curr_index = key_index;
|
|
} else if (mwifiex_set_encode(priv, NULL, NULL, 0, key_index,
|
|
NULL, 0)) {
|
|
wiphy_err(wiphy, "set default Tx key index\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to add a network key.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_add_key(struct wiphy *wiphy, struct net_device *netdev,
|
|
u8 key_index, bool pairwise, const u8 *mac_addr,
|
|
struct key_params *params)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev);
|
|
struct mwifiex_wep_key *wep_key;
|
|
const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
const u8 *peer_mac = pairwise ? mac_addr : bc_mac;
|
|
|
|
if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_UAP &&
|
|
(params->cipher == WLAN_CIPHER_SUITE_WEP40 ||
|
|
params->cipher == WLAN_CIPHER_SUITE_WEP104)) {
|
|
if (params->key && params->key_len) {
|
|
wep_key = &priv->wep_key[key_index];
|
|
memset(wep_key, 0, sizeof(struct mwifiex_wep_key));
|
|
memcpy(wep_key->key_material, params->key,
|
|
params->key_len);
|
|
wep_key->key_index = key_index;
|
|
wep_key->key_length = params->key_len;
|
|
priv->sec_info.wep_enabled = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (mwifiex_set_encode(priv, params, params->key, params->key_len,
|
|
key_index, peer_mac, 0)) {
|
|
wiphy_err(wiphy, "crypto keys added\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function sends domain information to the firmware.
|
|
*
|
|
* The following information are passed to the firmware -
|
|
* - Country codes
|
|
* - Sub bands (first channel, number of channels, maximum Tx power)
|
|
*/
|
|
static int mwifiex_send_domain_info_cmd_fw(struct wiphy *wiphy)
|
|
{
|
|
u8 no_of_triplet = 0;
|
|
struct ieee80211_country_ie_triplet *t;
|
|
u8 no_of_parsed_chan = 0;
|
|
u8 first_chan = 0, next_chan = 0, max_pwr = 0;
|
|
u8 i, flag = 0;
|
|
enum ieee80211_band band;
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_channel *ch;
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv;
|
|
struct mwifiex_802_11d_domain_reg *domain_info = &adapter->domain_reg;
|
|
|
|
/* Set country code */
|
|
domain_info->country_code[0] = adapter->country_code[0];
|
|
domain_info->country_code[1] = adapter->country_code[1];
|
|
domain_info->country_code[2] = ' ';
|
|
|
|
band = mwifiex_band_to_radio_type(adapter->config_bands);
|
|
if (!wiphy->bands[band]) {
|
|
wiphy_err(wiphy, "11D: setting domain info in FW\n");
|
|
return -1;
|
|
}
|
|
|
|
sband = wiphy->bands[band];
|
|
|
|
for (i = 0; i < sband->n_channels ; i++) {
|
|
ch = &sband->channels[i];
|
|
if (ch->flags & IEEE80211_CHAN_DISABLED)
|
|
continue;
|
|
|
|
if (!flag) {
|
|
flag = 1;
|
|
first_chan = (u32) ch->hw_value;
|
|
next_chan = first_chan;
|
|
max_pwr = ch->max_power;
|
|
no_of_parsed_chan = 1;
|
|
continue;
|
|
}
|
|
|
|
if (ch->hw_value == next_chan + 1 &&
|
|
ch->max_power == max_pwr) {
|
|
next_chan++;
|
|
no_of_parsed_chan++;
|
|
} else {
|
|
t = &domain_info->triplet[no_of_triplet];
|
|
t->chans.first_channel = first_chan;
|
|
t->chans.num_channels = no_of_parsed_chan;
|
|
t->chans.max_power = max_pwr;
|
|
no_of_triplet++;
|
|
first_chan = (u32) ch->hw_value;
|
|
next_chan = first_chan;
|
|
max_pwr = ch->max_power;
|
|
no_of_parsed_chan = 1;
|
|
}
|
|
}
|
|
|
|
if (flag) {
|
|
t = &domain_info->triplet[no_of_triplet];
|
|
t->chans.first_channel = first_chan;
|
|
t->chans.num_channels = no_of_parsed_chan;
|
|
t->chans.max_power = max_pwr;
|
|
no_of_triplet++;
|
|
}
|
|
|
|
domain_info->no_of_triplet = no_of_triplet;
|
|
|
|
priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY);
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11D_DOMAIN_INFO,
|
|
HostCmd_ACT_GEN_SET, 0, NULL, false)) {
|
|
wiphy_err(wiphy, "11D: setting domain info in FW\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 regulatory domain callback function.
|
|
*
|
|
* This function is called when the regulatory domain is changed due to the
|
|
* following reasons -
|
|
* - Set by driver
|
|
* - Set by system core
|
|
* - Set by user
|
|
* - Set bt Country IE
|
|
*/
|
|
static void mwifiex_reg_notifier(struct wiphy *wiphy,
|
|
struct regulatory_request *request)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv = mwifiex_get_priv(adapter,
|
|
MWIFIEX_BSS_ROLE_ANY);
|
|
|
|
wiphy_dbg(wiphy, "info: cfg80211 regulatory domain callback for %c%c\n",
|
|
request->alpha2[0], request->alpha2[1]);
|
|
|
|
switch (request->initiator) {
|
|
case NL80211_REGDOM_SET_BY_DRIVER:
|
|
case NL80211_REGDOM_SET_BY_CORE:
|
|
case NL80211_REGDOM_SET_BY_USER:
|
|
case NL80211_REGDOM_SET_BY_COUNTRY_IE:
|
|
break;
|
|
default:
|
|
wiphy_err(wiphy, "unknown regdom initiator: %d\n",
|
|
request->initiator);
|
|
return;
|
|
}
|
|
|
|
/* Don't send world or same regdom info to firmware */
|
|
if (strncmp(request->alpha2, "00", 2) &&
|
|
strncmp(request->alpha2, adapter->country_code,
|
|
sizeof(request->alpha2))) {
|
|
memcpy(adapter->country_code, request->alpha2,
|
|
sizeof(request->alpha2));
|
|
mwifiex_send_domain_info_cmd_fw(wiphy);
|
|
mwifiex_dnld_txpwr_table(priv);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function sets the fragmentation threshold.
|
|
*
|
|
* The fragmentation threshold value must lie between MWIFIEX_FRAG_MIN_VALUE
|
|
* and MWIFIEX_FRAG_MAX_VALUE.
|
|
*/
|
|
static int
|
|
mwifiex_set_frag(struct mwifiex_private *priv, u32 frag_thr)
|
|
{
|
|
if (frag_thr < MWIFIEX_FRAG_MIN_VALUE ||
|
|
frag_thr > MWIFIEX_FRAG_MAX_VALUE)
|
|
frag_thr = MWIFIEX_FRAG_MAX_VALUE;
|
|
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_SET, FRAG_THRESH_I,
|
|
&frag_thr, true);
|
|
}
|
|
|
|
/*
|
|
* This function sets the RTS threshold.
|
|
|
|
* The rts value must lie between MWIFIEX_RTS_MIN_VALUE
|
|
* and MWIFIEX_RTS_MAX_VALUE.
|
|
*/
|
|
static int
|
|
mwifiex_set_rts(struct mwifiex_private *priv, u32 rts_thr)
|
|
{
|
|
if (rts_thr < MWIFIEX_RTS_MIN_VALUE || rts_thr > MWIFIEX_RTS_MAX_VALUE)
|
|
rts_thr = MWIFIEX_RTS_MAX_VALUE;
|
|
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_SET, RTS_THRESH_I,
|
|
&rts_thr, true);
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to set wiphy parameters.
|
|
*
|
|
* This function can be used to set the RTS threshold and the
|
|
* Fragmentation threshold of the driver.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv;
|
|
struct mwifiex_uap_bss_param *bss_cfg;
|
|
int ret, bss_started, i;
|
|
|
|
for (i = 0; i < adapter->priv_num; i++) {
|
|
priv = adapter->priv[i];
|
|
|
|
switch (priv->bss_role) {
|
|
case MWIFIEX_BSS_ROLE_UAP:
|
|
bss_cfg = kzalloc(sizeof(struct mwifiex_uap_bss_param),
|
|
GFP_KERNEL);
|
|
if (!bss_cfg)
|
|
return -ENOMEM;
|
|
|
|
mwifiex_set_sys_config_invalid_data(bss_cfg);
|
|
|
|
if (changed & WIPHY_PARAM_RTS_THRESHOLD)
|
|
bss_cfg->rts_threshold = wiphy->rts_threshold;
|
|
if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
|
|
bss_cfg->frag_threshold = wiphy->frag_threshold;
|
|
if (changed & WIPHY_PARAM_RETRY_LONG)
|
|
bss_cfg->retry_limit = wiphy->retry_long;
|
|
|
|
bss_started = priv->bss_started;
|
|
|
|
ret = mwifiex_send_cmd(priv, HostCmd_CMD_UAP_BSS_STOP,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
NULL, true);
|
|
if (ret) {
|
|
wiphy_err(wiphy, "Failed to stop the BSS\n");
|
|
kfree(bss_cfg);
|
|
return ret;
|
|
}
|
|
|
|
ret = mwifiex_send_cmd(priv, HostCmd_CMD_UAP_SYS_CONFIG,
|
|
HostCmd_ACT_GEN_SET,
|
|
UAP_BSS_PARAMS_I, bss_cfg,
|
|
false);
|
|
|
|
kfree(bss_cfg);
|
|
|
|
if (ret) {
|
|
wiphy_err(wiphy, "Failed to set bss config\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!bss_started)
|
|
break;
|
|
|
|
ret = mwifiex_send_cmd(priv, HostCmd_CMD_UAP_BSS_START,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
NULL, false);
|
|
if (ret) {
|
|
wiphy_err(wiphy, "Failed to start BSS\n");
|
|
return ret;
|
|
}
|
|
|
|
break;
|
|
case MWIFIEX_BSS_ROLE_STA:
|
|
if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
|
|
ret = mwifiex_set_rts(priv,
|
|
wiphy->rts_threshold);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
|
|
ret = mwifiex_set_frag(priv,
|
|
wiphy->frag_threshold);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mwifiex_cfg80211_deinit_p2p(struct mwifiex_private *priv)
|
|
{
|
|
u16 mode = P2P_MODE_DISABLE;
|
|
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA)
|
|
mwifiex_set_bss_role(priv, MWIFIEX_BSS_ROLE_STA);
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_P2P_MODE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mode, true))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function initializes the functionalities for P2P client.
|
|
* The P2P client initialization sequence is:
|
|
* disable -> device -> client
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_init_p2p_client(struct mwifiex_private *priv)
|
|
{
|
|
u16 mode;
|
|
|
|
if (mwifiex_cfg80211_deinit_p2p(priv))
|
|
return -1;
|
|
|
|
mode = P2P_MODE_DEVICE;
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_P2P_MODE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mode, true))
|
|
return -1;
|
|
|
|
mode = P2P_MODE_CLIENT;
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_P2P_MODE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mode, true))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function initializes the functionalities for P2P GO.
|
|
* The P2P GO initialization sequence is:
|
|
* disable -> device -> GO
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_init_p2p_go(struct mwifiex_private *priv)
|
|
{
|
|
u16 mode;
|
|
|
|
if (mwifiex_cfg80211_deinit_p2p(priv))
|
|
return -1;
|
|
|
|
mode = P2P_MODE_DEVICE;
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_P2P_MODE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mode, true))
|
|
return -1;
|
|
|
|
mode = P2P_MODE_GO;
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_P2P_MODE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mode, true))
|
|
return -1;
|
|
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP)
|
|
mwifiex_set_bss_role(priv, MWIFIEX_BSS_ROLE_UAP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to change interface type.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
enum nl80211_iftype type, u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
int ret;
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
switch (dev->ieee80211_ptr->iftype) {
|
|
case NL80211_IFTYPE_ADHOC:
|
|
switch (type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
break;
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
wiphy_warn(wiphy, "%s: kept type as IBSS\n", dev->name);
|
|
case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */
|
|
return 0;
|
|
case NL80211_IFTYPE_AP:
|
|
default:
|
|
wiphy_err(wiphy, "%s: changing to %d not supported\n",
|
|
dev->name, type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_STATION:
|
|
switch (type) {
|
|
case NL80211_IFTYPE_ADHOC:
|
|
break;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
if (mwifiex_cfg80211_init_p2p_client(priv))
|
|
return -EFAULT;
|
|
dev->ieee80211_ptr->iftype = type;
|
|
return 0;
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
if (mwifiex_cfg80211_init_p2p_go(priv))
|
|
return -EFAULT;
|
|
dev->ieee80211_ptr->iftype = type;
|
|
return 0;
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
wiphy_warn(wiphy, "%s: kept type as STA\n", dev->name);
|
|
case NL80211_IFTYPE_STATION: /* This shouldn't happen */
|
|
return 0;
|
|
case NL80211_IFTYPE_AP:
|
|
default:
|
|
wiphy_err(wiphy, "%s: changing to %d not supported\n",
|
|
dev->name, type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
switch (type) {
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
wiphy_warn(wiphy, "%s: kept type as AP\n", dev->name);
|
|
case NL80211_IFTYPE_AP: /* This shouldn't happen */
|
|
return 0;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
case NL80211_IFTYPE_STATION:
|
|
default:
|
|
wiphy_err(wiphy, "%s: changing to %d not supported\n",
|
|
dev->name, type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
switch (type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
if (mwifiex_cfg80211_deinit_p2p(priv))
|
|
return -EFAULT;
|
|
dev->ieee80211_ptr->iftype = type;
|
|
return 0;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
default:
|
|
wiphy_err(wiphy, "%s: unknown iftype: %d\n",
|
|
dev->name, dev->ieee80211_ptr->iftype);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
dev->ieee80211_ptr->iftype = type;
|
|
priv->bss_mode = type;
|
|
mwifiex_deauthenticate(priv, NULL);
|
|
|
|
priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
|
|
ret = mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE,
|
|
HostCmd_ACT_GEN_SET, 0, NULL, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
mwifiex_parse_htinfo(struct mwifiex_private *priv, u8 tx_htinfo,
|
|
struct rate_info *rate)
|
|
{
|
|
struct mwifiex_adapter *adapter = priv->adapter;
|
|
|
|
if (adapter->is_hw_11ac_capable) {
|
|
/* bit[1-0]: 00=LG 01=HT 10=VHT */
|
|
if (tx_htinfo & BIT(0)) {
|
|
/* HT */
|
|
rate->mcs = priv->tx_rate;
|
|
rate->flags |= RATE_INFO_FLAGS_MCS;
|
|
}
|
|
if (tx_htinfo & BIT(1)) {
|
|
/* VHT */
|
|
rate->mcs = priv->tx_rate & 0x0F;
|
|
rate->flags |= RATE_INFO_FLAGS_VHT_MCS;
|
|
}
|
|
|
|
if (tx_htinfo & (BIT(1) | BIT(0))) {
|
|
/* HT or VHT */
|
|
switch (tx_htinfo & (BIT(3) | BIT(2))) {
|
|
case 0:
|
|
/* This will be 20MHz */
|
|
break;
|
|
case (BIT(2)):
|
|
rate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
|
|
break;
|
|
case (BIT(3)):
|
|
rate->flags |= RATE_INFO_FLAGS_80_MHZ_WIDTH;
|
|
break;
|
|
case (BIT(3) | BIT(2)):
|
|
rate->flags |= RATE_INFO_FLAGS_160_MHZ_WIDTH;
|
|
break;
|
|
}
|
|
|
|
if (tx_htinfo & BIT(4))
|
|
rate->flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
|
|
if ((priv->tx_rate >> 4) == 1)
|
|
rate->nss = 2;
|
|
else
|
|
rate->nss = 1;
|
|
}
|
|
} else {
|
|
/*
|
|
* Bit 0 in tx_htinfo indicates that current Tx rate
|
|
* is 11n rate. Valid MCS index values for us are 0 to 15.
|
|
*/
|
|
if ((tx_htinfo & BIT(0)) && (priv->tx_rate < 16)) {
|
|
rate->mcs = priv->tx_rate;
|
|
rate->flags |= RATE_INFO_FLAGS_MCS;
|
|
if (tx_htinfo & BIT(1))
|
|
rate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
|
|
if (tx_htinfo & BIT(2))
|
|
rate->flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function dumps the station information on a buffer.
|
|
*
|
|
* The following information are shown -
|
|
* - Total bytes transmitted
|
|
* - Total bytes received
|
|
* - Total packets transmitted
|
|
* - Total packets received
|
|
* - Signal quality level
|
|
* - Transmission rate
|
|
*/
|
|
static int
|
|
mwifiex_dump_station_info(struct mwifiex_private *priv,
|
|
struct station_info *sinfo)
|
|
{
|
|
u32 rate;
|
|
|
|
sinfo->filled = STATION_INFO_RX_BYTES | STATION_INFO_TX_BYTES |
|
|
STATION_INFO_RX_PACKETS | STATION_INFO_TX_PACKETS |
|
|
STATION_INFO_TX_BITRATE |
|
|
STATION_INFO_SIGNAL | STATION_INFO_SIGNAL_AVG;
|
|
|
|
/* Get signal information from the firmware */
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_RSSI_INFO,
|
|
HostCmd_ACT_GEN_GET, 0, NULL, true)) {
|
|
dev_err(priv->adapter->dev, "failed to get signal information\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (mwifiex_drv_get_data_rate(priv, &rate)) {
|
|
dev_err(priv->adapter->dev, "getting data rate\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Get DTIM period information from firmware */
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_GET, DTIM_PERIOD_I,
|
|
&priv->dtim_period, true);
|
|
|
|
mwifiex_parse_htinfo(priv, priv->tx_htinfo, &sinfo->txrate);
|
|
|
|
sinfo->signal_avg = priv->bcn_rssi_avg;
|
|
sinfo->rx_bytes = priv->stats.rx_bytes;
|
|
sinfo->tx_bytes = priv->stats.tx_bytes;
|
|
sinfo->rx_packets = priv->stats.rx_packets;
|
|
sinfo->tx_packets = priv->stats.tx_packets;
|
|
sinfo->signal = priv->bcn_rssi_avg;
|
|
/* bit rate is in 500 kb/s units. Convert it to 100kb/s units */
|
|
sinfo->txrate.legacy = rate * 5;
|
|
|
|
if (priv->bss_mode == NL80211_IFTYPE_STATION) {
|
|
sinfo->filled |= STATION_INFO_BSS_PARAM;
|
|
sinfo->bss_param.flags = 0;
|
|
if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap &
|
|
WLAN_CAPABILITY_SHORT_PREAMBLE)
|
|
sinfo->bss_param.flags |=
|
|
BSS_PARAM_FLAGS_SHORT_PREAMBLE;
|
|
if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap &
|
|
WLAN_CAPABILITY_SHORT_SLOT_TIME)
|
|
sinfo->bss_param.flags |=
|
|
BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
|
|
sinfo->bss_param.dtim_period = priv->dtim_period;
|
|
sinfo->bss_param.beacon_interval =
|
|
priv->curr_bss_params.bss_descriptor.beacon_period;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to get station information.
|
|
*
|
|
* This function only works in connected mode, and dumps the
|
|
* requested station information, if available.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac, struct station_info *sinfo)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (!priv->media_connected)
|
|
return -ENOENT;
|
|
if (memcmp(mac, priv->cfg_bssid, ETH_ALEN))
|
|
return -ENOENT;
|
|
|
|
return mwifiex_dump_station_info(priv, sinfo);
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to dump station information.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *dev,
|
|
int idx, u8 *mac, struct station_info *sinfo)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (!priv->media_connected || idx)
|
|
return -ENOENT;
|
|
|
|
memcpy(mac, priv->cfg_bssid, ETH_ALEN);
|
|
|
|
return mwifiex_dump_station_info(priv, sinfo);
|
|
}
|
|
|
|
/* Supported rates to be advertised to the cfg80211 */
|
|
static struct ieee80211_rate mwifiex_rates[] = {
|
|
{.bitrate = 10, .hw_value = 2, },
|
|
{.bitrate = 20, .hw_value = 4, },
|
|
{.bitrate = 55, .hw_value = 11, },
|
|
{.bitrate = 110, .hw_value = 22, },
|
|
{.bitrate = 60, .hw_value = 12, },
|
|
{.bitrate = 90, .hw_value = 18, },
|
|
{.bitrate = 120, .hw_value = 24, },
|
|
{.bitrate = 180, .hw_value = 36, },
|
|
{.bitrate = 240, .hw_value = 48, },
|
|
{.bitrate = 360, .hw_value = 72, },
|
|
{.bitrate = 480, .hw_value = 96, },
|
|
{.bitrate = 540, .hw_value = 108, },
|
|
};
|
|
|
|
/* Channel definitions to be advertised to cfg80211 */
|
|
static struct ieee80211_channel mwifiex_channels_2ghz[] = {
|
|
{.center_freq = 2412, .hw_value = 1, },
|
|
{.center_freq = 2417, .hw_value = 2, },
|
|
{.center_freq = 2422, .hw_value = 3, },
|
|
{.center_freq = 2427, .hw_value = 4, },
|
|
{.center_freq = 2432, .hw_value = 5, },
|
|
{.center_freq = 2437, .hw_value = 6, },
|
|
{.center_freq = 2442, .hw_value = 7, },
|
|
{.center_freq = 2447, .hw_value = 8, },
|
|
{.center_freq = 2452, .hw_value = 9, },
|
|
{.center_freq = 2457, .hw_value = 10, },
|
|
{.center_freq = 2462, .hw_value = 11, },
|
|
{.center_freq = 2467, .hw_value = 12, },
|
|
{.center_freq = 2472, .hw_value = 13, },
|
|
{.center_freq = 2484, .hw_value = 14, },
|
|
};
|
|
|
|
static struct ieee80211_supported_band mwifiex_band_2ghz = {
|
|
.channels = mwifiex_channels_2ghz,
|
|
.n_channels = ARRAY_SIZE(mwifiex_channels_2ghz),
|
|
.bitrates = mwifiex_rates,
|
|
.n_bitrates = ARRAY_SIZE(mwifiex_rates),
|
|
};
|
|
|
|
static struct ieee80211_channel mwifiex_channels_5ghz[] = {
|
|
{.center_freq = 5040, .hw_value = 8, },
|
|
{.center_freq = 5060, .hw_value = 12, },
|
|
{.center_freq = 5080, .hw_value = 16, },
|
|
{.center_freq = 5170, .hw_value = 34, },
|
|
{.center_freq = 5190, .hw_value = 38, },
|
|
{.center_freq = 5210, .hw_value = 42, },
|
|
{.center_freq = 5230, .hw_value = 46, },
|
|
{.center_freq = 5180, .hw_value = 36, },
|
|
{.center_freq = 5200, .hw_value = 40, },
|
|
{.center_freq = 5220, .hw_value = 44, },
|
|
{.center_freq = 5240, .hw_value = 48, },
|
|
{.center_freq = 5260, .hw_value = 52, },
|
|
{.center_freq = 5280, .hw_value = 56, },
|
|
{.center_freq = 5300, .hw_value = 60, },
|
|
{.center_freq = 5320, .hw_value = 64, },
|
|
{.center_freq = 5500, .hw_value = 100, },
|
|
{.center_freq = 5520, .hw_value = 104, },
|
|
{.center_freq = 5540, .hw_value = 108, },
|
|
{.center_freq = 5560, .hw_value = 112, },
|
|
{.center_freq = 5580, .hw_value = 116, },
|
|
{.center_freq = 5600, .hw_value = 120, },
|
|
{.center_freq = 5620, .hw_value = 124, },
|
|
{.center_freq = 5640, .hw_value = 128, },
|
|
{.center_freq = 5660, .hw_value = 132, },
|
|
{.center_freq = 5680, .hw_value = 136, },
|
|
{.center_freq = 5700, .hw_value = 140, },
|
|
{.center_freq = 5745, .hw_value = 149, },
|
|
{.center_freq = 5765, .hw_value = 153, },
|
|
{.center_freq = 5785, .hw_value = 157, },
|
|
{.center_freq = 5805, .hw_value = 161, },
|
|
{.center_freq = 5825, .hw_value = 165, },
|
|
};
|
|
|
|
static struct ieee80211_supported_band mwifiex_band_5ghz = {
|
|
.channels = mwifiex_channels_5ghz,
|
|
.n_channels = ARRAY_SIZE(mwifiex_channels_5ghz),
|
|
.bitrates = mwifiex_rates + 4,
|
|
.n_bitrates = ARRAY_SIZE(mwifiex_rates) - 4,
|
|
};
|
|
|
|
|
|
/* Supported crypto cipher suits to be advertised to cfg80211 */
|
|
static const u32 mwifiex_cipher_suites[] = {
|
|
WLAN_CIPHER_SUITE_WEP40,
|
|
WLAN_CIPHER_SUITE_WEP104,
|
|
WLAN_CIPHER_SUITE_TKIP,
|
|
WLAN_CIPHER_SUITE_CCMP,
|
|
WLAN_CIPHER_SUITE_AES_CMAC,
|
|
};
|
|
|
|
/* Supported mgmt frame types to be advertised to cfg80211 */
|
|
static const struct ieee80211_txrx_stypes
|
|
mwifiex_mgmt_stypes[NUM_NL80211_IFTYPES] = {
|
|
[NL80211_IFTYPE_STATION] = {
|
|
.tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
|
|
.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
|
|
},
|
|
[NL80211_IFTYPE_AP] = {
|
|
.tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
|
|
.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
|
|
},
|
|
[NL80211_IFTYPE_P2P_CLIENT] = {
|
|
.tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
|
|
.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
|
|
},
|
|
[NL80211_IFTYPE_P2P_GO] = {
|
|
.tx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_RESP >> 4),
|
|
.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
|
|
BIT(IEEE80211_STYPE_PROBE_REQ >> 4),
|
|
},
|
|
};
|
|
|
|
/*
|
|
* CFG802.11 operation handler for setting bit rates.
|
|
*
|
|
* Function configures data rates to firmware using bitrate mask
|
|
* provided by cfg80211.
|
|
*/
|
|
static int mwifiex_cfg80211_set_bitrate_mask(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
const u8 *peer,
|
|
const struct cfg80211_bitrate_mask *mask)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
u16 bitmap_rates[MAX_BITMAP_RATES_SIZE];
|
|
enum ieee80211_band band;
|
|
struct mwifiex_adapter *adapter = priv->adapter;
|
|
|
|
if (!priv->media_connected) {
|
|
dev_err(adapter->dev,
|
|
"Can not set Tx data rate in disconnected state\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
band = mwifiex_band_to_radio_type(priv->curr_bss_params.band);
|
|
|
|
memset(bitmap_rates, 0, sizeof(bitmap_rates));
|
|
|
|
/* Fill HR/DSSS rates. */
|
|
if (band == IEEE80211_BAND_2GHZ)
|
|
bitmap_rates[0] = mask->control[band].legacy & 0x000f;
|
|
|
|
/* Fill OFDM rates */
|
|
if (band == IEEE80211_BAND_2GHZ)
|
|
bitmap_rates[1] = (mask->control[band].legacy & 0x0ff0) >> 4;
|
|
else
|
|
bitmap_rates[1] = mask->control[band].legacy;
|
|
|
|
/* Fill HT MCS rates */
|
|
bitmap_rates[2] = mask->control[band].ht_mcs[0];
|
|
if (adapter->hw_dev_mcs_support == HT_STREAM_2X2)
|
|
bitmap_rates[2] |= mask->control[band].ht_mcs[1] << 8;
|
|
|
|
/* Fill VHT MCS rates */
|
|
if (adapter->fw_api_ver == MWIFIEX_FW_V15) {
|
|
bitmap_rates[10] = mask->control[band].vht_mcs[0];
|
|
if (adapter->hw_dev_mcs_support == HT_STREAM_2X2)
|
|
bitmap_rates[11] = mask->control[band].vht_mcs[1];
|
|
}
|
|
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_TX_RATE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, bitmap_rates, true);
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler for connection quality monitoring.
|
|
*
|
|
* This function subscribes/unsubscribes HIGH_RSSI and LOW_RSSI
|
|
* events to FW.
|
|
*/
|
|
static int mwifiex_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
s32 rssi_thold, u32 rssi_hyst)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
struct mwifiex_ds_misc_subsc_evt subsc_evt;
|
|
|
|
priv->cqm_rssi_thold = rssi_thold;
|
|
priv->cqm_rssi_hyst = rssi_hyst;
|
|
|
|
memset(&subsc_evt, 0x00, sizeof(struct mwifiex_ds_misc_subsc_evt));
|
|
subsc_evt.events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH;
|
|
|
|
/* Subscribe/unsubscribe low and high rssi events */
|
|
if (rssi_thold && rssi_hyst) {
|
|
subsc_evt.action = HostCmd_ACT_BITWISE_SET;
|
|
subsc_evt.bcn_l_rssi_cfg.abs_value = abs(rssi_thold);
|
|
subsc_evt.bcn_h_rssi_cfg.abs_value = abs(rssi_thold);
|
|
subsc_evt.bcn_l_rssi_cfg.evt_freq = 1;
|
|
subsc_evt.bcn_h_rssi_cfg.evt_freq = 1;
|
|
return mwifiex_send_cmd(priv,
|
|
HostCmd_CMD_802_11_SUBSCRIBE_EVENT,
|
|
0, 0, &subsc_evt, true);
|
|
} else {
|
|
subsc_evt.action = HostCmd_ACT_BITWISE_CLR;
|
|
return mwifiex_send_cmd(priv,
|
|
HostCmd_CMD_802_11_SUBSCRIBE_EVENT,
|
|
0, 0, &subsc_evt, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* cfg80211 operation handler for change_beacon.
|
|
* Function retrieves and sets modified management IEs to FW.
|
|
*/
|
|
static int mwifiex_cfg80211_change_beacon(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_beacon_data *data)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP) {
|
|
wiphy_err(wiphy, "%s: bss_type mismatched\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!priv->bss_started) {
|
|
wiphy_err(wiphy, "%s: bss not started\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mwifiex_set_mgmt_ies(priv, data)) {
|
|
wiphy_err(wiphy, "%s: setting mgmt ies failed\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* cfg80211 operation handler for del_station.
|
|
* Function deauthenticates station which value is provided in mac parameter.
|
|
* If mac is NULL/broadcast, all stations in associated station list are
|
|
* deauthenticated. If bss is not started or there are no stations in
|
|
* associated stations list, no action is taken.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
struct mwifiex_sta_node *sta_node;
|
|
unsigned long flags;
|
|
|
|
if (list_empty(&priv->sta_list) || !priv->bss_started)
|
|
return 0;
|
|
|
|
if (!mac || is_broadcast_ether_addr(mac)) {
|
|
wiphy_dbg(wiphy, "%s: NULL/broadcast mac address\n", __func__);
|
|
list_for_each_entry(sta_node, &priv->sta_list, list) {
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_STA_DEAUTH,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
sta_node->mac_addr, true))
|
|
return -1;
|
|
mwifiex_uap_del_sta_data(priv, sta_node);
|
|
}
|
|
} else {
|
|
wiphy_dbg(wiphy, "%s: mac address %pM\n", __func__, mac);
|
|
spin_lock_irqsave(&priv->sta_list_spinlock, flags);
|
|
sta_node = mwifiex_get_sta_entry(priv, mac);
|
|
spin_unlock_irqrestore(&priv->sta_list_spinlock, flags);
|
|
if (sta_node) {
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_STA_DEAUTH,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
sta_node->mac_addr, true))
|
|
return -1;
|
|
mwifiex_uap_del_sta_data(priv, sta_node);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mwifiex_cfg80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv = mwifiex_get_priv(adapter,
|
|
MWIFIEX_BSS_ROLE_ANY);
|
|
struct mwifiex_ds_ant_cfg ant_cfg;
|
|
|
|
if (!tx_ant || !rx_ant)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (adapter->hw_dev_mcs_support != HT_STREAM_2X2) {
|
|
/* Not a MIMO chip. User should provide specific antenna number
|
|
* for Tx/Rx path or enable all antennas for diversity
|
|
*/
|
|
if (tx_ant != rx_ant)
|
|
return -EOPNOTSUPP;
|
|
|
|
if ((tx_ant & (tx_ant - 1)) &&
|
|
(tx_ant != BIT(adapter->number_of_antenna) - 1))
|
|
return -EOPNOTSUPP;
|
|
|
|
if ((tx_ant == BIT(adapter->number_of_antenna) - 1) &&
|
|
(priv->adapter->number_of_antenna > 1)) {
|
|
tx_ant = RF_ANTENNA_AUTO;
|
|
rx_ant = RF_ANTENNA_AUTO;
|
|
}
|
|
} else {
|
|
struct ieee80211_sta_ht_cap *ht_info;
|
|
int rx_mcs_supp;
|
|
enum ieee80211_band band;
|
|
|
|
if ((tx_ant == 0x1 && rx_ant == 0x1)) {
|
|
adapter->user_dev_mcs_support = HT_STREAM_1X1;
|
|
if (adapter->is_hw_11ac_capable)
|
|
adapter->usr_dot_11ac_mcs_support =
|
|
MWIFIEX_11AC_MCS_MAP_1X1;
|
|
} else {
|
|
adapter->user_dev_mcs_support = HT_STREAM_2X2;
|
|
if (adapter->is_hw_11ac_capable)
|
|
adapter->usr_dot_11ac_mcs_support =
|
|
MWIFIEX_11AC_MCS_MAP_2X2;
|
|
}
|
|
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
|
|
if (!adapter->wiphy->bands[band])
|
|
continue;
|
|
|
|
ht_info = &adapter->wiphy->bands[band]->ht_cap;
|
|
rx_mcs_supp =
|
|
GET_RXMCSSUPP(adapter->user_dev_mcs_support);
|
|
memset(&ht_info->mcs, 0, adapter->number_of_antenna);
|
|
memset(&ht_info->mcs, 0xff, rx_mcs_supp);
|
|
}
|
|
}
|
|
|
|
ant_cfg.tx_ant = tx_ant;
|
|
ant_cfg.rx_ant = rx_ant;
|
|
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_RF_ANTENNA,
|
|
HostCmd_ACT_GEN_SET, 0, &ant_cfg, true);
|
|
}
|
|
|
|
/* cfg80211 operation handler for stop ap.
|
|
* Function stops BSS running at uAP interface.
|
|
*/
|
|
static int mwifiex_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (mwifiex_del_mgmt_ies(priv))
|
|
wiphy_err(wiphy, "Failed to delete mgmt IEs!\n");
|
|
|
|
priv->ap_11n_enabled = 0;
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_BSS_STOP,
|
|
HostCmd_ACT_GEN_SET, 0, NULL, true)) {
|
|
wiphy_err(wiphy, "Failed to stop the BSS\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* cfg80211 operation handler for start_ap.
|
|
* Function sets beacon period, DTIM period, SSID and security into
|
|
* AP config structure.
|
|
* AP is configured with these settings and BSS is started.
|
|
*/
|
|
static int mwifiex_cfg80211_start_ap(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_ap_settings *params)
|
|
{
|
|
struct mwifiex_uap_bss_param *bss_cfg;
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
u8 config_bands = 0;
|
|
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP)
|
|
return -1;
|
|
if (mwifiex_set_mgmt_ies(priv, ¶ms->beacon))
|
|
return -1;
|
|
|
|
bss_cfg = kzalloc(sizeof(struct mwifiex_uap_bss_param), GFP_KERNEL);
|
|
if (!bss_cfg)
|
|
return -ENOMEM;
|
|
|
|
mwifiex_set_sys_config_invalid_data(bss_cfg);
|
|
|
|
if (params->beacon_interval)
|
|
bss_cfg->beacon_period = params->beacon_interval;
|
|
if (params->dtim_period)
|
|
bss_cfg->dtim_period = params->dtim_period;
|
|
|
|
if (params->ssid && params->ssid_len) {
|
|
memcpy(bss_cfg->ssid.ssid, params->ssid, params->ssid_len);
|
|
bss_cfg->ssid.ssid_len = params->ssid_len;
|
|
}
|
|
|
|
switch (params->hidden_ssid) {
|
|
case NL80211_HIDDEN_SSID_NOT_IN_USE:
|
|
bss_cfg->bcast_ssid_ctl = 1;
|
|
break;
|
|
case NL80211_HIDDEN_SSID_ZERO_LEN:
|
|
bss_cfg->bcast_ssid_ctl = 0;
|
|
break;
|
|
case NL80211_HIDDEN_SSID_ZERO_CONTENTS:
|
|
/* firmware doesn't support this type of hidden SSID */
|
|
default:
|
|
kfree(bss_cfg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bss_cfg->channel = ieee80211_frequency_to_channel(
|
|
params->chandef.chan->center_freq);
|
|
|
|
/* Set appropriate bands */
|
|
if (params->chandef.chan->band == IEEE80211_BAND_2GHZ) {
|
|
bss_cfg->band_cfg = BAND_CONFIG_BG;
|
|
config_bands = BAND_B | BAND_G;
|
|
|
|
if (params->chandef.width > NL80211_CHAN_WIDTH_20_NOHT)
|
|
config_bands |= BAND_GN;
|
|
} else {
|
|
bss_cfg->band_cfg = BAND_CONFIG_A;
|
|
config_bands = BAND_A;
|
|
|
|
if (params->chandef.width > NL80211_CHAN_WIDTH_20_NOHT)
|
|
config_bands |= BAND_AN;
|
|
|
|
if (params->chandef.width > NL80211_CHAN_WIDTH_40)
|
|
config_bands |= BAND_AAC;
|
|
}
|
|
|
|
if (!((config_bands | priv->adapter->fw_bands) &
|
|
~priv->adapter->fw_bands))
|
|
priv->adapter->config_bands = config_bands;
|
|
|
|
mwifiex_set_uap_rates(bss_cfg, params);
|
|
mwifiex_send_domain_info_cmd_fw(wiphy);
|
|
|
|
if (mwifiex_set_secure_params(priv, bss_cfg, params)) {
|
|
kfree(bss_cfg);
|
|
wiphy_err(wiphy, "Failed to parse secuirty parameters!\n");
|
|
return -1;
|
|
}
|
|
|
|
mwifiex_set_ht_params(priv, bss_cfg, params);
|
|
|
|
if (priv->adapter->is_hw_11ac_capable) {
|
|
mwifiex_set_vht_params(priv, bss_cfg, params);
|
|
mwifiex_set_vht_width(priv, params->chandef.width,
|
|
priv->ap_11ac_enabled);
|
|
}
|
|
|
|
if (priv->ap_11ac_enabled)
|
|
mwifiex_set_11ac_ba_params(priv);
|
|
else
|
|
mwifiex_set_ba_params(priv);
|
|
|
|
mwifiex_set_wmm_params(priv, bss_cfg, params);
|
|
|
|
if (params->inactivity_timeout > 0) {
|
|
/* sta_ao_timer/ps_sta_ao_timer is in unit of 100ms */
|
|
bss_cfg->sta_ao_timer = 10 * params->inactivity_timeout;
|
|
bss_cfg->ps_sta_ao_timer = 10 * params->inactivity_timeout;
|
|
}
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_BSS_STOP,
|
|
HostCmd_ACT_GEN_SET, 0, NULL, true)) {
|
|
wiphy_err(wiphy, "Failed to stop the BSS\n");
|
|
kfree(bss_cfg);
|
|
return -1;
|
|
}
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_SYS_CONFIG,
|
|
HostCmd_ACT_GEN_SET,
|
|
UAP_BSS_PARAMS_I, bss_cfg, false)) {
|
|
wiphy_err(wiphy, "Failed to set the SSID\n");
|
|
kfree(bss_cfg);
|
|
return -1;
|
|
}
|
|
|
|
kfree(bss_cfg);
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_UAP_BSS_START,
|
|
HostCmd_ACT_GEN_SET, 0, NULL, false)) {
|
|
wiphy_err(wiphy, "Failed to start the BSS\n");
|
|
return -1;
|
|
}
|
|
|
|
if (priv->sec_info.wep_enabled)
|
|
priv->curr_pkt_filter |= HostCmd_ACT_MAC_WEP_ENABLE;
|
|
else
|
|
priv->curr_pkt_filter &= ~HostCmd_ACT_MAC_WEP_ENABLE;
|
|
|
|
if (mwifiex_send_cmd(priv, HostCmd_CMD_MAC_CONTROL,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
&priv->curr_pkt_filter, true))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler for disconnection request.
|
|
*
|
|
* This function does not work when there is already a disconnection
|
|
* procedure going on.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev,
|
|
u16 reason_code)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (mwifiex_deauthenticate(priv, NULL))
|
|
return -EFAULT;
|
|
|
|
wiphy_dbg(wiphy, "info: successfully disconnected from %pM:"
|
|
" reason code %d\n", priv->cfg_bssid, reason_code);
|
|
|
|
memset(priv->cfg_bssid, 0, ETH_ALEN);
|
|
priv->hs2_enabled = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function informs the CFG802.11 subsystem of a new IBSS.
|
|
*
|
|
* The following information are sent to the CFG802.11 subsystem
|
|
* to register the new IBSS. If we do not register the new IBSS,
|
|
* a kernel panic will result.
|
|
* - SSID
|
|
* - SSID length
|
|
* - BSSID
|
|
* - Channel
|
|
*/
|
|
static int mwifiex_cfg80211_inform_ibss_bss(struct mwifiex_private *priv)
|
|
{
|
|
struct ieee80211_channel *chan;
|
|
struct mwifiex_bss_info bss_info;
|
|
struct cfg80211_bss *bss;
|
|
int ie_len;
|
|
u8 ie_buf[IEEE80211_MAX_SSID_LEN + sizeof(struct ieee_types_header)];
|
|
enum ieee80211_band band;
|
|
|
|
if (mwifiex_get_bss_info(priv, &bss_info))
|
|
return -1;
|
|
|
|
ie_buf[0] = WLAN_EID_SSID;
|
|
ie_buf[1] = bss_info.ssid.ssid_len;
|
|
|
|
memcpy(&ie_buf[sizeof(struct ieee_types_header)],
|
|
&bss_info.ssid.ssid, bss_info.ssid.ssid_len);
|
|
ie_len = ie_buf[1] + sizeof(struct ieee_types_header);
|
|
|
|
band = mwifiex_band_to_radio_type(priv->curr_bss_params.band);
|
|
chan = __ieee80211_get_channel(priv->wdev->wiphy,
|
|
ieee80211_channel_to_frequency(bss_info.bss_chan,
|
|
band));
|
|
|
|
bss = cfg80211_inform_bss(priv->wdev->wiphy, chan,
|
|
bss_info.bssid, 0, WLAN_CAPABILITY_IBSS,
|
|
0, ie_buf, ie_len, 0, GFP_KERNEL);
|
|
cfg80211_put_bss(priv->wdev->wiphy, bss);
|
|
memcpy(priv->cfg_bssid, bss_info.bssid, ETH_ALEN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function connects with a BSS.
|
|
*
|
|
* This function handles both Infra and Ad-Hoc modes. It also performs
|
|
* validity checking on the provided parameters, disconnects from the
|
|
* current BSS (if any), sets up the association/scan parameters,
|
|
* including security settings, and performs specific SSID scan before
|
|
* trying to connect.
|
|
*
|
|
* For Infra mode, the function returns failure if the specified SSID
|
|
* is not found in scan table. However, for Ad-Hoc mode, it can create
|
|
* the IBSS if it does not exist. On successful completion in either case,
|
|
* the function notifies the CFG802.11 subsystem of the new BSS connection.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_assoc(struct mwifiex_private *priv, size_t ssid_len,
|
|
const u8 *ssid, const u8 *bssid, int mode,
|
|
struct ieee80211_channel *channel,
|
|
struct cfg80211_connect_params *sme, bool privacy)
|
|
{
|
|
struct cfg80211_ssid req_ssid;
|
|
int ret, auth_type = 0;
|
|
struct cfg80211_bss *bss = NULL;
|
|
u8 is_scanning_required = 0;
|
|
|
|
memset(&req_ssid, 0, sizeof(struct cfg80211_ssid));
|
|
|
|
req_ssid.ssid_len = ssid_len;
|
|
if (ssid_len > IEEE80211_MAX_SSID_LEN) {
|
|
dev_err(priv->adapter->dev, "invalid SSID - aborting\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(req_ssid.ssid, ssid, ssid_len);
|
|
if (!req_ssid.ssid_len || req_ssid.ssid[0] < 0x20) {
|
|
dev_err(priv->adapter->dev, "invalid SSID - aborting\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* As this is new association, clear locally stored
|
|
* keys and security related flags */
|
|
priv->sec_info.wpa_enabled = false;
|
|
priv->sec_info.wpa2_enabled = false;
|
|
priv->wep_key_curr_index = 0;
|
|
priv->sec_info.encryption_mode = 0;
|
|
priv->sec_info.is_authtype_auto = 0;
|
|
ret = mwifiex_set_encode(priv, NULL, NULL, 0, 0, NULL, 1);
|
|
|
|
if (mode == NL80211_IFTYPE_ADHOC) {
|
|
/* "privacy" is set only for ad-hoc mode */
|
|
if (privacy) {
|
|
/*
|
|
* Keep WLAN_CIPHER_SUITE_WEP104 for now so that
|
|
* the firmware can find a matching network from the
|
|
* scan. The cfg80211 does not give us the encryption
|
|
* mode at this stage so just setting it to WEP here.
|
|
*/
|
|
priv->sec_info.encryption_mode =
|
|
WLAN_CIPHER_SUITE_WEP104;
|
|
priv->sec_info.authentication_mode =
|
|
NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* Now handle infra mode. "sme" is valid for infra mode only */
|
|
if (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
|
|
auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
priv->sec_info.is_authtype_auto = 1;
|
|
} else {
|
|
auth_type = sme->auth_type;
|
|
}
|
|
|
|
if (sme->crypto.n_ciphers_pairwise) {
|
|
priv->sec_info.encryption_mode =
|
|
sme->crypto.ciphers_pairwise[0];
|
|
priv->sec_info.authentication_mode = auth_type;
|
|
}
|
|
|
|
if (sme->crypto.cipher_group) {
|
|
priv->sec_info.encryption_mode = sme->crypto.cipher_group;
|
|
priv->sec_info.authentication_mode = auth_type;
|
|
}
|
|
if (sme->ie)
|
|
ret = mwifiex_set_gen_ie(priv, sme->ie, sme->ie_len);
|
|
|
|
if (sme->key) {
|
|
if (mwifiex_is_alg_wep(priv->sec_info.encryption_mode)) {
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: setting wep encryption"
|
|
" with key len %d\n", sme->key_len);
|
|
priv->wep_key_curr_index = sme->key_idx;
|
|
ret = mwifiex_set_encode(priv, NULL, sme->key,
|
|
sme->key_len, sme->key_idx,
|
|
NULL, 0);
|
|
}
|
|
}
|
|
done:
|
|
/*
|
|
* Scan entries are valid for some time (15 sec). So we can save one
|
|
* active scan time if we just try cfg80211_get_bss first. If it fails
|
|
* then request scan and cfg80211_get_bss() again for final output.
|
|
*/
|
|
while (1) {
|
|
if (is_scanning_required) {
|
|
/* Do specific SSID scanning */
|
|
if (mwifiex_request_scan(priv, &req_ssid)) {
|
|
dev_err(priv->adapter->dev, "scan error\n");
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
/* Find the BSS we want using available scan results */
|
|
if (mode == NL80211_IFTYPE_ADHOC)
|
|
bss = cfg80211_get_bss(priv->wdev->wiphy, channel,
|
|
bssid, ssid, ssid_len,
|
|
WLAN_CAPABILITY_IBSS,
|
|
WLAN_CAPABILITY_IBSS);
|
|
else
|
|
bss = cfg80211_get_bss(priv->wdev->wiphy, channel,
|
|
bssid, ssid, ssid_len,
|
|
WLAN_CAPABILITY_ESS,
|
|
WLAN_CAPABILITY_ESS);
|
|
|
|
if (!bss) {
|
|
if (is_scanning_required) {
|
|
dev_warn(priv->adapter->dev,
|
|
"assoc: requested bss not found in scan results\n");
|
|
break;
|
|
}
|
|
is_scanning_required = 1;
|
|
} else {
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: trying to associate to '%s' bssid %pM\n",
|
|
(char *) req_ssid.ssid, bss->bssid);
|
|
memcpy(&priv->cfg_bssid, bss->bssid, ETH_ALEN);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = mwifiex_bss_start(priv, bss, &req_ssid);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mode == NL80211_IFTYPE_ADHOC) {
|
|
/* Inform the BSS information to kernel, otherwise
|
|
* kernel will give a panic after successful assoc */
|
|
if (mwifiex_cfg80211_inform_ibss_bss(priv))
|
|
return -EFAULT;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler for association request.
|
|
*
|
|
* This function does not work when the current mode is set to Ad-Hoc, or
|
|
* when there is already an association procedure going on. The given BSS
|
|
* information is used to associate.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_connect_params *sme)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
int ret;
|
|
|
|
if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA) {
|
|
wiphy_err(wiphy,
|
|
"%s: reject infra assoc request in non-STA role\n",
|
|
dev->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv->wdev && priv->wdev->current_bss) {
|
|
wiphy_warn(wiphy, "%s: already connected\n", dev->name);
|
|
return -EALREADY;
|
|
}
|
|
|
|
wiphy_dbg(wiphy, "info: Trying to associate to %s and bssid %pM\n",
|
|
(char *) sme->ssid, sme->bssid);
|
|
|
|
ret = mwifiex_cfg80211_assoc(priv, sme->ssid_len, sme->ssid, sme->bssid,
|
|
priv->bss_mode, sme->channel, sme, 0);
|
|
if (!ret) {
|
|
cfg80211_connect_result(priv->netdev, priv->cfg_bssid, NULL, 0,
|
|
NULL, 0, WLAN_STATUS_SUCCESS,
|
|
GFP_KERNEL);
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: associated to bssid %pM successfully\n",
|
|
priv->cfg_bssid);
|
|
} else {
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: association to bssid %pM failed\n",
|
|
priv->cfg_bssid);
|
|
memset(priv->cfg_bssid, 0, ETH_ALEN);
|
|
|
|
if (ret > 0)
|
|
cfg80211_connect_result(priv->netdev, priv->cfg_bssid,
|
|
NULL, 0, NULL, 0, ret,
|
|
GFP_KERNEL);
|
|
else
|
|
cfg80211_connect_result(priv->netdev, priv->cfg_bssid,
|
|
NULL, 0, NULL, 0,
|
|
WLAN_STATUS_UNSPECIFIED_FAILURE,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function sets following parameters for ibss network.
|
|
* - channel
|
|
* - start band
|
|
* - 11n flag
|
|
* - secondary channel offset
|
|
*/
|
|
static int mwifiex_set_ibss_params(struct mwifiex_private *priv,
|
|
struct cfg80211_ibss_params *params)
|
|
{
|
|
struct wiphy *wiphy = priv->wdev->wiphy;
|
|
struct mwifiex_adapter *adapter = priv->adapter;
|
|
int index = 0, i;
|
|
u8 config_bands = 0;
|
|
|
|
if (params->chandef.chan->band == IEEE80211_BAND_2GHZ) {
|
|
if (!params->basic_rates) {
|
|
config_bands = BAND_B | BAND_G;
|
|
} else {
|
|
for (i = 0; i < mwifiex_band_2ghz.n_bitrates; i++) {
|
|
/*
|
|
* Rates below 6 Mbps in the table are CCK
|
|
* rates; 802.11b and from 6 they are OFDM;
|
|
* 802.11G
|
|
*/
|
|
if (mwifiex_rates[i].bitrate == 60) {
|
|
index = 1 << i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (params->basic_rates < index) {
|
|
config_bands = BAND_B;
|
|
} else {
|
|
config_bands = BAND_G;
|
|
if (params->basic_rates % index)
|
|
config_bands |= BAND_B;
|
|
}
|
|
}
|
|
|
|
if (cfg80211_get_chandef_type(¶ms->chandef) !=
|
|
NL80211_CHAN_NO_HT)
|
|
config_bands |= BAND_G | BAND_GN;
|
|
} else {
|
|
if (cfg80211_get_chandef_type(¶ms->chandef) ==
|
|
NL80211_CHAN_NO_HT)
|
|
config_bands = BAND_A;
|
|
else
|
|
config_bands = BAND_AN | BAND_A;
|
|
}
|
|
|
|
if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands)) {
|
|
adapter->config_bands = config_bands;
|
|
adapter->adhoc_start_band = config_bands;
|
|
|
|
if ((config_bands & BAND_GN) || (config_bands & BAND_AN))
|
|
adapter->adhoc_11n_enabled = true;
|
|
else
|
|
adapter->adhoc_11n_enabled = false;
|
|
}
|
|
|
|
adapter->sec_chan_offset =
|
|
mwifiex_chan_type_to_sec_chan_offset(
|
|
cfg80211_get_chandef_type(¶ms->chandef));
|
|
priv->adhoc_channel = ieee80211_frequency_to_channel(
|
|
params->chandef.chan->center_freq);
|
|
|
|
wiphy_dbg(wiphy, "info: set ibss band %d, chan %d, chan offset %d\n",
|
|
config_bands, priv->adhoc_channel, adapter->sec_chan_offset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to join an IBSS.
|
|
*
|
|
* This function does not work in any mode other than Ad-Hoc, or if
|
|
* a join operation is already in progress.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_ibss_params *params)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
int ret = 0;
|
|
|
|
if (priv->bss_mode != NL80211_IFTYPE_ADHOC) {
|
|
wiphy_err(wiphy, "request to join ibss received "
|
|
"when station is not in ibss mode\n");
|
|
goto done;
|
|
}
|
|
|
|
wiphy_dbg(wiphy, "info: trying to join to %s and bssid %pM\n",
|
|
(char *) params->ssid, params->bssid);
|
|
|
|
mwifiex_set_ibss_params(priv, params);
|
|
|
|
ret = mwifiex_cfg80211_assoc(priv, params->ssid_len, params->ssid,
|
|
params->bssid, priv->bss_mode,
|
|
params->chandef.chan, NULL,
|
|
params->privacy);
|
|
done:
|
|
if (!ret) {
|
|
cfg80211_ibss_joined(priv->netdev, priv->cfg_bssid,
|
|
params->chandef.chan, GFP_KERNEL);
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: joined/created adhoc network with bssid"
|
|
" %pM successfully\n", priv->cfg_bssid);
|
|
} else {
|
|
dev_dbg(priv->adapter->dev,
|
|
"info: failed creating/joining adhoc network\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler to leave an IBSS.
|
|
*
|
|
* This function does not work if a leave operation is
|
|
* already in progress.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
wiphy_dbg(wiphy, "info: disconnecting from essid %pM\n",
|
|
priv->cfg_bssid);
|
|
if (mwifiex_deauthenticate(priv, NULL))
|
|
return -EFAULT;
|
|
|
|
memset(priv->cfg_bssid, 0, ETH_ALEN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CFG802.11 operation handler for scan request.
|
|
*
|
|
* This function issues a scan request to the firmware based upon
|
|
* the user specified scan configuration. On successfull completion,
|
|
* it also informs the results.
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_scan(struct wiphy *wiphy,
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
struct net_device *dev = request->wdev->netdev;
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
int i, offset, ret;
|
|
struct ieee80211_channel *chan;
|
|
struct ieee_types_header *ie;
|
|
struct mwifiex_user_scan_cfg *user_scan_cfg;
|
|
|
|
wiphy_dbg(wiphy, "info: received scan request on %s\n", dev->name);
|
|
|
|
if ((request->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) &&
|
|
atomic_read(&priv->wmm.tx_pkts_queued) >=
|
|
MWIFIEX_MIN_TX_PENDING_TO_CANCEL_SCAN) {
|
|
dev_dbg(priv->adapter->dev, "scan rejected due to traffic\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Block scan request if scan operation or scan cleanup when interface
|
|
* is disabled is in process
|
|
*/
|
|
if (priv->scan_request || priv->scan_aborting) {
|
|
dev_err(priv->adapter->dev, "cmd: Scan already in process..\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
user_scan_cfg = kzalloc(sizeof(*user_scan_cfg), GFP_KERNEL);
|
|
if (!user_scan_cfg)
|
|
return -ENOMEM;
|
|
|
|
priv->scan_request = request;
|
|
|
|
user_scan_cfg->num_ssids = request->n_ssids;
|
|
user_scan_cfg->ssid_list = request->ssids;
|
|
|
|
if (request->ie && request->ie_len) {
|
|
offset = 0;
|
|
for (i = 0; i < MWIFIEX_MAX_VSIE_NUM; i++) {
|
|
if (priv->vs_ie[i].mask != MWIFIEX_VSIE_MASK_CLEAR)
|
|
continue;
|
|
priv->vs_ie[i].mask = MWIFIEX_VSIE_MASK_SCAN;
|
|
ie = (struct ieee_types_header *)(request->ie + offset);
|
|
memcpy(&priv->vs_ie[i].ie, ie, sizeof(*ie) + ie->len);
|
|
offset += sizeof(*ie) + ie->len;
|
|
|
|
if (offset >= request->ie_len)
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < min_t(u32, request->n_channels,
|
|
MWIFIEX_USER_SCAN_CHAN_MAX); i++) {
|
|
chan = request->channels[i];
|
|
user_scan_cfg->chan_list[i].chan_number = chan->hw_value;
|
|
user_scan_cfg->chan_list[i].radio_type = chan->band;
|
|
|
|
if (chan->flags & IEEE80211_CHAN_NO_IR)
|
|
user_scan_cfg->chan_list[i].scan_type =
|
|
MWIFIEX_SCAN_TYPE_PASSIVE;
|
|
else
|
|
user_scan_cfg->chan_list[i].scan_type =
|
|
MWIFIEX_SCAN_TYPE_ACTIVE;
|
|
|
|
user_scan_cfg->chan_list[i].scan_time = 0;
|
|
}
|
|
|
|
ret = mwifiex_scan_networks(priv, user_scan_cfg);
|
|
kfree(user_scan_cfg);
|
|
if (ret) {
|
|
dev_err(priv->adapter->dev, "scan failed: %d\n", ret);
|
|
priv->scan_aborting = false;
|
|
priv->scan_request = NULL;
|
|
return ret;
|
|
}
|
|
|
|
if (request->ie && request->ie_len) {
|
|
for (i = 0; i < MWIFIEX_MAX_VSIE_NUM; i++) {
|
|
if (priv->vs_ie[i].mask == MWIFIEX_VSIE_MASK_SCAN) {
|
|
priv->vs_ie[i].mask = MWIFIEX_VSIE_MASK_CLEAR;
|
|
memset(&priv->vs_ie[i].ie, 0,
|
|
MWIFIEX_MAX_VSIE_LEN);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mwifiex_setup_vht_caps(struct ieee80211_sta_vht_cap *vht_info,
|
|
struct mwifiex_private *priv)
|
|
{
|
|
struct mwifiex_adapter *adapter = priv->adapter;
|
|
|
|
vht_info->vht_supported = true;
|
|
|
|
vht_info->cap = adapter->hw_dot_11ac_dev_cap;
|
|
/* Update MCS support for VHT */
|
|
vht_info->vht_mcs.rx_mcs_map = cpu_to_le16(
|
|
adapter->hw_dot_11ac_mcs_support & 0xFFFF);
|
|
vht_info->vht_mcs.rx_highest = 0;
|
|
vht_info->vht_mcs.tx_mcs_map = cpu_to_le16(
|
|
adapter->hw_dot_11ac_mcs_support >> 16);
|
|
vht_info->vht_mcs.tx_highest = 0;
|
|
}
|
|
|
|
/*
|
|
* This function sets up the CFG802.11 specific HT capability fields
|
|
* with default values.
|
|
*
|
|
* The following default values are set -
|
|
* - HT Supported = True
|
|
* - Maximum AMPDU length factor = IEEE80211_HT_MAX_AMPDU_64K
|
|
* - Minimum AMPDU spacing = IEEE80211_HT_MPDU_DENSITY_NONE
|
|
* - HT Capabilities supported by firmware
|
|
* - MCS information, Rx mask = 0xff
|
|
* - MCD information, Tx parameters = IEEE80211_HT_MCS_TX_DEFINED (0x01)
|
|
*/
|
|
static void
|
|
mwifiex_setup_ht_caps(struct ieee80211_sta_ht_cap *ht_info,
|
|
struct mwifiex_private *priv)
|
|
{
|
|
int rx_mcs_supp;
|
|
struct ieee80211_mcs_info mcs_set;
|
|
u8 *mcs = (u8 *)&mcs_set;
|
|
struct mwifiex_adapter *adapter = priv->adapter;
|
|
|
|
ht_info->ht_supported = true;
|
|
ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
|
|
ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE;
|
|
|
|
memset(&ht_info->mcs, 0, sizeof(ht_info->mcs));
|
|
|
|
/* Fill HT capability information */
|
|
if (ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
|
if (ISSUPP_SHORTGI20(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_SGI_20;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_SGI_20;
|
|
|
|
if (ISSUPP_SHORTGI40(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_SGI_40;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_SGI_40;
|
|
|
|
if (adapter->user_dev_mcs_support == HT_STREAM_2X2)
|
|
ht_info->cap |= 3 << IEEE80211_HT_CAP_RX_STBC_SHIFT;
|
|
else
|
|
ht_info->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT;
|
|
|
|
if (ISSUPP_TXSTBC(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_TX_STBC;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_TX_STBC;
|
|
|
|
if (ISSUPP_GREENFIELD(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_GRN_FLD;
|
|
|
|
if (ISENABLED_40MHZ_INTOLERANT(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_40MHZ_INTOLERANT;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT;
|
|
|
|
if (ISSUPP_RXLDPC(adapter->hw_dot_11n_dev_cap))
|
|
ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING;
|
|
else
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_LDPC_CODING;
|
|
|
|
ht_info->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU;
|
|
ht_info->cap |= IEEE80211_HT_CAP_SM_PS;
|
|
|
|
rx_mcs_supp = GET_RXMCSSUPP(adapter->user_dev_mcs_support);
|
|
/* Set MCS for 1x1/2x2 */
|
|
memset(mcs, 0xff, rx_mcs_supp);
|
|
/* Clear all the other values */
|
|
memset(&mcs[rx_mcs_supp], 0,
|
|
sizeof(struct ieee80211_mcs_info) - rx_mcs_supp);
|
|
if (priv->bss_mode == NL80211_IFTYPE_STATION ||
|
|
ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap))
|
|
/* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */
|
|
SETHT_MCS32(mcs_set.rx_mask);
|
|
|
|
memcpy((u8 *) &ht_info->mcs, mcs, sizeof(struct ieee80211_mcs_info));
|
|
|
|
ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
|
|
}
|
|
|
|
/*
|
|
* create a new virtual interface with the given name
|
|
*/
|
|
struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy,
|
|
const char *name,
|
|
enum nl80211_iftype type,
|
|
u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_private *priv;
|
|
struct net_device *dev;
|
|
void *mdev_priv;
|
|
struct wireless_dev *wdev;
|
|
|
|
if (!adapter)
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
switch (type) {
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_ADHOC:
|
|
priv = adapter->priv[MWIFIEX_BSS_TYPE_STA];
|
|
if (priv->bss_mode) {
|
|
wiphy_err(wiphy,
|
|
"cannot create multiple sta/adhoc ifaces\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
|
|
if (!wdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
wdev->wiphy = wiphy;
|
|
priv->wdev = wdev;
|
|
wdev->iftype = NL80211_IFTYPE_STATION;
|
|
|
|
if (type == NL80211_IFTYPE_UNSPECIFIED)
|
|
priv->bss_mode = NL80211_IFTYPE_STATION;
|
|
else
|
|
priv->bss_mode = type;
|
|
|
|
priv->bss_type = MWIFIEX_BSS_TYPE_STA;
|
|
priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II;
|
|
priv->bss_priority = 0;
|
|
priv->bss_role = MWIFIEX_BSS_ROLE_STA;
|
|
priv->bss_num = 0;
|
|
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
priv = adapter->priv[MWIFIEX_BSS_TYPE_UAP];
|
|
|
|
if (priv->bss_mode) {
|
|
wiphy_err(wiphy, "Can't create multiple AP interfaces");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
|
|
if (!wdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
priv->wdev = wdev;
|
|
wdev->wiphy = wiphy;
|
|
wdev->iftype = NL80211_IFTYPE_AP;
|
|
|
|
priv->bss_type = MWIFIEX_BSS_TYPE_UAP;
|
|
priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II;
|
|
priv->bss_priority = 0;
|
|
priv->bss_role = MWIFIEX_BSS_ROLE_UAP;
|
|
priv->bss_started = 0;
|
|
priv->bss_num = 0;
|
|
priv->bss_mode = type;
|
|
|
|
break;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
priv = adapter->priv[MWIFIEX_BSS_TYPE_P2P];
|
|
|
|
if (priv->bss_mode) {
|
|
wiphy_err(wiphy, "Can't create multiple P2P ifaces");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
|
|
if (!wdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
priv->wdev = wdev;
|
|
wdev->wiphy = wiphy;
|
|
|
|
/* At start-up, wpa_supplicant tries to change the interface
|
|
* to NL80211_IFTYPE_STATION if it is not managed mode.
|
|
*/
|
|
wdev->iftype = NL80211_IFTYPE_P2P_CLIENT;
|
|
priv->bss_mode = NL80211_IFTYPE_P2P_CLIENT;
|
|
|
|
/* Setting bss_type to P2P tells firmware that this interface
|
|
* is receiving P2P peers found during find phase and doing
|
|
* action frame handshake.
|
|
*/
|
|
priv->bss_type = MWIFIEX_BSS_TYPE_P2P;
|
|
|
|
priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II;
|
|
priv->bss_priority = MWIFIEX_BSS_ROLE_STA;
|
|
priv->bss_role = MWIFIEX_BSS_ROLE_STA;
|
|
priv->bss_started = 0;
|
|
priv->bss_num = 0;
|
|
|
|
if (mwifiex_cfg80211_init_p2p_client(priv)) {
|
|
wdev = ERR_PTR(-EFAULT);
|
|
goto done;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
wiphy_err(wiphy, "type not supported\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
dev = alloc_netdev_mqs(sizeof(struct mwifiex_private *), name,
|
|
NET_NAME_UNKNOWN, ether_setup,
|
|
IEEE80211_NUM_ACS, 1);
|
|
if (!dev) {
|
|
wiphy_err(wiphy, "no memory available for netdevice\n");
|
|
priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED;
|
|
wdev = ERR_PTR(-ENOMEM);
|
|
goto done;
|
|
}
|
|
|
|
mwifiex_init_priv_params(priv, dev);
|
|
priv->netdev = dev;
|
|
|
|
mwifiex_setup_ht_caps(&wiphy->bands[IEEE80211_BAND_2GHZ]->ht_cap, priv);
|
|
if (adapter->is_hw_11ac_capable)
|
|
mwifiex_setup_vht_caps(
|
|
&wiphy->bands[IEEE80211_BAND_2GHZ]->vht_cap, priv);
|
|
|
|
if (adapter->config_bands & BAND_A)
|
|
mwifiex_setup_ht_caps(
|
|
&wiphy->bands[IEEE80211_BAND_5GHZ]->ht_cap, priv);
|
|
|
|
if ((adapter->config_bands & BAND_A) && adapter->is_hw_11ac_capable)
|
|
mwifiex_setup_vht_caps(
|
|
&wiphy->bands[IEEE80211_BAND_5GHZ]->vht_cap, priv);
|
|
|
|
dev_net_set(dev, wiphy_net(wiphy));
|
|
dev->ieee80211_ptr = priv->wdev;
|
|
dev->ieee80211_ptr->iftype = priv->bss_mode;
|
|
memcpy(dev->dev_addr, wiphy->perm_addr, ETH_ALEN);
|
|
SET_NETDEV_DEV(dev, wiphy_dev(wiphy));
|
|
|
|
dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
|
|
dev->watchdog_timeo = MWIFIEX_DEFAULT_WATCHDOG_TIMEOUT;
|
|
dev->hard_header_len += MWIFIEX_MIN_DATA_HEADER_LEN;
|
|
dev->ethtool_ops = &mwifiex_ethtool_ops;
|
|
|
|
mdev_priv = netdev_priv(dev);
|
|
*((unsigned long *) mdev_priv) = (unsigned long) priv;
|
|
|
|
SET_NETDEV_DEV(dev, adapter->dev);
|
|
|
|
/* Register network device */
|
|
if (register_netdevice(dev)) {
|
|
wiphy_err(wiphy, "cannot register virtual network device\n");
|
|
free_netdev(dev);
|
|
priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED;
|
|
priv->netdev = NULL;
|
|
wdev = ERR_PTR(-EFAULT);
|
|
goto done;
|
|
}
|
|
|
|
sema_init(&priv->async_sem, 1);
|
|
|
|
dev_dbg(adapter->dev, "info: %s: Marvell 802.11 Adapter\n", dev->name);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
mwifiex_dev_debugfs_init(priv);
|
|
#endif
|
|
|
|
done:
|
|
if (IS_ERR(wdev)) {
|
|
kfree(priv->wdev);
|
|
priv->wdev = NULL;
|
|
}
|
|
|
|
return wdev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mwifiex_add_virtual_intf);
|
|
|
|
/*
|
|
* del_virtual_intf: remove the virtual interface determined by dev
|
|
*/
|
|
int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
mwifiex_dev_debugfs_remove(priv);
|
|
#endif
|
|
|
|
mwifiex_stop_net_dev_queue(priv->netdev, priv->adapter);
|
|
|
|
if (netif_carrier_ok(priv->netdev))
|
|
netif_carrier_off(priv->netdev);
|
|
|
|
if (wdev->netdev->reg_state == NETREG_REGISTERED)
|
|
unregister_netdevice(wdev->netdev);
|
|
|
|
/* Clear the priv in adapter */
|
|
priv->netdev->ieee80211_ptr = NULL;
|
|
priv->netdev = NULL;
|
|
kfree(wdev);
|
|
priv->wdev = NULL;
|
|
|
|
priv->media_connected = false;
|
|
|
|
priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mwifiex_del_virtual_intf);
|
|
|
|
static bool
|
|
mwifiex_is_pattern_supported(struct cfg80211_pkt_pattern *pat, s8 *byte_seq,
|
|
u8 max_byte_seq)
|
|
{
|
|
int j, k, valid_byte_cnt = 0;
|
|
bool dont_care_byte = false;
|
|
|
|
for (j = 0; j < DIV_ROUND_UP(pat->pattern_len, 8); j++) {
|
|
for (k = 0; k < 8; k++) {
|
|
if (pat->mask[j] & 1 << k) {
|
|
memcpy(byte_seq + valid_byte_cnt,
|
|
&pat->pattern[j * 8 + k], 1);
|
|
valid_byte_cnt++;
|
|
if (dont_care_byte)
|
|
return false;
|
|
} else {
|
|
if (valid_byte_cnt)
|
|
dont_care_byte = true;
|
|
}
|
|
|
|
if (valid_byte_cnt > max_byte_seq)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
byte_seq[max_byte_seq] = valid_byte_cnt;
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int mwifiex_cfg80211_suspend(struct wiphy *wiphy,
|
|
struct cfg80211_wowlan *wowlan)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
struct mwifiex_ds_mef_cfg mef_cfg;
|
|
struct mwifiex_mef_entry *mef_entry;
|
|
int i, filt_num = 0, ret;
|
|
bool first_pat = true;
|
|
u8 byte_seq[MWIFIEX_MEF_MAX_BYTESEQ + 1];
|
|
const u8 ipv4_mc_mac[] = {0x33, 0x33};
|
|
const u8 ipv6_mc_mac[] = {0x01, 0x00, 0x5e};
|
|
struct mwifiex_private *priv =
|
|
mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA);
|
|
|
|
if (!wowlan) {
|
|
dev_warn(adapter->dev, "None of the WOWLAN triggers enabled\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!priv->media_connected) {
|
|
dev_warn(adapter->dev,
|
|
"Can not configure WOWLAN in disconnected state\n");
|
|
return 0;
|
|
}
|
|
|
|
mef_entry = kzalloc(sizeof(*mef_entry), GFP_KERNEL);
|
|
if (!mef_entry)
|
|
return -ENOMEM;
|
|
|
|
memset(&mef_cfg, 0, sizeof(mef_cfg));
|
|
mef_cfg.num_entries = 1;
|
|
mef_cfg.mef_entry = mef_entry;
|
|
mef_entry->mode = MEF_MODE_HOST_SLEEP;
|
|
mef_entry->action = MEF_ACTION_ALLOW_AND_WAKEUP_HOST;
|
|
|
|
for (i = 0; i < wowlan->n_patterns; i++) {
|
|
memset(byte_seq, 0, sizeof(byte_seq));
|
|
if (!mwifiex_is_pattern_supported(&wowlan->patterns[i],
|
|
byte_seq,
|
|
MWIFIEX_MEF_MAX_BYTESEQ)) {
|
|
wiphy_err(wiphy, "Pattern not supported\n");
|
|
kfree(mef_entry);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!wowlan->patterns[i].pkt_offset) {
|
|
if (!(byte_seq[0] & 0x01) &&
|
|
(byte_seq[MWIFIEX_MEF_MAX_BYTESEQ] == 1)) {
|
|
mef_cfg.criteria |= MWIFIEX_CRITERIA_UNICAST;
|
|
continue;
|
|
} else if (is_broadcast_ether_addr(byte_seq)) {
|
|
mef_cfg.criteria |= MWIFIEX_CRITERIA_BROADCAST;
|
|
continue;
|
|
} else if ((!memcmp(byte_seq, ipv4_mc_mac, 2) &&
|
|
(byte_seq[MWIFIEX_MEF_MAX_BYTESEQ] == 2)) ||
|
|
(!memcmp(byte_seq, ipv6_mc_mac, 3) &&
|
|
(byte_seq[MWIFIEX_MEF_MAX_BYTESEQ] == 3))) {
|
|
mef_cfg.criteria |= MWIFIEX_CRITERIA_MULTICAST;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
mef_entry->filter[filt_num].repeat = 1;
|
|
mef_entry->filter[filt_num].offset =
|
|
wowlan->patterns[i].pkt_offset;
|
|
memcpy(mef_entry->filter[filt_num].byte_seq, byte_seq,
|
|
sizeof(byte_seq));
|
|
mef_entry->filter[filt_num].filt_type = TYPE_EQ;
|
|
|
|
if (first_pat)
|
|
first_pat = false;
|
|
else
|
|
mef_entry->filter[filt_num].filt_action = TYPE_AND;
|
|
|
|
filt_num++;
|
|
}
|
|
|
|
if (wowlan->magic_pkt) {
|
|
mef_cfg.criteria |= MWIFIEX_CRITERIA_UNICAST;
|
|
mef_entry->filter[filt_num].repeat = 16;
|
|
memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr,
|
|
ETH_ALEN);
|
|
mef_entry->filter[filt_num].byte_seq[MWIFIEX_MEF_MAX_BYTESEQ] =
|
|
ETH_ALEN;
|
|
mef_entry->filter[filt_num].offset = 28;
|
|
mef_entry->filter[filt_num].filt_type = TYPE_EQ;
|
|
if (filt_num)
|
|
mef_entry->filter[filt_num].filt_action = TYPE_OR;
|
|
|
|
filt_num++;
|
|
mef_entry->filter[filt_num].repeat = 16;
|
|
memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr,
|
|
ETH_ALEN);
|
|
mef_entry->filter[filt_num].byte_seq[MWIFIEX_MEF_MAX_BYTESEQ] =
|
|
ETH_ALEN;
|
|
mef_entry->filter[filt_num].offset = 56;
|
|
mef_entry->filter[filt_num].filt_type = TYPE_EQ;
|
|
mef_entry->filter[filt_num].filt_action = TYPE_OR;
|
|
}
|
|
|
|
if (!mef_cfg.criteria)
|
|
mef_cfg.criteria = MWIFIEX_CRITERIA_BROADCAST |
|
|
MWIFIEX_CRITERIA_UNICAST |
|
|
MWIFIEX_CRITERIA_MULTICAST;
|
|
|
|
ret = mwifiex_send_cmd(priv, HostCmd_CMD_MEF_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &mef_cfg, true);
|
|
|
|
kfree(mef_entry);
|
|
return ret;
|
|
}
|
|
|
|
static int mwifiex_cfg80211_resume(struct wiphy *wiphy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void mwifiex_cfg80211_set_wakeup(struct wiphy *wiphy,
|
|
bool enabled)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
|
|
device_set_wakeup_enable(adapter->dev, enabled);
|
|
}
|
|
#endif
|
|
|
|
static int mwifiex_get_coalesce_pkt_type(u8 *byte_seq)
|
|
{
|
|
const u8 ipv4_mc_mac[] = {0x33, 0x33};
|
|
const u8 ipv6_mc_mac[] = {0x01, 0x00, 0x5e};
|
|
const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff};
|
|
|
|
if ((byte_seq[0] & 0x01) &&
|
|
(byte_seq[MWIFIEX_COALESCE_MAX_BYTESEQ] == 1))
|
|
return PACKET_TYPE_UNICAST;
|
|
else if (!memcmp(byte_seq, bc_mac, 4))
|
|
return PACKET_TYPE_BROADCAST;
|
|
else if ((!memcmp(byte_seq, ipv4_mc_mac, 2) &&
|
|
byte_seq[MWIFIEX_COALESCE_MAX_BYTESEQ] == 2) ||
|
|
(!memcmp(byte_seq, ipv6_mc_mac, 3) &&
|
|
byte_seq[MWIFIEX_COALESCE_MAX_BYTESEQ] == 3))
|
|
return PACKET_TYPE_MULTICAST;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mwifiex_fill_coalesce_rule_info(struct mwifiex_private *priv,
|
|
struct cfg80211_coalesce_rules *crule,
|
|
struct mwifiex_coalesce_rule *mrule)
|
|
{
|
|
u8 byte_seq[MWIFIEX_COALESCE_MAX_BYTESEQ + 1];
|
|
struct filt_field_param *param;
|
|
int i;
|
|
|
|
mrule->max_coalescing_delay = crule->delay;
|
|
|
|
param = mrule->params;
|
|
|
|
for (i = 0; i < crule->n_patterns; i++) {
|
|
memset(byte_seq, 0, sizeof(byte_seq));
|
|
if (!mwifiex_is_pattern_supported(&crule->patterns[i],
|
|
byte_seq,
|
|
MWIFIEX_COALESCE_MAX_BYTESEQ)) {
|
|
dev_err(priv->adapter->dev, "Pattern not supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!crule->patterns[i].pkt_offset) {
|
|
u8 pkt_type;
|
|
|
|
pkt_type = mwifiex_get_coalesce_pkt_type(byte_seq);
|
|
if (pkt_type && mrule->pkt_type) {
|
|
dev_err(priv->adapter->dev,
|
|
"Multiple packet types not allowed\n");
|
|
return -EOPNOTSUPP;
|
|
} else if (pkt_type) {
|
|
mrule->pkt_type = pkt_type;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (crule->condition == NL80211_COALESCE_CONDITION_MATCH)
|
|
param->operation = RECV_FILTER_MATCH_TYPE_EQ;
|
|
else
|
|
param->operation = RECV_FILTER_MATCH_TYPE_NE;
|
|
|
|
param->operand_len = byte_seq[MWIFIEX_COALESCE_MAX_BYTESEQ];
|
|
memcpy(param->operand_byte_stream, byte_seq,
|
|
param->operand_len);
|
|
param->offset = crule->patterns[i].pkt_offset;
|
|
param++;
|
|
|
|
mrule->num_of_fields++;
|
|
}
|
|
|
|
if (!mrule->pkt_type) {
|
|
dev_err(priv->adapter->dev,
|
|
"Packet type can not be determined\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mwifiex_cfg80211_set_coalesce(struct wiphy *wiphy,
|
|
struct cfg80211_coalesce *coalesce)
|
|
{
|
|
struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy);
|
|
int i, ret;
|
|
struct mwifiex_ds_coalesce_cfg coalesce_cfg;
|
|
struct mwifiex_private *priv =
|
|
mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA);
|
|
|
|
memset(&coalesce_cfg, 0, sizeof(coalesce_cfg));
|
|
if (!coalesce) {
|
|
dev_dbg(adapter->dev,
|
|
"Disable coalesce and reset all previous rules\n");
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_COALESCE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0,
|
|
&coalesce_cfg, true);
|
|
}
|
|
|
|
coalesce_cfg.num_of_rules = coalesce->n_rules;
|
|
for (i = 0; i < coalesce->n_rules; i++) {
|
|
ret = mwifiex_fill_coalesce_rule_info(priv, &coalesce->rules[i],
|
|
&coalesce_cfg.rule[i]);
|
|
if (ret) {
|
|
dev_err(priv->adapter->dev,
|
|
"Recheck the patterns provided for rule %d\n",
|
|
i + 1);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return mwifiex_send_cmd(priv, HostCmd_CMD_COALESCE_CFG,
|
|
HostCmd_ACT_GEN_SET, 0, &coalesce_cfg, true);
|
|
}
|
|
|
|
/* cfg80211 ops handler for tdls_mgmt.
|
|
* Function prepares TDLS action frame packets and forwards them to FW
|
|
*/
|
|
static int
|
|
mwifiex_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *peer, u8 action_code, u8 dialog_token,
|
|
u16 status_code, u32 peer_capability,
|
|
bool initiator, const u8 *extra_ies,
|
|
size_t extra_ies_len)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
int ret;
|
|
|
|
if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
|
|
return -ENOTSUPP;
|
|
|
|
/* make sure we are in station mode and connected */
|
|
if (!(priv->bss_type == MWIFIEX_BSS_TYPE_STA && priv->media_connected))
|
|
return -ENOTSUPP;
|
|
|
|
switch (action_code) {
|
|
case WLAN_TDLS_SETUP_REQUEST:
|
|
dev_dbg(priv->adapter->dev,
|
|
"Send TDLS Setup Request to %pM status_code=%d\n", peer,
|
|
status_code);
|
|
ret = mwifiex_send_tdls_data_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
case WLAN_TDLS_SETUP_RESPONSE:
|
|
dev_dbg(priv->adapter->dev,
|
|
"Send TDLS Setup Response to %pM status_code=%d\n",
|
|
peer, status_code);
|
|
ret = mwifiex_send_tdls_data_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
case WLAN_TDLS_SETUP_CONFIRM:
|
|
dev_dbg(priv->adapter->dev,
|
|
"Send TDLS Confirm to %pM status_code=%d\n", peer,
|
|
status_code);
|
|
ret = mwifiex_send_tdls_data_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
case WLAN_TDLS_TEARDOWN:
|
|
dev_dbg(priv->adapter->dev, "Send TDLS Tear down to %pM\n",
|
|
peer);
|
|
ret = mwifiex_send_tdls_data_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
case WLAN_TDLS_DISCOVERY_REQUEST:
|
|
dev_dbg(priv->adapter->dev,
|
|
"Send TDLS Discovery Request to %pM\n", peer);
|
|
ret = mwifiex_send_tdls_data_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
case WLAN_PUB_ACTION_TDLS_DISCOVER_RES:
|
|
dev_dbg(priv->adapter->dev,
|
|
"Send TDLS Discovery Response to %pM\n", peer);
|
|
ret = mwifiex_send_tdls_action_frame(priv, peer, action_code,
|
|
dialog_token, status_code,
|
|
extra_ies, extra_ies_len);
|
|
break;
|
|
default:
|
|
dev_warn(priv->adapter->dev,
|
|
"Unknown TDLS mgmt/action frame %pM\n", peer);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
mwifiex_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *peer, enum nl80211_tdls_operation action)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS) ||
|
|
!(wiphy->flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP))
|
|
return -ENOTSUPP;
|
|
|
|
/* make sure we are in station mode and connected */
|
|
if (!(priv->bss_type == MWIFIEX_BSS_TYPE_STA && priv->media_connected))
|
|
return -ENOTSUPP;
|
|
|
|
dev_dbg(priv->adapter->dev,
|
|
"TDLS peer=%pM, oper=%d\n", peer, action);
|
|
|
|
switch (action) {
|
|
case NL80211_TDLS_ENABLE_LINK:
|
|
action = MWIFIEX_TDLS_ENABLE_LINK;
|
|
break;
|
|
case NL80211_TDLS_DISABLE_LINK:
|
|
action = MWIFIEX_TDLS_DISABLE_LINK;
|
|
break;
|
|
case NL80211_TDLS_TEARDOWN:
|
|
/* shouldn't happen!*/
|
|
dev_warn(priv->adapter->dev,
|
|
"tdls_oper: teardown from driver not supported\n");
|
|
return -EINVAL;
|
|
case NL80211_TDLS_SETUP:
|
|
/* shouldn't happen!*/
|
|
dev_warn(priv->adapter->dev,
|
|
"tdls_oper: setup from driver not supported\n");
|
|
return -EINVAL;
|
|
case NL80211_TDLS_DISCOVERY_REQ:
|
|
/* shouldn't happen!*/
|
|
dev_warn(priv->adapter->dev,
|
|
"tdls_oper: discovery from driver not supported\n");
|
|
return -EINVAL;
|
|
default:
|
|
dev_err(priv->adapter->dev,
|
|
"tdls_oper: operation not supported\n");
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return mwifiex_tdls_oper(priv, peer, action);
|
|
}
|
|
|
|
static int
|
|
mwifiex_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac, struct station_parameters *params)
|
|
{
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
|
|
return -ENOTSUPP;
|
|
|
|
/* make sure we are in station mode and connected */
|
|
if ((priv->bss_type != MWIFIEX_BSS_TYPE_STA) || !priv->media_connected)
|
|
return -ENOTSUPP;
|
|
|
|
return mwifiex_tdls_oper(priv, mac, MWIFIEX_TDLS_CREATE_LINK);
|
|
}
|
|
|
|
static int
|
|
mwifiex_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *mac,
|
|
struct station_parameters *params)
|
|
{
|
|
int ret;
|
|
struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
|
|
|
|
/* we support change_station handler only for TDLS peers*/
|
|
if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
|
|
return -ENOTSUPP;
|
|
|
|
/* make sure we are in station mode and connected */
|
|
if ((priv->bss_type != MWIFIEX_BSS_TYPE_STA) || !priv->media_connected)
|
|
return -ENOTSUPP;
|
|
|
|
priv->sta_params = params;
|
|
|
|
ret = mwifiex_tdls_oper(priv, mac, MWIFIEX_TDLS_CONFIG_LINK);
|
|
priv->sta_params = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* station cfg80211 operations */
|
|
static struct cfg80211_ops mwifiex_cfg80211_ops = {
|
|
.add_virtual_intf = mwifiex_add_virtual_intf,
|
|
.del_virtual_intf = mwifiex_del_virtual_intf,
|
|
.change_virtual_intf = mwifiex_cfg80211_change_virtual_intf,
|
|
.scan = mwifiex_cfg80211_scan,
|
|
.connect = mwifiex_cfg80211_connect,
|
|
.disconnect = mwifiex_cfg80211_disconnect,
|
|
.get_station = mwifiex_cfg80211_get_station,
|
|
.dump_station = mwifiex_cfg80211_dump_station,
|
|
.set_wiphy_params = mwifiex_cfg80211_set_wiphy_params,
|
|
.join_ibss = mwifiex_cfg80211_join_ibss,
|
|
.leave_ibss = mwifiex_cfg80211_leave_ibss,
|
|
.add_key = mwifiex_cfg80211_add_key,
|
|
.del_key = mwifiex_cfg80211_del_key,
|
|
.mgmt_tx = mwifiex_cfg80211_mgmt_tx,
|
|
.mgmt_frame_register = mwifiex_cfg80211_mgmt_frame_register,
|
|
.remain_on_channel = mwifiex_cfg80211_remain_on_channel,
|
|
.cancel_remain_on_channel = mwifiex_cfg80211_cancel_remain_on_channel,
|
|
.set_default_key = mwifiex_cfg80211_set_default_key,
|
|
.set_power_mgmt = mwifiex_cfg80211_set_power_mgmt,
|
|
.set_tx_power = mwifiex_cfg80211_set_tx_power,
|
|
.set_bitrate_mask = mwifiex_cfg80211_set_bitrate_mask,
|
|
.start_ap = mwifiex_cfg80211_start_ap,
|
|
.stop_ap = mwifiex_cfg80211_stop_ap,
|
|
.change_beacon = mwifiex_cfg80211_change_beacon,
|
|
.set_cqm_rssi_config = mwifiex_cfg80211_set_cqm_rssi_config,
|
|
.set_antenna = mwifiex_cfg80211_set_antenna,
|
|
.del_station = mwifiex_cfg80211_del_station,
|
|
#ifdef CONFIG_PM
|
|
.suspend = mwifiex_cfg80211_suspend,
|
|
.resume = mwifiex_cfg80211_resume,
|
|
.set_wakeup = mwifiex_cfg80211_set_wakeup,
|
|
#endif
|
|
.set_coalesce = mwifiex_cfg80211_set_coalesce,
|
|
.tdls_mgmt = mwifiex_cfg80211_tdls_mgmt,
|
|
.tdls_oper = mwifiex_cfg80211_tdls_oper,
|
|
.add_station = mwifiex_cfg80211_add_station,
|
|
.change_station = mwifiex_cfg80211_change_station,
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static const struct wiphy_wowlan_support mwifiex_wowlan_support = {
|
|
.flags = WIPHY_WOWLAN_MAGIC_PKT,
|
|
.n_patterns = MWIFIEX_MEF_MAX_FILTERS,
|
|
.pattern_min_len = 1,
|
|
.pattern_max_len = MWIFIEX_MAX_PATTERN_LEN,
|
|
.max_pkt_offset = MWIFIEX_MAX_OFFSET_LEN,
|
|
};
|
|
#endif
|
|
|
|
static bool mwifiex_is_valid_alpha2(const char *alpha2)
|
|
{
|
|
if (!alpha2 || strlen(alpha2) != 2)
|
|
return false;
|
|
|
|
if (isalpha(alpha2[0]) && isalpha(alpha2[1]))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct wiphy_coalesce_support mwifiex_coalesce_support = {
|
|
.n_rules = MWIFIEX_COALESCE_MAX_RULES,
|
|
.max_delay = MWIFIEX_MAX_COALESCING_DELAY,
|
|
.n_patterns = MWIFIEX_COALESCE_MAX_FILTERS,
|
|
.pattern_min_len = 1,
|
|
.pattern_max_len = MWIFIEX_MAX_PATTERN_LEN,
|
|
.max_pkt_offset = MWIFIEX_MAX_OFFSET_LEN,
|
|
};
|
|
|
|
/*
|
|
* This function registers the device with CFG802.11 subsystem.
|
|
*
|
|
* The function creates the wireless device/wiphy, populates it with
|
|
* default parameters and handler function pointers, and finally
|
|
* registers the device.
|
|
*/
|
|
|
|
int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter)
|
|
{
|
|
int ret;
|
|
void *wdev_priv;
|
|
struct wiphy *wiphy;
|
|
struct mwifiex_private *priv = adapter->priv[MWIFIEX_BSS_TYPE_STA];
|
|
u8 *country_code;
|
|
u32 thr, retry;
|
|
|
|
/* create a new wiphy for use with cfg80211 */
|
|
wiphy = wiphy_new(&mwifiex_cfg80211_ops,
|
|
sizeof(struct mwifiex_adapter *));
|
|
if (!wiphy) {
|
|
dev_err(adapter->dev, "%s: creating new wiphy\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH;
|
|
wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN;
|
|
wiphy->mgmt_stypes = mwifiex_mgmt_stypes;
|
|
wiphy->max_remain_on_channel_duration = 5000;
|
|
wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
|
|
BIT(NL80211_IFTYPE_ADHOC) |
|
|
BIT(NL80211_IFTYPE_P2P_CLIENT) |
|
|
BIT(NL80211_IFTYPE_P2P_GO) |
|
|
BIT(NL80211_IFTYPE_AP);
|
|
|
|
wiphy->bands[IEEE80211_BAND_2GHZ] = &mwifiex_band_2ghz;
|
|
if (adapter->config_bands & BAND_A)
|
|
wiphy->bands[IEEE80211_BAND_5GHZ] = &mwifiex_band_5ghz;
|
|
else
|
|
wiphy->bands[IEEE80211_BAND_5GHZ] = NULL;
|
|
|
|
wiphy->iface_combinations = &mwifiex_iface_comb_ap_sta;
|
|
wiphy->n_iface_combinations = 1;
|
|
|
|
/* Initialize cipher suits */
|
|
wiphy->cipher_suites = mwifiex_cipher_suites;
|
|
wiphy->n_cipher_suites = ARRAY_SIZE(mwifiex_cipher_suites);
|
|
|
|
memcpy(wiphy->perm_addr, priv->curr_addr, ETH_ALEN);
|
|
wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
|
|
wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME |
|
|
WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD |
|
|
WIPHY_FLAG_AP_UAPSD |
|
|
WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
|
|
|
|
if (ISSUPP_TDLS_ENABLED(adapter->fw_cap_info))
|
|
wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS |
|
|
WIPHY_FLAG_TDLS_EXTERNAL_SETUP;
|
|
|
|
#ifdef CONFIG_PM
|
|
wiphy->wowlan = &mwifiex_wowlan_support;
|
|
#endif
|
|
|
|
wiphy->coalesce = &mwifiex_coalesce_support;
|
|
|
|
wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
|
|
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
|
|
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P;
|
|
|
|
wiphy->available_antennas_tx = BIT(adapter->number_of_antenna) - 1;
|
|
wiphy->available_antennas_rx = BIT(adapter->number_of_antenna) - 1;
|
|
|
|
wiphy->features |= NL80211_FEATURE_HT_IBSS |
|
|
NL80211_FEATURE_INACTIVITY_TIMER |
|
|
NL80211_FEATURE_LOW_PRIORITY_SCAN |
|
|
NL80211_FEATURE_NEED_OBSS_SCAN;
|
|
|
|
/* Reserve space for mwifiex specific private data for BSS */
|
|
wiphy->bss_priv_size = sizeof(struct mwifiex_bss_priv);
|
|
|
|
wiphy->reg_notifier = mwifiex_reg_notifier;
|
|
|
|
/* Set struct mwifiex_adapter pointer in wiphy_priv */
|
|
wdev_priv = wiphy_priv(wiphy);
|
|
*(unsigned long *)wdev_priv = (unsigned long)adapter;
|
|
|
|
set_wiphy_dev(wiphy, priv->adapter->dev);
|
|
|
|
ret = wiphy_register(wiphy);
|
|
if (ret < 0) {
|
|
dev_err(adapter->dev,
|
|
"%s: wiphy_register failed: %d\n", __func__, ret);
|
|
wiphy_free(wiphy);
|
|
return ret;
|
|
}
|
|
|
|
if (reg_alpha2 && mwifiex_is_valid_alpha2(reg_alpha2)) {
|
|
wiphy_info(wiphy, "driver hint alpha2: %2.2s\n", reg_alpha2);
|
|
regulatory_hint(wiphy, reg_alpha2);
|
|
} else {
|
|
country_code = mwifiex_11d_code_2_region(adapter->region_code);
|
|
if (country_code)
|
|
wiphy_info(wiphy, "ignoring F/W country code %2.2s\n",
|
|
country_code);
|
|
}
|
|
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_GET, FRAG_THRESH_I, &thr, true);
|
|
wiphy->frag_threshold = thr;
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_GET, RTS_THRESH_I, &thr, true);
|
|
wiphy->rts_threshold = thr;
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_GET, SHORT_RETRY_LIM_I, &retry, true);
|
|
wiphy->retry_short = (u8) retry;
|
|
mwifiex_send_cmd(priv, HostCmd_CMD_802_11_SNMP_MIB,
|
|
HostCmd_ACT_GEN_GET, LONG_RETRY_LIM_I, &retry, true);
|
|
wiphy->retry_long = (u8) retry;
|
|
|
|
adapter->wiphy = wiphy;
|
|
return ret;
|
|
}
|