7539 lines
185 KiB
C
Executable File
7539 lines
185 KiB
C
Executable File
/*
|
|
* Copyright (c) 2004-2011 Atheros Communications Inc.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <linux/moduleparam.h>
|
|
#ifndef CE_OLD_KERNEL_SUPPORT_2_6_23
|
|
#include <linux/gpio.h>
|
|
#endif
|
|
#include <linux/interrupt.h>
|
|
#include <linux/time.h>
|
|
#include <linux/inetdevice.h>
|
|
#include "core.h"
|
|
#include "cfg80211.h"
|
|
#include "debug.h"
|
|
#include "hif-ops.h"
|
|
#include "testmode.h"
|
|
#include "cfg80211_btcoex.h"
|
|
#ifdef ATH6KL_DIAGNOSTIC
|
|
#include "diagnose.h"
|
|
#endif
|
|
#include "pm.h"
|
|
#include "rttm.h"
|
|
|
|
unsigned int debug_quirks = ATH6KL_MODULE_DEF_DEBUG_QUIRKS;
|
|
|
|
|
|
module_param(debug_quirks, uint, 0644);
|
|
|
|
#define RATETAB_ENT(_rate, _rateid, _flags) { \
|
|
.bitrate = (_rate), \
|
|
.flags = (_flags), \
|
|
.hw_value = (_rateid), \
|
|
}
|
|
|
|
#define CHAN2G(_channel, _freq, _flags) { \
|
|
.band = IEEE80211_BAND_2GHZ, \
|
|
.hw_value = (_channel), \
|
|
.center_freq = (_freq), \
|
|
.flags = (_flags), \
|
|
.max_antenna_gain = 0, \
|
|
.max_power = 30, \
|
|
}
|
|
|
|
#define CHAN5G(_channel, _flags) { \
|
|
.band = IEEE80211_BAND_5GHZ, \
|
|
.hw_value = (_channel), \
|
|
.center_freq = 5000 + (5 * (_channel)), \
|
|
.flags = (_flags), \
|
|
.max_antenna_gain = 0, \
|
|
.max_power = 30, \
|
|
}
|
|
|
|
static struct ieee80211_rate ath6kl_rates[] = {
|
|
RATETAB_ENT(10, 0x1, 0),
|
|
RATETAB_ENT(20, 0x2, 0),
|
|
RATETAB_ENT(55, 0x4, 0),
|
|
RATETAB_ENT(110, 0x8, 0),
|
|
RATETAB_ENT(60, 0x10, 0),
|
|
RATETAB_ENT(90, 0x20, 0),
|
|
RATETAB_ENT(120, 0x40, 0),
|
|
RATETAB_ENT(180, 0x80, 0),
|
|
RATETAB_ENT(240, 0x100, 0),
|
|
RATETAB_ENT(360, 0x200, 0),
|
|
RATETAB_ENT(480, 0x400, 0),
|
|
RATETAB_ENT(540, 0x800, 0),
|
|
};
|
|
|
|
#define ath6kl_a_rates (ath6kl_rates + 4)
|
|
#define ath6kl_a_rates_size 8
|
|
#define ath6kl_g_rates (ath6kl_rates + 0)
|
|
#define ath6kl_g_rates_size 12
|
|
#define ath6kl_b_rates_size 4
|
|
|
|
/* 802.1d to AC mapping. Refer pg 57 of WMM-test-plan-v1.2 */
|
|
static const u8 up_to_ac[] = {
|
|
WMM_AC_BE,
|
|
WMM_AC_BK,
|
|
WMM_AC_BK,
|
|
WMM_AC_BE,
|
|
WMM_AC_VI,
|
|
WMM_AC_VI,
|
|
WMM_AC_VO,
|
|
WMM_AC_VO,
|
|
};
|
|
|
|
static struct ieee80211_channel ath6kl_2ghz_channels[] = {
|
|
CHAN2G(1, 2412, 0),
|
|
CHAN2G(2, 2417, 0),
|
|
CHAN2G(3, 2422, 0),
|
|
CHAN2G(4, 2427, 0),
|
|
CHAN2G(5, 2432, 0),
|
|
CHAN2G(6, 2437, 0),
|
|
CHAN2G(7, 2442, 0),
|
|
CHAN2G(8, 2447, 0),
|
|
CHAN2G(9, 2452, 0),
|
|
CHAN2G(10, 2457, 0),
|
|
CHAN2G(11, 2462, 0),
|
|
CHAN2G(12, 2467, 0),
|
|
CHAN2G(13, 2472, 0),
|
|
CHAN2G(14, 2484, 0),
|
|
};
|
|
|
|
static struct ieee80211_channel ath6kl_5ghz_a_channels[] = {
|
|
CHAN5G(34, 0), CHAN5G(36, 0),
|
|
CHAN5G(38, 0), CHAN5G(40, 0),
|
|
CHAN5G(42, 0), CHAN5G(44, 0),
|
|
CHAN5G(46, 0), CHAN5G(48, 0),
|
|
CHAN5G(52, 0), CHAN5G(56, 0),
|
|
CHAN5G(60, 0), CHAN5G(64, 0),
|
|
CHAN5G(100, 0), CHAN5G(104, 0),
|
|
CHAN5G(108, 0), CHAN5G(112, 0),
|
|
CHAN5G(116, 0), CHAN5G(120, 0),
|
|
CHAN5G(124, 0), CHAN5G(128, 0),
|
|
CHAN5G(132, 0), CHAN5G(136, 0),
|
|
CHAN5G(140, 0), CHAN5G(144, 0),
|
|
CHAN5G(149, 0), CHAN5G(153, 0),
|
|
CHAN5G(157, 0), CHAN5G(161, 0),
|
|
CHAN5G(165, 0),
|
|
};
|
|
|
|
static struct ieee80211_supported_band ath6kl_band_2ghz = {
|
|
.n_channels = ARRAY_SIZE(ath6kl_2ghz_channels),
|
|
.channels = ath6kl_2ghz_channels,
|
|
.n_bitrates = ath6kl_g_rates_size,
|
|
.bitrates = ath6kl_g_rates,
|
|
.ht_cap = {
|
|
.ht_supported = true,
|
|
.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
|
|
IEEE80211_HT_CAP_SGI_20 |
|
|
IEEE80211_HT_CAP_SGI_40 |
|
|
IEEE80211_HT_CAP_TX_STBC |
|
|
0x100 | /* FIXME : One chain RX STBC */
|
|
IEEE80211_HT_CAP_SM_PS,
|
|
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
|
|
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_8,
|
|
.mcs = {
|
|
.rx_mask = { 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
},
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_supported_band ath6kl_band_5ghz = {
|
|
.n_channels = ARRAY_SIZE(ath6kl_5ghz_a_channels),
|
|
.channels = ath6kl_5ghz_a_channels,
|
|
.n_bitrates = ath6kl_a_rates_size,
|
|
.bitrates = ath6kl_a_rates,
|
|
.ht_cap = {
|
|
.ht_supported = true,
|
|
.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
|
|
IEEE80211_HT_CAP_SGI_20 |
|
|
IEEE80211_HT_CAP_SGI_40 |
|
|
IEEE80211_HT_CAP_TX_STBC |
|
|
0x100 | /* FIXME : One chain RX STBC */
|
|
IEEE80211_HT_CAP_SM_PS,
|
|
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
|
|
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_8,
|
|
.mcs = {
|
|
.rx_mask = { 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
},
|
|
},
|
|
};
|
|
|
|
/* Max. 2 devices = 1STA + 1SOFTAP */
|
|
static const struct ieee80211_iface_limit ath6kl_limits_sta_ap[] = {
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_STATION),
|
|
},
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_AP),
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_iface_combination
|
|
ath6kl_iface_combinations_sta_ap[] = {
|
|
{
|
|
.num_different_channels = 1,
|
|
.max_interfaces = 2,
|
|
.limits = ath6kl_limits_sta_ap,
|
|
.n_limits = ARRAY_SIZE(ath6kl_limits_sta_ap),
|
|
},
|
|
};
|
|
|
|
/* Max. 2 devices = 1STA&P2P-DEVICE + 1P2P-GO|P2P-CLIENT */
|
|
static const struct ieee80211_iface_limit ath6kl_limits_p2p_concurrent2[] = {
|
|
{
|
|
.max = 2,
|
|
.types = BIT(NL80211_IFTYPE_STATION),
|
|
},
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
|
|
BIT(NL80211_IFTYPE_P2P_GO),
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_iface_combination
|
|
ath6kl_iface_combinations_p2p_concurrent2[] = {
|
|
{
|
|
.num_different_channels = 1,
|
|
.max_interfaces = 2,
|
|
.limits = ath6kl_limits_p2p_concurrent2,
|
|
.n_limits = ARRAY_SIZE(ath6kl_limits_p2p_concurrent2),
|
|
},
|
|
};
|
|
|
|
/* Max. 3 devices = 1STA + 1P2P-DEVICE + 1P2P-GO|P2P-CLIENT */
|
|
static const struct ieee80211_iface_limit ath6kl_limits_p2p_concurrent3[] = {
|
|
{
|
|
.max = 3,
|
|
.types = BIT(NL80211_IFTYPE_STATION),
|
|
},
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
|
|
BIT(NL80211_IFTYPE_P2P_GO),
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_iface_combination
|
|
ath6kl_iface_combinations_p2p_concurrent3[] = {
|
|
{
|
|
.num_different_channels = 1,
|
|
.max_interfaces = 3,
|
|
.limits = ath6kl_limits_p2p_concurrent3,
|
|
.n_limits = ARRAY_SIZE(ath6kl_limits_p2p_concurrent3),
|
|
},
|
|
};
|
|
|
|
/* Max. 4 devices = 1STA + 1P2P-DEVICE + 2P2P-GO|P2P-CLIENT */
|
|
static const struct ieee80211_iface_limit ath6kl_limits_p2p_concurrent4[] = {
|
|
{
|
|
.max = 4,
|
|
.types = BIT(NL80211_IFTYPE_STATION),
|
|
},
|
|
{
|
|
.max = 2,
|
|
.types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
|
|
BIT(NL80211_IFTYPE_P2P_GO),
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_iface_combination
|
|
ath6kl_iface_combinations_p2p_concurrent4[] = {
|
|
{
|
|
.num_different_channels = 1,
|
|
.max_interfaces = 4,
|
|
.limits = ath6kl_limits_p2p_concurrent4,
|
|
.n_limits = ARRAY_SIZE(ath6kl_limits_p2p_concurrent4),
|
|
},
|
|
};
|
|
|
|
/* Max. 4 devices = 1STA + 1P2P-DEVICE + 1P2P-GO|P2P-CLIENT + 1SOFTAP */
|
|
static const struct ieee80211_iface_limit ath6kl_limits_p2p_concurrent4_1[] = {
|
|
{
|
|
.max = 3,
|
|
.types = BIT(NL80211_IFTYPE_STATION),
|
|
},
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_AP),
|
|
},
|
|
{
|
|
.max = 1,
|
|
.types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
|
|
BIT(NL80211_IFTYPE_P2P_GO),
|
|
},
|
|
};
|
|
|
|
static struct ieee80211_iface_combination
|
|
ath6kl_iface_combinations_p2p_concurrent4_1[] = {
|
|
{
|
|
.num_different_channels = 1,
|
|
.max_interfaces = 4,
|
|
.limits = ath6kl_limits_p2p_concurrent4_1,
|
|
.n_limits = ARRAY_SIZE(ath6kl_limits_p2p_concurrent4_1),
|
|
},
|
|
};
|
|
|
|
#define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */
|
|
|
|
#ifdef PMF_SUPPORT
|
|
#define WLAN_AKM_SUITE_8021X_SHA256 0x000FAC05
|
|
#define WLAN_AKM_SUITE_PSK_SHA256 0x000FAC06
|
|
|
|
static int ath6kl_set_akm_suites(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_connect_params *sme);
|
|
static int ath6kl_set_rsn_cap(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *ies, int ies_len);
|
|
#endif
|
|
|
|
static void ath6kl_eapol_shprotect_timer_handler(unsigned long ptr);
|
|
|
|
static int ath6kl_set_wpa_version(struct ath6kl_vif *vif,
|
|
enum nl80211_wpa_versions wpa_version)
|
|
{
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: %u\n", __func__, wpa_version);
|
|
|
|
if (!wpa_version) {
|
|
vif->auth_mode = NONE_AUTH;
|
|
} else if (wpa_version & NL80211_WPA_VERSION_2) {
|
|
vif->auth_mode = WPA2_AUTH;
|
|
} else if (wpa_version & NL80211_WPA_VERSION_1) {
|
|
vif->auth_mode = WPA_AUTH;
|
|
} else {
|
|
ath6kl_err("%s: %u not supported\n", __func__, wpa_version);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_set_auth_type(struct ath6kl_vif *vif,
|
|
enum nl80211_auth_type auth_type)
|
|
{
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: 0x%x\n", __func__, auth_type);
|
|
|
|
switch (auth_type) {
|
|
case NL80211_AUTHTYPE_OPEN_SYSTEM:
|
|
vif->dot11_auth_mode = OPEN_AUTH;
|
|
break;
|
|
case NL80211_AUTHTYPE_SHARED_KEY:
|
|
vif->dot11_auth_mode = SHARED_AUTH;
|
|
break;
|
|
case NL80211_AUTHTYPE_NETWORK_EAP:
|
|
vif->dot11_auth_mode = LEAP_AUTH;
|
|
break;
|
|
|
|
case NL80211_AUTHTYPE_AUTOMATIC:
|
|
vif->dot11_auth_mode = OPEN_AUTH | SHARED_AUTH;
|
|
break;
|
|
|
|
default:
|
|
ath6kl_err("%s: 0x%x not spported\n", __func__, auth_type);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_set_cipher(struct ath6kl_vif *vif, u32 cipher, bool ucast)
|
|
{
|
|
u8 *ar_cipher = ucast ? &vif->prwise_crypto : &vif->grp_crypto;
|
|
u8 *ar_cipher_len = ucast ? &vif->prwise_crypto_len :
|
|
&vif->grp_crypto_len;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: cipher 0x%x, ucast %u\n",
|
|
__func__, cipher, ucast);
|
|
|
|
switch (cipher) {
|
|
case 0:
|
|
/* our own hack to use value 0 as no crypto used */
|
|
*ar_cipher = NONE_CRYPT;
|
|
*ar_cipher_len = 0;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
*ar_cipher = WEP_CRYPT;
|
|
*ar_cipher_len = 5;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
*ar_cipher = WEP_CRYPT;
|
|
*ar_cipher_len = 13;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
*ar_cipher = TKIP_CRYPT;
|
|
*ar_cipher_len = 0;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
*ar_cipher = AES_CRYPT;
|
|
*ar_cipher_len = 0;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
*ar_cipher = WAPI_CRYPT;
|
|
*ar_cipher_len = 0;
|
|
break;
|
|
default:
|
|
ath6kl_err("cipher 0x%x not supported\n", cipher);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ath6kl_set_key_mgmt(struct ath6kl_vif *vif, u32 key_mgmt)
|
|
{
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: 0x%x\n", __func__, key_mgmt);
|
|
|
|
if (key_mgmt == WLAN_AKM_SUITE_PSK) {
|
|
if (vif->auth_mode == WPA_AUTH)
|
|
vif->auth_mode = WPA_PSK_AUTH;
|
|
else if (vif->auth_mode == WPA2_AUTH)
|
|
vif->auth_mode = WPA2_PSK_AUTH;
|
|
} else if (key_mgmt == 0x00409600) {
|
|
if (vif->auth_mode == WPA_AUTH)
|
|
vif->auth_mode = WPA_AUTH_CCKM;
|
|
else if (vif->auth_mode == WPA2_AUTH)
|
|
vif->auth_mode = WPA2_AUTH_CCKM;
|
|
#ifdef PMF_SUPPORT
|
|
} else if (key_mgmt == WLAN_AKM_SUITE_PSK_SHA256) {
|
|
vif->auth_mode = WPA2_PSK_SHA256_AUTH;
|
|
} else if ((key_mgmt != WLAN_AKM_SUITE_8021X) &&
|
|
(key_mgmt != WLAN_AKM_SUITE_8021X_SHA256)) {
|
|
#else
|
|
} else if (key_mgmt != WLAN_AKM_SUITE_8021X) {
|
|
#endif
|
|
vif->auth_mode = NONE_AUTH;
|
|
}
|
|
}
|
|
|
|
#ifdef ATH6KL_SUPPORT_WIFI_KTK
|
|
static void ath6kl_install_ktk_ptk(struct ath6kl_vif *vif)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
struct ath6kl_key *ptk = NULL;
|
|
enum wmi_sync_flag sync_flag = SYNC_BOTH_WMIFLAG;
|
|
int status;
|
|
char buff[32];
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s\n", __func__);
|
|
|
|
memset(buff, 0, 32);
|
|
memcpy(buff, ar->ktk_passphrase, 16);
|
|
|
|
/* set ptk at index 0 */
|
|
ptk = &vif->keys[0];
|
|
memset(ptk, 0, sizeof(struct ath6kl_key));
|
|
|
|
ptk->key_len = 16;
|
|
memcpy(ptk->key, ar->ktk_passphrase, ptk->key_len);
|
|
ptk->seq_len = 6;
|
|
memset(ptk->seq, 0, ptk->seq_len);
|
|
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_WMI_SYC))
|
|
sync_flag = NO_SYNC_WMIFLAG;
|
|
|
|
status = ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, 0,
|
|
KTK_CRYPT, GROUP_USAGE | TX_USAGE,
|
|
ptk->key_len,
|
|
ptk->seq,
|
|
ptk->seq_len,
|
|
ptk->key,
|
|
KEY_OP_INIT_VAL,
|
|
NULL,
|
|
sync_flag);
|
|
|
|
if (status)
|
|
ath6kl_err("%s: set ptk at index 0 failed\n", __func__);
|
|
}
|
|
#endif
|
|
|
|
static bool __ath6kl_cfg80211_ready(struct ath6kl *ar)
|
|
{
|
|
if (!test_bit(WMI_READY, &ar->flag)) {
|
|
ath6kl_err("wmi is not ready\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ath6kl_cfg80211_ready(struct ath6kl_vif *vif)
|
|
{
|
|
if (!__ath6kl_cfg80211_ready(vif->ar))
|
|
return false;
|
|
|
|
#if defined(USB_AUTO_SUSPEND)
|
|
if ((vif->ar->state == ATH6KL_STATE_WOW) ||
|
|
(vif->ar->state == ATH6KL_STATE_DEEPSLEEP) ||
|
|
(vif->ar->state == ATH6KL_STATE_PRE_SUSPEND)) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"autopm ignore wlan disabled in autopm mode!\n");
|
|
} else {
|
|
if (!test_bit(WLAN_ENABLED, &vif->flags))
|
|
return false;
|
|
}
|
|
#else
|
|
if (!test_bit(WLAN_ENABLED, &vif->flags))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ath6kl_is_wpa_ie(const u8 *pos)
|
|
{
|
|
return pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
|
|
pos[2] == 0x00 && pos[3] == 0x50 &&
|
|
pos[4] == 0xf2 && pos[5] == 0x01;
|
|
}
|
|
|
|
static bool ath6kl_is_rsn_ie(const u8 *pos)
|
|
{
|
|
return pos[0] == WLAN_EID_RSN;
|
|
}
|
|
|
|
static bool ath6kl_is_wps_ie(const u8 *pos)
|
|
{
|
|
return (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
|
|
pos[1] >= 4 &&
|
|
pos[2] == 0x00 && pos[3] == 0x50 && pos[4] == 0xf2 &&
|
|
pos[5] == 0x04);
|
|
}
|
|
|
|
static bool ath6kl_is_wmm_ie(const u8 *pos)
|
|
{
|
|
return (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
|
|
pos[1] >= 4 &&
|
|
pos[2] == 0x00 && pos[3] == 0x50 && pos[4] == 0xf2 &&
|
|
pos[5] == 0x02);
|
|
}
|
|
|
|
static bool ath6kl_is_exteneded_cap_ie(const u8 *pos)
|
|
{
|
|
return (pos[0] == WLAN_EID_EXT_CAPABILITY &&
|
|
pos[1] == 4);
|
|
}
|
|
|
|
static int ath6kl_set_assoc_req_ies(struct ath6kl_vif *vif, const u8 *ies,
|
|
size_t ies_len)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
const u8 *pos;
|
|
u8 *buf = NULL;
|
|
size_t len = 0;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Clear previously set flag
|
|
*/
|
|
|
|
vif->connect_ctrl_flags &= ~CONNECT_WPS_FLAG;
|
|
|
|
/*
|
|
* Filter out RSN/WPA or P2P/WFD IE(s)
|
|
*/
|
|
if (ies && ies_len) {
|
|
buf = kmalloc(ies_len, GFP_KERNEL);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
pos = ies;
|
|
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
|
|
/* Strip the EXTENDED CAPABILITY IE */
|
|
if (ath6kl_is_exteneded_cap_ie(pos)) {
|
|
pos += 2 + pos[1];
|
|
continue;
|
|
}
|
|
|
|
if (!(ath6kl_is_wpa_ie(pos) ||
|
|
ath6kl_is_rsn_ie(pos))) {
|
|
if ((ath6kl_is_p2p_ie(pos) ||
|
|
ath6kl_is_wfd_ie(pos)) &&
|
|
!ath6kl_p2p_ie_append(vif,
|
|
P2P_IE_IN_ASSOC_REQ))
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"Remove Asso's P2P IE\n");
|
|
else {
|
|
memcpy(buf + len, pos, 2 + pos[1]);
|
|
len += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
if (ath6kl_is_wps_ie(pos))
|
|
vif->connect_ctrl_flags |= CONNECT_WPS_FLAG;
|
|
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WMI_FRAME_ASSOC_REQ, buf, len);
|
|
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_nliftype_to_drv_iftype(enum nl80211_iftype type, u8 *nw_type)
|
|
{
|
|
switch (type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
*nw_type = INFRA_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
*nw_type = ADHOC_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
*nw_type = AP_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
*nw_type = INFRA_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
*nw_type = AP_NETWORK;
|
|
break;
|
|
default:
|
|
ath6kl_err("invalid interface type %u\n", type);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type,
|
|
u8 *if_idx, u8 *nw_type)
|
|
{
|
|
int i;
|
|
|
|
if (ath6kl_nliftype_to_drv_iftype(type, nw_type))
|
|
return false;
|
|
|
|
if (ar->ibss_if_active || ((type == NL80211_IFTYPE_ADHOC) &&
|
|
ar->num_vif))
|
|
return false;
|
|
|
|
/* we use firmware index in 1, 4, 3, 2 order to align with
|
|
* firmware design in the dedicate interface case
|
|
*/
|
|
if (ar->p2p_concurrent && ar->p2p_dedicate) {
|
|
if ((ar->p2p_concurrent_ap) &&
|
|
(type == NL80211_IFTYPE_AP)) {
|
|
for (i = 0; i < ar->max_norm_iface; i++) {
|
|
if ((ar->avail_idx_map >> i) & BIT(0)) {
|
|
*if_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (type == NL80211_IFTYPE_STATION ||
|
|
type == NL80211_IFTYPE_AP ||
|
|
type == NL80211_IFTYPE_ADHOC) {
|
|
for (i = (ar->vif_max - 1); i > 0; i--) {
|
|
if ((ar->avail_idx_map >> i) & BIT(0)) {
|
|
*if_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == NL80211_IFTYPE_P2P_CLIENT ||
|
|
type == NL80211_IFTYPE_P2P_GO) {
|
|
for (i = (ar->vif_max - 1); i >
|
|
(ar->max_norm_iface - 1); i--) {
|
|
if ((ar->avail_idx_map >> i) & BIT(0)) {
|
|
*if_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
if (type == NL80211_IFTYPE_STATION ||
|
|
type == NL80211_IFTYPE_AP || type == NL80211_IFTYPE_ADHOC) {
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
if ((ar->avail_idx_map >> i) & BIT(0)) {
|
|
*if_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == NL80211_IFTYPE_P2P_CLIENT ||
|
|
type == NL80211_IFTYPE_P2P_GO) {
|
|
for (i = ar->max_norm_iface; i < ar->vif_max; i++) {
|
|
if ((ar->avail_idx_map >> i) & BIT(0)) {
|
|
*if_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* avoid using roam in p2p, ap, adhoc mode, and current USB devices */
|
|
/* use in connect_cmd, disconnect event, ap_start */
|
|
/* only support lrssi roam at this time*/
|
|
void ath6kl_judge_roam_parameter(
|
|
struct ath6kl_vif *vif,
|
|
bool call_on_disconnect)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
u8 connected_count = 0;
|
|
bool lrssi_scan_enable = true;
|
|
struct ath6kl_vif *vif_temp;
|
|
|
|
if (ar->roam_mode == ATH6KL_MODULEROAM_DISABLE ||
|
|
ar->roam_mode == ATH6KL_MODULEROAM_DISABLE_LRSSI_SCAN) {
|
|
lrssi_scan_enable = false;
|
|
goto DONE;
|
|
} else if (ar->roam_mode == ATH6KL_MODULEROAM_NO_LRSSI_SCAN_AT_MULTI) {
|
|
|
|
lrssi_scan_enable = true;
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
if (vif->fw_vif_idx == vif_temp->fw_vif_idx)
|
|
continue;
|
|
if (test_bit(CONNECTED, &vif_temp->flags))
|
|
connected_count++;
|
|
|
|
if (call_on_disconnect) {
|
|
if ((connected_count == 1) &&
|
|
(vif_temp->wdev.iftype ==
|
|
NL80211_IFTYPE_STATION)) {
|
|
lrssi_scan_enable = true;
|
|
} else {
|
|
lrssi_scan_enable = false;
|
|
}
|
|
} else if (connected_count) {
|
|
lrssi_scan_enable = false;
|
|
goto DONE;
|
|
}
|
|
}
|
|
} else { /* ATH6KL_MODULEROAM_ENABLE_ALL */
|
|
lrssi_scan_enable = true;
|
|
}
|
|
|
|
/* disable roam when it is in any other mode */
|
|
if (!call_on_disconnect &&
|
|
vif->wdev.iftype != NL80211_IFTYPE_STATION)
|
|
lrssi_scan_enable = false;
|
|
|
|
DONE:
|
|
if (lrssi_scan_enable) {
|
|
ath6kl_wmi_set_roam_ctrl_cmd(ar->wmi,
|
|
0,
|
|
ar->low_rssi_roam_params.lrssi_scan_period,
|
|
ar->low_rssi_roam_params.lrssi_scan_threshold,
|
|
ar->low_rssi_roam_params.lrssi_roam_threshold,
|
|
ar->low_rssi_roam_params.roam_rssi_floor);
|
|
} else {
|
|
ath6kl_wmi_set_roam_ctrl_cmd(ar->wmi,
|
|
0, 0xFFFF, 0, 0, 100);
|
|
}
|
|
|
|
}
|
|
|
|
static void switch_tid_rx_timeout(
|
|
struct ath6kl_vif *vif,
|
|
bool enlarge_aggr_rx_timer)
|
|
{
|
|
u8 i, j = 0;
|
|
struct aggr_conn_info *aggr_conn;
|
|
struct rxtid *rxtid;
|
|
|
|
for (i = 0; i < AP_MAX_NUM_STA; i++) {
|
|
aggr_conn = vif->sta_list[i].aggr_conn_cntxt;
|
|
for (j = 0; j < NUM_OF_TIDS; j++) {
|
|
rxtid = AGGR_GET_RXTID(aggr_conn, j);
|
|
spin_lock_bh(&rxtid->lock);
|
|
switch (up_to_ac[j]) {
|
|
case WMM_AC_BK:
|
|
case WMM_AC_BE:
|
|
case WMM_AC_VI:
|
|
if (enlarge_aggr_rx_timer)
|
|
aggr_conn->tid_timeout_setting[j] =
|
|
MCC_AGGR_RX_TIMEOUT;
|
|
else
|
|
aggr_conn->tid_timeout_setting[j] =
|
|
AGGR_RX_TIMEOUT;
|
|
break;
|
|
case WMM_AC_VO:
|
|
if (enlarge_aggr_rx_timer)
|
|
aggr_conn->tid_timeout_setting[j] =
|
|
MCC_AGGR_RX_TIMEOUT_VO;
|
|
else
|
|
aggr_conn->tid_timeout_setting[j] =
|
|
AGGR_RX_TIMEOUT_VO;
|
|
break;
|
|
}
|
|
spin_unlock_bh(&rxtid->lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
void ath6kl_check_autopm_onoff(struct ath6kl *ar, bool call_on_disconnect)
|
|
{
|
|
struct ath6kl_vif *vif_temp;
|
|
/*
|
|
* Switch Auto PM On/Off
|
|
* In folloing case, we will turn off AUTOPM
|
|
* 1. p2p0-P2P-xxx is connected
|
|
* 2. any nw_type is AP network
|
|
*/
|
|
int vif_cnt = 0;
|
|
int autopm_turn_on = 1;
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
vif_cnt++;
|
|
if (test_bit(CONNECTED, &vif_temp->flags)) {
|
|
if (vif_cnt > 1) {
|
|
autopm_turn_on = 0;
|
|
break;
|
|
}
|
|
if (vif_temp->nw_type == AP_NETWORK) {
|
|
autopm_turn_on = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_AUTOPM,
|
|
"autopm next mode(%d) %s --> %s, vif_cnt %d cur-delay_cnt/time %d/%d\n",
|
|
call_on_disconnect,
|
|
(ar->autopm_turn_on ? "ON" : "OFF"),
|
|
(autopm_turn_on ? "ON" : "OFF"),
|
|
vif_cnt,
|
|
ar->autopm_defer_delay_change_cnt,
|
|
ar->autopm_curr_delay_time);
|
|
|
|
if (autopm_turn_on) {
|
|
ath6kl_hif_auto_pm_turnon(ar);
|
|
if (call_on_disconnect) {
|
|
ar->autopm_defer_delay_change_cnt =
|
|
USB_SUSPEND_DEFER_DELAY_CHANGE_CNT;
|
|
ath6kl_hif_auto_pm_set_delay(ar,
|
|
USB_SUSPEND_DELAY_REENABLE);
|
|
}
|
|
} else {
|
|
ath6kl_hif_auto_pm_turnoff(ar);
|
|
ar->autopm_defer_delay_change_cnt = 0;
|
|
ath6kl_hif_auto_pm_set_delay(ar,
|
|
USB_SUSPEND_DELAY_MAX);
|
|
}
|
|
|
|
ar->autopm_turn_on = autopm_turn_on;
|
|
}
|
|
#endif
|
|
|
|
/* This function is used by sta/p2pclient/go interface to switch paramters
|
|
* needed for MCC/connection specific parameters
|
|
*/
|
|
void ath6kl_switch_parameter_based_on_connection(
|
|
struct ath6kl_vif *vif,
|
|
bool call_on_disconnect)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
u8 connected_count = 0;
|
|
struct ath6kl_vif *vif_temp;
|
|
bool mcc = false;
|
|
u16 pre_vifch = 0;
|
|
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
if (test_bit(CONNECTED, &vif_temp->flags)) {
|
|
connected_count++;
|
|
if (pre_vifch == 0) {
|
|
pre_vifch = vif_temp->bss_ch;
|
|
} else if (vif_temp->bss_ch != 0 &&
|
|
pre_vifch != vif_temp->bss_ch) {
|
|
mcc = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (connected_count) {
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
if (test_bit(CONNECTED, &vif_temp->flags)) {
|
|
if (call_on_disconnect &&
|
|
vif->fw_vif_idx == vif_temp->fw_vif_idx)
|
|
continue;
|
|
if (vif->wdev.iftype ==
|
|
NL80211_IFTYPE_STATION && !mcc)
|
|
switch_tid_rx_timeout(vif_temp, false);
|
|
else
|
|
switch_tid_rx_timeout(vif_temp, true);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Delete rekey protection timer if
|
|
* 1. all vifs disconnected
|
|
* 2. the timer was only fireed by this vif.
|
|
*/
|
|
if ((call_on_disconnect) &&
|
|
test_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag)) {
|
|
if ((connected_count == 0) ||
|
|
(ar->eapol_shprotect_vif == (1 << vif->fw_vif_idx))) {
|
|
clear_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag);
|
|
del_timer(&ar->eapol_shprotect_timer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Switch scan parameter
|
|
* Currently, when there are more than one vif connected,
|
|
* revise the passive dwell time as the default
|
|
* ATH6KL_SCAN_PAS_DEWELL_TIME
|
|
*/
|
|
|
|
if (connected_count) {
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
vif_temp->sc_params.pas_chdwell_time =
|
|
ATH6KL_SCAN_PAS_DEWELL_TIME;
|
|
}
|
|
} else {
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
memcpy(&vif->sc_params,
|
|
&vif->sc_params_default,
|
|
sizeof(struct wmi_scan_params_cmd));
|
|
}
|
|
}
|
|
|
|
if (mcc)
|
|
set_bit(MCC_ENABLED, &ar->flag);
|
|
else
|
|
clear_bit(MCC_ENABLED, &ar->flag);
|
|
|
|
if (ar->conf_flags & ATH6KL_CONF_ENABLE_FLOWCTRL) {
|
|
if (ar->conf_flags & ATH6KL_CONF_DISABLE_SKIP_FLOWCTRL) {
|
|
clear_bit(SKIP_FLOWCTRL_EVENT, &ar->flag);
|
|
} else {
|
|
if (test_bit(MCC_ENABLED, &ar->flag))
|
|
clear_bit(SKIP_FLOWCTRL_EVENT, &ar->flag);
|
|
else
|
|
set_bit(SKIP_FLOWCTRL_EVENT, &ar->flag);
|
|
}
|
|
} else {
|
|
clear_bit(SKIP_FLOWCTRL_EVENT, &ar->flag);
|
|
}
|
|
|
|
if (mcc) {
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
if (test_bit(CONNECTED, &vif_temp->flags)) {
|
|
if (call_on_disconnect &&
|
|
vif->fw_vif_idx == vif_temp->fw_vif_idx)
|
|
continue;
|
|
if (vif_temp->nw_type == AP_NETWORK) {
|
|
ath6kl_ap_keepalive_config(vif_temp,
|
|
ATH6KL_AP_KA_INTERVAL_DEFAULT,
|
|
ATH6KL_AP_KA_RECLAIM_CYCLE_MCC);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
list_for_each_entry(vif_temp, &ar->vif_list, list) {
|
|
if (test_bit(CONNECTED, &vif_temp->flags)) {
|
|
if (call_on_disconnect &&
|
|
vif->fw_vif_idx == vif_temp->fw_vif_idx)
|
|
continue;
|
|
if (vif_temp->nw_type == AP_NETWORK) {
|
|
ath6kl_ap_keepalive_config(vif_temp,
|
|
ATH6KL_AP_KA_INTERVAL_DEFAULT,
|
|
ATH6KL_AP_KA_RECLAIM_CYCLE_SCC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update HIF queue policy */
|
|
ath6kl_hif_pipe_set_max_queue_number(ar, mcc);
|
|
|
|
/* Reconfigurate the PS mode case by case. */
|
|
ath6kl_p2p_reconfig_ps(ar, mcc, call_on_disconnect);
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
ath6kl_check_autopm_onoff(ar, call_on_disconnect);
|
|
#endif /* USB_AUTO_SUSPEND */
|
|
}
|
|
|
|
static inline void _change_sta_scan_plan(struct ath6kl_vif *vif)
|
|
{
|
|
if (vif->ssid_len) {
|
|
int i, chan_num;
|
|
u16 chan_list[64]; /* WMI_MAX_CHANNELS */
|
|
|
|
memset(chan_list, 0, sizeof(u16) * 64);
|
|
chan_num = ath6kl_bss_post_proc_candidate_bss(vif,
|
|
vif->ssid,
|
|
vif->ssid_len,
|
|
chan_list);
|
|
|
|
if (chan_num) {
|
|
vif->scan_plan.type = ATH6KL_SCAN_PLAN_HOST_ORDER;
|
|
|
|
/* For STA, prefer to use 5G APs. */
|
|
if (chan_num <= 21) { /* (64 / 3) */
|
|
vif->scan_plan.numChan = chan_num * 3;
|
|
|
|
for (i = 0; i < chan_num; i++)
|
|
vif->scan_plan.chanList[i * 3] =
|
|
vif->scan_plan.chanList[i * 3 + 1] =
|
|
vif->scan_plan.chanList[i * 3 + 2] =
|
|
chan_list[chan_num - 1 - i];
|
|
} else {
|
|
vif->scan_plan.numChan = chan_num;
|
|
|
|
for (i = 0; i < chan_num; i++)
|
|
vif->scan_plan.chanList[i] =
|
|
chan_list[chan_num - 1 - i];
|
|
}
|
|
|
|
/*
|
|
* If only one channel need to try and 1s' disconnection
|
|
* timeout is enough.
|
|
*/
|
|
if (chan_num == 1)
|
|
ath6kl_wmi_disctimeout_cmd(vif->ar->wmi,
|
|
vif->fw_vif_idx,
|
|
1);
|
|
} else
|
|
vif->scan_plan.type = ATH6KL_SCAN_PLAN_REVERSE_ORDER;
|
|
} else
|
|
vif->scan_plan.type = ATH6KL_SCAN_PLAN_REVERSE_ORDER;
|
|
|
|
return;
|
|
}
|
|
|
|
static inline void _change_p2p_scan_plan(struct ath6kl_vif *vif)
|
|
{
|
|
#define _MAX_STAY_TIME (200) /* 2 Beacon Time */
|
|
struct ath6kl *ar = vif->ar;
|
|
struct ath6kl_vif *tmp;
|
|
bool change_scan_plan = true;
|
|
int i;
|
|
|
|
/*
|
|
* Only fine tune for SCC case to stay the target channel
|
|
* at least 2 beacon time for connection try rather than report
|
|
* disconnect immediately.
|
|
*/
|
|
|
|
list_for_each_entry(tmp, &ar->vif_list, list) {
|
|
if ((tmp != vif) &&
|
|
test_bit(CONNECTED, &tmp->flags) &&
|
|
(tmp->bss_ch != vif->ch_hint)) {
|
|
change_scan_plan = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (change_scan_plan) {
|
|
vif->scan_plan.type = ATH6KL_SCAN_PLAN_HOST_ORDER;
|
|
vif->scan_plan.numChan = _MAX_STAY_TIME /
|
|
vif->sc_params.maxact_chdwell_time;
|
|
|
|
for (i = 0; i < vif->scan_plan.numChan; i++)
|
|
vif->scan_plan.chanList[i] = vif->ch_hint;
|
|
}
|
|
|
|
return;
|
|
#undef _MAX_STAY_TIME
|
|
}
|
|
|
|
void ath6kl_change_scan_plan(struct ath6kl_vif *vif, bool reset)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
/* Default */
|
|
vif->scan_plan.type = ATH6KL_SCAN_PLAN_REVERSE_ORDER;
|
|
vif->scan_plan.numChan = 0;
|
|
|
|
if (!reset) {
|
|
/*
|
|
* Currently, only fine tune scan-plan when turn-on target
|
|
* roaming.
|
|
*/
|
|
if (!(ar->wiphy->flags & WIPHY_FLAG_SUPPORTS_FW_ROAM))
|
|
goto done;
|
|
|
|
if (vif->wdev.iftype == NL80211_IFTYPE_STATION)
|
|
_change_sta_scan_plan(vif);
|
|
else if ((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) &&
|
|
(vif->ssid_len)) {
|
|
struct cfg80211_bss *bss;
|
|
|
|
/*
|
|
* Firmware roaming is global setting in cfg80211 but
|
|
* not in target and P2P-Client actually no need
|
|
* roaming and therefore pass the channel & bssid hint
|
|
* to target.
|
|
*/
|
|
bss = ath6kl_bss_get(vif->ar,
|
|
NULL,
|
|
NULL,
|
|
vif->ssid,
|
|
vif->ssid_len,
|
|
WLAN_CAPABILITY_ESS,
|
|
WLAN_CAPABILITY_ESS);
|
|
|
|
if (bss && bss->channel) {
|
|
#ifdef CFG80211_SAFE_BSS_INFO_ACCESS
|
|
rcu_read_lock();
|
|
#endif
|
|
vif->ch_hint = bss->channel->center_freq;
|
|
memcpy(vif->req_bssid, bss->bssid, ETH_ALEN);
|
|
#ifdef CFG80211_SAFE_BSS_INFO_ACCESS
|
|
rcu_read_unlock();
|
|
#endif
|
|
}
|
|
|
|
if (bss)
|
|
ath6kl_bss_put(vif->ar, bss);
|
|
|
|
/* Change scan-plan to host order if need. */
|
|
_change_p2p_scan_plan(vif);
|
|
}
|
|
}
|
|
|
|
done:
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG | ATH6KL_DBG_EXT_SCAN,
|
|
"change scan plan, vif %d reset %d type %d numChan %d\n",
|
|
vif->fw_vif_idx,
|
|
reset,
|
|
vif->scan_plan.type,
|
|
vif->scan_plan.numChan);
|
|
|
|
/* Update to the target */
|
|
ath6kl_wmi_set_scan_chan_plan(ar->wmi,
|
|
vif->fw_vif_idx,
|
|
vif->scan_plan.type,
|
|
vif->scan_plan.numChan,
|
|
vif->scan_plan.chanList);
|
|
|
|
return;
|
|
}
|
|
|
|
/* WAR some framework implementation,
|
|
* Called after auth/prwise,dot11 get set.
|
|
*/
|
|
static void ath6kl_wep_auth_auto(struct ath6kl_vif *vif)
|
|
{
|
|
if ((vif->prwise_crypto_len != 0) &&
|
|
(vif->auth_mode == NONE_AUTH) &&
|
|
(vif->prwise_crypto == WEP_CRYPT) &&
|
|
(vif->dot11_auth_mode == OPEN_AUTH)) {
|
|
|
|
vif->dot11_auth_mode = OPEN_AUTH | SHARED_AUTH;
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s,"
|
|
"dot11_auth_mode change %d\n",
|
|
__func__, vif->dot11_auth_mode);
|
|
}
|
|
}
|
|
|
|
static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_connect_params *sme)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int status, left;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) {
|
|
ath6kl_err("%s destroy in progress %lu\n", __func__ ,ar->flag);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (test_bit(SKIP_SCAN, &ar->flag) &&
|
|
((sme->channel && sme->channel->center_freq == 0) ||
|
|
(sme->bssid && is_zero_ether_addr(sme->bssid)))) {
|
|
ath6kl_err("SkipScan: channel or bssid invalid\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ar->autopm_turn_on) {
|
|
ar->autopm_defer_delay_change_cnt =
|
|
USB_SUSPEND_DEFER_DELAY_FOR_P2P;
|
|
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_MAX);
|
|
}
|
|
#endif
|
|
|
|
vif->sme_state = SME_CONNECTING;
|
|
|
|
if (ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)]) {
|
|
/*
|
|
* sleep until the command queue drains
|
|
*/
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)] == 0,
|
|
WMI_TIMEOUT);
|
|
if (left == 0) {
|
|
ath6kl_warn("clear wmi ctrl data timeout connect\n");
|
|
up(&ar->sem);
|
|
return -ETIMEDOUT;
|
|
} else if (signal_pending(current)) {
|
|
ath6kl_err("cmd queue drain timeout\n");
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
/* Diable background scan */
|
|
vif->sc_params.bg_period = 0xFFFF;
|
|
ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->sc_params.fg_start_period,
|
|
vif->sc_params.fg_end_period,
|
|
vif->sc_params.bg_period,
|
|
vif->sc_params.minact_chdwell_time,
|
|
vif->sc_params.maxact_chdwell_time,
|
|
vif->sc_params.pas_chdwell_time,
|
|
vif->sc_params.short_scan_ratio,
|
|
vif->sc_params.scan_ctrl_flags,
|
|
vif->sc_params.max_dfsch_act_time,
|
|
vif->sc_params.maxact_scan_per_ssid);
|
|
|
|
ath6kl_judge_roam_parameter(vif, false);
|
|
|
|
if (vif->wdev.iftype == NL80211_IFTYPE_STATION)
|
|
ath6kl_wmi_set_green_tx_params(ar->wmi, &ar->green_tx_params);
|
|
|
|
if (ath6kl_wmi_set_rate_ctrl_cmd(ar->wmi,
|
|
vif->fw_vif_idx, RATECTRL_MODE_PERONLY))
|
|
ath6kl_err("set rate_ctrl failed\n");
|
|
|
|
if (sme->ie && (sme->ie_len > 0)) {
|
|
status = ath6kl_set_assoc_req_ies(vif, sme->ie, sme->ie_len);
|
|
if (status) {
|
|
up(&ar->sem);
|
|
return status;
|
|
}
|
|
} else
|
|
vif->connect_ctrl_flags &= ~CONNECT_WPS_FLAG;
|
|
|
|
#ifdef PMF_SUPPORT
|
|
/* Update AKM suites. */
|
|
if (ath6kl_set_akm_suites(wiphy, dev, sme)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Update RSN Capabilities. */
|
|
if (ath6kl_set_rsn_cap(wiphy, dev, sme->ie, sme->ie_len)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
#endif
|
|
|
|
vif->connect_ctrl_flags |= CONNECT_IGNORE_WPAx_GROUP_CIPHER;
|
|
|
|
if (test_bit(CONNECTED, &vif->flags) &&
|
|
vif->ssid_len == sme->ssid_len &&
|
|
!memcmp(vif->ssid, sme->ssid, vif->ssid_len)) {
|
|
vif->reconnect_flag = true;
|
|
status = ath6kl_wmi_reconnect_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->req_bssid,
|
|
vif->ch_hint);
|
|
|
|
up(&ar->sem);
|
|
if (status) {
|
|
ath6kl_err("wmi_reconnect_cmd failed\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
} else if (vif->ssid_len == sme->ssid_len &&
|
|
!memcmp(vif->ssid, sme->ssid, vif->ssid_len)) {
|
|
ath6kl_disconnect(vif);
|
|
}
|
|
|
|
memset(vif->ssid, 0, sizeof(vif->ssid));
|
|
vif->ssid_len = sme->ssid_len;
|
|
memcpy(vif->ssid, sme->ssid, sme->ssid_len);
|
|
|
|
if (sme->channel)
|
|
vif->ch_hint = sme->channel->center_freq;
|
|
|
|
memset(vif->req_bssid, 0, sizeof(vif->req_bssid));
|
|
if (sme->bssid && !is_broadcast_ether_addr(sme->bssid))
|
|
memcpy(vif->req_bssid, sme->bssid, sizeof(vif->req_bssid));
|
|
|
|
ath6kl_set_wpa_version(vif, sme->crypto.wpa_versions);
|
|
|
|
status = ath6kl_set_auth_type(vif, sme->auth_type);
|
|
if (status) {
|
|
up(&ar->sem);
|
|
return status;
|
|
}
|
|
|
|
if (sme->crypto.n_ciphers_pairwise)
|
|
ath6kl_set_cipher(vif, sme->crypto.ciphers_pairwise[0], true);
|
|
else
|
|
ath6kl_set_cipher(vif, 0, true);
|
|
|
|
ath6kl_set_cipher(vif, sme->crypto.cipher_group, false);
|
|
|
|
if (sme->crypto.n_akm_suites)
|
|
ath6kl_set_key_mgmt(vif, sme->crypto.akm_suites[0]);
|
|
|
|
/* WAR framework for wep auth */
|
|
ath6kl_wep_auth_auto(vif);
|
|
|
|
if ((sme->key_len) &&
|
|
(vif->auth_mode == NONE_AUTH) &&
|
|
(vif->prwise_crypto == WEP_CRYPT)) {
|
|
struct ath6kl_key *key = NULL;
|
|
|
|
if (sme->key_idx > WMI_MAX_KEY_INDEX) {
|
|
ath6kl_err("key index %d out of bounds\n",
|
|
sme->key_idx);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
key = &vif->keys[sme->key_idx];
|
|
key->key_len = sme->key_len;
|
|
memcpy(key->key, sme->key, key->key_len);
|
|
key->cipher = vif->prwise_crypto;
|
|
vif->def_txkey_index = sme->key_idx;
|
|
|
|
ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, sme->key_idx,
|
|
vif->prwise_crypto,
|
|
GROUP_USAGE | TX_USAGE,
|
|
key->key_len,
|
|
NULL, 0,
|
|
key->key, KEY_OP_INIT_VAL, NULL,
|
|
NO_SYNC_WMIFLAG);
|
|
}
|
|
|
|
if (!vif->usr_bss_filter) {
|
|
clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
|
|
if (ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ALL_BSS_FILTER, 0) != 0) {
|
|
ath6kl_err("couldn't set bss filtering\n");
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
vif->nw_type = vif->next_mode;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: connect called with authmode %d dot11 auth %d"
|
|
" PW crypto %d PW crypto len %d GRP crypto %d"
|
|
" GRP crypto len %d channel hint %u\n",
|
|
__func__,
|
|
vif->auth_mode, vif->dot11_auth_mode, vif->prwise_crypto,
|
|
vif->prwise_crypto_len, vif->grp_crypto,
|
|
vif->grp_crypto_len, vif->ch_hint);
|
|
|
|
vif->reconnect_flag = 0;
|
|
|
|
/*
|
|
* Set disconnection timeout to 0 to cause firmware report
|
|
* disconnection event immediately rather than waiting defualt timer
|
|
* timeout (10sec) or supplicant trigger connection timeout (10sec).
|
|
*/
|
|
ath6kl_wmi_disctimeout_cmd(ar->wmi, vif->fw_vif_idx, 0);
|
|
|
|
ath6kl_change_scan_plan(vif, false);
|
|
|
|
status = ath6kl_wmi_connect_cmd(ar->wmi, vif->fw_vif_idx, vif->nw_type,
|
|
vif->dot11_auth_mode, vif->auth_mode,
|
|
vif->prwise_crypto,
|
|
vif->prwise_crypto_len,
|
|
vif->grp_crypto, vif->grp_crypto_len,
|
|
vif->ssid_len, vif->ssid,
|
|
vif->req_bssid, vif->ch_hint,
|
|
vif->connect_ctrl_flags);
|
|
|
|
up(&ar->sem);
|
|
|
|
if (status == -EINVAL) {
|
|
memset(vif->ssid, 0, sizeof(vif->ssid));
|
|
vif->ssid_len = 0;
|
|
ath6kl_err("invalid request\n");
|
|
return -ENOENT;
|
|
} else if (status) {
|
|
ath6kl_err("ath6kl_wmi_connect_cmd failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if ((!(vif->connect_ctrl_flags & CONNECT_DO_WPA_OFFLOAD)) &&
|
|
((vif->auth_mode == WPA_PSK_AUTH)
|
|
|| (vif->auth_mode == WPA2_PSK_AUTH))) {
|
|
mod_timer(&vif->disconnect_timer,
|
|
jiffies + msecs_to_jiffies(DISCON_TIMER_INTVAL));
|
|
}
|
|
|
|
vif->connect_ctrl_flags &= ~CONNECT_DO_WPA_OFFLOAD;
|
|
set_bit(CONNECT_PEND, &vif->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct cfg80211_bss *ath6kl_add_bss_if_needed(struct ath6kl_vif *vif,
|
|
enum network_type nw_type,
|
|
const u8 *bssid,
|
|
struct ieee80211_channel *chan,
|
|
const u8 *beacon_ie, size_t beacon_ie_len)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
struct cfg80211_bss *bss;
|
|
u16 cap_mask, cap_val;
|
|
u8 *ie;
|
|
|
|
if (nw_type & ADHOC_NETWORK) {
|
|
cap_mask = WLAN_CAPABILITY_IBSS;
|
|
cap_val = WLAN_CAPABILITY_IBSS;
|
|
} else {
|
|
cap_mask = WLAN_CAPABILITY_ESS;
|
|
cap_val = WLAN_CAPABILITY_ESS;
|
|
}
|
|
|
|
bss = ath6kl_bss_get(ar, chan, bssid,
|
|
vif->ssid, vif->ssid_len,
|
|
cap_mask, cap_val);
|
|
if (bss == NULL) {
|
|
/*
|
|
* Since cfg80211 may not yet know about the BSS,
|
|
* generate a partial entry until the first BSS info
|
|
* event becomes available.
|
|
*
|
|
* Prepend SSID element since it is not included in the Beacon
|
|
* IEs from the target.
|
|
*/
|
|
ie = kmalloc(2 + vif->ssid_len + beacon_ie_len, GFP_KERNEL);
|
|
if (ie == NULL)
|
|
return NULL;
|
|
ie[0] = WLAN_EID_SSID;
|
|
ie[1] = vif->ssid_len;
|
|
memcpy(ie + 2, vif->ssid, vif->ssid_len);
|
|
memcpy(ie + 2 + vif->ssid_len, beacon_ie, beacon_ie_len);
|
|
bss = cfg80211_inform_bss(ar->wiphy, chan,
|
|
bssid, 0, cap_val, 100,
|
|
ie, 2 + vif->ssid_len + beacon_ie_len,
|
|
0, GFP_KERNEL);
|
|
if (bss)
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "added bss %pM to "
|
|
"cfg80211\n", bssid);
|
|
kfree(ie);
|
|
} else
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "cfg80211 already has a bss\n");
|
|
|
|
return bss;
|
|
}
|
|
|
|
static bool ath6kl_roamed_indicate(struct ath6kl_vif *vif,
|
|
u8 *bssid, bool reset_hk_prot)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
if ((vif->sme_state == SME_CONNECTED) &&
|
|
(ar->wiphy->flags & WIPHY_FLAG_SUPPORTS_FW_ROAM) &&
|
|
((!test_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags)) ||
|
|
(reset_hk_prot == true))) {
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: %pM roam, vif->flags 0x%lu,"
|
|
"reset_hk_prot %x\n",
|
|
__func__, bssid, vif->flags, reset_hk_prot);
|
|
return true;
|
|
}
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: %pM notroam\n", __func__, bssid);
|
|
return false;
|
|
}
|
|
|
|
static bool ath6kl_handshake_protect(struct ath6kl_vif *vif, u8 *bssid)
|
|
{
|
|
bool reset_handshake_pro = false;
|
|
|
|
if ((vif->auth_mode > NONE_AUTH) &&
|
|
(vif->prwise_crypto > WEP_CRYPT) &&
|
|
(vif->nw_type == INFRA_NETWORK)) {
|
|
|
|
if ((memcmp(bssid, vif->bssid, ETH_ALEN) != 0) ||
|
|
(!test_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags))) {
|
|
spin_lock_bh(&vif->if_lock);
|
|
set_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags);
|
|
if (vif->pend_skb)
|
|
ath6kl_flush_pend_skb(vif);
|
|
set_bit(FIRST_EAPOL_PENDSENT, &vif->flags);
|
|
mod_timer(&vif->shprotect_timer,
|
|
jiffies + ATH6KL_HANDSHAKE_PROC_TIMEOUT);
|
|
spin_unlock_bh(&vif->if_lock);
|
|
reset_handshake_pro = true;
|
|
} else {
|
|
reset_handshake_pro = false;
|
|
}
|
|
|
|
} else {
|
|
spin_lock_bh(&vif->if_lock);
|
|
clear_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags);
|
|
if (vif->pend_skb)
|
|
ath6kl_flush_pend_skb(vif);
|
|
del_timer(&vif->shprotect_timer);
|
|
spin_unlock_bh(&vif->if_lock);
|
|
reset_handshake_pro = false;
|
|
}
|
|
|
|
return reset_handshake_pro;
|
|
}
|
|
|
|
void ath6kl_cfg80211_connect_result(struct ath6kl_vif *vif,
|
|
const u8 *bssid,
|
|
const u8 *req_ie,
|
|
size_t req_ie_len,
|
|
const u8 *resp_ie,
|
|
size_t resp_ie_len,
|
|
u16 status,
|
|
gfp_t gfp)
|
|
{
|
|
bool need_pending;
|
|
|
|
ath6kl_change_scan_plan(vif, true);
|
|
|
|
need_pending = ath6kl_p2p_pending_connect_event(vif,
|
|
bssid,
|
|
req_ie,
|
|
req_ie_len,
|
|
resp_ie,
|
|
resp_ie_len,
|
|
status,
|
|
gfp);
|
|
if (!need_pending)
|
|
cfg80211_connect_result(vif->ndev,
|
|
bssid,
|
|
req_ie,
|
|
req_ie_len,
|
|
resp_ie,
|
|
resp_ie_len,
|
|
status,
|
|
gfp);
|
|
|
|
return;
|
|
}
|
|
|
|
void ath6kl_cfg80211_disconnected(struct ath6kl_vif *vif,
|
|
u16 reason,
|
|
u8 *ie,
|
|
size_t ie_len,
|
|
gfp_t gfp)
|
|
{
|
|
ath6kl_change_scan_plan(vif, true);
|
|
|
|
ath6kl_p2p_pending_disconnect_event(vif, reason, ie, ie_len, gfp);
|
|
cfg80211_disconnected(vif->ndev,
|
|
reason,
|
|
ie,
|
|
ie_len,
|
|
gfp);
|
|
|
|
return;
|
|
}
|
|
|
|
void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel,
|
|
u8 *bssid, u16 listen_intvl,
|
|
u16 beacon_intvl,
|
|
enum network_type nw_type,
|
|
u8 beacon_ie_len, u8 assoc_req_len,
|
|
u8 assoc_resp_len, u8 *assoc_info)
|
|
{
|
|
struct ieee80211_channel *chan;
|
|
struct ath6kl *ar = vif->ar;
|
|
struct cfg80211_bss *bss;
|
|
bool reset_hk_prot = false;
|
|
|
|
/* capinfo + listen interval */
|
|
u8 assoc_req_ie_offset = sizeof(u16) + sizeof(u16);
|
|
|
|
/* capinfo + status code + associd */
|
|
u8 assoc_resp_ie_offset = sizeof(u16) + sizeof(u16) + sizeof(u16);
|
|
|
|
u8 *assoc_req_ie = assoc_info + beacon_ie_len + assoc_req_ie_offset;
|
|
u8 *assoc_resp_ie = assoc_info + beacon_ie_len + assoc_req_len +
|
|
assoc_resp_ie_offset;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: bssid %pM\n", __func__, bssid);
|
|
|
|
assoc_req_len -= assoc_req_ie_offset;
|
|
assoc_resp_len -= assoc_resp_ie_offset;
|
|
|
|
/*
|
|
* Store Beacon interval here; DTIM period will be available only once
|
|
* a Beacon frame from the AP is seen.
|
|
*/
|
|
vif->assoc_bss_beacon_int = beacon_intvl;
|
|
clear_bit(DTIM_PERIOD_AVAIL, &vif->flags);
|
|
|
|
if (nw_type & ADHOC_NETWORK) {
|
|
if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: ath6k not in ibss mode\n", __func__);
|
|
return;
|
|
}
|
|
#ifdef ATH6KL_SUPPORT_WIFI_KTK
|
|
if (ar->ktk_active)
|
|
ath6kl_install_ktk_ptk(vif);
|
|
#endif
|
|
}
|
|
|
|
if (nw_type & INFRA_NETWORK) {
|
|
if (vif->wdev.iftype != NL80211_IFTYPE_STATION &&
|
|
vif->wdev.iftype != NL80211_IFTYPE_P2P_CLIENT) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: ath6k not in station mode\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
chan = ieee80211_get_channel(ar->wiphy, (int) channel);
|
|
|
|
if (chan == NULL) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: ath6k could not get channel information\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
bss = ath6kl_add_bss_if_needed(vif, nw_type, bssid, chan,
|
|
assoc_info, beacon_ie_len);
|
|
if (!bss) {
|
|
ath6kl_err("could not add cfg80211 bss entry\n");
|
|
return;
|
|
}
|
|
|
|
if (nw_type & ADHOC_NETWORK) {
|
|
#ifdef ATH6KL_DIAGNOSTIC
|
|
wifi_diag_mac_fsm_event(vif,
|
|
(enum wifi_diag_mac_fsm_t)WIFI_DIAG_MAC_FSM_CONNECTED,
|
|
vif->diag.connect_seq_num);
|
|
#endif
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "ad-hoc %s selected\n",
|
|
nw_type & ADHOC_CREATOR ? "creator" : "joiner");
|
|
cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL);
|
|
ath6kl_bss_put(vif->ar, bss);
|
|
return;
|
|
}
|
|
|
|
if (nw_type & INFRA_NETWORK)
|
|
vif->phymode = ((u16)nw_type >> 8) & 0xff;
|
|
|
|
reset_hk_prot = ath6kl_handshake_protect(vif, bssid);
|
|
|
|
if (vif->sme_state == SME_CONNECTING) {
|
|
/* inform connect result to cfg80211 */
|
|
vif->sme_state = SME_CONNECTED;
|
|
#ifdef ATH6KL_DIAGNOSTIC
|
|
wifi_diag_mac_fsm_event(vif,
|
|
(enum wifi_diag_mac_fsm_t)WIFI_DIAG_MAC_FSM_CONNECTED,
|
|
vif->diag.connect_seq_num);
|
|
#endif
|
|
ath6kl_bss_put(vif->ar, bss);
|
|
ath6kl_cfg80211_connect_result(vif, bssid,
|
|
assoc_req_ie, assoc_req_len,
|
|
assoc_resp_ie, assoc_resp_len,
|
|
WLAN_STATUS_SUCCESS, GFP_KERNEL);
|
|
} else if (ath6kl_roamed_indicate(vif, bssid, reset_hk_prot) == true) {
|
|
cfg80211_roamed_bss(vif->ndev, bss, assoc_req_ie, assoc_req_len,
|
|
assoc_resp_ie, assoc_resp_len, GFP_KERNEL);
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ar->autopm_turn_on) {
|
|
ar->autopm_defer_delay_change_cnt =
|
|
USB_SUSPEND_DEFER_DELAY_CHANGE_CNT;
|
|
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_CONNECTED);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy,
|
|
struct net_device *dev, u16 reason_code)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int ret;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG | ATH6KL_DBG_EXT_INFO1, "%s: reason=%u\n",
|
|
__func__, reason_code);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) {
|
|
ath6kl_err("%s destroy in progress %lu\n", __func__, ar->flag);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
vif->reconnect_flag = 0;
|
|
ret = ath6kl_disconnect(vif);
|
|
memset(vif->ssid, 0, sizeof(vif->ssid));
|
|
vif->ssid_len = 0;
|
|
|
|
if (!test_bit(SKIP_SCAN, &ar->flag))
|
|
memset(vif->req_bssid, 0, sizeof(vif->req_bssid));
|
|
|
|
up(&ar->sem);
|
|
|
|
vif->sme_state = SME_DISCONNECTED;
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ar->autopm_turn_on) {
|
|
ar->autopm_defer_delay_change_cnt = 0;
|
|
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_MAX);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* To avoid race condition between driver and supplicant, waiting
|
|
* until received disconnect event.
|
|
*/
|
|
if ((!ret) &&
|
|
(vif->nw_type == INFRA_NETWORK) &&
|
|
(test_bit(CONNECTED, &vif->flags))) {
|
|
if (test_bit(DISCONNECT_PEND, &vif->flags)) {
|
|
/*
|
|
* Already be called by other commands
|
|
* (ex, interface down), so ignore.
|
|
*/
|
|
return 0;
|
|
}
|
|
set_bit(DISCONNECT_PEND, &vif->flags);
|
|
wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(DISCONNECT_PEND,
|
|
&vif->flags),
|
|
(HZ/2));
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_err("wait DISCONNECT timeout!\n");
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason,
|
|
u8 *bssid, u8 assoc_resp_len,
|
|
u8 *assoc_info, u16 proto_reason)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG | ATH6KL_DBG_EXT_INFO1,
|
|
"%s: reason=%u, proto_reason %u, flag %lu\n",
|
|
__func__, reason, proto_reason, ar->flag);
|
|
|
|
/* avoid wmi event be processed while driver unloading */
|
|
if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) {
|
|
ath6kl_err("%s destroy in progress %lu\n", __func__, ar->flag);
|
|
return;
|
|
}
|
|
|
|
if (vif->scan_req) {
|
|
del_timer(&vif->vifscan_timer);
|
|
ath6kl_wmi_abort_scan_cmd(ar->wmi, vif->fw_vif_idx);
|
|
cfg80211_scan_done(vif->scan_req, true);
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ath6kl_hif_auto_pm_get_usage_cnt(ar) == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"%s: warnning refcnt=0, my=%d/%d\n",
|
|
__func__,
|
|
ar->auto_pm_cnt,
|
|
ar->auto_pm_fail_cnt);
|
|
} else
|
|
ath6kl_hif_auto_pm_enable(ar);
|
|
#endif
|
|
|
|
vif->scan_req = NULL;
|
|
clear_bit(SCANNING, &vif->flags);
|
|
}
|
|
|
|
if (vif->nw_type & ADHOC_NETWORK) {
|
|
if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: ath6k not in ibss mode\n", __func__);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (vif->nw_type & INFRA_NETWORK) {
|
|
if (vif->wdev.iftype != NL80211_IFTYPE_STATION &&
|
|
vif->wdev.iftype != NL80211_IFTYPE_P2P_CLIENT) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: ath6k not in station mode\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (vif->pend_skb)
|
|
ath6kl_flush_pend_skb(vif);
|
|
|
|
/*
|
|
* Send a disconnect command to target when a disconnect event is
|
|
* received with reason code other than 3 (DISCONNECT_CMD - disconnect
|
|
* request from host) to make the firmware stop trying to connect even
|
|
* after giving disconnect event. There will be one more disconnect
|
|
* event for this disconnect command with reason code DISCONNECT_CMD
|
|
* which will be notified to cfg80211.
|
|
*/
|
|
|
|
if (reason != DISCONNECT_CMD) {
|
|
if ((reason == AUTH_FAILED) &&
|
|
(vif->dot11_auth_mode & SHARED_AUTH))
|
|
vif->next_conn_status = WLAN_STATUS_CHALLENGE_FAIL;
|
|
else
|
|
vif->next_conn_status = WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx);
|
|
return;
|
|
}
|
|
|
|
clear_bit(CONNECT_PEND, &vif->flags);
|
|
|
|
if (vif->sme_state == SME_CONNECTING) {
|
|
u16 status = WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
if (vif->next_conn_status)
|
|
status = vif->next_conn_status;
|
|
|
|
ath6kl_cfg80211_connect_result(vif,
|
|
bssid, NULL, 0,
|
|
NULL, 0,
|
|
status,
|
|
GFP_KERNEL);
|
|
} else if (vif->sme_state == SME_CONNECTED) {
|
|
ath6kl_cfg80211_disconnected(vif, proto_reason,
|
|
NULL, 0, GFP_KERNEL);
|
|
}
|
|
#ifdef ATH6KL_DIAGNOSTIC
|
|
wifi_diag_mac_fsm_event(vif,
|
|
(enum wifi_diag_mac_fsm_t)WIFI_DIAG_MAC_FSM_DISCONNECTED,
|
|
vif->diag.disconnect_seq_num);
|
|
#endif
|
|
|
|
vif->sme_state = SME_DISCONNECTED;
|
|
vif->next_conn_status = WLAN_STATUS_SUCCESS;
|
|
|
|
if (reason == DISCONNECT_CMD) {
|
|
if (test_bit(DISCONNECT_PEND, &vif->flags) &&
|
|
(vif->nw_type == INFRA_NETWORK)) {
|
|
clear_bit(DISCONNECT_PEND, &vif->flags);
|
|
wake_up(&ar->event_wq);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ath6kl_cfg80211_change_bss(struct wiphy *wiphy,
|
|
struct net_device *ndev, struct bss_parameters *params)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (params->ap_isolate >= 0)
|
|
vif->intra_bss = !params->ap_isolate;
|
|
|
|
/* FIXME : support others. */
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ath6kl_scan_timer_handler(unsigned long ptr)
|
|
{
|
|
struct ath6kl_vif *vif = (struct ath6kl_vif *)ptr;
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s scan timer hit\n", __func__);
|
|
if (vif->scan_req) {
|
|
ath6kl_wmi_abort_scan_cmd(ar->wmi, vif->fw_vif_idx);
|
|
cfg80211_scan_done(vif->scan_req, true);
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
/*
|
|
* Here maybe false alarmif race-condition happened between
|
|
* scan-comp-event & scan-abort-command.
|
|
*/
|
|
if (ath6kl_hif_auto_pm_get_usage_cnt(ar) == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG |
|
|
ATH6KL_DBG_EXT_SCAN |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"%s: warnning refcnt=0, my=%d/%d\n",
|
|
__func__,
|
|
ar->auto_pm_cnt,
|
|
ar->auto_pm_fail_cnt);
|
|
} else
|
|
ath6kl_hif_auto_pm_enable(ar);
|
|
#endif
|
|
|
|
vif->scan_req = NULL;
|
|
clear_bit(SCANNING, &vif->flags);
|
|
}
|
|
}
|
|
|
|
/* assume we support not more than two differnet channels */
|
|
static int ath6kl_scan_timeout_cal(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
u16 connected_count = 0;
|
|
|
|
if (!(ar->wiphy->flags & WIPHY_FLAG_SUPPORTS_FW_ROAM))
|
|
return ATH6KL_SCAN_TIMEOUT_WITHOUT_ROAM;
|
|
|
|
if (ath6kl_scan_timeout &&
|
|
(ath6kl_scan_timeout < ATH6KL_SCAN_TIMEOUT_SHORT))
|
|
return ATH6KL_SCAN_TIMEOUT_SHORT;
|
|
|
|
list_for_each_entry(vif, &ar->vif_list, list) {
|
|
if (test_bit(CONNECTED, &vif->flags)) {
|
|
connected_count++;
|
|
if (connected_count == 1)
|
|
return ATH6KL_SCAN_TIMEOUT_ONE_CON;
|
|
else if (connected_count > 1)
|
|
return ATH6KL_SCAN_TIMEOUT_LONG;
|
|
}
|
|
}
|
|
|
|
return ATH6KL_SCAN_TIMEOUT_SHORT;
|
|
}
|
|
|
|
static int ath6kl_set_probe_req_ies(struct ath6kl_vif *vif, const u8 *ies,
|
|
size_t ies_len)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
const u8 *pos;
|
|
u8 *buf = NULL;
|
|
size_t len = 0;
|
|
int ret;
|
|
|
|
/*
|
|
* Filter out P2P/WFD IE(s)
|
|
*/
|
|
if (ies && ies_len) {
|
|
buf = kmalloc(ies_len, GFP_KERNEL);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
pos = ies;
|
|
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
if ((ath6kl_is_p2p_ie(pos) ||
|
|
ath6kl_is_wfd_ie(pos)) &&
|
|
!ath6kl_p2p_ie_append(vif,
|
|
P2P_IE_IN_PROBE_REQ))
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"Remove Probe's P2P IE\n");
|
|
else {
|
|
memcpy(buf + len, pos, 2 + pos[1]);
|
|
len += 2 + pos[1];
|
|
}
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WMI_FRAME_PROBE_REQ, buf, len);
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool _ath6kl_scanband_ignore_ch(struct ath6kl_vif *vif, u16 freq)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
if (freq == vif->scanband_ignore_chan[i])
|
|
return true;
|
|
if (vif->scanband_ignore_chan[i] == 0)
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static s8 ath6kl_scanband(struct ath6kl_vif *vif,
|
|
u16 *channels,
|
|
s8 n_channels,
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
int i;
|
|
u8 skip_chan_num = 0;
|
|
u8 num_chan = n_channels;
|
|
|
|
switch (vif->scanband_type) {
|
|
case SCANBAND_TYPE_CHAN_ONLY:
|
|
channels[0] = vif->scanband_chan;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Only signal channel scan, channel %d\n",
|
|
channels[0]);
|
|
num_chan = 1;
|
|
break;
|
|
case SCANBAND_TYPE_5G:
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Only 5G channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (request->channels[i]->center_freq <= 2484) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
break;
|
|
case SCANBAND_TYPE_2G:
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Only 2G channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (request->channels[i]->center_freq > 2484) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
break;
|
|
case SCANBAND_TYPE_P2PCHAN:
|
|
num_chan = ath6kl_p2p_build_scan_chan(vif,
|
|
n_channels,
|
|
channels);
|
|
if (num_chan) {
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Only P2P channels scan, full scan instead\n");
|
|
|
|
break;
|
|
} else
|
|
num_chan = n_channels;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Only P2P channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (!ath6kl_reg_is_p2p_channel(vif->ar,
|
|
request->channels[i]->center_freq)) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
break;
|
|
case SCANBAND_TYPE_2_P2PCHAN:
|
|
num_chan = ath6kl_p2p_build_scan_chan(vif,
|
|
n_channels,
|
|
channels);
|
|
if (num_chan) {
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"x2 P2P channels scan, full scan instead\n");
|
|
|
|
break;
|
|
} else
|
|
num_chan = n_channels;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"x2 P2P channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (!ath6kl_reg_is_p2p_channel(vif->ar,
|
|
request->channels[i]->center_freq)) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
/* Avoid to scan lots of channels. */
|
|
if (num_chan <= (WMI_MAX_CHANNELS >> 1)) {
|
|
memcpy(&channels[num_chan],
|
|
&channels[0],
|
|
num_chan * sizeof(u16));
|
|
num_chan *= 2;
|
|
}
|
|
break;
|
|
case SCANBAND_TYPE_IGNORE_DFS:
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"No DFS channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (ath6kl_reg_is_dfs_channel(vif->ar,
|
|
request->channels[i]->center_freq)) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
break;
|
|
case SCANBAND_TYPE_IGNORE_CH:
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"Ignore channels scan, channel list - ");
|
|
for (i = 0; i < n_channels; i++) {
|
|
if (_ath6kl_scanband_ignore_ch(vif,
|
|
request->channels[i]->center_freq)) {
|
|
skip_chan_num++;
|
|
continue;
|
|
}
|
|
channels[i - skip_chan_num] =
|
|
request->channels[i]->center_freq;
|
|
ath6kl_dbg(ATH6KL_DBG_INFO | ATH6KL_DBG_EXT_SCAN,
|
|
"%d ", channels[i - skip_chan_num]);
|
|
}
|
|
num_chan -= skip_chan_num;
|
|
break;
|
|
default:
|
|
for (i = 0; i < n_channels; i++)
|
|
channels[i] = request->channels[i]->center_freq;
|
|
break;
|
|
}
|
|
|
|
return num_chan;
|
|
}
|
|
|
|
static int _ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
s8 n_channels = 0;
|
|
u16 *channels = NULL;
|
|
int ret = 0;
|
|
u32 force_fg_scan = 0;
|
|
bool sche_scan_trig, left;
|
|
|
|
if (test_bit(DISABLE_SCAN, &ar->flag)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_SCAN,
|
|
"scan is disabled temporarily\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (vif->sme_state == SME_CONNECTING) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_SCAN,
|
|
"Connection on-going, reject scan\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG | ATH6KL_DBG_EXT_SCAN,
|
|
"%s\n", __func__);
|
|
|
|
/*
|
|
* Last Cancel-RoC not yet finished. To update vif->last_cancel_roc_id
|
|
* first to avoid wrong cookie report to supplicant.
|
|
*/
|
|
if (test_bit(ROC_CANCEL_PEND, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Scan but Cancel-RoC not yet back, wait it finish %x\n",
|
|
vif->last_cancel_roc_id);
|
|
|
|
wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(ROC_ONGOING, &vif->flags),
|
|
WMI_TIMEOUT);
|
|
if (signal_pending(current)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : target did not respond\n");
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
/* RoC is ongoing and stop it first. */
|
|
if (test_bit(ROC_ONGOING, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Scan but On-going-RoC, cancel it first %x\n",
|
|
vif->last_roc_id);
|
|
|
|
set_bit(ROC_CANCEL_PEND, &vif->flags);
|
|
if (ath6kl_wmi_cancel_remain_on_chnl_cmd(ar->wmi,
|
|
vif->fw_vif_idx) != 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : cancel ROC failed\n");
|
|
clear_bit(ROC_CANCEL_PEND, &vif->flags);
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(ROC_ONGOING, &vif->flags),
|
|
WMI_TIMEOUT);
|
|
|
|
if (left == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : wait cancel RoC timeout\n");
|
|
clear_bit(ROC_CANCEL_PEND, &vif->flags);
|
|
clear_bit(ROC_ONGOING, &vif->flags);
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : target did not respond\n");
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
sche_scan_trig = ath6kl_sched_scan_trigger(vif);
|
|
|
|
if (!vif->usr_bss_filter) {
|
|
clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
|
|
|
|
/*
|
|
* EV: 109005
|
|
* Fix bug that Probe response from the connected AP
|
|
* will be filtered by setting this filter ,
|
|
* thus the AP's information won't be updated.
|
|
*/
|
|
ret = ath6kl_wmi_bssfilter_cmd(
|
|
ar->wmi, vif->fw_vif_idx, ALL_BSS_FILTER , 0);
|
|
|
|
|
|
if (ret) {
|
|
ath6kl_err("couldn't set bss filtering\n");
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if ((request->n_ssids && request->ssids[0].ssid_len) &&
|
|
(!sche_scan_trig)) {
|
|
u8 i;
|
|
|
|
if (request->n_ssids > (MAX_PROBED_SSID_INDEX - 1))
|
|
request->n_ssids = MAX_PROBED_SSID_INDEX - 1;
|
|
|
|
for (i = 0; i < request->n_ssids; i++)
|
|
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
|
|
i + 1, SPECIFIC_SSID_FLAG,
|
|
request->ssids[i].ssid_len,
|
|
request->ssids[i].ssid);
|
|
} else if (ar->p2p)
|
|
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
|
|
MAX_PROBED_SSID_INDEX, ANY_SSID_FLAG,
|
|
0,
|
|
NULL);
|
|
|
|
if ((request->ie) &&
|
|
(!sche_scan_trig)) {
|
|
ret = ath6kl_set_probe_req_ies(vif,
|
|
request->ie,
|
|
request->ie_len);
|
|
if (ret) {
|
|
ath6kl_err("failed to set Probe Request appie for "
|
|
"scan");
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Scan only the requested channels if the request specifies a set of
|
|
* channels. If the list is longer than the target supports, do not
|
|
* configure the list and instead, scan all available channels.
|
|
*/
|
|
if ((request->n_channels > 0 &&
|
|
request->n_channels <= WMI_MAX_CHANNELS) &&
|
|
(!sche_scan_trig)) {
|
|
|
|
n_channels = request->n_channels;
|
|
|
|
channels = kzalloc(WMI_MAX_CHANNELS * sizeof(u16) * 2,
|
|
GFP_KERNEL);
|
|
if (channels == NULL) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_SCAN,
|
|
"failed to set scan chan, scan all channel\n");
|
|
n_channels = 0;
|
|
}
|
|
|
|
if (n_channels) {
|
|
/* Rearrange according scanband */
|
|
n_channels = ath6kl_scanband(vif,
|
|
channels,
|
|
n_channels,
|
|
request);
|
|
}
|
|
}
|
|
|
|
if (test_bit(CONNECTED, &vif->flags))
|
|
force_fg_scan = 1;
|
|
|
|
if (test_and_set_bit(SCANNING, &vif->flags)) {
|
|
kfree(channels);
|
|
up(&ar->sem);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (test_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags) ||
|
|
test_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_SCAN,
|
|
"EAPOL %s_handshake_protect reject scan\n",
|
|
(test_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag) ?
|
|
"rekey" : "connect"));
|
|
clear_bit(SCANNING, &vif->flags);
|
|
kfree(channels);
|
|
up(&ar->sem);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Disable DFS channel scan */
|
|
vif->sc_params.scan_ctrl_flags |= ENABLE_DFS_SKIP_CTRL_FLAGS;
|
|
|
|
ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->sc_params.fg_start_period,
|
|
vif->sc_params.fg_end_period,
|
|
vif->sc_params.bg_period,
|
|
vif->sc_params.minact_chdwell_time,
|
|
vif->sc_params.maxact_chdwell_time,
|
|
vif->sc_params.pas_chdwell_time,
|
|
vif->sc_params.short_scan_ratio,
|
|
vif->sc_params.scan_ctrl_flags,
|
|
vif->sc_params.max_dfsch_act_time,
|
|
vif->sc_params.maxact_scan_per_ssid);
|
|
|
|
if (ret) {
|
|
ath6kl_err("ath6kl_cfg80211_scan: set scan parameter failed\n");
|
|
clear_bit(SCANNING, &vif->flags);
|
|
kfree(channels);
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
|
|
ret = ath6kl_wmi_startscan_cmd(ar->wmi, vif->fw_vif_idx, WMI_LONG_SCAN,
|
|
force_fg_scan, false, 0,
|
|
ATH6KL_FG_SCAN_INTERVAL,
|
|
n_channels, channels);
|
|
if (ret) {
|
|
ath6kl_err("wmi_startscan_cmd failed\n");
|
|
clear_bit(SCANNING, &vif->flags);
|
|
} else {
|
|
vif->scan_req = request;
|
|
mod_timer(&vif->vifscan_timer,
|
|
jiffies + ath6kl_scan_timeout_cal(ar));
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
/* Disable autopm until scan finished. */
|
|
ath6kl_hif_auto_pm_disable(ar);
|
|
#endif
|
|
|
|
}
|
|
|
|
kfree(channels);
|
|
|
|
ath6kl_bss_post_proc_bss_scan_start(vif);
|
|
ath6kl_p2p_rc_scan_start(vif, false);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CFG80211_NETDEV_REPLACED_BY_WDEV
|
|
static int ath6kl_cfg80211_scan(struct wiphy *wiphy,
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
BUG_ON(!request->wdev);
|
|
BUG_ON(!request->wdev->netdev);
|
|
|
|
return _ath6kl_cfg80211_scan(wiphy,
|
|
request->wdev->netdev,
|
|
request);
|
|
}
|
|
#else
|
|
static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
|
|
struct cfg80211_scan_request *request)
|
|
{
|
|
return _ath6kl_cfg80211_scan(wiphy, ndev, request);
|
|
}
|
|
#endif
|
|
|
|
void ath6kl_cfg80211_scan_complete_event(struct ath6kl_vif *vif, bool aborted)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
int i;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG | ATH6KL_DBG_EXT_SCAN,
|
|
"%s: status%s\n", __func__,
|
|
aborted ? " aborted" : " complete");
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
/*
|
|
* Here maybe flase alarm. Ex, th6kl_cfg80211_stop() try
|
|
* to finish all actions after stop the device and not really
|
|
* wait event back, or this interface will be deleted later
|
|
* (like p2p-p2p0-x) for some reaseon.
|
|
*/
|
|
if (ath6kl_hif_auto_pm_get_usage_cnt(ar) == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG |
|
|
ATH6KL_DBG_EXT_SCAN |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"%s: warnning refcnt=0, my=%d/%d\n",
|
|
__func__,
|
|
ar->auto_pm_cnt,
|
|
ar->auto_pm_fail_cnt);
|
|
} else
|
|
ath6kl_hif_auto_pm_enable(ar);
|
|
#endif
|
|
|
|
del_timer(&vif->vifscan_timer);
|
|
|
|
if (test_bit(SCANNING_WAIT, &vif->flags)) {
|
|
clear_bit(SCANNING_WAIT, &vif->flags);
|
|
wake_up(&ar->event_wq);
|
|
}
|
|
|
|
if (!vif->scan_req)
|
|
return;
|
|
|
|
if (aborted)
|
|
goto out;
|
|
|
|
if (vif->scan_req->n_ssids && vif->scan_req->ssids[0].ssid_len) {
|
|
for (i = 0; i < vif->scan_req->n_ssids; i++) {
|
|
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
|
|
i + 1, DISABLE_SSID_FLAG,
|
|
0, NULL);
|
|
}
|
|
} else if (ar->p2p)
|
|
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
|
|
MAX_PROBED_SSID_INDEX,
|
|
DISABLE_SSID_FLAG,
|
|
0, NULL);
|
|
|
|
out:
|
|
cfg80211_scan_done(vif->scan_req, aborted);
|
|
vif->scan_req = NULL;
|
|
clear_bit(SCANNING, &vif->flags);
|
|
}
|
|
|
|
#ifdef PMF_SUPPORT
|
|
static int ath6kl_cfg80211_add_mgmt_key(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
u8 key_index, bool pairwise,
|
|
const u8 *mac_addr,
|
|
struct key_params *params)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
struct ath6kl_key *key = NULL;
|
|
enum wmi_sync_flag sync_flag = NO_SYNC_WMIFLAG;
|
|
int ret;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (!params)
|
|
return -EINVAL;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (key_index < WMI_MIN_IGTK_INDEX || key_index > WMI_MAX_IGTK_INDEX) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: key index %d out of bounds\n", __func__,
|
|
key_index);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
key = &vif->keys[key_index];
|
|
memset(key, 0, sizeof(struct ath6kl_key));
|
|
|
|
if (params) {
|
|
int seq_len = params->seq_len;
|
|
if (params->key_len > WMI_IGTK_KEY_LEN ||
|
|
seq_len > sizeof(key->seq)) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
key->key_len = params->key_len;
|
|
memcpy(key->key, params->key, key->key_len);
|
|
key->seq_len = seq_len;
|
|
memcpy(key->seq, params->seq, key->seq_len);
|
|
key->cipher = params->cipher;
|
|
}
|
|
|
|
if (vif->nw_type == AP_NETWORK && !pairwise && params) {
|
|
/* Do something here for AP/GO mode. */
|
|
;
|
|
}
|
|
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_WMI_SYC))
|
|
sync_flag = NO_SYNC_WMIFLAG;
|
|
|
|
ret = ath6kl_wmi_addkey_igtk_cmd(ar->wmi, vif->fw_vif_idx, key_index,
|
|
key->key_len, key->seq,
|
|
key->key, sync_flag);
|
|
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_cfg80211_add_key(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
u8 key_index, bool pairwise,
|
|
const u8 *mac_addr,
|
|
struct key_params *params)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
struct ath6kl_key *key = NULL;
|
|
enum wmi_sync_flag sync_flag = SYNC_BOTH_WMIFLAG;
|
|
u8 key_usage;
|
|
u8 key_type;
|
|
int ret;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (!params)
|
|
return -EINVAL;
|
|
|
|
#ifdef PMF_SUPPORT
|
|
if (params->cipher == WLAN_CIPHER_SUITE_AES_CMAC)
|
|
return ath6kl_cfg80211_add_mgmt_key(wiphy, ndev, key_index,
|
|
pairwise, mac_addr, params);
|
|
#endif
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (params->cipher == CCKM_KRK_CIPHER_SUITE) {
|
|
if (params->key_len != WMI_KRK_LEN) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ath6kl_wmi_add_krk_cmd(ar->wmi, vif->fw_vif_idx,
|
|
params->key);
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
|
|
if (key_index > WMI_MAX_KEY_INDEX) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: key index %d out of bounds\n", __func__,
|
|
key_index);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
key = &vif->keys[key_index];
|
|
memset(key, 0, sizeof(struct ath6kl_key));
|
|
|
|
if (pairwise)
|
|
key_usage = PAIRWISE_USAGE;
|
|
else
|
|
key_usage = GROUP_USAGE;
|
|
|
|
if (params) {
|
|
int seq_len = params->seq_len;
|
|
if (params->cipher == WLAN_CIPHER_SUITE_SMS4 &&
|
|
seq_len > ATH6KL_KEY_SEQ_LEN) {
|
|
/* Only first half of the WPI PN is configured */
|
|
seq_len = ATH6KL_KEY_SEQ_LEN;
|
|
}
|
|
if (params->key_len > WLAN_MAX_KEY_LEN ||
|
|
seq_len > sizeof(key->seq)) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
key->key_len = params->key_len;
|
|
memcpy(key->key, params->key, key->key_len);
|
|
key->seq_len = seq_len;
|
|
memcpy(key->seq, params->seq, key->seq_len);
|
|
key->cipher = params->cipher;
|
|
}
|
|
|
|
switch (key->cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
key_type = WEP_CRYPT;
|
|
break;
|
|
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
key_type = TKIP_CRYPT;
|
|
break;
|
|
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
key_type = AES_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
key_type = WAPI_CRYPT;
|
|
break;
|
|
|
|
default:
|
|
up(&ar->sem);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (((vif->auth_mode == WPA_PSK_AUTH)
|
|
|| (vif->auth_mode == WPA2_PSK_AUTH))
|
|
&& (key_usage & GROUP_USAGE))
|
|
del_timer(&vif->disconnect_timer);
|
|
|
|
if (key_usage & GROUP_USAGE) {
|
|
if (vif->pend_skb) {
|
|
ath6kl_err("eapol protect shall be off already\n");
|
|
ath6kl_flush_pend_skb(vif);
|
|
}
|
|
|
|
spin_lock_bh(&vif->if_lock);
|
|
clear_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags);
|
|
del_timer(&vif->shprotect_timer);
|
|
spin_unlock_bh(&vif->if_lock);
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: index %d, key_len %d, key_type 0x%x, key_usage 0x%x, seq_len %d\n",
|
|
__func__, key_index, key->key_len, key_type,
|
|
key_usage, key->seq_len);
|
|
|
|
if (vif->nw_type == AP_NETWORK && !pairwise &&
|
|
(key_type == TKIP_CRYPT || key_type == AES_CRYPT) && params) {
|
|
vif->ap_mode_bkey.valid = true;
|
|
vif->ap_mode_bkey.key_index = key_index;
|
|
vif->ap_mode_bkey.key_type = key_type;
|
|
vif->ap_mode_bkey.key_len = key->key_len;
|
|
memcpy(vif->ap_mode_bkey.key, key->key, key->key_len);
|
|
if (!test_bit(CONNECTED, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delay initial group "
|
|
"key configuration until AP mode has been "
|
|
"started\n");
|
|
/*
|
|
* The key will be set in ath6kl_connect_ap_mode() once
|
|
* the connected event is received from the target.
|
|
*/
|
|
up(&ar->sem);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (vif->next_mode == AP_NETWORK && key_type == WEP_CRYPT &&
|
|
!test_bit(CONNECTED, &vif->flags)) {
|
|
/*
|
|
* Store the key locally so that it can be re-configured after
|
|
* the AP mode has properly started
|
|
* (ath6kl_install_statioc_wep_keys).
|
|
*/
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delay WEP key configuration "
|
|
"until AP mode has been started\n");
|
|
vif->wep_key_list[key_index].key_len = key->key_len;
|
|
memcpy(vif->wep_key_list[key_index].key, key->key,
|
|
key->key_len);
|
|
up(&ar->sem);
|
|
return 0;
|
|
}
|
|
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_WMI_SYC))
|
|
sync_flag = NO_SYNC_WMIFLAG;
|
|
|
|
ret = ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, key_index,
|
|
key_type, key_usage, key->key_len,
|
|
key->seq, key->seq_len, key->key,
|
|
KEY_OP_INIT_VAL,
|
|
(u8 *) mac_addr, sync_flag);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev,
|
|
u8 key_index, bool pairwise,
|
|
const u8 *mac_addr)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
int ret;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (key_index > WMI_MAX_KEY_INDEX) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: key index %d out of bounds\n", __func__,
|
|
key_index);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!vif->keys[key_index].key_len) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: index %d is empty\n", __func__, key_index);
|
|
up(&ar->sem);
|
|
return 0;
|
|
}
|
|
|
|
vif->keys[key_index].key_len = 0;
|
|
|
|
ret = ath6kl_wmi_deletekey_cmd(ar->wmi, vif->fw_vif_idx, key_index);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev,
|
|
u8 key_index, bool pairwise,
|
|
const u8 *mac_addr, void *cookie,
|
|
void (*callback) (void *cookie,
|
|
struct key_params *))
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
struct ath6kl_key *key = NULL;
|
|
struct key_params params;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&vif->ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (key_index > WMI_MAX_KEY_INDEX) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: key index %d out of bounds\n", __func__,
|
|
key_index);
|
|
up(&vif->ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
key = &vif->keys[key_index];
|
|
memset(¶ms, 0, sizeof(params));
|
|
params.cipher = key->cipher;
|
|
params.key_len = key->key_len;
|
|
params.seq_len = key->seq_len;
|
|
params.seq = key->seq;
|
|
params.key = key->key;
|
|
|
|
callback(cookie, ¶ms);
|
|
|
|
up(&vif->ar->sem);
|
|
|
|
return key->key_len ? 0 : -ENOENT;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_set_default_key(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
u8 key_index, bool unicast,
|
|
bool multicast)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
struct ath6kl_key *key = NULL;
|
|
u8 key_usage;
|
|
enum crypto_type key_type = NONE_CRYPT;
|
|
enum wmi_sync_flag sync_flag = SYNC_BOTH_WMIFLAG;
|
|
int ret;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (key_index > WMI_MAX_KEY_INDEX) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: key index %d out of bounds\n",
|
|
__func__, key_index);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!vif->keys[key_index].key_len) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: invalid key index %d\n",
|
|
__func__, key_index);
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
vif->def_txkey_index = key_index;
|
|
key = &vif->keys[vif->def_txkey_index];
|
|
key_usage = GROUP_USAGE;
|
|
if (vif->prwise_crypto == WEP_CRYPT)
|
|
key_usage |= TX_USAGE;
|
|
if (unicast)
|
|
key_type = vif->prwise_crypto;
|
|
if (multicast)
|
|
key_type = vif->grp_crypto;
|
|
|
|
if (vif->next_mode ==
|
|
AP_NETWORK && !test_bit(CONNECTED, &vif->flags)) {
|
|
up(&ar->sem);
|
|
return 0; /* Delay until AP mode has been started */
|
|
}
|
|
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_WMI_SYC))
|
|
sync_flag = NO_SYNC_WMIFLAG;
|
|
|
|
ret = ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->def_txkey_index,
|
|
key_type, key_usage,
|
|
key->key_len, key->seq, key->seq_len,
|
|
key->key,
|
|
KEY_OP_INIT_VAL, NULL,
|
|
sync_flag);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ath6kl_cfg80211_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid,
|
|
bool ismcast)
|
|
{
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: keyid %d, ismcast %d\n", __func__, keyid, ismcast);
|
|
|
|
cfg80211_michael_mic_failure(vif->ndev, vif->bssid,
|
|
(ismcast ? NL80211_KEYTYPE_GROUP :
|
|
NL80211_KEYTYPE_PAIRWISE), keyid, NULL,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif;
|
|
int ret;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: changed 0x%x\n", __func__,
|
|
changed);
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
|
|
ret = ath6kl_wmi_set_rts_cmd(ar->wmi, 0, wiphy->rts_threshold);
|
|
if (ret != 0) {
|
|
ath6kl_err("ath6kl_wmi_set_rts_cmd failed\n");
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The type nl80211_tx_power_setting replaces the following
|
|
* data type from 2.6.36 onwards
|
|
*/
|
|
static int _ath6kl_cfg80211_set_txpower(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
enum nl80211_tx_power_setting type,
|
|
int mbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
u8 ath6kl_dbm;
|
|
int dbm = MBM_TO_DBM(mbm);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x, dbm %d\n", __func__,
|
|
type, dbm);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
switch (type) {
|
|
case NL80211_TX_POWER_AUTOMATIC:
|
|
up(&ar->sem);
|
|
return 0;
|
|
case NL80211_TX_POWER_LIMITED:
|
|
ar->tx_pwr = ath6kl_dbm = dbm;
|
|
break;
|
|
default:
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x not supported\n",
|
|
__func__, type);
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ath6kl_wmi_set_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx, ath6kl_dbm);
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _ath6kl_cfg80211_get_txpower(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
int *dbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (test_bit(CONNECTED, &vif->flags)) {
|
|
ar->tx_pwr = 0;
|
|
|
|
if (ath6kl_wmi_get_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx) != 0) {
|
|
ath6kl_err("ath6kl_wmi_get_tx_pwr_cmd failed\n");
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
wait_event_interruptible_timeout(ar->event_wq, ar->tx_pwr != 0,
|
|
5 * HZ);
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_err("target did not respond\n");
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
*dbm = ar->tx_pwr;
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CFG80211_TX_POWER_PER_WDEV
|
|
static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
enum nl80211_tx_power_setting type,
|
|
int mbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct net_device *ndev;
|
|
|
|
if (wdev == NULL) {
|
|
struct ath6kl_vif *vif = ath6kl_vif_first(ar);
|
|
|
|
if (!vif)
|
|
return -EIO;
|
|
ndev = vif->ndev;
|
|
} else
|
|
ndev = wdev->netdev;
|
|
|
|
return _ath6kl_cfg80211_set_txpower(wiphy, ndev, type, mbm);
|
|
}
|
|
|
|
static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
int *dbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct net_device *ndev;
|
|
|
|
if (wdev == NULL) {
|
|
struct ath6kl_vif *vif = ath6kl_vif_first(ar);
|
|
|
|
if (!vif)
|
|
return -EIO;
|
|
ndev = vif->ndev;
|
|
} else
|
|
ndev = wdev->netdev;
|
|
|
|
return _ath6kl_cfg80211_get_txpower(wiphy, ndev , dbm);
|
|
}
|
|
#else
|
|
static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy,
|
|
enum nl80211_tx_power_setting type,
|
|
int mbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = ath6kl_vif_first(ar);
|
|
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
return _ath6kl_cfg80211_set_txpower(wiphy, vif->ndev, type, mbm);
|
|
}
|
|
|
|
static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy, int *dbm)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = ath6kl_vif_first(ar);
|
|
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
return _ath6kl_cfg80211_get_txpower(wiphy, vif->ndev, dbm);
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_cfg80211_set_power_mgmt(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
bool pmgmt, int timeout)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct wmi_power_mode_cmd mode;
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: pmgmt %d, timeout %d\n",
|
|
__func__, pmgmt, timeout);
|
|
|
|
if (test_bit(PS_STICK, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "PS mode already stick (%d).\n",
|
|
vif->last_pwr_mode);
|
|
return 0;
|
|
}
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (pmgmt) {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: rec power\n", __func__);
|
|
mode.pwr_mode = REC_POWER;
|
|
} else {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: max perf\n", __func__);
|
|
mode.pwr_mode = MAX_PERF_POWER;
|
|
}
|
|
|
|
if (ath6kl_wmi_powermode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
mode.pwr_mode) != 0) {
|
|
ath6kl_err("wmi_powermode_cmd failed\n");
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct net_device *_ath6kl_cfg80211_add_iface(struct wiphy *wiphy,
|
|
char *name,
|
|
enum nl80211_iftype type,
|
|
u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
struct net_device *ndev;
|
|
u8 if_idx, nw_type;
|
|
|
|
if (!__ath6kl_cfg80211_ready(ar))
|
|
return NULL;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (ar->num_vif == ar->vif_max) {
|
|
ath6kl_err("Reached maximum number of supported vif\n");
|
|
up(&ar->sem);
|
|
return NULL;
|
|
}
|
|
|
|
if (!ath6kl_is_valid_iftype(ar, type, &if_idx, &nw_type)) {
|
|
ath6kl_err("Not a supported interface type\n");
|
|
up(&ar->sem);
|
|
return NULL;
|
|
}
|
|
|
|
ndev = ath6kl_interface_add(ar, name, type, if_idx, nw_type);
|
|
if (!ndev) {
|
|
up(&ar->sem);
|
|
return NULL;
|
|
}
|
|
|
|
ar->num_vif++;
|
|
|
|
up(&ar->sem);
|
|
|
|
return ndev;
|
|
}
|
|
|
|
static int _ath6kl_cfg80211_del_iface(struct wiphy *wiphy,
|
|
struct net_device *ndev)
|
|
{
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
|
|
if (!__ath6kl_cfg80211_ready(ar))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
/* fix EV110820 */
|
|
clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
|
|
ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx,
|
|
NONE_BSS_FILTER, 0);
|
|
|
|
ath6kl_judge_roam_parameter(vif, true);
|
|
ath6kl_switch_parameter_based_on_connection(vif, true);
|
|
|
|
spin_lock_bh(&ar->list_lock);
|
|
list_del(&vif->list);
|
|
spin_unlock_bh(&ar->list_lock);
|
|
|
|
ath6kl_cleanup_vif(vif, test_bit(WMI_READY, &ar->flag));
|
|
|
|
ath6kl_deinit_if_data(vif);
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CFG80211_NETDEV_REPLACED_BY_WDEV
|
|
static struct wireless_dev *ath6kl_cfg80211_add_iface(struct wiphy *wiphy,
|
|
const char *name,
|
|
enum nl80211_iftype type,
|
|
u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
struct net_device *dev;
|
|
|
|
dev = _ath6kl_cfg80211_add_iface(wiphy,
|
|
(char *)name,
|
|
type,
|
|
flags,
|
|
params);
|
|
|
|
if (dev)
|
|
return dev->ieee80211_ptr;
|
|
else
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
static int ath6kl_cfg80211_del_iface(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_cfg80211_del_iface(wiphy, wdev->netdev);
|
|
}
|
|
#else
|
|
static struct net_device *ath6kl_cfg80211_add_iface(struct wiphy *wiphy,
|
|
char *name,
|
|
enum nl80211_iftype type,
|
|
u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
struct net_device *dev;
|
|
|
|
dev = _ath6kl_cfg80211_add_iface(wiphy,
|
|
name,
|
|
type,
|
|
flags,
|
|
params);
|
|
|
|
if (dev)
|
|
return dev;
|
|
else
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
static int ath6kl_cfg80211_del_iface(struct wiphy *wiphy,
|
|
struct net_device *ndev)
|
|
{
|
|
return _ath6kl_cfg80211_del_iface(wiphy, ndev);
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_cfg80211_change_iface(struct wiphy *wiphy,
|
|
struct net_device *ndev,
|
|
enum nl80211_iftype type, u32 *flags,
|
|
struct vif_params *params)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(ndev);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type %u\n", __func__, type);
|
|
|
|
if (down_interruptible(&vif->ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
switch (type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
vif->next_mode = INFRA_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
vif->next_mode = ADHOC_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
vif->next_mode = AP_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
vif->next_mode = INFRA_NETWORK;
|
|
break;
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
vif->next_mode = AP_NETWORK;
|
|
break;
|
|
default:
|
|
ath6kl_err("invalid interface type %u\n", type);
|
|
up(&vif->ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
vif->wdev.iftype = type;
|
|
|
|
up(&vif->ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_join_ibss(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_ibss_params *ibss_param)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
struct ieee80211_channel *chan;
|
|
int status;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
vif->ssid_len = ibss_param->ssid_len;
|
|
memcpy(vif->ssid, ibss_param->ssid, vif->ssid_len);
|
|
|
|
#ifdef CFG80211_NEW_CHAN_DEFINITION
|
|
chan = ibss_param->chandef.chan;
|
|
#else
|
|
chan = ibss_param->channel;
|
|
#endif
|
|
|
|
if (chan)
|
|
vif->ch_hint = chan->center_freq;
|
|
|
|
if (ibss_param->channel_fixed) {
|
|
/*
|
|
* TODO: channel_fixed: The channel should be fixed, do not
|
|
* search for IBSSs to join on other channels. Target
|
|
* firmware does not support this feature, needs to be
|
|
* updated.
|
|
*/
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
/* Diable background scan */
|
|
vif->sc_params.bg_period = 0xFFFF;
|
|
#ifdef ATH6KL_SUPPORT_WIFI_KTK
|
|
vif->sc_params.minact_chdwell_time = 0;
|
|
vif->sc_params.maxact_chdwell_time = 105;
|
|
#endif
|
|
ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->sc_params.fg_start_period,
|
|
vif->sc_params.fg_end_period,
|
|
vif->sc_params.bg_period,
|
|
vif->sc_params.minact_chdwell_time,
|
|
vif->sc_params.maxact_chdwell_time,
|
|
vif->sc_params.pas_chdwell_time,
|
|
vif->sc_params.short_scan_ratio,
|
|
vif->sc_params.scan_ctrl_flags,
|
|
vif->sc_params.max_dfsch_act_time,
|
|
vif->sc_params.maxact_scan_per_ssid);
|
|
|
|
memset(vif->req_bssid, 0, sizeof(vif->req_bssid));
|
|
if (ibss_param->bssid && !is_broadcast_ether_addr(ibss_param->bssid))
|
|
memcpy(vif->req_bssid, ibss_param->bssid,
|
|
sizeof(vif->req_bssid));
|
|
|
|
ath6kl_set_wpa_version(vif, 0);
|
|
|
|
status = ath6kl_set_auth_type(vif, NL80211_AUTHTYPE_OPEN_SYSTEM);
|
|
if (status) {
|
|
up(&ar->sem);
|
|
return status;
|
|
}
|
|
|
|
#ifdef ATH6KL_SUPPORT_WIFI_KTK
|
|
if (!ar->ktk_active) {
|
|
#endif
|
|
if (ibss_param->privacy) {
|
|
ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_WEP40, true);
|
|
ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_WEP40, false);
|
|
} else {
|
|
ath6kl_set_cipher(vif, 0, true);
|
|
ath6kl_set_cipher(vif, 0, false);
|
|
}
|
|
#ifdef ATH6KL_SUPPORT_WIFI_KTK
|
|
} else {
|
|
ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_CCMP, true);
|
|
ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_CCMP, false);
|
|
}
|
|
#endif
|
|
|
|
vif->nw_type = vif->next_mode;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"%s: connect called with authmode %d dot11 auth %d"
|
|
" PW crypto %d PW crypto len %d GRP crypto %d"
|
|
" GRP crypto len %d channel hint %u\n",
|
|
__func__,
|
|
vif->auth_mode, vif->dot11_auth_mode, vif->prwise_crypto,
|
|
vif->prwise_crypto_len, vif->grp_crypto,
|
|
vif->grp_crypto_len, vif->ch_hint);
|
|
|
|
status = ath6kl_wmi_connect_cmd(ar->wmi, vif->fw_vif_idx, vif->nw_type,
|
|
vif->dot11_auth_mode, vif->auth_mode,
|
|
vif->prwise_crypto,
|
|
vif->prwise_crypto_len,
|
|
vif->grp_crypto, vif->grp_crypto_len,
|
|
vif->ssid_len, vif->ssid,
|
|
vif->req_bssid, vif->ch_hint,
|
|
vif->connect_ctrl_flags);
|
|
set_bit(CONNECT_PEND, &vif->flags);
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_leave_ibss(struct wiphy *wiphy,
|
|
struct net_device *dev)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&vif->ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
ath6kl_disconnect(vif);
|
|
memset(vif->ssid, 0, sizeof(vif->ssid));
|
|
vif->ssid_len = 0;
|
|
|
|
up(&vif->ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const u32 cipher_suites[] = {
|
|
WLAN_CIPHER_SUITE_WEP40,
|
|
WLAN_CIPHER_SUITE_WEP104,
|
|
WLAN_CIPHER_SUITE_TKIP,
|
|
WLAN_CIPHER_SUITE_CCMP,
|
|
CCKM_KRK_CIPHER_SUITE,
|
|
WLAN_CIPHER_SUITE_SMS4,
|
|
#ifdef PMF_SUPPORT
|
|
WLAN_CIPHER_SUITE_AES_CMAC,
|
|
#endif
|
|
};
|
|
|
|
static bool is_rate_legacy(s32 rate)
|
|
{
|
|
static const s32 legacy[] = { 1000, 2000, 5500, 11000,
|
|
6000, 9000, 12000, 18000, 24000,
|
|
36000, 48000, 54000
|
|
};
|
|
u8 i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(legacy); i++)
|
|
if (rate == legacy[i])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool is_rate_ht20(s32 rate, u8 *mcs, bool *sgi)
|
|
{
|
|
static const s32 ht20[] = { 6500, 13000, 19500, 26000, 39000,
|
|
52000, 58500, 65000
|
|
};
|
|
static const s32 ht20_sgi[] = { 7200, 14400, 21700, 28900, 43300,
|
|
57800, 65000, 72200
|
|
};
|
|
u8 i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ht20); i++) {
|
|
if (rate == ht20[i]) {
|
|
*sgi = false;
|
|
*mcs = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ht20_sgi); i++) {
|
|
if (rate == ht20_sgi[i]) {
|
|
*sgi = true;
|
|
*mcs = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool is_rate_ht40(s32 rate, u8 *mcs, bool *sgi)
|
|
{
|
|
static const s32 ht40[] = { 13500, 27000, 40500, 54000,
|
|
81000, 108000, 121500, 135000
|
|
};
|
|
static const s32 ht40_sgi[] = { 15000, 30000, 45000, 60000,
|
|
90000, 120000, 135000, 150000
|
|
};
|
|
u8 i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ht40); i++) {
|
|
if (rate == ht40[i]) {
|
|
*sgi = false;
|
|
*mcs = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ht40_sgi); i++) {
|
|
if (rate == ht40_sgi[i]) {
|
|
*sgi = true;
|
|
*mcs = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int __get_rate_info(struct rate_info *txrate, s32 rate, s8 rate_idx)
|
|
{
|
|
s8 rate_id = rate_idx & 0x7f;
|
|
|
|
txrate->flags = 0;
|
|
if (rate_id <= 11) {
|
|
txrate->legacy = rate / 100;
|
|
} else if (rate_id <= 43) {
|
|
txrate->flags |= RATE_INFO_FLAGS_MCS;
|
|
if (rate_idx >> 7)
|
|
txrate->flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
|
|
if (rate_id <= 27)
|
|
txrate->mcs = rate_id - 12;
|
|
else {
|
|
txrate->mcs = rate_id - 28;
|
|
txrate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
|
|
}
|
|
} else {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"invalid rate from ar6004 stats: rate %d idx %d\n",
|
|
rate, rate_idx);
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _get_sta_info(struct ath6kl_vif *vif,
|
|
struct station_info *sinfo)
|
|
{
|
|
struct target_stats *stats = &vif->target_stats;
|
|
s32 rate;
|
|
s8 rateid;
|
|
u8 mcs;
|
|
bool sgi;
|
|
|
|
/* Not only STA but also IBSS mode. */
|
|
|
|
if (stats->rx_byte) {
|
|
sinfo->rx_bytes = stats->rx_byte;
|
|
sinfo->filled |= STATION_INFO_RX_BYTES;
|
|
sinfo->rx_packets = stats->rx_pkt;
|
|
sinfo->filled |= STATION_INFO_RX_PACKETS;
|
|
}
|
|
|
|
if (stats->tx_byte) {
|
|
sinfo->tx_bytes = stats->tx_byte;
|
|
sinfo->filled |= STATION_INFO_TX_BYTES;
|
|
sinfo->tx_packets = stats->tx_pkt;
|
|
sinfo->filled |= STATION_INFO_TX_PACKETS;
|
|
}
|
|
|
|
sinfo->signal = stats->cs_rssi;
|
|
sinfo->filled |= STATION_INFO_SIGNAL;
|
|
|
|
rate = stats->tx_ucast_rate;
|
|
|
|
if (vif->ar->target_type == TARGET_TYPE_AR6004) {
|
|
rateid = stats->tx_rate_index;
|
|
if (__get_rate_info(&sinfo->txrate, rate, rateid))
|
|
ath6kl_debug_war(vif->ar, ATH6KL_WAR_INVALID_RATE);
|
|
else
|
|
sinfo->filled |= STATION_INFO_TX_BITRATE;
|
|
} else {
|
|
sinfo->txrate.flags = 0;
|
|
if (is_rate_legacy(rate)) {
|
|
sinfo->txrate.legacy = rate / 100;
|
|
sinfo->filled |= STATION_INFO_TX_BITRATE;
|
|
} else if (is_rate_ht20(rate, &mcs, &sgi)) {
|
|
if (sgi)
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
sinfo->txrate.mcs = mcs;
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS;
|
|
sinfo->filled |= STATION_INFO_TX_BITRATE;
|
|
} else if (is_rate_ht40(rate, &mcs, &sgi)) {
|
|
if (sgi)
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
sinfo->txrate.mcs = mcs;
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS;
|
|
sinfo->filled |= STATION_INFO_TX_BITRATE;
|
|
} else {
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
|
|
"invalid rate from stats: %d\n", rate);
|
|
ath6kl_debug_war(vif->ar, ATH6KL_WAR_INVALID_RATE);
|
|
}
|
|
}
|
|
|
|
if ((vif->nw_type == INFRA_NETWORK) &&
|
|
test_bit(CONNECTED, &vif->flags) &&
|
|
test_bit(DTIM_PERIOD_AVAIL, &vif->flags) &&
|
|
vif->nw_type == INFRA_NETWORK) {
|
|
sinfo->filled |= STATION_INFO_BSS_PARAM;
|
|
sinfo->bss_param.flags = 0;
|
|
sinfo->bss_param.dtim_period = vif->assoc_bss_dtim_period;
|
|
sinfo->bss_param.beacon_interval = vif->assoc_bss_beacon_int;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void _get_ap_sta_info(struct ath6kl_vif *vif,
|
|
struct wmi_per_sta_stat *sta,
|
|
u8 *mac_addr,
|
|
struct station_info *sinfo)
|
|
{
|
|
s32 rate;
|
|
s8 rateid;
|
|
|
|
if (ath6kl_ap_keepalive_by_supp(vif))
|
|
sinfo->inactive_time =
|
|
ath6kl_ap_keepalive_get_inactive_time(vif, mac_addr);
|
|
else {
|
|
/*
|
|
* Always report 1 sec. to let supplicant bypass
|
|
* its keep-alive mechanims.
|
|
*/
|
|
sinfo->inactive_time = 1 * 1000;
|
|
}
|
|
sinfo->filled |= STATION_INFO_INACTIVE_TIME;
|
|
|
|
sinfo->rx_bytes = sta->rx_bytes;
|
|
sinfo->filled |= STATION_INFO_RX_BYTES;
|
|
sinfo->rx_packets = sta->rx_pkts;
|
|
sinfo->filled |= STATION_INFO_RX_PACKETS;
|
|
|
|
sinfo->tx_bytes = sta->tx_bytes;
|
|
sinfo->filled |= STATION_INFO_TX_BYTES;
|
|
sinfo->tx_packets = sta->tx_pkts;
|
|
sinfo->filled |= STATION_INFO_TX_PACKETS;
|
|
|
|
if (vif->ar->target_type == TARGET_TYPE_AR6004) {
|
|
rate = ath6kl_wmi_get_rate_ar6004(sta->tx_ucast_rate);
|
|
rateid = sta->tx_ucast_rate;
|
|
if (__get_rate_info(&sinfo->txrate, rate, rateid))
|
|
ath6kl_debug_war(vif->ar, ATH6KL_WAR_INVALID_RATE);
|
|
else
|
|
sinfo->filled |= STATION_INFO_TX_BITRATE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static int ath6kl_get_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac, struct station_info *sinfo)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
struct ath6kl_sta *conn = NULL;
|
|
long left;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
conn = ath6kl_find_sta(vif, mac);
|
|
if (conn == NULL) {
|
|
ath6kl_err("Can't find %02x%02x%02x%02x%02x%02x\n",
|
|
mac[0], mac[1], mac[2],
|
|
mac[3], mac[4], mac[5]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
if (memcmp(mac, vif->bssid, ETH_ALEN) != 0)
|
|
return -ENOENT;
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
/* skip to update FW statistics in 1min when wow suspend */
|
|
if (ar->state == ATH6KL_STATE_WOW) {
|
|
struct timeval cur_time;
|
|
long diff_time;
|
|
|
|
do_gettimeofday(&cur_time);
|
|
if (cur_time.tv_sec >= vif->target_stats.update_time.tv_sec) {
|
|
diff_time = cur_time.tv_sec
|
|
- vif->target_stats.update_time.tv_sec;
|
|
|
|
if (diff_time <= 60)
|
|
goto skip_fw_stats;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (down_interruptible(&ar->sem))
|
|
return -EBUSY;
|
|
|
|
set_bit(STATS_UPDATE_PEND, &vif->flags);
|
|
|
|
if (ath6kl_wmi_get_stats_cmd(ar->wmi, vif->fw_vif_idx)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(STATS_UPDATE_PEND,
|
|
&vif->flags),
|
|
WMI_TIMEOUT);
|
|
|
|
clear_bit(STATS_UPDATE_PEND, &vif->flags);
|
|
up(&ar->sem);
|
|
|
|
if (left == 0)
|
|
return -ETIMEDOUT;
|
|
else if (left < 0)
|
|
return left;
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
skip_fw_stats:
|
|
#endif
|
|
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
struct wmi_ap_mode_stat *ap = &vif->ap_stats;
|
|
struct wmi_per_sta_stat *sta = NULL;
|
|
|
|
for (left = 0; left < AP_MAX_NUM_STA; left++) {
|
|
if (conn->aid == ap->sta[left].aid) {
|
|
sta = &ap->sta[left];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sta)
|
|
return -EINVAL;
|
|
|
|
_get_ap_sta_info(vif, sta, mac, sinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
_get_sta_info(vif, sinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_dump_station(struct wiphy *wiphy, struct net_device *dev,
|
|
int idx, u8 *mac, struct station_info *sinfo)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
struct ath6kl_sta *conn;
|
|
struct wmi_ap_mode_stat *ap;
|
|
struct wmi_per_sta_stat *sta;
|
|
long left;
|
|
int next;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
/* Get AP stats only at least one station associated. */
|
|
if ((vif->sta_list_index == 0) ||
|
|
(idx >= AP_MAX_NUM_STA))
|
|
return -ENOENT;
|
|
|
|
/* Only need to update it when 1st STA dump. */
|
|
if (idx == 0)
|
|
vif->last_dump_ap_stats_idx = 0;
|
|
else
|
|
goto update_done;
|
|
} else {
|
|
if ((idx != 0) ||
|
|
!test_bit(CONNECTED, &vif->flags))
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (down_interruptible(&ar->sem))
|
|
return -EBUSY;
|
|
|
|
set_bit(STATS_UPDATE_PEND, &vif->flags);
|
|
|
|
if (ath6kl_wmi_get_stats_cmd(ar->wmi, vif->fw_vif_idx)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(STATS_UPDATE_PEND,
|
|
&vif->flags),
|
|
WMI_TIMEOUT);
|
|
|
|
clear_bit(STATS_UPDATE_PEND, &vif->flags);
|
|
up(&ar->sem);
|
|
|
|
if (left == 0)
|
|
return -ETIMEDOUT;
|
|
else if (left < 0)
|
|
return left;
|
|
|
|
update_done:
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
if (vif->last_dump_ap_stats_idx >= AP_MAX_NUM_STA)
|
|
return -ENOENT;
|
|
|
|
/* Find next STA */
|
|
ap = &vif->ap_stats;
|
|
for (next = vif->last_dump_ap_stats_idx;
|
|
next < AP_MAX_NUM_STA;
|
|
next++) {
|
|
sta = &(ap->sta[next]);
|
|
if (sta->aid) {
|
|
conn = ath6kl_find_sta_by_aid(vif, sta->aid);
|
|
if (conn)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* All STAs reported */
|
|
if (next == AP_MAX_NUM_STA)
|
|
return -ENOENT;
|
|
|
|
/* Set to the next one. */
|
|
vif->last_dump_ap_stats_idx = next + 1;
|
|
|
|
memcpy(mac, conn->mac, ETH_ALEN);
|
|
_get_ap_sta_info(vif, sta, conn->mac, sinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
memcpy(mac, vif->ndev->dev_addr, ETH_ALEN);
|
|
_get_sta_info(vif, sinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_set_bitrate_mask(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
const u8 *peer,
|
|
const struct cfg80211_bitrate_mask *mask)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int ret, disabled;
|
|
|
|
WARN_ON(peer);
|
|
if ((peer) ||
|
|
((mask->control[NL80211_BAND_2GHZ].legacy != 0xfff) &&
|
|
(mask->control[NL80211_BAND_2GHZ].legacy != 0xff0))) {
|
|
/* FIXME : not support yet */
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (mask->control[NL80211_BAND_2GHZ].legacy &
|
|
((1 << ath6kl_b_rates_size) - 1))
|
|
disabled = 0;
|
|
else
|
|
disabled = 1;
|
|
|
|
ret = ath6kl_wmi_disable_11b_rates_cmd(ar->wmi, vif->fw_vif_idx,
|
|
disabled);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_set_pmksa(struct wiphy *wiphy, struct net_device *netdev,
|
|
struct cfg80211_pmksa *pmksa)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(netdev);
|
|
struct ath6kl_vif *vif = netdev_priv(netdev);
|
|
int ret;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
ret = ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx, pmksa->bssid,
|
|
pmksa->pmkid, true);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_del_pmksa(struct wiphy *wiphy, struct net_device *netdev,
|
|
struct cfg80211_pmksa *pmksa)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(netdev);
|
|
struct ath6kl_vif *vif = netdev_priv(netdev);
|
|
int ret;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
ret = ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx, pmksa->bssid,
|
|
pmksa->pmkid, false);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(netdev);
|
|
struct ath6kl_vif *vif = netdev_priv(netdev);
|
|
int ret;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (test_bit(CONNECTED, &vif->flags)) {
|
|
ret = ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx,
|
|
vif->bssid, NULL, false);
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
int ret, left;
|
|
#ifndef CONFIG_ANDROID
|
|
int pos;
|
|
u32 filter = 0;
|
|
u8 mask[WOW_MASK_SIZE];
|
|
#endif
|
|
u16 i;
|
|
struct in_device *in_dev;
|
|
struct in_ifaddr *ifa;
|
|
unsigned char src_ip[4];
|
|
|
|
/*if already in wow state just return without error*/
|
|
if (ar->state == ATH6KL_STATE_WOW)
|
|
return 0;
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
#if (!defined(CONFIG_ANDROID) && !defined(USB_AUTO_SUSPEND))
|
|
if (!ar->get_wow_pattern) {
|
|
/* Clear existing WOW patterns */
|
|
for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++)
|
|
ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WOW_LIST_ID, i);
|
|
/* Configure new WOW patterns */
|
|
for (i = 0; i < wow->n_patterns; i++) {
|
|
if (ath6kl_wow_ext) {
|
|
ret = ath6kl_wmi_add_wow_ext_pattern_cmd(
|
|
ar->wmi,
|
|
vif->fw_vif_idx, WOW_LIST_ID,
|
|
wow->patterns[i].pattern_len,
|
|
i,
|
|
0,
|
|
wow->patterns[i].pattern,
|
|
wow->patterns[i].mask);
|
|
/* filter for wow ext pattern */
|
|
filter |= WOW_FILTER_OPTION_PATTERNS;
|
|
} else {
|
|
/*
|
|
* Convert given nl80211 specific mask value to
|
|
* equivalent driver specific mask value
|
|
* and send it to the chip along with patterns.
|
|
* For example, if the mask value defined
|
|
* in struct cfg80211_wowlan is 0xA
|
|
* (equivalent binary is 1010), then equivalent
|
|
* driver specific mask value is
|
|
* "0xFF 0x00 0xFF 0x00".
|
|
*/
|
|
memset(&mask, 0, sizeof(mask));
|
|
for (pos = 0;
|
|
pos < wow->patterns[i].pattern_len;
|
|
pos++) {
|
|
if (wow->patterns[i].mask[pos / 8] &
|
|
(0x1 << (pos % 8)))
|
|
mask[pos] = 0xFF;
|
|
}
|
|
/*
|
|
* Note: Pattern's offset is not passed
|
|
* as part of wowlan parameter from CFG layer.
|
|
* So it's always passed as ZERO to
|
|
* the firmware.
|
|
* It means, given WOW patterns are always
|
|
* matched from the first byte
|
|
* of received pkt in the firmware.
|
|
*/
|
|
ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi,
|
|
vif->fw_vif_idx, WOW_LIST_ID,
|
|
wow->patterns[i].pattern_len,
|
|
0 /* pattern offset */,
|
|
wow->patterns[i].pattern, mask);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
} else
|
|
filter |= WOW_FILTER_OPTION_PATTERNS;
|
|
|
|
if (wow) {
|
|
if (wow->disconnect || wow->any)
|
|
filter |= WOW_FILTER_OPTION_NWK_DISASSOC;
|
|
|
|
if (wow->magic_pkt || wow->any)
|
|
filter |= WOW_FILTER_OPTION_MAGIC_PACKET;
|
|
|
|
if (wow->gtk_rekey_failure || wow->any) {
|
|
filter |= (WOW_FILTER_OPTION_EAP_REQ |
|
|
WOW_FILTER_OPTION_8021X_4WAYHS |
|
|
WOW_FILTER_OPTION_GTK_ERROR |
|
|
WOW_FILTER_OPTION_OFFLOAD_GTK);
|
|
}
|
|
|
|
if (wow->eap_identity_req || wow->any)
|
|
filter |= WOW_FILTER_OPTION_EAP_REQ;
|
|
|
|
if (wow->four_way_handshake || wow->any)
|
|
filter |= WOW_FILTER_OPTION_8021X_4WAYHS;
|
|
|
|
if (vif->arp_offload_ip_set || wow->any)
|
|
filter |= WOW_FILTER_OPTION_OFFLOAD_ARP;
|
|
}
|
|
|
|
/*Do GTK offload in WPA/WPA2 auth mode connection.*/
|
|
if (vif->auth_mode == WPA2_AUTH_CCKM || vif->auth_mode == WPA2_PSK_AUTH
|
|
|| vif->auth_mode == WPA_AUTH_CCKM || vif->auth_mode == WPA_PSK_AUTH){
|
|
filter |= WOW_FILTER_OPTION_OFFLOAD_GTK;
|
|
}
|
|
|
|
if (filter || (wow && wow->n_patterns)) {
|
|
ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_WOW_MODE_ENABLE,
|
|
filter,
|
|
WOW_HOST_REQ_DELAY);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
#endif /*!CONFIG_ANDROID*/
|
|
|
|
/* Setup own IP addr for ARP agent. */
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
struct ath6kl_vif *mvif = ath6kl_get_vif_by_index(ar, i);
|
|
if (!mvif)
|
|
continue;
|
|
|
|
memset(src_ip, 0x0, 4);
|
|
in_dev = __in_dev_get_rtnl(mvif->ndev);
|
|
if (in_dev) {
|
|
ifa = in_dev->ifa_list;
|
|
if (ifa && ifa->ifa_local) {
|
|
if (mvif->arp_offload_ip != ifa->ifa_local) {
|
|
memcpy(src_ip, &ifa->ifa_local, 4);
|
|
if (!ath6kl_wmi_set_arp_offload_ip_cmd(
|
|
ar->wmi, mvif->fw_vif_idx,
|
|
src_ip)) {
|
|
mvif->arp_offload_ip =
|
|
ifa->ifa_local;
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN,
|
|
"%s: enable %s arp offload %d.%d.%d.%d\n",
|
|
__func__,
|
|
mvif->ndev->name,
|
|
src_ip[0], src_ip[1],
|
|
src_ip[2], src_ip[3]);
|
|
}
|
|
}
|
|
} else if (mvif->arp_offload_ip != 0) {
|
|
if (!ath6kl_wmi_set_arp_offload_ip_cmd(
|
|
ar->wmi, mvif->fw_vif_idx,
|
|
src_ip)) {
|
|
mvif->arp_offload_ip = 0;
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN,
|
|
"%s: disable %s arp offload\n",
|
|
__func__,
|
|
mvif->ndev->name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
clear_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags);
|
|
|
|
ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_HOST_MODE_ASLEEP);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
test_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags),
|
|
WMI_TIMEOUT);
|
|
|
|
if (left == 0) {
|
|
ath6kl_warn("timeout, didn't get host sleep cmd "
|
|
"processed event\n");
|
|
ret = -ETIMEDOUT;
|
|
} else if (left < 0) {
|
|
ath6kl_warn("error while waiting for host sleep cmd "
|
|
"processed event %d\n", left);
|
|
ret = left;
|
|
}
|
|
|
|
if (ar->tx_pending[ar->ctrl_ep]) {
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
ar->tx_pending[ar->ctrl_ep] == 0, WMI_TIMEOUT);
|
|
if (left == 0) {
|
|
ath6kl_warn("clear wmi ctrl data timeout\n");
|
|
ret = -ETIMEDOUT;
|
|
} else if (left < 0) {
|
|
ath6kl_warn("clear wmi ctrl data failed: %d\n", left);
|
|
ret = left;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
if (ret)
|
|
ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_HOST_MODE_AWAKE);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_wow_resume(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
int ret;
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_HOST_MODE_AWAKE);
|
|
|
|
/* Sync replay_counter back to the user. */
|
|
if (!ret) {
|
|
if ((vif->nw_type == INFRA_NETWORK) &&
|
|
test_bit(CONNECTED, &vif->flags) &&
|
|
(vif->ar->last_wow_fliter &
|
|
WOW_FILTER_OPTION_8021X_4WAYHS) &&
|
|
(vif->auth_mode == WPA2_AUTH_CCKM ||
|
|
vif->auth_mode == WPA2_PSK_AUTH ||
|
|
vif->auth_mode == WPA_AUTH_CCKM ||
|
|
vif->auth_mode == WPA_PSK_AUTH))
|
|
ret = ath6kl_wmi_get_gtk_offload(ar->wmi,
|
|
vif->fw_vif_idx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
static irqreturn_t ath6kl_wow_irq(int irq, void *dev_id)
|
|
{
|
|
struct rttm_context *prttm = NULL;
|
|
|
|
prttm = DEV_GETRTT_HDL();
|
|
|
|
if ((prttm) && (prttm->rttdhclkcal_active)) {
|
|
struct timespec ts;
|
|
|
|
getnstimeofday(&ts);
|
|
prttm->rttd2h2_clk.tabs_h2[prttm->dhclkcal_index].sec =
|
|
ts.tv_sec;
|
|
prttm->rttd2h2_clk.tabs_h2[prttm->dhclkcal_index].nsec =
|
|
ts.tv_nsec;
|
|
prttm->dhclkcal_index++;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
static bool ath6kl_cfg80211_need_suspend(struct ath6kl *ar, u32 *suspend_vif)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
int i;
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return false;
|
|
|
|
if (!test_bit(WMI_READY, &ar->flag)) {
|
|
ath6kl_err("deepsleep failed as wmi is not ready\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If one of all virtual interfaces is AP mode then
|
|
* force to awake.
|
|
*/
|
|
*suspend_vif = 0;
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if (vif) {
|
|
#ifdef CE_SUPPORT
|
|
if (vif->nw_type != AP_NETWORK)
|
|
*suspend_vif |= (1 << i);
|
|
#else
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
*suspend_vif = 0;
|
|
return false;
|
|
}
|
|
*suspend_vif |= (1 << i);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int ath6kl_cfg80211_deepsleep_suspend(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
int i, ret = 0, left;
|
|
u32 need_suspend_vif = 0;
|
|
|
|
if (!ath6kl_cfg80211_need_suspend(ar, &need_suspend_vif))
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"deep sleep suspend, need_suspend_vif 0x%x\n",
|
|
need_suspend_vif);
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
if (ar->wow_irq) {
|
|
if (disable_irq_wake(ar->wow_irq))
|
|
ath6kl_err("Couldn't disable hostwake IRQ wakeup mode\n");
|
|
|
|
free_irq(ar->wow_irq, ar);
|
|
}
|
|
#endif
|
|
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
if (need_suspend_vif & (1 << i)) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if (vif) {
|
|
clear_bit(WLAN_ENABLED, &vif->flags);
|
|
netif_dormant_on(vif->ndev);
|
|
set_bit(DORMANT, &vif->flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
/*
|
|
* After McK3.x, we won't have credit full problem as before,
|
|
* so we could move ATH6KL_STATE_DEEPSLEEP after stop_all()
|
|
*/
|
|
ath6kl_cfg80211_stop_all(ar);
|
|
|
|
spin_lock_bh(&ar->state_lock);
|
|
ar->state = ATH6KL_STATE_DEEPSLEEP;
|
|
spin_unlock_bh(&ar->state_lock);
|
|
#else
|
|
spin_lock_bh(&ar->state_lock);
|
|
ar->state = ATH6KL_STATE_DEEPSLEEP;
|
|
spin_unlock_bh(&ar->state_lock);
|
|
|
|
ath6kl_cfg80211_stop_all(ar);
|
|
#endif
|
|
|
|
/*
|
|
* Flush data packets and wait for all control packets
|
|
* to be cleared in TX path before deep sleep suspend.
|
|
*/
|
|
ath6kl_tx_data_cleanup(ar);
|
|
|
|
if (ar->tx_pending[ar->ctrl_ep]) {
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
ar->tx_pending[ar->ctrl_ep] == 0, WMI_TIMEOUT);
|
|
if (left == 0) {
|
|
ath6kl_warn("clear wmi ctrl data timeout txpend %d\n",
|
|
ar->tx_pending[ar->ctrl_ep]);
|
|
ret = -ETIMEDOUT;
|
|
} else if (left < 0) {
|
|
ath6kl_warn("clear wmi ctrl data failed:%d tx_pend=%d\n",
|
|
left, ar->tx_pending[ar->ctrl_ep]);
|
|
ret = left;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *_get_suspend_mode_string(enum ath6kl_cfg_suspend_mode mode)
|
|
{
|
|
if (mode == ATH6KL_CFG_SUSPEND_DEEPSLEEP)
|
|
return "DEEPSLEEP";
|
|
else if (mode == ATH6KL_CFG_SUSPEND_CUTPOWER)
|
|
return "CUTPOWER";
|
|
else if (mode == ATH6KL_CFG_SUSPEND_WOW)
|
|
return "WOW";
|
|
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
static char *_get_suspend_stat_string(enum ath6kl_state state)
|
|
{
|
|
if (state == ATH6KL_STATE_OFF)
|
|
return "OFF";
|
|
else if (state == ATH6KL_STATE_ON)
|
|
return "ON";
|
|
else if (state == ATH6KL_STATE_DEEPSLEEP)
|
|
return "DEEPSLEEP";
|
|
else if (state == ATH6KL_STATE_CUTPOWER)
|
|
return "CUTPOWER";
|
|
else if (state == ATH6KL_STATE_WOW)
|
|
return "WOW";
|
|
else if (state == ATH6KL_STATE_PRE_SUSPEND)
|
|
return "PRE-SUSPEND";
|
|
else if (state == ATH6KL_STATE_PRE_SUSPEND_DEEPSLEEP)
|
|
return "PRE-DEEPSLEEP";
|
|
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
int ath6kl_cfg80211_suspend(struct ath6kl *ar,
|
|
enum ath6kl_cfg_suspend_mode mode,
|
|
struct cfg80211_wowlan *wow)
|
|
{
|
|
int ret;
|
|
u32 need_suspend_vif = 0;
|
|
|
|
/* make sure no AP mode at any vif*/
|
|
if (ath6kl_cfg80211_need_suspend(ar, &need_suspend_vif)
|
|
&& ar->get_wow_pattern == true)
|
|
mode = ATH6KL_CFG_SUSPEND_WOW;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"Start suspend mode %s, ar->state %s\n",
|
|
_get_suspend_mode_string(mode),
|
|
_get_suspend_stat_string(ar->state));
|
|
|
|
switch (mode) {
|
|
case ATH6KL_CFG_SUSPEND_WOW:
|
|
/* Flush all non control pkts in TX path */
|
|
ath6kl_tx_data_cleanup(ar);
|
|
#ifdef USB_AUTO_SUSPEND
|
|
ar->state = ATH6KL_STATE_PRE_SUSPEND;
|
|
#endif
|
|
ret = ath6kl_wow_suspend(ar, wow);
|
|
if (ret) {
|
|
ath6kl_err("wow suspend failed: %d\n", ret);
|
|
#ifdef USB_AUTO_SUSPEND
|
|
ar->state = ATH6KL_STATE_WOW;
|
|
#endif
|
|
return ret;
|
|
}
|
|
spin_lock_bh(&ar->state_lock);
|
|
ar->state = ATH6KL_STATE_WOW;
|
|
spin_unlock_bh(&ar->state_lock);
|
|
break;
|
|
|
|
case ATH6KL_CFG_SUSPEND_DEEPSLEEP:
|
|
if (ar->state == ATH6KL_STATE_DEEPSLEEP)
|
|
break;
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
ar->state = ATH6KL_STATE_PRE_SUSPEND_DEEPSLEEP;
|
|
#endif
|
|
ret = ath6kl_cfg80211_deepsleep_suspend(ar);
|
|
if (ret) {
|
|
if (ret != -ENOTSUPP)
|
|
ath6kl_err("deepsleep suspend failed:"
|
|
" %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
break;
|
|
|
|
case ATH6KL_CFG_SUSPEND_CUTPOWER:
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
if (ar->wow_irq) {
|
|
if (disable_irq_wake(ar->wow_irq))
|
|
ath6kl_err("Couldn't disable hostwake IRQ wakeup mode\n");
|
|
|
|
free_irq(ar->wow_irq, ar);
|
|
}
|
|
#endif
|
|
|
|
ath6kl_cfg80211_stop_all(ar);
|
|
|
|
if (ar->state == ATH6KL_STATE_OFF) {
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND,
|
|
"suspend hw off, no action for cutpower\n");
|
|
break;
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND, "suspend cutting power\n");
|
|
|
|
ret = ath6kl_init_hw_stop(ar);
|
|
if (ret) {
|
|
ath6kl_warn("failed to stop hw during suspend: %d\n",
|
|
ret);
|
|
}
|
|
|
|
ar->state = ATH6KL_STATE_CUTPOWER;
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"End suspend mode %s, ar->state %s\n",
|
|
_get_suspend_mode_string(mode),
|
|
_get_suspend_stat_string(ar->state));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath6kl_cfg80211_resume(struct ath6kl *ar)
|
|
{
|
|
int i, ret;
|
|
struct ath6kl_vif *vif;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"Start resume, ar->state %s\n",
|
|
_get_suspend_stat_string(ar->state));
|
|
|
|
switch (ar->state) {
|
|
case ATH6KL_STATE_WOW:
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_timeout(&ar->wake_lock, 3*HZ);
|
|
#else
|
|
/* TODO: What should I do if there is no wake lock?? */
|
|
#endif
|
|
|
|
spin_lock_bh(&ar->state_lock);
|
|
ar->state = ATH6KL_STATE_ON;
|
|
spin_unlock_bh(&ar->state_lock);
|
|
|
|
ret = ath6kl_wow_resume(ar);
|
|
if (ret) {
|
|
ath6kl_warn("wow mode resume failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
spin_lock_bh(&ar->usb_pm_lock);
|
|
ath6kl_auto_pm_wakeup_resume(ar);
|
|
spin_unlock_bh(&ar->usb_pm_lock);
|
|
#endif
|
|
break;
|
|
|
|
case ATH6KL_STATE_DEEPSLEEP:
|
|
spin_lock_bh(&ar->state_lock);
|
|
ar->state = ATH6KL_STATE_ON;
|
|
spin_unlock_bh(&ar->state_lock);
|
|
#ifdef USB_AUTO_SUSPEND
|
|
spin_lock_bh(&ar->usb_pm_lock);
|
|
ath6kl_auto_pm_wakeup_resume(ar);
|
|
spin_unlock_bh(&ar->usb_pm_lock);
|
|
#endif
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
if (ar->wow_irq) {
|
|
int ret;
|
|
ret = request_irq(ar->wow_irq, ath6kl_wow_irq,
|
|
IRQF_SHARED | IRQF_TRIGGER_RISING,
|
|
"ar6000" "sdiowakeup", ar);
|
|
if (!ret) {
|
|
ret = enable_irq_wake(ar->wow_irq);
|
|
if (ret < 0) {
|
|
ath6kl_err("Couldn't enable WoW IRQ as wakeup interrupt");
|
|
return ret;
|
|
}
|
|
ath6kl_info("ath6kl: WoW IRQ %d\n",
|
|
ar->wow_irq);
|
|
}
|
|
}
|
|
#endif
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if (vif) {
|
|
if (test_bit(DORMANT, &vif->flags)) {
|
|
set_bit(WLAN_ENABLED, &vif->flags);
|
|
netif_dormant_off(vif->ndev);
|
|
clear_bit(DORMANT, &vif->flags);
|
|
}
|
|
|
|
/* restore previous power mode */
|
|
if (vif->last_pwr_mode != vif->saved_pwr_mode) {
|
|
if (ath6kl_wmi_powermode_cmd(ar->wmi, i,
|
|
vif->saved_pwr_mode) != 0) {
|
|
ath6kl_err("wmi powermode command failed during resume\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case ATH6KL_STATE_CUTPOWER:
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND, "resume restoring power\n");
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
if (ar->wow_irq) {
|
|
int ret;
|
|
ret = request_irq(ar->wow_irq, ath6kl_wow_irq,
|
|
IRQF_SHARED | IRQF_TRIGGER_RISING,
|
|
"ar6000" "sdiowakeup", ar);
|
|
if (!ret) {
|
|
ret = enable_irq_wake(ar->wow_irq);
|
|
if (ret < 0) {
|
|
ath6kl_err("Couldn't enable WoW IRQ as wakeup interrupt");
|
|
return ret;
|
|
}
|
|
ath6kl_info("ath6kl: WoW IRQ %d\n",
|
|
ar->wow_irq);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ret = ath6kl_init_hw_start(ar);
|
|
if (ret) {
|
|
ath6kl_warn("Failed to boot hw in resume: %d\n", ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
|
|
ATH6KL_DBG_EXT_AUTOPM,
|
|
"End resume, ar->state %s\n",
|
|
_get_suspend_stat_string(ar->state));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
/* hif layer decides what suspend mode to use */
|
|
static int __ath6kl_cfg80211_suspend(struct wiphy *wiphy,
|
|
struct cfg80211_wowlan *wow)
|
|
{
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
|
|
#if defined(USB_AUTO_SUSPEND)
|
|
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode))
|
|
return 0;
|
|
#endif
|
|
|
|
return ath6kl_hif_suspend(ar, wow);
|
|
}
|
|
|
|
static int __ath6kl_cfg80211_resume(struct wiphy *wiphy)
|
|
{
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
|
|
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode))
|
|
return 0;
|
|
|
|
return ath6kl_hif_resume(ar);
|
|
}
|
|
|
|
/*
|
|
* FIXME: WOW suspend mode is selected if the host sdio controller supports
|
|
* both sdio irq wake up and keep power. The target pulls sdio data line to
|
|
* wake up the host when WOW pattern matches. This causes sdio irq handler
|
|
* is being called in the host side which internally hits ath6kl's RX path.
|
|
*
|
|
* Since sdio interrupt is not disabled, RX path executes even before
|
|
* the host executes the actual resume operation from PM module.
|
|
*
|
|
* In the current scenario, WOW resume should happen before start processing
|
|
* any data from the target. So It's required to perform WOW resume in RX path.
|
|
* Ideally we should perform WOW resume only in the actual platform
|
|
* resume path. This area needs bit rework to avoid WOW resume in RX path.
|
|
*
|
|
* ath6kl_check_wow_status() is called from ath6kl_rx().
|
|
*/
|
|
void ath6kl_check_wow_status(struct ath6kl *ar)
|
|
{
|
|
if (ar->state == ATH6KL_STATE_WOW)
|
|
ath6kl_cfg80211_resume(ar);
|
|
}
|
|
|
|
#else
|
|
|
|
void ath6kl_check_wow_status(struct ath6kl *ar)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#ifndef CFG80211_NO_SET_CHAN_OPERATION
|
|
static int ath6kl_set_channel(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
|
|
if (dev == NULL)
|
|
return -EBUSY;
|
|
|
|
vif = netdev_priv(dev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&vif->ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: center_freq=%u hw_value=%u channel_type=%d\n",
|
|
__func__, chan->center_freq, chan->hw_value, channel_type);
|
|
vif->next_chan = chan->center_freq;
|
|
|
|
if (ath6kl_mod_debug_quirks(vif->ar,
|
|
ATH6KL_MODULE_ENABLE_P2P_CHANMODE)) {
|
|
if (channel_type == NL80211_CHAN_HT40PLUS)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT40PLUS;
|
|
else if (channel_type == NL80211_CHAN_HT40MINUS)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT40MINUS;
|
|
else if (channel_type == NL80211_CHAN_HT20)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT20;
|
|
else
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_NONE;
|
|
} else
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_NONE;
|
|
|
|
up(&vif->ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_set_ap_probe_resp_ies(struct ath6kl_vif *vif,
|
|
const u8 *ies, size_t ies_len)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
const u8 *pos;
|
|
u8 *buf = NULL;
|
|
size_t len = 0;
|
|
int ret;
|
|
|
|
/*
|
|
* Filter out P2P/WFD IE(s) since they will be included depending on
|
|
* the Probe Request frame in ath6kl_wmi_send_go_probe_response_cmd().
|
|
*/
|
|
|
|
if (ies && ies_len) {
|
|
buf = kmalloc(ies_len, GFP_KERNEL);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
pos = ies;
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
if ((!ath6kl_is_p2p_ie(pos)) &&
|
|
(!ath6kl_is_wfd_ie(pos))) {
|
|
memcpy(buf + len, pos, 2 + pos[1]);
|
|
len += 2 + pos[1];
|
|
}
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WMI_FRAME_PROBE_RESP, buf, len);
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_set_uapsd(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *ies, int ies_len)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
const u8 *pos;
|
|
int uapsd = 0; /* default is OFF */
|
|
bool found = false;
|
|
|
|
if (ies && ies_len) {
|
|
pos = ies;
|
|
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
|
|
if (ath6kl_is_wmm_ie(pos)) {
|
|
found = true;
|
|
if (pos[8] & 0x80) /* QOS-INFO, BIT(7) */
|
|
uapsd = 1;
|
|
break;
|
|
}
|
|
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
clear_bit(WMM_ENABLED, &vif->flags);
|
|
|
|
return ath6kl_wmi_ap_set_apsd(ar->wmi, vif->fw_vif_idx, uapsd);
|
|
}
|
|
|
|
#ifdef PMF_SUPPORT
|
|
static int ath6kl_set_akm_suites(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_connect_params *sme)
|
|
{
|
|
const u8 *pos, *ies = sme->ie;
|
|
int ies_len = sme->ie_len;
|
|
int ret = 0;
|
|
|
|
if (ies && ies_len) {
|
|
pos = ies;
|
|
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
|
|
if (ath6kl_is_rsn_ie(pos)) {
|
|
int offset, i;
|
|
|
|
/*
|
|
* VER [2] +
|
|
* 1 GROUP Cipher [4] +
|
|
* n Pairwise Ciphers [2 + 4*n] +
|
|
* n AKM suites [2 + 4*n] +
|
|
* RSN-Capabilities [2] +
|
|
* n PMKID Count/List [2 + 16*n] +
|
|
* 1 Group Mgmt Cipher [4]
|
|
*/
|
|
if (pos[1] <= 18)
|
|
break;
|
|
|
|
/* Get/Set AKM value */
|
|
offset = 1 + 1 + 2 + 4 + 2 + (pos[8] * 4);
|
|
if (pos[offset]) {
|
|
sme->crypto.n_akm_suites = pos[offset];
|
|
sme->crypto.akm_suites[0]
|
|
= pos[offset + 2];
|
|
for (i = 1; i < 4; i++) {
|
|
sme->crypto.akm_suites[0]
|
|
= sme->crypto.akm_suites[0]
|
|
<< 8;
|
|
sme->crypto.akm_suites[0]
|
|
+= pos[offset + 2 + i];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_set_rsn_cap(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *ies, int ies_len)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
const u8 *pos;
|
|
u16 rsn_cap = ATH6KL_RSN_CAP_NULLCONF;
|
|
int ret = 0;
|
|
|
|
if (ies && ies_len) {
|
|
pos = ies;
|
|
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
|
|
if (ath6kl_is_rsn_ie(pos)) {
|
|
int offset;
|
|
|
|
/*
|
|
* VER[2] +
|
|
* 1 GROUP Cipher[4] +
|
|
* 1 Pairwise Cipher[2+4] +
|
|
* 1 Auth suit[2+4] = 18
|
|
*/
|
|
if (pos[1] <= 18)
|
|
break;
|
|
|
|
/* Get RSN-CAP offset */
|
|
offset = 1 + 1 + 2 + 4 + 2 + (pos[8] * 4);
|
|
offset += 2 + (pos[offset] * 4);
|
|
if (offset > (pos[1] + 2))
|
|
break;
|
|
|
|
rsn_cap = (pos[offset] |
|
|
(pos[offset + 1] << 8));
|
|
break;
|
|
}
|
|
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
vif->last_rsn_cap = ATH6KL_RSN_CAP_NULLCONF;
|
|
if (rsn_cap != ATH6KL_RSN_CAP_NULLCONF) {
|
|
ret = ath6kl_wmi_get_rsn_cap(ar->wmi, vif->fw_vif_idx);
|
|
if (!ret)
|
|
ret = ath6kl_wmi_set_rsn_cap(ar->wmi,
|
|
vif->fw_vif_idx, rsn_cap);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u16 _ath6kl_ap_get_channel(struct ath6kl_vif *vif,
|
|
struct ath6kl_beacon_parameters *info)
|
|
{
|
|
u16 chan = 0;
|
|
|
|
/* The wpa_supplicant will set-channel first then start AP. */
|
|
|
|
#ifdef CFG80211_NO_SET_CHAN_OPERATION
|
|
/*
|
|
* New cfg80211 implementation will cache channel information and
|
|
* setup it when start AP operation.
|
|
* Here get the channel information from the parameter of start AP
|
|
* operation then set to driver.
|
|
*/
|
|
if (ath6kl_mod_debug_quirks(vif->ar,
|
|
ATH6KL_MODULE_ENABLE_P2P_CHANMODE)) {
|
|
if (info->channel_type == NL80211_CHAN_HT40PLUS)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT40PLUS;
|
|
else if (info->channel_type == NL80211_CHAN_HT40MINUS)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT40MINUS;
|
|
else if (info->channel_type == NL80211_CHAN_HT20)
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_HT20;
|
|
else
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_NONE;
|
|
} else
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_NONE;
|
|
|
|
WARN_ON(!info->channel);
|
|
if (info->channel)
|
|
vif->next_chan = info->channel->center_freq;
|
|
else {
|
|
vif->next_chan = 0;
|
|
vif->next_chan_type = ATH6KL_CHAN_TYPE_NONE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The driver need to cache channel information in the set-channel
|
|
* operation for old cfg80211 implemenation.
|
|
* Here just report the cached channel information.
|
|
*/
|
|
chan = vif->next_chan;
|
|
if (vif->next_chan_type != ATH6KL_CHAN_TYPE_NONE) {
|
|
enum wmi_connect_ap_channel_type chan_type =
|
|
AP_CHANNEL_TYPE_NONE;
|
|
|
|
if (vif->next_chan_type == ATH6KL_CHAN_TYPE_HT40PLUS)
|
|
chan_type = AP_CHANNEL_TYPE_HT40PLUS;
|
|
else if (vif->next_chan_type == ATH6KL_CHAN_TYPE_HT40MINUS)
|
|
chan_type = AP_CHANNEL_TYPE_HT40MINUS;
|
|
else if (vif->next_chan_type == ATH6KL_CHAN_TYPE_HT20)
|
|
chan_type = AP_CHANNEL_TYPE_HT20;
|
|
|
|
chan |= (chan_type << WMI_CONNECT_AP_CHAN_SELECT_OFFSET);
|
|
}
|
|
|
|
/* Overwrite the channel if AP recommand channel turns on */
|
|
chan = ath6kl_ap_rc_get(vif, chan);
|
|
|
|
return chan;
|
|
}
|
|
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
static void _ath6kl_acl_config(struct ath6kl_vif *vif,
|
|
const struct cfg80211_acl_data *acl)
|
|
{
|
|
enum ap_acl_mode mode;
|
|
u8 mac_addr[ETH_ALEN];
|
|
int i;
|
|
|
|
/* Remove all ACL address first. */
|
|
ath6kl_ap_acl_config_mac_list_reset(vif);
|
|
|
|
/* Disable ACL. */
|
|
ath6kl_ap_acl_config_policy(vif, AP_ACL_MODE_DISABLE);
|
|
|
|
/* Turn-on if need. */
|
|
if (acl->n_acl_entries == 0)
|
|
mode = AP_ACL_MODE_DISABLE;
|
|
else if (acl->acl_policy == NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED)
|
|
mode = AP_ACL_MODE_ALLOW;
|
|
else if (acl->acl_policy == NL80211_ACL_POLICY_DENY_UNLESS_LISTED)
|
|
mode = AP_ACL_MODE_DENY;
|
|
else {
|
|
WARN_ON(1);
|
|
mode = AP_ACL_MODE_DISABLE;
|
|
}
|
|
ath6kl_ap_acl_config_policy(vif, mode);
|
|
|
|
/* Add new ACL address. */
|
|
for (i = 0; i < acl->n_acl_entries; i++) {
|
|
if (i < AP_ACL_SIZE) {
|
|
memcpy(mac_addr, acl->mac_addrs[i].addr, ETH_ALEN);
|
|
ath6kl_ap_acl_config_mac_list(vif, mac_addr, false);
|
|
} else {
|
|
ath6kl_err("Wrong ACL number %d\n", acl->n_acl_entries);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ath6kl_beacon_parameters *info, bool add)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
struct ieee80211_mgmt *mgmt;
|
|
u8 *ies;
|
|
int ies_len;
|
|
struct wmi_connect_cmd p;
|
|
int res;
|
|
int i;
|
|
u16 chan;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: add=%d\n", __func__, add);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (vif->next_mode != AP_NETWORK) {
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (info->beacon_ies) {
|
|
u8 *beacon_ies = (u8 *)info->beacon_ies;
|
|
size_t beacon_ies_len = info->beacon_ies_len;
|
|
|
|
ath6kl_p2p_ps_user_app_ie(vif->p2p_ps_info_ctx,
|
|
WMI_FRAME_BEACON,
|
|
&beacon_ies,
|
|
&beacon_ies_len);
|
|
|
|
res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WMI_FRAME_BEACON,
|
|
beacon_ies,
|
|
beacon_ies_len);
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* IOT : Set default DTIM period to 1 .
|
|
* wpa_supplicant default is 2 and target default is 5.
|
|
*/
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: set DTIM from %d to 1.\n",
|
|
__func__, info->dtim_period);
|
|
ath6kl_wmi_set_dtim_cmd(ar->wmi, vif->fw_vif_idx, 1);
|
|
|
|
if (info->proberesp_ies) {
|
|
res = ath6kl_set_ap_probe_resp_ies(vif, info->proberesp_ies,
|
|
info->proberesp_ies_len);
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
}
|
|
if (info->assocresp_ies) {
|
|
res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WMI_FRAME_ASSOC_RESP,
|
|
info->assocresp_ies,
|
|
info->assocresp_ies_len);
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (!add) {
|
|
up(&ar->sem);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
/* Config ACL */
|
|
if (info->acl)
|
|
_ath6kl_acl_config(vif, info->acl);
|
|
#endif
|
|
|
|
/* Config keep-alive */
|
|
if (info->inactivity_timeout)
|
|
ath6kl_ap_keepalive_config_by_supp(vif,
|
|
info->inactivity_timeout);
|
|
|
|
/* Turn-on/off uAPS. */
|
|
if (ath6kl_set_uapsd(wiphy, dev, info->tail, info->tail_len)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Update RSN Capabilities. */
|
|
if (ath6kl_set_rsn_cap(wiphy, dev, info->tail, info->tail_len)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Turn off power saving mode, if the first interface is AP mode */
|
|
if (vif == ath6kl_vif_first(ar)) {
|
|
if (ath6kl_wmi_powermode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
MAX_PERF_POWER)) {
|
|
up(&ar->sem);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
vif->ap_mode_bkey.valid = false;
|
|
|
|
if (info->beacon_interval) {
|
|
res = ath6kl_wmi_set_beacon_interval_cmd(ar->wmi,
|
|
vif->fw_vif_idx, info->beacon_interval);
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (info->head == NULL) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
mgmt = (struct ieee80211_mgmt *) info->head;
|
|
ies = mgmt->u.beacon.variable;
|
|
if (ies > info->head + info->head_len) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
ies_len = info->head + info->head_len - ies;
|
|
|
|
if (info->ssid == NULL) {
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
memcpy(vif->ssid, info->ssid, info->ssid_len);
|
|
vif->ssid_len = info->ssid_len;
|
|
|
|
if (info->hidden_ssid == NL80211_HIDDEN_SSID_ZERO_LEN) {
|
|
up(&ar->sem);
|
|
return -ENOTSUPP;
|
|
} else if (info->hidden_ssid == NL80211_HIDDEN_SSID_ZERO_CONTENTS) {
|
|
res = ath6kl_wmi_set_hidden_ssid_cmd(
|
|
ar->wmi, vif->fw_vif_idx, 1);
|
|
} else {
|
|
res = ath6kl_wmi_set_hidden_ssid_cmd(
|
|
ar->wmi, vif->fw_vif_idx, 0);
|
|
}
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
|
|
res = ath6kl_set_auth_type(vif, info->auth_type);
|
|
if (res) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
|
|
for (i = 0; i < info->crypto.n_akm_suites; i++) {
|
|
switch (info->crypto.akm_suites[i]) {
|
|
case WLAN_AKM_SUITE_8021X:
|
|
if (info->crypto.wpa_versions & NL80211_WPA_VERSION_1)
|
|
p.auth_mode |= WPA_AUTH;
|
|
if (info->crypto.wpa_versions & NL80211_WPA_VERSION_2)
|
|
p.auth_mode |= WPA2_AUTH;
|
|
break;
|
|
case WLAN_AKM_SUITE_PSK:
|
|
if (info->crypto.wpa_versions & NL80211_WPA_VERSION_1)
|
|
p.auth_mode |= WPA_PSK_AUTH;
|
|
if (info->crypto.wpa_versions & NL80211_WPA_VERSION_2)
|
|
p.auth_mode |= WPA2_PSK_AUTH;
|
|
break;
|
|
}
|
|
}
|
|
if (p.auth_mode == 0)
|
|
p.auth_mode = NONE_AUTH;
|
|
vif->auth_mode = p.auth_mode;
|
|
|
|
for (i = 0; i < info->crypto.n_ciphers_pairwise; i++) {
|
|
switch (info->crypto.ciphers_pairwise[i]) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
p.prwise_crypto_type |= WEP_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
p.prwise_crypto_type |= TKIP_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
p.prwise_crypto_type |= AES_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
p.prwise_crypto_type |= WAPI_CRYPT;
|
|
break;
|
|
}
|
|
}
|
|
if (p.prwise_crypto_type == 0) {
|
|
p.prwise_crypto_type = NONE_CRYPT;
|
|
ath6kl_set_cipher(vif, 0, true);
|
|
} else if (info->crypto.n_ciphers_pairwise == 1)
|
|
ath6kl_set_cipher(vif, info->crypto.ciphers_pairwise[0], true);
|
|
|
|
switch (info->crypto.cipher_group) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
p.grp_crypto_type = WEP_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
p.grp_crypto_type = TKIP_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
p.grp_crypto_type = AES_CRYPT;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_SMS4:
|
|
p.grp_crypto_type = WAPI_CRYPT;
|
|
break;
|
|
default:
|
|
p.grp_crypto_type = NONE_CRYPT;
|
|
break;
|
|
}
|
|
ath6kl_set_cipher(vif, info->crypto.cipher_group, false);
|
|
|
|
p.nw_type = AP_NETWORK;
|
|
vif->nw_type = vif->next_mode;
|
|
|
|
p.ssid_len = vif->ssid_len;
|
|
memcpy(p.ssid, vif->ssid, vif->ssid_len);
|
|
p.dot11_auth_mode = vif->dot11_auth_mode;
|
|
|
|
chan = _ath6kl_ap_get_channel(vif, info);
|
|
p.ch = cpu_to_le16(chan);
|
|
|
|
res = ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, &p);
|
|
if (res < 0) {
|
|
up(&ar->sem);
|
|
return res;
|
|
}
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef NL80211_CMD_START_STOP_AP
|
|
static int ath6kl_start_ap(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_ap_settings *settings)
|
|
{
|
|
struct ath6kl_beacon_parameters *info;
|
|
int ret = 0;
|
|
|
|
info = kzalloc(sizeof(struct ath6kl_beacon_parameters), GFP_ATOMIC);
|
|
if (info == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Fetch to local setting */
|
|
info->beacon_interval = settings->beacon_interval;
|
|
info->dtim_period = settings->dtim_period;
|
|
info->ssid = settings->ssid;
|
|
info->ssid_len = settings->ssid_len;
|
|
info->hidden_ssid = settings->hidden_ssid;
|
|
memcpy(&info->crypto,
|
|
&settings->crypto,
|
|
sizeof(struct cfg80211_crypto_settings));
|
|
info->privacy = settings->privacy;
|
|
info->auth_type = settings->auth_type;
|
|
info->inactivity_timeout = settings->inactivity_timeout;
|
|
#ifdef CFG80211_NO_SET_CHAN_OPERATION
|
|
#ifdef CFG80211_NEW_CHAN_DEFINITION
|
|
info->channel = settings->chandef.chan;
|
|
info->channel_type = cfg80211_get_chandef_type(&settings->chandef);
|
|
#else
|
|
info->channel = settings->channel;
|
|
info->channel_type = settings->channel_type;
|
|
#endif
|
|
#endif
|
|
info->p2p_ctwindow = 0;
|
|
info->p2p_opp_ps = false;
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
info->acl = settings->acl;
|
|
#endif
|
|
|
|
/*
|
|
* The target will take care DFS behavior and the host just need to
|
|
* report events back to the user.
|
|
*/
|
|
info->radar_required = false;
|
|
|
|
info->head = settings->beacon.head;
|
|
info->tail = settings->beacon.tail;
|
|
info->head_len = settings->beacon.head_len;
|
|
info->tail_len = settings->beacon.tail_len;
|
|
info->beacon_ies = settings->beacon.beacon_ies;
|
|
info->beacon_ies_len = settings->beacon.beacon_ies_len;
|
|
info->proberesp_ies = settings->beacon.proberesp_ies;
|
|
info->proberesp_ies_len = settings->beacon.proberesp_ies_len;
|
|
info->assocresp_ies = settings->beacon.assocresp_ies;
|
|
info->assocresp_ies_len = settings->beacon.assocresp_ies_len;
|
|
info->probe_resp = settings->beacon.probe_resp;
|
|
info->probe_resp_len = settings->beacon.probe_resp_len;
|
|
|
|
ret = ath6kl_ap_beacon(wiphy, dev, info, true);
|
|
kfree(info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_change_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
struct cfg80211_beacon_data *settings)
|
|
{
|
|
struct ath6kl_beacon_parameters *info;
|
|
int ret = 0;
|
|
|
|
info = kzalloc(sizeof(struct ath6kl_beacon_parameters), GFP_ATOMIC);
|
|
if (info == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Fetch to local setting */
|
|
info->head = settings->head;
|
|
info->tail = settings->tail;
|
|
info->head_len = settings->head_len;
|
|
info->tail_len = settings->tail_len;
|
|
info->beacon_ies = settings->beacon_ies;
|
|
info->beacon_ies_len = settings->beacon_ies_len;
|
|
info->proberesp_ies = settings->proberesp_ies;
|
|
info->proberesp_ies_len = settings->proberesp_ies_len;
|
|
info->assocresp_ies = settings->assocresp_ies;
|
|
info->assocresp_ies_len = settings->assocresp_ies_len;
|
|
info->probe_resp = settings->probe_resp;
|
|
info->probe_resp_len = settings->probe_resp_len;
|
|
|
|
ret = ath6kl_ap_beacon(wiphy, dev, info, false);
|
|
kfree(info);
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static int ath6kl_add_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
struct beacon_parameters *settings)
|
|
{
|
|
struct ath6kl_beacon_parameters *info;
|
|
int ret = 0;
|
|
|
|
info = kzalloc(sizeof(struct ath6kl_beacon_parameters), GFP_ATOMIC);
|
|
if (info == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Fetch to local setting */
|
|
info->beacon_interval = settings->interval;
|
|
info->dtim_period = settings->dtim_period;
|
|
info->ssid = settings->ssid;
|
|
info->ssid_len = settings->ssid_len;
|
|
info->hidden_ssid = settings->hidden_ssid;
|
|
memcpy(&info->crypto,
|
|
&settings->crypto,
|
|
sizeof(struct cfg80211_crypto_settings));
|
|
info->privacy = settings->privacy;
|
|
info->auth_type = settings->auth_type;
|
|
|
|
info->head = settings->head;
|
|
info->tail = settings->tail;
|
|
info->head_len = settings->head_len;
|
|
info->tail_len = settings->tail_len;
|
|
info->beacon_ies = settings->beacon_ies;
|
|
info->beacon_ies_len = settings->beacon_ies_len;
|
|
info->proberesp_ies = settings->proberesp_ies;
|
|
info->proberesp_ies_len = settings->proberesp_ies_len;
|
|
info->assocresp_ies = settings->assocresp_ies;
|
|
info->assocresp_ies_len = settings->assocresp_ies_len;
|
|
info->probe_resp = settings->probe_resp;
|
|
info->probe_resp_len = settings->probe_resp_len;
|
|
|
|
ret = ath6kl_ap_beacon(wiphy, dev, info, true);
|
|
kfree(info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_set_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
struct beacon_parameters *settings)
|
|
{
|
|
struct ath6kl_beacon_parameters *info;
|
|
int ret = 0;
|
|
|
|
info = kzalloc(sizeof(struct ath6kl_beacon_parameters), GFP_ATOMIC);
|
|
if (info == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Fetch to local setting */
|
|
info->head = settings->head;
|
|
info->tail = settings->tail;
|
|
info->head_len = settings->head_len;
|
|
info->tail_len = settings->tail_len;
|
|
info->beacon_ies = settings->beacon_ies;
|
|
info->beacon_ies_len = settings->beacon_ies_len;
|
|
info->proberesp_ies = settings->proberesp_ies;
|
|
info->proberesp_ies_len = settings->proberesp_ies_len;
|
|
info->assocresp_ies = settings->assocresp_ies;
|
|
info->assocresp_ies_len = settings->assocresp_ies_len;
|
|
info->probe_resp = settings->probe_resp;
|
|
info->probe_resp_len = settings->probe_resp_len;
|
|
|
|
ret = ath6kl_ap_beacon(wiphy, dev, info, false);
|
|
kfree(info);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
static int ath6kl_del_beacon(struct wiphy *wiphy, struct net_device *dev)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (vif->nw_type != AP_NETWORK) {
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (!test_bit(CONNECTED, &vif->flags)) {
|
|
up(&ar->sem);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
/* Back to origional value. */
|
|
if (vif->last_rsn_cap != ATH6KL_RSN_CAP_NULLCONF)
|
|
ath6kl_wmi_set_rsn_cap(ar->wmi, vif->fw_vif_idx,
|
|
vif->last_rsn_cap);
|
|
|
|
/* Stop keep-alive. */
|
|
ath6kl_ap_keepalive_stop(vif);
|
|
|
|
/* Stop ACL. */
|
|
ath6kl_ap_acl_stop(vif);
|
|
|
|
/* Stop Admission-Control */
|
|
ath6kl_ap_admc_stop(vif);
|
|
|
|
ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx);
|
|
clear_bit(CONNECTED, &vif->flags);
|
|
ath6kl_judge_roam_parameter(vif, true);
|
|
ath6kl_switch_parameter_based_on_connection(vif, true);
|
|
|
|
up(&ar->sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const u8 bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
|
|
static int ath6kl_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
const u8 *addr = mac ? mac : bcast_addr;
|
|
int ret;
|
|
struct ath6kl_sta *sta_conn = NULL;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (!is_broadcast_ether_addr(addr)) {
|
|
sta_conn = ath6kl_find_sta(vif, (u8 *)addr);
|
|
if (sta_conn == NULL) {
|
|
up(&ar->sem);
|
|
ret = 0;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, WMI_AP_DEAUTH,
|
|
addr, WLAN_REASON_PREV_AUTH_NOT_VALID);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev,
|
|
u8 *mac, struct station_parameters *params)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int ret = 0;
|
|
struct ath6kl_sta *sta_conn = NULL;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if (vif->nw_type != AP_NETWORK) {
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Use this only for authorizing/unauthorizing a station */
|
|
if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) {
|
|
up(&ar->sem);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
|
|
ret = ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx,
|
|
WMI_AP_MLME_AUTHORIZE, mac, 0);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
if (!is_broadcast_ether_addr(mac)) {
|
|
sta_conn = ath6kl_find_sta(vif, mac);
|
|
if (sta_conn == NULL) {
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx,
|
|
WMI_AP_MLME_UNAUTHORIZE, mac, 0);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _ath6kl_remain_on_channel(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type,
|
|
unsigned int duration,
|
|
u64 *cookie)
|
|
{
|
|
#define MAX_ROC_PERIOD \
|
|
(ATH6KL_ROC_MAX_PERIOD * HZ + HZ / ATH6KL_ROC_MAX_PERIOD)
|
|
#define MAX_SCAN_PERIOD (ATH6KL_SCAN_FG_MAX_PERIOD * HZ)
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
u32 id;
|
|
int ret;
|
|
long left;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ar->autopm_turn_on) {
|
|
ar->autopm_defer_delay_change_cnt =
|
|
USB_SUSPEND_DEFER_DELAY_FOR_P2P;
|
|
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_MAX);
|
|
}
|
|
#endif
|
|
|
|
/* If already ongoing scan then wait it finish. */
|
|
if (vif->scan_req) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Schedule a RoC but on-going Scan %x\n",
|
|
vif->last_roc_id);
|
|
|
|
wait_event_interruptible_timeout(ar->event_wq,
|
|
!vif->scan_req,
|
|
MAX_SCAN_PERIOD);
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : last scan not yet finish?\n");
|
|
up(&ar->sem);
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
/* If already pending remain-on-channel then reject request. */
|
|
if (test_bit(ROC_PEND, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Receive duplicate ROC.\n");
|
|
up(&ar->sem);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* If ongoing remain-on-channel and wait it finish. */
|
|
if (test_bit(ROC_ONGOING, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Last RoC not yet finish, %x %d %d",
|
|
vif->last_roc_id,
|
|
((test_bit(ROC_PEND, &vif->flags)) ? 1 : 0),
|
|
((test_bit(ROC_ONGOING, &vif->flags)) ? 1 : 0));
|
|
|
|
set_bit(ROC_WAIT_EVENT, &vif->flags);
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(ROC_ONGOING, &vif->flags),
|
|
MAX_ROC_PERIOD);
|
|
|
|
if (left == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : wait ROC_WAIT_EVENT timeout\n");
|
|
clear_bit(ROC_WAIT_EVENT, &vif->flags);
|
|
clear_bit(ROC_ONGOING, &vif->flags);
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : last RoC not yet finish?\n");
|
|
up(&ar->sem);
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
spin_lock_bh(&vif->if_lock);
|
|
id = ++vif->last_roc_id;
|
|
if (id == 0) {
|
|
/* Do not use 0 as the cookie value */
|
|
id = ++vif->last_roc_id;
|
|
}
|
|
*cookie = id;
|
|
spin_unlock_bh(&vif->if_lock);
|
|
|
|
/* Cache request channel and report to cfg80211 when target reject. */
|
|
vif->last_roc_channel = chan;
|
|
vif->last_roc_duration = duration;
|
|
|
|
set_bit(ROC_PEND, &vif->flags);
|
|
ret = ath6kl_wmi_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx,
|
|
chan->center_freq, duration);
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
#undef MAX_ROC_PERIOD
|
|
#undef MAX_SCAN_PERIOD
|
|
}
|
|
|
|
static int _ath6kl_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
u64 cookie)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int ret;
|
|
long left;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
#ifdef USB_AUTO_SUSPEND
|
|
if (ar->autopm_turn_on) {
|
|
ar->autopm_defer_delay_change_cnt =
|
|
USB_SUSPEND_DEFER_DELAY_FOR_P2P;
|
|
ath6kl_hif_auto_pm_set_delay(ar, USB_SUSPEND_DELAY_MAX);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* RoC not yet start but be cancelled. Wait it started then cancel
|
|
* it by order to avoid wrong cookie report to supplicant.
|
|
*/
|
|
if (test_bit(ROC_PEND, &vif->flags)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : RoC not start but canceled %x\n",
|
|
vif->last_roc_id);
|
|
|
|
set_bit(ROC_WAIT_EVENT, &vif->flags);
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
test_bit(ROC_ONGOING, &vif->flags),
|
|
WMI_TIMEOUT);
|
|
|
|
/*
|
|
* Another corner case is that last RoC not yet start & also
|
|
* be rejected by target, but be canceled. In this case,
|
|
* treat WMI_TIMEOUT as target reject then return.
|
|
*/
|
|
if (left == 0) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : wait ROC_WAIT_EVENT timeout, %lld %d\n",
|
|
cookie,
|
|
vif->last_roc_id);
|
|
clear_bit(ROC_WAIT_EVENT, &vif->flags);
|
|
clear_bit(ROC_PEND, &vif->flags);
|
|
up(&ar->sem);
|
|
return 0;
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : target did not respond\n");
|
|
up(&ar->sem);
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
spin_lock_bh(&vif->if_lock);
|
|
if (cookie != vif->last_roc_id) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Cancel-RoC %llx but current is %x\n", cookie,
|
|
vif->last_roc_id);
|
|
|
|
spin_unlock_bh(&vif->if_lock);
|
|
up(&ar->sem);
|
|
return -ENOENT;
|
|
}
|
|
|
|
vif->last_cancel_roc_id = cookie;
|
|
set_bit(ROC_CANCEL_PEND, &vif->flags);
|
|
spin_unlock_bh(&vif->if_lock);
|
|
|
|
ret = ath6kl_wmi_cancel_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx);
|
|
|
|
up(&ar->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _ath6kl_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
enum nl80211_channel_type channel_type,
|
|
bool channel_type_valid, unsigned int wait,
|
|
const u8 *buf, size_t len, bool no_cck,
|
|
bool dont_wait_for_ack, u64 *cookie)
|
|
{
|
|
struct ath6kl *ar = ath6kl_priv(dev);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
u32 id;
|
|
u32 wmi_data_flags = 0;
|
|
const struct ieee80211_mgmt *mgmt;
|
|
int ret = 0;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (down_interruptible(&ar->sem)) {
|
|
ath6kl_err("busy, couldn't get access\n");
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
if ((ar->p2p) && !ar->p2p_compat &&
|
|
(ar->p2p_concurrent) &&
|
|
(vif->fw_vif_idx == (ar->vif_max - 1)) &&
|
|
!test_bit(ROC_ONGOING, &vif->flags)) {
|
|
|
|
if (ath6kl_p2p_is_p2p_frame(ar, buf, len)) {
|
|
ath6kl_dbg(ATH6KL_DBG_EXT_ROC,
|
|
"RoC : Channel closed, ignore action frame\n");
|
|
up(&ar->sem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
}
|
|
|
|
id = vif->send_action_id++;
|
|
if (id == 0) {
|
|
/*
|
|
* 0 is a reserved value in the WMI command and shall not be
|
|
* used for the command.
|
|
*/
|
|
id = vif->send_action_id++;
|
|
}
|
|
|
|
*cookie = id;
|
|
|
|
/* AP mode Power saving processing */
|
|
if (vif->nw_type == AP_NETWORK) {
|
|
if (ath6kl_mgmt_powersave_ap(vif,
|
|
id,
|
|
chan->center_freq,
|
|
wait,
|
|
buf,
|
|
len,
|
|
no_cck,
|
|
dont_wait_for_ack,
|
|
&wmi_data_flags)) {
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) buf;
|
|
if (buf + len >= mgmt->u.probe_resp.variable &&
|
|
vif->nw_type == AP_NETWORK && test_bit(CONNECTED, &vif->flags) &&
|
|
ieee80211_is_probe_resp(mgmt->frame_control)) {
|
|
/*
|
|
* Send Probe Response frame in AP mode using a separate WMI
|
|
* command to allow the target to fill in the generic IEs.
|
|
*/
|
|
ret = ath6kl_wmi_send_go_probe_response_cmd(ar->wmi, vif,
|
|
buf, len,
|
|
chan->center_freq);
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
|
|
ret = ath6kl_wmi_send_action_cmd(ar->wmi, vif->fw_vif_idx, id,
|
|
chan->center_freq, wait,
|
|
buf, len);
|
|
up(&ar->sem);
|
|
return ret;
|
|
}
|
|
|
|
static void _ath6kl_mgmt_frame_register(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
u16 frame_type, bool reg)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: frame_type=0x%x reg=%d\n",
|
|
__func__, frame_type, reg);
|
|
if (frame_type == IEEE80211_STYPE_PROBE_REQ) {
|
|
/*
|
|
* Note: This notification callback is not allowed to sleep, so
|
|
* we cannot send WMI_PROBE_REQ_REPORT_CMD here. Instead, we
|
|
* hardcode target to report Probe Request frames all the time.
|
|
*/
|
|
vif->probe_req_report = reg;
|
|
}
|
|
#ifdef CE_SUPPORT
|
|
if (frame_type == IEEE80211_STYPE_PROBE_RESP)
|
|
vif->probe_resp_report = reg;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CFG80211_NETDEV_REPLACED_BY_WDEV
|
|
#ifdef CFG80211_REMOVE_ROC_CHAN_TYPE
|
|
static int ath6kl_remain_on_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan,
|
|
unsigned int duration,
|
|
u64 *cookie)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_remain_on_channel(wiphy,
|
|
wdev->netdev,
|
|
chan,
|
|
NL80211_CHAN_NO_HT,
|
|
duration,
|
|
cookie);
|
|
}
|
|
#else
|
|
static int ath6kl_remain_on_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type,
|
|
unsigned int duration,
|
|
u64 *cookie)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_remain_on_channel(wiphy,
|
|
wdev->netdev,
|
|
chan,
|
|
channel_type,
|
|
duration,
|
|
cookie);
|
|
}
|
|
#endif
|
|
|
|
static int ath6kl_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
u64 cookie)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_cancel_remain_on_channel(wiphy,
|
|
wdev->netdev,
|
|
cookie);
|
|
}
|
|
|
|
#ifdef CFG80211_REMOVE_ROC_CHAN_TYPE
|
|
static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
unsigned int wait,
|
|
const u8 *buf, size_t len, bool no_cck,
|
|
bool dont_wait_for_ack, u64 *cookie)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_mgmt_tx(wiphy,
|
|
wdev->netdev,
|
|
chan,
|
|
offchan,
|
|
NL80211_CHAN_NO_HT,
|
|
true,
|
|
wait,
|
|
buf,
|
|
len,
|
|
no_cck,
|
|
dont_wait_for_ack,
|
|
cookie);
|
|
}
|
|
#else
|
|
static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
enum nl80211_channel_type channel_type,
|
|
bool channel_type_valid, unsigned int wait,
|
|
const u8 *buf, size_t len, bool no_cck,
|
|
bool dont_wait_for_ack, u64 *cookie)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_mgmt_tx(wiphy,
|
|
wdev->netdev,
|
|
chan,
|
|
offchan,
|
|
channel_type,
|
|
channel_type_valid,
|
|
wait,
|
|
buf,
|
|
len,
|
|
no_cck,
|
|
dont_wait_for_ack,
|
|
cookie);
|
|
}
|
|
#endif
|
|
|
|
static void ath6kl_mgmt_frame_register(struct wiphy *wiphy,
|
|
struct wireless_dev *wdev,
|
|
u16 frame_type, bool reg)
|
|
{
|
|
BUG_ON(!wdev->netdev);
|
|
|
|
return _ath6kl_mgmt_frame_register(wiphy,
|
|
wdev->netdev,
|
|
frame_type,
|
|
reg);
|
|
}
|
|
#else
|
|
static int ath6kl_remain_on_channel(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type channel_type,
|
|
unsigned int duration,
|
|
u64 *cookie)
|
|
{
|
|
return _ath6kl_remain_on_channel(wiphy,
|
|
dev,
|
|
chan,
|
|
channel_type,
|
|
duration,
|
|
cookie);
|
|
}
|
|
|
|
static int ath6kl_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
u64 cookie)
|
|
{
|
|
return _ath6kl_cancel_remain_on_channel(wiphy,
|
|
dev,
|
|
cookie);
|
|
}
|
|
|
|
static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
|
|
struct ieee80211_channel *chan, bool offchan,
|
|
enum nl80211_channel_type channel_type,
|
|
bool channel_type_valid, unsigned int wait,
|
|
const u8 *buf, size_t len, bool no_cck,
|
|
bool dont_wait_for_ack, u64 *cookie)
|
|
{
|
|
return _ath6kl_mgmt_tx(wiphy,
|
|
dev,
|
|
chan,
|
|
offchan,
|
|
channel_type,
|
|
channel_type_valid,
|
|
wait,
|
|
buf,
|
|
len,
|
|
no_cck,
|
|
dont_wait_for_ack,
|
|
cookie);
|
|
}
|
|
|
|
static void ath6kl_mgmt_frame_register(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
u16 frame_type, bool reg)
|
|
{
|
|
return _ath6kl_mgmt_frame_register(wiphy,
|
|
dev,
|
|
frame_type,
|
|
reg);
|
|
}
|
|
#endif
|
|
|
|
int ath6kl_set_gtk_rekey_offload(struct wiphy *wiphy,
|
|
struct net_device *dev, struct cfg80211_gtk_rekey_data *data)
|
|
{
|
|
int ret = 0;
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct wmi_gtk_offload_op cmd;
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_TRC, "%s: vif %d\n",
|
|
__func__,
|
|
vif->fw_vif_idx);
|
|
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
/* Only support GTK offload for 1st interface now. */
|
|
if (vif->fw_vif_idx != 0)
|
|
return 0;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
if (!data)
|
|
return ret;
|
|
|
|
memset(&cmd, 0, sizeof(struct wmi_gtk_offload_op));
|
|
|
|
memcpy(cmd.kek, data->kek, GTK_OFFLOAD_KEK_BYTES);
|
|
memcpy(cmd.kck, data->kck, GTK_OFFLOAD_KCK_BYTES);
|
|
memcpy(cmd.replay_counter, data->replay_ctr, GTK_REPLAY_COUNTER_BYTES);
|
|
|
|
ret = ath6kl_wmi_set_gtk_offload(ar->wmi, vif->fw_vif_idx, cmd.kek,
|
|
cmd.kck, cmd.replay_counter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_ap_probe_client(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *peer, u64 *cookie)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
struct ath6kl_sta *conn;
|
|
int ret;
|
|
|
|
BUG_ON(vif->nw_type != AP_NETWORK);
|
|
|
|
conn = ath6kl_find_sta(vif, (u8 *)peer);
|
|
if (conn)
|
|
ret = ath6kl_wmi_ap_poll_sta(ar->wmi,
|
|
vif->fw_vif_idx,
|
|
conn->aid);
|
|
else {
|
|
ret = -EINTR;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO,
|
|
"can't find sta %02x:%02x:%02x:%02x:%02x:%02x, vif-idx %d\n",
|
|
peer[0], peer[1], peer[2], peer[3], peer[4], peer[5],
|
|
vif->fw_vif_idx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool ath6kl_sched_scan_trigger(struct ath6kl_vif *vif)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
int i;
|
|
|
|
/* NOT YET */
|
|
|
|
if (!ar->sche_scan)
|
|
return false;
|
|
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
struct ath6kl_vif *vif_trig;
|
|
|
|
vif_trig = ath6kl_get_vif_by_index(ar, i);
|
|
if ((vif_trig) &&
|
|
(vif_trig != vif) &&
|
|
(!test_bit(CONNECTED, &vif_trig->flags)) &&
|
|
(vif_trig->sche_scan_interval)) {
|
|
ath6kl_dbg(ATH6KL_DBG_INFO, "sche scan triggered\n");
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void ath6kl_sched_scan_timer(unsigned long ptr)
|
|
{
|
|
struct ath6kl_vif *vif = (struct ath6kl_vif *)ptr;
|
|
|
|
/* NOT YET */
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO, "report sche scan\n");
|
|
|
|
cfg80211_sched_scan_results(vif->ar->wiphy);
|
|
|
|
mod_timer(&vif->sche_scan_timer,
|
|
jiffies + msecs_to_jiffies(vif->sche_scan_interval));
|
|
|
|
return;
|
|
}
|
|
|
|
static int ath6kl_sched_scan_start(struct wiphy *wiphy,
|
|
struct net_device *dev,
|
|
struct cfg80211_sched_scan_request *request)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
int ret = 0;
|
|
|
|
/* NOT YET */
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO,
|
|
"start sche scan, interval %d, n_ssids %d, n_channels %d\n",
|
|
request->interval,
|
|
request->n_ssids,
|
|
request->n_channels);
|
|
|
|
if ((vif->ar->sche_scan) &&
|
|
(vif->nw_type == INFRA_NETWORK)) {
|
|
vif->sche_scan_interval = request->interval;
|
|
|
|
mod_timer(&vif->sche_scan_timer,
|
|
jiffies +
|
|
msecs_to_jiffies(vif->sche_scan_interval));
|
|
} else
|
|
ret = -EOPNOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ath6kl_sched_scan_stop(struct wiphy *wiphy,
|
|
struct net_device *dev)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
/* NOT YET */
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_INFO, "stop sche scan\n");
|
|
|
|
if (vif->sche_scan_interval) {
|
|
del_timer(&vif->sche_scan_timer);
|
|
vif->sche_scan_interval = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_sched_scan_init(struct ath6kl_vif *vif)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
/* NOT YET */
|
|
|
|
if (ar->sche_scan) {
|
|
vif->sche_scan_interval = 0;
|
|
|
|
init_timer(&vif->sche_scan_timer);
|
|
vif->sche_scan_timer.function = ath6kl_sched_scan_timer;
|
|
vif->sche_scan_timer.data = (unsigned long)(vif);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_sched_scan_deinit(struct ath6kl_vif *vif)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
ath6kl_sched_scan_stop(ar->wiphy, vif->ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_ANDROID) || defined(USB_AUTO_SUSPEND)
|
|
int ath6kl_set_wow_mode(struct wiphy *wiphy, struct cfg80211_wowlan *wow)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
int ret = 0, pos, i;
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
u32 filter = 0;
|
|
u8 mask[WOW_MASK_SIZE];
|
|
u32 host_req_delay = WOW_HOST_REQ_DELAY;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "%s: +++\n", __func__);
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif) {
|
|
ret = -EIO;
|
|
goto FAIL;
|
|
}
|
|
|
|
if (!ath6kl_cfg80211_ready(vif)) {
|
|
ret = -EIO;
|
|
goto FAIL;
|
|
}
|
|
|
|
if (!ar->get_wow_pattern && WARN_ON(!wow))
|
|
goto FAIL;
|
|
|
|
/* Reset wakeup delay time to 2 secs for sdio, keep 5 secs for
|
|
* usb now
|
|
*/
|
|
if (ar->hif_type == ATH6KL_HIF_TYPE_SDIO)
|
|
host_req_delay = 2000;
|
|
|
|
/* for hsic mode, reduce the wakeup time to 2 secs */
|
|
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode))
|
|
host_req_delay = 2000;
|
|
|
|
/* Clear existing WOW patterns */
|
|
if (!ar->get_wow_pattern) {
|
|
for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++)
|
|
ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WOW_LIST_ID, i);
|
|
|
|
/* Configure new WOW patterns */
|
|
for (i = 0; i < wow->n_patterns; i++) {
|
|
|
|
if (ath6kl_wow_ext) {
|
|
ret = ath6kl_wmi_add_wow_ext_pattern_cmd(
|
|
ar->wmi,
|
|
vif->fw_vif_idx, WOW_LIST_ID,
|
|
wow->patterns[i].pattern_len,
|
|
i,
|
|
0,
|
|
wow->patterns[i].pattern,
|
|
wow->patterns[i].mask);
|
|
/* filter for wow ext pattern */
|
|
filter |= WOW_FILTER_OPTION_PATTERNS;
|
|
} else {
|
|
/*
|
|
* Convert given nl80211 specific mask value to
|
|
* equivalent driver specific mask value
|
|
* and send it to the chip along with patterns.
|
|
* For example, if the mask value defined
|
|
* in struct cfg80211_wowlan is 0xA
|
|
* (equivalent binary is 1010), then equivalent
|
|
* driver specific mask value is
|
|
* "0xFF 0x00 0xFF 0x00".
|
|
*/
|
|
memset(&mask, 0, sizeof(mask));
|
|
for (pos = 0;
|
|
pos < wow->patterns[i].pattern_len;
|
|
pos++) {
|
|
if (wow->patterns[i].mask[pos / 8] &
|
|
(0x1 << (pos % 8)))
|
|
mask[pos] = 0xFF;
|
|
}
|
|
/*
|
|
* Note: Pattern's offset is not passed
|
|
* as part of wowlan parameter from CFG layer.
|
|
* So it's always passed as ZERO to
|
|
* the firmware.
|
|
* It means, given WOW patterns are always
|
|
* matched from the first byte
|
|
* of received pkt in the firmware.
|
|
*/
|
|
ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi,
|
|
vif->fw_vif_idx, WOW_LIST_ID,
|
|
wow->patterns[i].pattern_len,
|
|
0 /* pattern offset */,
|
|
wow->patterns[i].pattern, mask);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
} else
|
|
filter |= WOW_FILTER_OPTION_PATTERNS;
|
|
|
|
if (wow) {
|
|
if (wow->disconnect || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_NWK_DISASSOC\n");
|
|
filter |= WOW_FILTER_OPTION_NWK_DISASSOC;
|
|
}
|
|
|
|
if (wow->magic_pkt || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_MAGIC_PACKET\n");
|
|
filter |= WOW_FILTER_OPTION_MAGIC_PACKET;
|
|
}
|
|
if (wow->gtk_rekey_failure || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN,
|
|
"filter: WOW_FILTER_OPTION_OFFLOAD_GTK/GTK_ERROR\n");
|
|
filter |= (WOW_FILTER_OPTION_EAP_REQ |
|
|
WOW_FILTER_OPTION_8021X_4WAYHS |
|
|
WOW_FILTER_OPTION_GTK_ERROR |
|
|
WOW_FILTER_OPTION_OFFLOAD_GTK);
|
|
}
|
|
if (wow->eap_identity_req || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_EAP_REQ\n");
|
|
filter |= WOW_FILTER_OPTION_EAP_REQ;
|
|
|
|
}
|
|
|
|
if (wow->four_way_handshake || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_8021X_4WAYHS\n");
|
|
filter |= WOW_FILTER_OPTION_8021X_4WAYHS;
|
|
}
|
|
|
|
if (vif->arp_offload_ip_set || wow->any) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_OFFLOAD_ARP\n");
|
|
filter |= WOW_FILTER_OPTION_OFFLOAD_ARP;
|
|
}
|
|
}
|
|
|
|
/*Do GTK offload in WPA/WPA2 auth mode connection.*/
|
|
if (vif->auth_mode == WPA2_AUTH_CCKM || vif->auth_mode == WPA2_PSK_AUTH
|
|
|| vif->auth_mode == WPA_AUTH_CCKM || vif->auth_mode == WPA_PSK_AUTH){
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "filter: WOW_FILTER_OPTION_OFFLOAD_GTK\n");
|
|
filter |= WOW_FILTER_OPTION_OFFLOAD_GTK;
|
|
}
|
|
|
|
if (filter || (wow && wow->n_patterns)) {
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "Set filter: 0x%x ", filter);
|
|
ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_WOW_MODE_ENABLE,
|
|
filter,
|
|
host_req_delay);
|
|
}
|
|
|
|
set_bit(WLAN_WOW_ENABLE, &vif->flags);
|
|
|
|
FAIL:
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "%s: --- return %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int ath6kl_clear_wow_mode(struct wiphy *wiphy)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
int ret = 0;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "%s: +++\n", __func__);
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return -EIO;
|
|
ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_WOW_MODE_DISABLE,
|
|
0,
|
|
0);
|
|
|
|
ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_HOST_MODE_AWAKE);
|
|
|
|
clear_bit(WLAN_WOW_ENABLE, &vif->flags);
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "%s: ---\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
#endif /*CONFIG_ANDROID*/
|
|
|
|
static const struct ieee80211_txrx_stypes
|
|
ath6kl_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)
|
|
#ifdef CE_SUPPORT
|
|
| BIT(IEEE80211_STYPE_PROBE_RESP >> 4)
|
|
#endif
|
|
},
|
|
[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)
|
|
#ifdef CE_SUPPORT
|
|
| BIT(IEEE80211_STYPE_PROBE_RESP >> 4)
|
|
#endif
|
|
},
|
|
[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)
|
|
#ifdef CE_SUPPORT
|
|
| BIT(IEEE80211_STYPE_PROBE_RESP >> 4)
|
|
#endif
|
|
},
|
|
[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)
|
|
#ifdef CE_SUPPORT
|
|
|BIT(IEEE80211_STYPE_PROBE_RESP >> 4)
|
|
#endif
|
|
},
|
|
};
|
|
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
static int ath6kl_cfg80211_ap_acl(struct wiphy *wiphy, struct net_device *dev,
|
|
const struct cfg80211_acl_data *params)
|
|
{
|
|
struct ath6kl_vif *vif = netdev_priv(dev);
|
|
|
|
if (params)
|
|
_ath6kl_acl_config(vif, params);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* NOTE : this table may be over-wrote by ath6kl_change_cfg80211_ops() call. */
|
|
static struct cfg80211_ops ath6kl_cfg80211_ops = {
|
|
.add_virtual_intf = ath6kl_cfg80211_add_iface,
|
|
.del_virtual_intf = ath6kl_cfg80211_del_iface,
|
|
.change_virtual_intf = ath6kl_cfg80211_change_iface,
|
|
.change_bss = ath6kl_cfg80211_change_bss,
|
|
.scan = ath6kl_cfg80211_scan,
|
|
.connect = ath6kl_cfg80211_connect,
|
|
.disconnect = ath6kl_cfg80211_disconnect,
|
|
.add_key = ath6kl_cfg80211_add_key,
|
|
.get_key = ath6kl_cfg80211_get_key,
|
|
.del_key = ath6kl_cfg80211_del_key,
|
|
.set_default_key = ath6kl_cfg80211_set_default_key,
|
|
.set_wiphy_params = ath6kl_cfg80211_set_wiphy_params,
|
|
.set_tx_power = ath6kl_cfg80211_set_txpower,
|
|
.get_tx_power = ath6kl_cfg80211_get_txpower,
|
|
.set_power_mgmt = ath6kl_cfg80211_set_power_mgmt,
|
|
.join_ibss = ath6kl_cfg80211_join_ibss,
|
|
.leave_ibss = ath6kl_cfg80211_leave_ibss,
|
|
.get_station = ath6kl_get_station,
|
|
.dump_station = ath6kl_dump_station,
|
|
.set_bitrate_mask = ath6kl_cfg80211_set_bitrate_mask,
|
|
.set_pmksa = ath6kl_set_pmksa,
|
|
.del_pmksa = ath6kl_del_pmksa,
|
|
.flush_pmksa = ath6kl_flush_pmksa,
|
|
CFG80211_TESTMODE_CMD(ath6kl_tm_cmd)
|
|
#ifdef CONFIG_PM
|
|
.suspend = __ath6kl_cfg80211_suspend,
|
|
.resume = __ath6kl_cfg80211_resume,
|
|
#ifdef NL80211_CMD_GET_WOWLAN_QCA
|
|
.set_wow_mode = ath6kl_set_wow_mode,
|
|
.clr_wow_mode = ath6kl_clear_wow_mode,
|
|
#endif
|
|
#endif
|
|
.set_rekey_data = ath6kl_set_gtk_rekey_offload,
|
|
#ifndef CFG80211_NO_SET_CHAN_OPERATION
|
|
.set_channel = ath6kl_set_channel,
|
|
#endif
|
|
#ifdef NL80211_CMD_START_STOP_AP
|
|
.start_ap = ath6kl_start_ap,
|
|
.change_beacon = ath6kl_change_beacon,
|
|
.stop_ap = ath6kl_del_beacon,
|
|
#else
|
|
.add_beacon = ath6kl_add_beacon,
|
|
.set_beacon = ath6kl_set_beacon,
|
|
.del_beacon = ath6kl_del_beacon,
|
|
#endif
|
|
.del_station = ath6kl_del_station,
|
|
.change_station = ath6kl_change_station,
|
|
.remain_on_channel = ath6kl_remain_on_channel,
|
|
.cancel_remain_on_channel = ath6kl_cancel_remain_on_channel,
|
|
.mgmt_tx = ath6kl_mgmt_tx,
|
|
.mgmt_frame_register = ath6kl_mgmt_frame_register,
|
|
#ifdef NL80211_CMD_BTCOEX_QCA
|
|
.notify_btcoex = ath6kl_notify_btcoex,
|
|
#endif
|
|
};
|
|
|
|
void ath6kl_cfg80211_stop(struct ath6kl_vif *vif)
|
|
{
|
|
vif->saved_pwr_mode = vif->last_pwr_mode;
|
|
|
|
/* put all interfaces to power save */
|
|
if (ath6kl_wmi_powermode_cmd(vif->ar->wmi,
|
|
vif->fw_vif_idx, REC_POWER) != 0) {
|
|
ath6kl_err("wmi powermode command failed during suspend\n");
|
|
}
|
|
|
|
switch (vif->sme_state) {
|
|
case SME_CONNECTING:
|
|
ath6kl_cfg80211_connect_result(vif, vif->bssid, NULL, 0,
|
|
NULL, 0,
|
|
WLAN_STATUS_UNSPECIFIED_FAILURE,
|
|
GFP_KERNEL);
|
|
break;
|
|
case SME_CONNECTED:
|
|
default:
|
|
/*
|
|
* FIXME: oddly enough smeState is in DISCONNECTED during
|
|
* suspend, why? Need to send disconnected event in that
|
|
* state.
|
|
*/
|
|
ath6kl_cfg80211_disconnected(vif, 0, NULL, 0, GFP_KERNEL);
|
|
break;
|
|
}
|
|
|
|
if (test_bit(CONNECTED, &vif->flags) ||
|
|
test_bit(CONNECT_PEND, &vif->flags))
|
|
ath6kl_wmi_disconnect_cmd(vif->ar->wmi, vif->fw_vif_idx);
|
|
|
|
vif->sme_state = SME_DISCONNECTED;
|
|
clear_bit(CONNECTED, &vif->flags);
|
|
clear_bit(CONNECT_PEND, &vif->flags);
|
|
clear_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags);
|
|
clear_bit(PS_STICK, &vif->flags);
|
|
del_timer(&vif->shprotect_timer);
|
|
|
|
/* disable scanning */
|
|
if (ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx,
|
|
0xFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0) != 0)
|
|
printk(KERN_WARNING "ath6kl: failed to disable scan "
|
|
"during suspend\n");
|
|
|
|
if (test_bit(SCANNING, &vif->flags))
|
|
ath6kl_cfg80211_scan_complete_event(vif, true);
|
|
|
|
return;
|
|
}
|
|
|
|
void ath6kl_cfg80211_stop_all(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif, *tmp_vif;
|
|
#ifdef CE_SUPPORT
|
|
list_for_each_entry_safe(vif, tmp_vif, &ar->vif_list, list) {
|
|
if (ar->state == ATH6KL_STATE_DEEPSLEEP) {
|
|
if (vif->nw_type != AP_NETWORK)
|
|
ath6kl_cfg80211_stop(vif);
|
|
}
|
|
}
|
|
#else
|
|
list_for_each_entry_safe(vif, tmp_vif, &ar->vif_list, list)
|
|
ath6kl_cfg80211_stop(vif);
|
|
#endif
|
|
}
|
|
|
|
static void ath6kl_change_cfg80211_ops(struct cfg80211_ops *cfg80211_ops)
|
|
{
|
|
/* Offload AP keep-alive function to user. */
|
|
if (debug_quirks & ATH6KL_MODULE_KEEPALIVE_BY_SUPP) {
|
|
WARN_ON(cfg80211_ops->probe_client);
|
|
|
|
cfg80211_ops->probe_client = ath6kl_ap_probe_client;
|
|
|
|
ath6kl_info("Offload AP Keep-alive to supplicant/hostapd\n");
|
|
}
|
|
|
|
/* Support Scheduled-Scan (a.k.a. PNO) */
|
|
if (debug_quirks & ATH6KL_MODULE_ENABLE_SCHE_SCAN) {
|
|
cfg80211_ops->sched_scan_start = ath6kl_sched_scan_start;
|
|
cfg80211_ops->sched_scan_stop = ath6kl_sched_scan_stop;
|
|
}
|
|
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
if (debug_quirks & ATH6KL_MODULE_AP_ACL_BY_NL80211) {
|
|
cfg80211_ops->set_mac_acl = ath6kl_cfg80211_ap_acl;
|
|
|
|
ath6kl_info("Configurate AP-ACL from NL80211.\n");
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static void ath6kl_reset_cfg80211_ops(struct cfg80211_ops *cfg80211_ops)
|
|
{
|
|
cfg80211_ops->probe_client = NULL;
|
|
|
|
cfg80211_ops->sched_scan_start = NULL;
|
|
cfg80211_ops->sched_scan_stop = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
static void _judge_p2p_framework(struct ath6kl *ar, unsigned int p2p_config)
|
|
{
|
|
ar->p2p = !!p2p_config;
|
|
|
|
ar->p2p_concurrent =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_ENABLE_DEDICATE) ||
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_NO_DEDICATE) ||
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_MULTICHAN) ||
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_COMPAT);
|
|
|
|
ar->p2p_dedicate =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_ENABLE_DEDICATE) ||
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_COMPAT);
|
|
|
|
ar->p2p_multichan_concurrent =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_MULTICHAN);
|
|
|
|
ar->p2p_compat =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_COMPAT);
|
|
|
|
ar->p2p_concurrent_ap =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_CONCURRENT_AP);
|
|
|
|
ar->p2p_in_pasv_chan =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_P2P_IN_PASSIVE_CHAN);
|
|
|
|
ar->p2p_wise_scan =
|
|
!!(p2p_config & ATH6KL_MODULEP2P_P2P_WISE_SCAN);
|
|
|
|
WARN_ON((!ar->p2p_concurrent) && (ar->p2p_multichan_concurrent));
|
|
WARN_ON((ar->p2p_concurrent_ap) &&
|
|
((!ar->p2p_concurrent) || (!ar->p2p_dedicate)));
|
|
WARN_ON((!ar->p2p) && (ar->p2p_wise_scan));
|
|
|
|
if (ar->p2p_concurrent) {
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_P2P_MAX_FW_VIF))
|
|
ar->vif_max = TARGET_VIF_MAX; /* TODO */
|
|
else {
|
|
if (ar->p2p_dedicate)
|
|
ar->vif_max = 3;
|
|
else
|
|
ar->vif_max = 2; /*currently we only support 2*/
|
|
|
|
}
|
|
} else
|
|
ar->vif_max = 1;
|
|
|
|
/*
|
|
* P2P-Concurrent w/ softAP:
|
|
* STA + AP : vif_max == 3 && ar->p2p_concurrent_ap
|
|
* STA + P2P + AP : vif_max >= 4 && ar->p2p_concurrent_ap (not yet)
|
|
* w/ MCC : (not yet)
|
|
*/
|
|
WARN_ON((ar->p2p_concurrent_ap) && (ar->vif_max >= TARGET_VIF_MAX));
|
|
WARN_ON((ar->p2p_concurrent_ap) && (ar->p2p_multichan_concurrent));
|
|
|
|
ar->max_norm_iface = 1;
|
|
if (ar->p2p_concurrent_ap)
|
|
ar->max_norm_iface++;
|
|
|
|
ar->p2p_frame_retry = true;
|
|
|
|
if (ar->p2p_compat)
|
|
ar->p2p_frame_not_report = true;
|
|
else
|
|
ar->p2p_frame_not_report = false;
|
|
|
|
ar->p2p_war_bad_intel_go = true;
|
|
ar->p2p_war_bad_broadcom_go = true;
|
|
ar->p2p_war_p2p_client_awake = true;
|
|
|
|
ar->p2p_ie_not_append = P2P_IE_IN_PROBE_REQ;
|
|
|
|
ath6kl_info("%dVAP/%d, P2P %s, concurrent %s %s,"
|
|
" %s dedicate p2p-device,"
|
|
" multi-channel-concurrent %s, p2p-compat %s%s%s%s\n",
|
|
ar->vif_max,
|
|
ar->max_norm_iface,
|
|
(ar->p2p ? "enable" : "disable"),
|
|
(ar->p2p_concurrent ? "on" : "off"),
|
|
(ar->p2p_concurrent_ap ? "with softAP" : ""),
|
|
(ar->p2p_dedicate ? "with" : "without"),
|
|
(ar->p2p_multichan_concurrent ? "enable" : "disable"),
|
|
(ar->p2p_compat ? "enable (ignore p2p_dedicate)" : "disable"),
|
|
(ar->p2p_ie_not_append ? ", sta-p2p-ie removed" : ""),
|
|
(ar->p2p_in_pasv_chan ? ", p2p_in_pasv_chan enable" : ""),
|
|
(ar->p2p_wise_scan ? ", p2p_wise_scan enable" : ""));
|
|
|
|
return;
|
|
}
|
|
|
|
static void _judge_vap_framework(struct ath6kl *ar, unsigned int vap_config)
|
|
{
|
|
int i;
|
|
int ap_num = 0, sta_num = 0;
|
|
|
|
/*
|
|
* TODO : To replace older ath6kl_p2p, in case of vap_config have
|
|
* P2P related VAP mode and transfer vap_config to XXX here
|
|
* then feed to _judge_p2p_framework() to configure P2P's
|
|
* framework.
|
|
*/
|
|
|
|
for (i = 0; i < ATH6KL_VIF_MAX; i++) {
|
|
ar->next_mode[i] = (vap_config >>
|
|
(i * ATH6KL_VAPMODE_OFFSET)) &
|
|
ATH6KL_VAPMODE_MASK;
|
|
|
|
if (ar->next_mode[i] == ATH6KL_VAPMODE_DISABLED)
|
|
break;
|
|
|
|
if (ar->next_mode[i] == ATH6KL_VAPMODE_STA)
|
|
sta_num++;
|
|
else if (ar->next_mode[i] == ATH6KL_VAPMODE_AP)
|
|
ap_num++;
|
|
}
|
|
|
|
/* Now, only support 1STA+1AP or 2AP */
|
|
WARN_ON((sta_num > 1) ||
|
|
(ap_num > 2) ||
|
|
(sta_num + ap_num > 2));
|
|
|
|
ar->vif_max = i;
|
|
ar->max_norm_iface = i;
|
|
|
|
ath6kl_info("%dVAP/%d, NO P2P, vapmode %d, %d, %d, %d\n",
|
|
ar->vif_max,
|
|
ar->max_norm_iface,
|
|
ar->next_mode[0], ar->next_mode[1],
|
|
ar->next_mode[2], ar->next_mode[3]);
|
|
|
|
}
|
|
|
|
struct ath6kl *ath6kl_core_alloc(struct device *dev)
|
|
{
|
|
struct ath6kl *ar;
|
|
struct wiphy *wiphy;
|
|
|
|
/*
|
|
* Overwrite cfg80211_ops function table if we need to
|
|
* enable/disable some features.
|
|
*/
|
|
ath6kl_change_cfg80211_ops(&ath6kl_cfg80211_ops);
|
|
|
|
/* create a new wiphy for use with cfg80211 */
|
|
wiphy = wiphy_new(&ath6kl_cfg80211_ops, sizeof(struct ath6kl));
|
|
|
|
if (!wiphy) {
|
|
ath6kl_err("couldn't allocate wiphy device\n");
|
|
return NULL;
|
|
}
|
|
|
|
ar = wiphy_priv(wiphy);
|
|
ar->mod_debug_quirks = debug_quirks;
|
|
|
|
ar->wiphy = wiphy;
|
|
ar->dev = dev;
|
|
|
|
BUG_ON((ath6kl_p2p && ath6kl_vap));
|
|
|
|
/*
|
|
* For backward compatible, keep ATH6KL_MODULE_TESTMODE_ENABLE as
|
|
* testmode = 1. Note that if want to configure as testmode=2,
|
|
* Must not configure ATH6KL_MODULE_TESTMODE_ENABLE,
|
|
* And configure testmode = 2 as module parameter directly.
|
|
*/
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_TESTMODE_ENABLE))
|
|
testmode = 1;
|
|
|
|
if (testmode ||
|
|
ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_ENABLE_EPPING)) {
|
|
ath6kl_p2p = 0;
|
|
ath6kl_vap = 0;
|
|
ath6kl_roam_mode = ATH6KL_MODULEROAM_DISABLE;
|
|
}
|
|
|
|
if (ath6kl_p2p)
|
|
_judge_p2p_framework(ar, ath6kl_p2p);
|
|
else if (ath6kl_vap)
|
|
_judge_vap_framework(ar, ath6kl_vap);
|
|
else {
|
|
ar->vif_max = 1;
|
|
ar->max_norm_iface = 1;
|
|
}
|
|
|
|
spin_lock_init(&ar->lock);
|
|
spin_lock_init(&ar->list_lock);
|
|
spin_lock_init(&ar->state_lock);
|
|
|
|
init_waitqueue_head(&ar->event_wq);
|
|
sema_init(&ar->sem, 1);
|
|
sema_init(&ar->wmi_evt_sem, 1);
|
|
|
|
INIT_LIST_HEAD(&ar->amsdu_rx_buffer_queue);
|
|
INIT_LIST_HEAD(&ar->vif_list);
|
|
|
|
setup_timer(&ar->eapol_shprotect_timer,
|
|
ath6kl_eapol_shprotect_timer_handler,
|
|
(unsigned long) ar);
|
|
|
|
clear_bit(WMI_ENABLED, &ar->flag);
|
|
clear_bit(SKIP_SCAN, &ar->flag);
|
|
clear_bit(DESTROY_IN_PROGRESS, &ar->flag);
|
|
clear_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag);
|
|
clear_bit(RECOVER_IN_PROCESS, &ar->flag);
|
|
|
|
ar->listen_intvl_t = A_DEFAULT_LISTEN_INTERVAL;
|
|
ar->listen_intvl_b = 0;
|
|
ar->tx_pwr = 0;
|
|
|
|
ar->low_rssi_roam_params.lrssi_scan_period = WMI_ROAM_LRSSI_SCAN_PERIOD;
|
|
ar->low_rssi_roam_params.lrssi_scan_threshold = \
|
|
WMI_ROAM_LRSSI_SCAN_THRESHOLD;
|
|
ar->low_rssi_roam_params.lrssi_roam_threshold = \
|
|
WMI_ROAM_LRSSI_ROAM_THRESHOLD;
|
|
ar->low_rssi_roam_params.roam_rssi_floor = WMI_ROAM_LRSSI_ROAM_FLOOR;
|
|
|
|
ar->state = ATH6KL_STATE_OFF;
|
|
|
|
if ((ar->p2p_concurrent) &&
|
|
(ar->p2p_dedicate))
|
|
ar->sche_scan = ath6kl_mod_debug_quirks(ar,
|
|
ATH6KL_MODULE_ENABLE_SCHE_SCAN);
|
|
|
|
ar->roam_mode = ath6kl_roam_mode;
|
|
|
|
if (ar->sche_scan &&
|
|
ar->roam_mode != ATH6KL_MODULEROAM_DISABLE) {
|
|
ath6kl_err("Roam shall be exclusive with schedule scan. Disabled\n");
|
|
ar->roam_mode = ATH6KL_MODULEROAM_DISABLE;
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
int ath6kl_register_ieee80211_hw(struct ath6kl *ar)
|
|
{
|
|
struct wiphy *wiphy = ar->wiphy;
|
|
int ret;
|
|
|
|
wiphy->mgmt_stypes = ath6kl_mgmt_stypes;
|
|
|
|
wiphy->max_remain_on_channel_duration = ATH6KL_ROC_MAX_PERIOD * 1000;
|
|
|
|
/* set device pointer for wiphy */
|
|
set_wiphy_dev(wiphy, ar->dev);
|
|
|
|
wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
|
|
BIT(NL80211_IFTYPE_ADHOC) |
|
|
BIT(NL80211_IFTYPE_AP);
|
|
if (ar->p2p) {
|
|
if (!IS_STA_AP_ONLY(ar))
|
|
wiphy->interface_modes |=
|
|
BIT(NL80211_IFTYPE_P2P_GO) |
|
|
BIT(NL80211_IFTYPE_P2P_CLIENT);
|
|
}
|
|
|
|
if (ar->p2p_concurrent) {
|
|
struct ieee80211_iface_combination
|
|
*ieee80211_iface_combination = NULL;
|
|
|
|
switch (ar->vif_max) {
|
|
case 4:
|
|
ieee80211_iface_combination =
|
|
ath6kl_iface_combinations_p2p_concurrent4;
|
|
wiphy->n_iface_combinations = ARRAY_SIZE(
|
|
ath6kl_iface_combinations_p2p_concurrent4);
|
|
break;
|
|
case 3:
|
|
ieee80211_iface_combination =
|
|
ath6kl_iface_combinations_p2p_concurrent3;
|
|
wiphy->n_iface_combinations = ARRAY_SIZE(
|
|
ath6kl_iface_combinations_p2p_concurrent3);
|
|
break;
|
|
case 2:
|
|
ieee80211_iface_combination =
|
|
ath6kl_iface_combinations_p2p_concurrent2;
|
|
wiphy->n_iface_combinations = ARRAY_SIZE(
|
|
ath6kl_iface_combinations_p2p_concurrent2);
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
}
|
|
|
|
/* Overwrite it if P2P-Concurrent w/ softAP mode. */
|
|
if (ar->p2p_concurrent_ap) {
|
|
if (IS_STA_AP_ONLY(ar)) {
|
|
ieee80211_iface_combination =
|
|
ath6kl_iface_combinations_sta_ap;
|
|
wiphy->n_iface_combinations = ARRAY_SIZE(
|
|
ath6kl_iface_combinations_sta_ap);
|
|
} else {
|
|
ieee80211_iface_combination =
|
|
ath6kl_iface_combinations_p2p_concurrent4_1;
|
|
wiphy->n_iface_combinations = ARRAY_SIZE(
|
|
ath6kl_iface_combinations_p2p_concurrent4_1);
|
|
}
|
|
}
|
|
|
|
/* Update max. channel support */
|
|
if (ar->p2p_multichan_concurrent &&
|
|
ieee80211_iface_combination != NULL) {
|
|
ieee80211_iface_combination[0].num_different_channels =
|
|
ieee80211_iface_combination[0].max_interfaces;
|
|
}
|
|
|
|
wiphy->iface_combinations = ieee80211_iface_combination;
|
|
|
|
if (ar->p2p_compat) {
|
|
wiphy->n_iface_combinations = 0;
|
|
wiphy->iface_combinations = NULL;
|
|
}
|
|
}
|
|
|
|
/* update MCS rate mask & TX STBC. */
|
|
if (!(ar->target_subtype & TARGET_SUBTYPE_2SS)) {
|
|
ath6kl_band_2ghz.ht_cap.mcs.rx_mask[1] = 0;
|
|
ath6kl_band_5ghz.ht_cap.mcs.rx_mask[1] = 0;
|
|
|
|
ath6kl_band_2ghz.ht_cap.cap &= ~IEEE80211_HT_CAP_TX_STBC;
|
|
ath6kl_band_5ghz.ht_cap.cap &= ~IEEE80211_HT_CAP_TX_STBC;
|
|
|
|
}
|
|
|
|
/* update 2G-HT40 capability. */
|
|
if (!(ar->target_subtype & TARGET_SUBTYPE_HT40)) {
|
|
ath6kl_band_2ghz.ht_cap.cap &=
|
|
~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
ath6kl_band_2ghz.ht_cap.cap &= ~IEEE80211_HT_CAP_SGI_40;
|
|
ath6kl_band_2ghz.ht_cap.cap &= ~IEEE80211_HT_CAP_DSSSCCK40;
|
|
}
|
|
|
|
/* max num of ssids that can be probed during scanning */
|
|
wiphy->max_scan_ssids = MAX_PROBED_SSID_INDEX;
|
|
wiphy->max_scan_ie_len = MAX_APP_IE_LEN;
|
|
if (ar->sche_scan) {
|
|
wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
|
|
wiphy->max_sched_scan_ssids = MAX_PROBED_SSID_INDEX;
|
|
wiphy->max_sched_scan_ie_len = MAX_APP_IE_LEN;
|
|
}
|
|
|
|
wiphy->bands[IEEE80211_BAND_2GHZ] = &ath6kl_band_2ghz;
|
|
if ((!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_5G)) &&
|
|
(ar->target_subtype & TARGET_SUBTYPE_DUAL))
|
|
wiphy->bands[IEEE80211_BAND_5GHZ] = &ath6kl_band_5ghz;
|
|
else {
|
|
ath6kl_info("Disable 5G support by %s!\n",
|
|
(ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_5G) ?
|
|
"driver" : "board-data"));
|
|
}
|
|
wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
|
|
|
|
wiphy->cipher_suites = cipher_suites;
|
|
wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
|
|
|
|
wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
|
|
#ifdef CFG80211_WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL
|
|
if ((ath6kl_cfg80211_ops.remain_on_channel) &&
|
|
(ath6kl_cfg80211_ops.cancel_remain_on_channel))
|
|
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
|
|
#endif
|
|
|
|
if (ar->roam_mode != ATH6KL_MODULEROAM_DISABLE)
|
|
wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM;
|
|
|
|
#ifdef NL80211_ATTR_AP_INACTIVITY_TIMEOUT
|
|
if (ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_KEEPALIVE_CONFIG_BY_SUPP))
|
|
wiphy->features |= NL80211_FEATURE_INACTIVITY_TIMER;
|
|
#endif
|
|
|
|
#ifdef NL80211_WIPHY_FEATURE_SCAN_FLUSH
|
|
/*
|
|
* The cfg80211 support it by default but we disabled here.
|
|
* To speed up the scan time and the channel dewell time is not really
|
|
* longer enough. Cache BSS information may be helpful to ath6kl.
|
|
*/
|
|
wiphy->features &= ~NL80211_FEATURE_SCAN_FLUSH;
|
|
#endif
|
|
|
|
#ifdef CFG80211_TX_POWER_PER_WDEV
|
|
wiphy->features |= NL80211_FEATURE_VIF_TXPOWER;
|
|
#endif
|
|
|
|
|
|
wiphy->wowlan.flags = WIPHY_WOWLAN_ANY |
|
|
WIPHY_WOWLAN_MAGIC_PKT |
|
|
WIPHY_WOWLAN_DISCONNECT |
|
|
WIPHY_WOWLAN_SUPPORTS_GTK_REKEY |
|
|
WIPHY_WOWLAN_GTK_REKEY_FAILURE |
|
|
WIPHY_WOWLAN_EAP_IDENTITY_REQ |
|
|
WIPHY_WOWLAN_4WAY_HANDSHAKE |
|
|
WIPHY_WOWLAN_RFKILL_RELEASE;
|
|
|
|
wiphy->wowlan.n_patterns =
|
|
(WOW_MAX_FILTER_LISTS*WOW_MAX_FILTERS_PER_LIST);
|
|
wiphy->wowlan.pattern_min_len = WOW_MIN_PATTERN_SIZE;
|
|
wiphy->wowlan.pattern_max_len = WOW_MAX_PATTERN_SIZE;
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "wow attr: flags: %x, n_patterns: %d, ",
|
|
wiphy->wowlan.flags,
|
|
wiphy->wowlan.n_patterns);
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN, "min_len: %d, max_len: %d\n",
|
|
wiphy->wowlan.pattern_min_len,
|
|
wiphy->wowlan.pattern_max_len);
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_init(&ar->wake_lock, WAKE_LOCK_SUSPEND, "ath6kl_suspend_wl");
|
|
#endif
|
|
|
|
if (ar->wow_irq) {
|
|
int ret;
|
|
ret = request_irq(ar->wow_irq, ath6kl_wow_irq,
|
|
IRQF_SHARED | IRQF_TRIGGER_RISING,
|
|
"ar6000" "sdiowakeup", ar);
|
|
if (!ret) {
|
|
ret = enable_irq_wake(ar->wow_irq);
|
|
if (ret < 0) {
|
|
ath6kl_err("Couldn't enable WoW IRQ as wakeup interrupt");
|
|
return ret;
|
|
}
|
|
printk("ath6kl: WoW IRQ %d\n", ar->wow_irq);
|
|
}
|
|
}
|
|
|
|
ath6kl_setup_android_resource(ar);
|
|
#endif
|
|
|
|
if (test_bit(INTERNAL_REGDB, &ar->flag) ||
|
|
test_bit(CFG80211_REGDB, &ar->flag)) {
|
|
#ifdef CFG80211_VOID_REG_NOTIFIER
|
|
wiphy->reg_notifier = ath6kl_reg_notifier2;
|
|
#else
|
|
wiphy->reg_notifier = ath6kl_reg_notifier;
|
|
#endif
|
|
wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY;
|
|
wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY;
|
|
wiphy->flags |= WIPHY_FLAG_DISABLE_BEACON_HINTS;
|
|
}
|
|
|
|
#ifdef NL80211_CMD_SET_AP_MAC_ACL
|
|
if (debug_quirks & ATH6KL_MODULE_AP_ACL_BY_NL80211)
|
|
wiphy->max_acl_mac_addrs = ATH6KL_AP_ACL_MAX_NUM;
|
|
#endif
|
|
|
|
ret = wiphy_register(wiphy);
|
|
if (ret < 0) {
|
|
ath6kl_err("couldn't register wiphy device\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath6kl_init_if_data(struct ath6kl_vif *vif)
|
|
{
|
|
int i;
|
|
struct ath6kl *ar = vif->ar;
|
|
|
|
vif->aggr_cntxt = aggr_init(vif);
|
|
if (!vif->aggr_cntxt) {
|
|
ath6kl_err("failed to initialize aggr\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < AP_MAX_NUM_STA; i++) {
|
|
vif->sta_list[i].aggr_conn_cntxt = aggr_init_conn(vif);
|
|
if (!vif->sta_list[i].aggr_conn_cntxt) {
|
|
ath6kl_err("failed to initialize aggr_node\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
#ifdef ACS_SUPPORT
|
|
vif->acs_ctx = ath6kl_acs_init(vif);
|
|
if (!vif->acs_ctx) {
|
|
ath6kl_err("failed to initialize acs");
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
vif->htcoex_ctx = ath6kl_htcoex_init(vif);
|
|
if (!vif->htcoex_ctx) {
|
|
ath6kl_err("failed to initialize htcoex\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef CONFIG_ANDROID
|
|
/* Enable htcoex for wlan0 in Android. Scan period is 60s */
|
|
if ((vif->fw_vif_idx == 0) &&
|
|
(vif->ar->target_subtype & TARGET_SUBTYPE_HT40))
|
|
ath6kl_htcoex_config(vif, ATH6KL_HTCOEX_SCAN_PERIOD, 0);
|
|
#endif
|
|
|
|
#ifndef CE_SUPPORT
|
|
/* WAR: CR480066 */
|
|
vif->bss_post_proc_ctx = ath6kl_bss_post_proc_init(vif);
|
|
if (!vif->bss_post_proc_ctx) {
|
|
ath6kl_err("failed to initialize bss_post_proc\n");
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
|
|
vif->p2p_ps_info_ctx = ath6kl_p2p_ps_init(vif);
|
|
if (!vif->p2p_ps_info_ctx) {
|
|
ath6kl_err("failed to initialize p2p_ps\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!test_bit(TESTMODE_EPPING, &ar->flag)) {
|
|
if (ath6kl_mod_debug_quirks(vif->ar,
|
|
ATH6KL_MODULE_KEEPALIVE_BY_SUPP)) {
|
|
vif->ap_keepalive_ctx =
|
|
ath6kl_ap_keepalive_init(vif,
|
|
AP_KA_MODE_BYSUPP);
|
|
} else if (ath6kl_mod_debug_quirks(vif->ar,
|
|
ATH6KL_MODULE_KEEPALIVE_CONFIG_BY_SUPP)) {
|
|
vif->ap_keepalive_ctx =
|
|
ath6kl_ap_keepalive_init(vif,
|
|
AP_KA_MODE_CONFIG_BYSUPP);
|
|
} else {
|
|
vif->ap_keepalive_ctx =
|
|
ath6kl_ap_keepalive_init(vif,
|
|
AP_KA_MODE_ENABLE);
|
|
}
|
|
|
|
if (!vif->ap_keepalive_ctx) {
|
|
ath6kl_err("failed to initialize ap_keepalive\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
vif->ap_acl_ctx = ath6kl_ap_acl_init(vif);
|
|
if (!vif->ap_acl_ctx) {
|
|
ath6kl_err("failed to initialize ap_acl\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
vif->ap_admc_ctx = ath6kl_ap_admc_init(vif, AP_ADMC_MODE_ACCEPT_ALWAYS);
|
|
if (!vif->ap_admc_ctx) {
|
|
ath6kl_err("failed to initialize ap_admc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ath6kl_ap_rc_init(vif);
|
|
|
|
setup_timer(&vif->disconnect_timer, disconnect_timer_handler,
|
|
(unsigned long) vif->ndev);
|
|
set_bit(WMM_ENABLED, &vif->flags);
|
|
spin_lock_init(&vif->if_lock);
|
|
|
|
setup_timer(&vif->vifscan_timer, ath6kl_scan_timer_handler,
|
|
(unsigned long) vif);
|
|
|
|
setup_timer(&vif->shprotect_timer, ath6kl_shprotect_timer_handler,
|
|
(unsigned long) vif);
|
|
spin_lock_init(&vif->pend_skb_lock);
|
|
|
|
vif->scan_req = NULL;
|
|
vif->pend_skb = NULL;
|
|
|
|
vif->scanband_chan = 0;
|
|
if (ar->p2p_wise_scan &&
|
|
(vif->fw_vif_idx != 0) &&
|
|
(vif->fw_vif_idx != (ar->vif_max - 1))) {
|
|
/* Only apply to P2P interfaces. */
|
|
vif->scanband_type = SCANBAND_TYPE_2_P2PCHAN;
|
|
} else
|
|
vif->scanband_type = SCANBAND_TYPE_ALL;
|
|
|
|
vif->saved_pwr_mode =
|
|
vif->last_pwr_mode = REC_POWER;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ath6kl_deinit_if_data(struct ath6kl_vif *vif)
|
|
{
|
|
struct ath6kl *ar = vif->ar;
|
|
int ctr;
|
|
|
|
#ifdef ATH6KL_DIAGNOSTIC
|
|
wifi_diag_timer_destroy(vif);
|
|
#endif
|
|
|
|
for (ctr = 0; ctr < AP_MAX_NUM_STA ; ctr++) {
|
|
if (vif->sta_list[ctr].psq_age_active) {
|
|
del_timer_sync(&vif->sta_list[ctr].psq_age_timer);
|
|
vif->sta_list[ctr].psq_age_active = 0;
|
|
}
|
|
if (!ath6kl_ps_queue_empty(&vif->sta_list[ctr].psq_data))
|
|
ath6kl_ps_queue_purge(&vif->sta_list[ctr].psq_data);
|
|
|
|
if (!ath6kl_ps_queue_empty(&vif->sta_list[ctr].psq_mgmt))
|
|
ath6kl_ps_queue_purge(&vif->sta_list[ctr].psq_mgmt);
|
|
|
|
aggr_module_destroy_conn(vif->sta_list[ctr].aggr_conn_cntxt);
|
|
}
|
|
|
|
netif_carrier_off(vif->ndev);
|
|
netif_stop_queue(vif->ndev);
|
|
ath6kl_tx_data_cleanup_by_if(vif);
|
|
|
|
aggr_module_destroy(vif->aggr_cntxt);
|
|
|
|
#ifdef ACS_SUPPORT
|
|
ath6kl_acs_deinit(vif);
|
|
#endif
|
|
|
|
ath6kl_htcoex_deinit(vif);
|
|
|
|
ath6kl_bss_post_proc_deinit(vif);
|
|
|
|
ath6kl_p2p_ps_deinit(vif);
|
|
|
|
ath6kl_ap_keepalive_deinit(vif);
|
|
|
|
ath6kl_ap_acl_deinit(vif);
|
|
|
|
ath6kl_ap_admc_deinit(vif);
|
|
|
|
ath6kl_sched_scan_deinit(vif);
|
|
|
|
ar->avail_idx_map |= BIT(vif->fw_vif_idx);
|
|
|
|
if (vif->nw_type == ADHOC_NETWORK)
|
|
ar->ibss_if_active = false;
|
|
|
|
del_timer(&vif->vifscan_timer);
|
|
|
|
unregister_netdevice(vif->ndev);
|
|
|
|
ar->num_vif--;
|
|
}
|
|
|
|
struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name,
|
|
enum nl80211_iftype type, u8 fw_vif_idx,
|
|
u8 nw_type)
|
|
{
|
|
struct net_device *ndev;
|
|
struct ath6kl_vif *vif;
|
|
int i;
|
|
#ifdef ATH6KL_HSIC_RECOVER
|
|
u8 zero_mac[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
#endif
|
|
|
|
ndev = alloc_netdev(sizeof(*vif), name, ether_setup);
|
|
if (!ndev)
|
|
return NULL;
|
|
|
|
vif = netdev_priv(ndev);
|
|
ndev->ieee80211_ptr = &vif->wdev;
|
|
vif->wdev.wiphy = ar->wiphy;
|
|
vif->ar = ar;
|
|
vif->ndev = ndev;
|
|
SET_NETDEV_DEV(ndev, wiphy_dev(vif->wdev.wiphy));
|
|
vif->wdev.netdev = ndev;
|
|
vif->wdev.iftype = type;
|
|
vif->fw_vif_idx = fw_vif_idx;
|
|
vif->nw_type = vif->next_mode = nw_type;
|
|
|
|
memcpy(ndev->dev_addr, ar->mac_addr, ETH_ALEN);
|
|
|
|
#ifdef ATH6KL_HSIC_RECOVER
|
|
/* If we don't get the mac address just in time */
|
|
if (memcmp(ar->mac_addr, zero_mac, ETH_ALEN) == 0 &&
|
|
cached_mac_valid == true) {
|
|
memcpy(ndev->dev_addr, cached_mac, ETH_ALEN);
|
|
}
|
|
#endif
|
|
|
|
if (fw_vif_idx != 0)
|
|
ndev->dev_addr[0] = (ndev->dev_addr[0] ^ (1 << fw_vif_idx)) |
|
|
0x2;
|
|
|
|
init_netdev(ndev);
|
|
|
|
ath6kl_init_control_info(vif);
|
|
|
|
/* ATH6KL_MODULE_ENABLE_EPPING is enable will skip
|
|
* the following procedure */
|
|
if (!ath6kl_mod_debug_quirks(vif->ar, ATH6KL_MODULE_ENABLE_EPPING)) {
|
|
/* TODO: Pass interface specific pointer instead of ar */
|
|
if (ath6kl_init_if_data(vif))
|
|
goto err;
|
|
}
|
|
|
|
if (register_netdevice(ndev))
|
|
goto err;
|
|
|
|
ar->avail_idx_map &= ~BIT(fw_vif_idx);
|
|
vif->sme_state = SME_DISCONNECTED;
|
|
set_bit(WLAN_ENABLED, &vif->flags);
|
|
ar->wlan_pwr_state = WLAN_POWER_STATE_ON;
|
|
set_bit(NETDEV_REGISTERED, &vif->flags);
|
|
|
|
if (type == NL80211_IFTYPE_ADHOC)
|
|
ar->ibss_if_active = true;
|
|
|
|
spin_lock_bh(&ar->list_lock);
|
|
list_add_tail(&vif->list, &ar->vif_list);
|
|
spin_unlock_bh(&ar->list_lock);
|
|
|
|
ath6kl_p2p_utils_init_port(vif, type);
|
|
|
|
ath6kl_sched_scan_init(vif);
|
|
|
|
return ndev;
|
|
|
|
err:
|
|
for (i = 0; i < AP_MAX_NUM_STA ; i++)
|
|
aggr_module_destroy_conn(vif->sta_list[i].aggr_conn_cntxt);
|
|
aggr_module_destroy(vif->aggr_cntxt);
|
|
ath6kl_htcoex_deinit(vif);
|
|
ath6kl_bss_post_proc_deinit(vif);
|
|
ath6kl_p2p_ps_deinit(vif);
|
|
ath6kl_ap_keepalive_deinit(vif);
|
|
ath6kl_ap_acl_deinit(vif);
|
|
ath6kl_ap_admc_deinit(vif);
|
|
free_netdev(ndev);
|
|
return NULL;
|
|
}
|
|
|
|
void ath6kl_deinit_ieee80211_hw(struct ath6kl *ar)
|
|
{
|
|
#ifdef CONFIG_ANDROID
|
|
if (ar->wow_irq) {
|
|
if (disable_irq_wake(ar->wow_irq))
|
|
ath6kl_err("Couldn't disable hostwake IRQ wakeup mode\n");
|
|
|
|
free_irq(ar->wow_irq, ar);
|
|
ar->wow_irq = 0;
|
|
}
|
|
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_destroy(&ar->wake_lock);
|
|
#endif
|
|
|
|
ath6kl_cleanup_android_resource(ar);
|
|
#endif
|
|
wiphy_unregister(ar->wiphy);
|
|
wiphy_free(ar->wiphy);
|
|
|
|
ath6kl_reset_cfg80211_ops(&ath6kl_cfg80211_ops);
|
|
}
|
|
|
|
void ath6kl_core_init_defer(struct work_struct *wk)
|
|
{
|
|
#define MAX_RD_WAIT_CNT (20) /* 20 * 100 = 2 sec. */
|
|
struct ath6kl *ar;
|
|
struct vif_params params;
|
|
int i;
|
|
|
|
ar = container_of(wk, struct ath6kl,
|
|
init_defer_wk);
|
|
|
|
/* Automatically create virtual interface. */
|
|
if (!ath6kl_mod_debug_quirks(ar,
|
|
ATH6KL_MODULE_DISABLE_AUTO_ADD_INF) &&
|
|
(ar->p2p_concurrent) &&
|
|
(ar->p2p_dedicate)) {
|
|
if (!IS_STA_AP_ONLY(ar)) {
|
|
ath6kl_info("Create dedicated p2p interface\n");
|
|
|
|
rtnl_lock();
|
|
params.use_4addr = 0;
|
|
if (ath6kl_cfg80211_add_iface(ar->wiphy,
|
|
ATH6KL_DEVNAME_DEF_P2P,
|
|
NL80211_IFTYPE_STATION,
|
|
NULL,
|
|
¶ms) == NULL) {
|
|
ath6kl_err("Create dedicated p2p interface fail!\n");
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
|
|
if (ar->p2p_concurrent_ap) {
|
|
ath6kl_info("Create concurrent ap interface\n");
|
|
|
|
rtnl_lock();
|
|
params.use_4addr = 0;
|
|
if (ath6kl_cfg80211_add_iface(ar->wiphy,
|
|
ATH6KL_DEVNAME_DEF_AP,
|
|
NL80211_IFTYPE_AP,
|
|
NULL,
|
|
¶ms) == NULL) {
|
|
ath6kl_err("Create concurrent ap interface fail!\n");
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
}
|
|
|
|
/* Automatically create virtual interface. */
|
|
if (!ath6kl_mod_debug_quirks(ar,
|
|
ATH6KL_MODULE_DISABLE_AUTO_ADD_INF) &&
|
|
(!ar->p2p)) {
|
|
for (i = 1; i < ATH6KL_VIF_MAX; i++) {
|
|
rtnl_lock();
|
|
params.use_4addr = 0;
|
|
if (ar->next_mode[i] == ATH6KL_VAPMODE_STA) {
|
|
if (ath6kl_cfg80211_add_iface(
|
|
ar->wiphy,
|
|
ATH6KL_DEVNAME_DEF_STA,
|
|
NL80211_IFTYPE_STATION,
|
|
NULL,
|
|
¶ms) == NULL) {
|
|
ath6kl_err("Create sta interface fail!\n");
|
|
}
|
|
} else if (ar->next_mode[i] == ATH6KL_VAPMODE_AP) {
|
|
if (ath6kl_cfg80211_add_iface(
|
|
ar->wiphy,
|
|
ATH6KL_DEVNAME_DEF_AP,
|
|
NL80211_IFTYPE_AP,
|
|
NULL,
|
|
¶ms) == NULL) {
|
|
ath6kl_err("Create ap interface fail!\n");
|
|
}
|
|
}
|
|
rtnl_unlock();
|
|
}
|
|
}
|
|
|
|
/* Wait target report WMI_REGDOMAIN_EVENTID done */
|
|
for (i = 0; i < MAX_RD_WAIT_CNT; i++) {
|
|
if (ath6kl_reg_is_init_done(ar)) {
|
|
/* wait more 2 jiffies for regdb updated */
|
|
schedule_timeout_interruptible(2);
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
}
|
|
|
|
clear_bit(INIT_DEFER_PROGRESS, &ar->flag);
|
|
if (!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_WAIT_DEFER))
|
|
wake_up(&ar->init_defer_wait_wq);
|
|
|
|
return;
|
|
#undef MAX_RD_WAIT_CNT
|
|
}
|
|
|
|
/* we use this flag to protect 4 way handshake */
|
|
void ath6kl_shprotect_timer_handler(unsigned long ptr)
|
|
{
|
|
struct ath6kl_vif *vif = (struct ath6kl_vif *)ptr;
|
|
struct ath6kl *ar = vif->ar;
|
|
u8 bssid[ETH_ALEN];
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s\n", __func__);
|
|
clear_bit(CONNECT_HANDSHAKE_PROTECT, &vif->flags);
|
|
|
|
if (vif->pend_skb) {
|
|
ath6kl_err("%s, shall not have pend skb\n", __func__);
|
|
ath6kl_flush_pend_skb(vif);
|
|
}
|
|
|
|
if (ar->wiphy->flags & WIPHY_FLAG_SUPPORTS_FW_ROAM) {
|
|
/*trigger roam, only work if firmware support*/
|
|
memset(bssid, 0, ETH_ALEN);
|
|
bssid[0] = 0xFF;
|
|
ath6kl_wmi_force_roam_cmd(ar->wmi, (const u8 *)bssid);
|
|
}
|
|
}
|
|
|
|
static void ath6kl_eapol_shprotect_timer_handler(unsigned long ptr)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)ptr;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s\n", __func__);
|
|
clear_bit(EAPOL_HANDSHAKE_PROTECT, &ar->flag);
|
|
ar->eapol_shprotect_vif = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
#if defined(CONFIG_ANDROID) || defined(USB_AUTO_SUSPEND)
|
|
int ath6kl_android_enable_wow_default(struct ath6kl *ar)
|
|
{
|
|
unsigned char mask = 0x3F;
|
|
int mask_len;
|
|
int rv = 0, i;
|
|
struct cfg80211_wowlan wow;
|
|
|
|
if (test_bit(TESTMODE_EPPING, &ar->flag))
|
|
return 0;
|
|
|
|
memset(&wow, 0, sizeof(wow));
|
|
|
|
/*default filters : ANY + all self MACs*/
|
|
wow.any = true;
|
|
wow.patterns = kcalloc(ar->vif_max,
|
|
sizeof(wow.patterns[0]), GFP_KERNEL);
|
|
if (!wow.patterns)
|
|
return -ENOMEM;
|
|
|
|
mask_len = DIV_ROUND_UP(ETH_ALEN, 8);
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
|
|
wow.patterns[i].mask = kmalloc(mask_len + ETH_ALEN, GFP_KERNEL);
|
|
if (!wow.patterns[i].mask) {
|
|
rv = -ENOMEM;
|
|
goto failed;
|
|
}
|
|
wow.patterns[i].pattern = wow.patterns[i].mask + mask_len;
|
|
memcpy(wow.patterns[i].mask, &mask, mask_len);
|
|
wow.patterns[i].pattern_len = ETH_ALEN;
|
|
memcpy(wow.patterns[i].pattern, ar->mac_addr, ETH_ALEN);
|
|
if (i != 0)
|
|
wow.patterns[i].pattern[0] =
|
|
(wow.patterns[i].pattern[0] ^ (1 << i)) | 0x2;
|
|
|
|
wow.n_patterns++;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_WOWLAN,
|
|
"Add wow pattern:%x:%x:%x:%x:%x:%x\n",
|
|
wow.patterns[i].pattern[0], wow.patterns[i].pattern[1],
|
|
wow.patterns[i].pattern[2], wow.patterns[i].pattern[3],
|
|
wow.patterns[i].pattern[4], wow.patterns[i].pattern[5]);
|
|
|
|
}
|
|
|
|
/*Set wow mode to target firmware*/
|
|
rv = ath6kl_set_wow_mode(ar->wiphy, &wow);
|
|
failed:
|
|
for (i = 0; i < ar->vif_max; i++)
|
|
kfree(wow.patterns[i].mask);
|
|
|
|
kfree(wow.patterns);
|
|
|
|
return rv;
|
|
}
|
|
|
|
bool ath6kl_android_need_wow_suspend(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif = NULL;
|
|
int i;
|
|
bool isConnected = false;
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return false;
|
|
|
|
if (!test_bit(WLAN_WOW_ENABLE, &vif->flags))
|
|
return false;
|
|
|
|
#ifdef ATH6KL_SUPPORT_WIFI_DISC
|
|
/* Always allow WoW suspend in discovery mode */
|
|
if (ar->disc_active)
|
|
return true;
|
|
#endif
|
|
|
|
/*If p2p-GO or softAP interface, we don't do Wow suspend.
|
|
*Otherwise, if one of the interfaces is connected, we do WoW
|
|
*to save power.
|
|
*/
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if (vif) {
|
|
/*if p2p-GO or softAP interface, don't do wow*/
|
|
if (vif->nw_type == AP_NETWORK)
|
|
return false;
|
|
else if (test_bit(CONNECTED, &vif->flags))
|
|
isConnected = true;
|
|
}
|
|
}
|
|
|
|
return isConnected;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ATH6KL_SUPPORT_WLAN_HB
|
|
int ath6kl_enable_wow_hb(struct ath6kl *ar)
|
|
{
|
|
u32 filter = 0;
|
|
int i, ret;
|
|
struct ath6kl_vif *vif;
|
|
|
|
vif = ath6kl_vif_first(ar);
|
|
if (!vif)
|
|
return -EIO;
|
|
|
|
if (!ath6kl_cfg80211_ready(vif))
|
|
return -EIO;
|
|
|
|
/* Clear existing WOW patterns */
|
|
for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++)
|
|
ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx,
|
|
WOW_LIST_ID, i);
|
|
|
|
filter |= WOW_FILTER_OPTION_MAGIC_PACKET |
|
|
WOW_FILTER_OPTION_NWK_DISASSOC;
|
|
|
|
/*Do GTK offload in WPA/WPA2 auth mode connection.*/
|
|
if (vif->auth_mode == WPA2_AUTH_CCKM ||
|
|
vif->auth_mode == WPA2_PSK_AUTH ||
|
|
vif->auth_mode == WPA_AUTH_CCKM ||
|
|
vif->auth_mode == WPA_PSK_AUTH) {
|
|
filter |= WOW_FILTER_OPTION_OFFLOAD_GTK;
|
|
}
|
|
|
|
ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
ATH6KL_WOW_MODE_ENABLE,
|
|
filter,
|
|
WOW_HOST_REQ_DELAY);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|