/*
* Marvell Wireless LAN device driver
*/
#include "decl.h"
#include "ioctl.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "wmm.h"
#include "11n.h"
#include "cfg80211.h"
static int disconnect_on_suspend = 1;
module_param(disconnect_on_suspend, int, 0644);
/*
* Copies the multicast address list from device to driver.
*
* This function does not validate the destination memory for
* size, and the calling function must ensure enough memory is
* available.
*/
int mwifiex_copy_mcast_addr(struct mwifiex_multicast_list *mlist,
struct net_device *dev)
{
int i = 0;
struct netdev_hw_addr *ha;
netdev_for_each_mc_addr(ha, dev)
memcpy(&mlist->mac_list[i++], ha->addr, ETH_ALEN);
return i;
}
/*
* Wait queue completion handler.
*
* This function waits on a cmd wait queue. It also cancels the pending
* request after waking up, in case of errors.
*/
int mwifiex_wait_queue_complete(struct mwifiex_adapter *adapter,
struct cmd_ctrl_node *cmd_queued)
{
int status;
/* Wait for completion */
status = wait_event_interruptible(adapter->cmd_wait_q.wait,
*(cmd_queued->condition));
if (status) {
dev_err(adapter->dev, "cmd_wait_q terminated: %d\n", status);
return status;
}
status = adapter->cmd_wait_q.status;
adapter->cmd_wait_q.status = 0;
return status;
}
/*
* This function prepares the correct firmware command and
* issues it to set the multicast list.
*
* This function can be used to enable promiscuous mode, or enable all
* multicast packets, or to enable selective multicast.
*/
int mwifiex_request_set_multicast_list(struct mwifiex_private *priv,
struct mwifiex_multicast_list *mcast_list)
{
int ret = 0;
u16 old_pkt_filter;
old_pkt_filter = priv->curr_pkt_filter;
if (mcast_list->mode == MWIFIEX_PROMISC_MODE) {
dev_dbg(priv->adapter->dev, "info: Enable Promiscuous mode\n");
priv->curr_pkt_filter |= HostCmd_ACT_MAC_PROMISCUOUS_ENABLE;
priv->curr_pkt_filter &=
~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE;
} else {
/* Multicast */
priv->curr_pkt_filter &= ~HostCmd_ACT_MAC_PROMISCUOUS_ENABLE;
if (mcast_list->mode == MWIFIEX_ALL_MULTI_MODE) {
dev_dbg(priv->adapter->dev,
"info: Enabling All Multicast!\n");
priv->curr_pkt_filter |=
HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE;
} else {
priv->curr_pkt_filter &=
~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE;
dev_dbg(priv->adapter->dev,
"info: Set multicast list=%d\n",
mcast_list->num_multicast_addr);
/* Send multicast addresses to firmware */
ret = mwifiex_send_cmd_async(priv,
HostCmd_CMD_MAC_MULTICAST_ADR,
HostCmd_ACT_GEN_SET, 0,
mcast_list);
}
}
dev_dbg(priv->adapter->dev,
"info: old_pkt_filter=%#x, curr_pkt_filter=%#x\n",
old_pkt_filter, priv->curr_pkt_filter);
if (old_pkt_filter != priv->curr_pkt_filter) {
ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_MAC_CONTROL,
HostCmd_ACT_GEN_SET,
0, &priv->curr_pkt_filter);
}
return ret;
}
/*
* This function fills bss descriptor structure using provided
* information.
* beacon_ie buffer is allocated in this function. It is caller's
* responsibility to free the memory.
*/
int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv,
struct cfg80211_bss *bss,
struct mwifiex_bssdescriptor *bss_desc)
{
u8 *beacon_ie;
size_t beacon_ie_len;
struct mwifiex_bss_priv *bss_priv = (void *)bss->priv;
const struct cfg80211_bss_ies *ies;
rcu_read_lock();
ies = rcu_dereference(bss->ies);
beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC);
beacon_ie_len = ies->len;
bss_desc->timestamp = ies->tsf;
rcu_read_unlock();
if (!beacon_ie) {
dev_err(priv->adapter->dev, " failed to alloc beacon_ie\n");
return -ENOMEM;
}
memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN);
bss_desc->rssi = bss->signal;
/* The caller of this function will free beacon_ie */
bss_desc->beacon_buf = beacon_ie;
bss_desc->beacon_buf_size = beacon_ie_len;
bss_desc->beacon_period = bss->beacon_interval;
bss_desc->cap_info_bitmap = bss->capability;
bss_desc->bss_band = bss_priv->band;
bss_desc->fw_tsf = bss_priv->fw_tsf;
if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_PRIVACY) {
dev_dbg(priv->adapter->dev, "info: InterpretIE: AP WEP enabled\n");
bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_8021X_WEP;
} else {
bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_ACCEPT_ALL;
}
if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_IBSS)
bss_desc->bss_mode = NL80211_IFTYPE_ADHOC;
else
bss_desc->bss_mode = NL80211_IFTYPE_STATION;
/* Disable 11ac by default. Enable it only where there
* exist VHT_CAP IE in AP beacon
*/
bss_desc->disable_11ac = true;
if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_SPECTRUM_MGMT)
bss_desc->sensed_11h = true;
return mwifiex_update_bss_desc_with_ie(priv->adapter, bss_desc);
}
static int mwifiex_process_country_ie(struct mwifiex_private *priv,
struct cfg80211_bss *bss)
{
const u8 *country_ie;
u8 country_ie_len;
struct mwifiex_802_11d_domain_reg *domain_info =
&priv->adapter->domain_reg;
rcu_read_lock();
country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY);
if (!country_ie) {
rcu_read_unlock();
return 0;
}
country_ie_len = country_ie[1];
if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) {
rcu_read_unlock();
return 0;
}
domain_info->country_code[0] = country_ie[2];
domain_info->country_code[1] = country_ie[3];
domain_info->country_code[2] = ' ';
country_ie_len -= IEEE80211_COUNTRY_STRING_LEN;
domain_info->no_of_triplet =
country_ie_len / sizeof(struct ieee80211_country_ie_triplet);
memcpy((u8 *)domain_info->triplet,
&country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len);
rcu_read_unlock();
if (mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11D_DOMAIN_INFO,
HostCmd_ACT_GEN_SET, 0, NULL)) {
wiphy_err(priv->adapter->wiphy,
"11D: setting domain info in FW\n");
return -1;
}
return 0;
}
/*
* In Ad-Hoc mode, the IBSS is created if not found in scan list.
* In both Ad-Hoc and infra mode, an deauthentication is performed
* first.
*/
int mwifiex_bss_start(struct mwifiex_private *priv, struct cfg80211_bss *bss,
struct cfg80211_ssid *req_ssid)
{
int ret;
struct mwifiex_adapter *adapter = priv->adapter;
struct mwifiex_bssdescriptor *bss_desc = NULL;
priv->scan_block = false;
if (bss) {
mwifiex_process_country_ie(priv, bss);
/* Allocate and fill new bss descriptor */
bss_desc = kzalloc(sizeof(struct mwifiex_bssdescriptor),
GFP_KERNEL);
if (!bss_desc)
return -ENOMEM;
ret = mwifiex_fill_new_bss_desc(priv, bss, bss_desc);
if (ret)
goto done;
}
if (priv->bss_mode == NL80211_IFTYPE_STATION ||
priv->bss_mode == NL80211_IFTYPE_P2P_CLIENT) {
u8 config_bands;
ret = mwifiex_deauthenticate(priv, NULL);
if (ret)
goto done;
if (!bss_desc)
return -1;
if (mwifiex_band_to_radio_type(bss_desc->bss_band) ==
HostCmd_SCAN_RADIO_TYPE_BG)
config_bands = BAND_B | BAND_G | BAND_GN | BAND_GAC;
else
config_bands = BAND_A | BAND_AN | BAND_AAC;
if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands))
adapter->config_bands = config_bands;
ret = mwifiex_check_network_compatibility(priv, bss_desc);
if (ret)
goto done;
if (mwifiex_11h_get_csa_closed_channel(priv) ==
(u8)bss_desc->channel) {
dev_err(adapter->dev,
"Attempt to reconnect on csa closed chan(%d)\n",
bss_desc->channel);
goto done;
}
dev_dbg(adapter->dev, "info: SSID found in scan list ... "
"associating...\n");
mwifiex_stop_net_dev_queue(priv->netdev, adapter);
if (netif_carrier_ok(priv->netdev))
netif_carrier_off(priv->netdev);
/* Clear any past association response stored for
* application retrieval */
priv->assoc_rsp_size = 0;
ret = mwifiex_associate(priv, bss_desc);
/* If auth type is auto and association fails using open mode,
* try to connect using shared mode */
if (ret == WLAN_STATUS_NOT_SUPPORTED_A