mwifiex/mxm_wifiex/wlan_src/mlinux/moal_uap_wext.c
Sherry Sun 2a2b474597 mxm_wifiex: update to mxm5x17322 release
New Additions/Removals/Improvements:
------------------------------------
1. WSW-18868: Improve FW download time after suspend/resume cycle
2. WSFD-484: WIFI 6E support
3. WSW-17954: Enable turbo mode command for mlanutl
4. WSW-19363: Add debug prints to WLAN driver regarding WPA_supplicant
5. WSW-19307: Support robust btc related commands [0x00e0] in mlanutl
6. WSW-16429: Disabling of WEP/TKIP on all V16 devices
7. Use CFG80211_VERSION_CODE to control cfg80211 related code
8. Enabled these new commands for mlanutl: min_ba_threshold, stop_su and ed_mac_ctrl_v3
9. WSFD-386: FW and VHAL Implementation for WOWLAN by adding code for MDNS wakeup

Driver Bug Fixes:
-----------------
1. WSW-18708: Setting domain info fails for W8801 Murata M.2 Module (2DS)
2. WSW-18541: Debug logs not seen if drvdbg is part of wifi_mod_para.conf
3. WSW-19136: Command 0x107 timeout is seen radomly during connect-disconnect test
4. WSW-18865: VHT80 TCP-tx throughput only run about 170Mbps, both STA and MMH mode
5. WSW-17923: 4K Video Stoppage Issue, Wi-Fi throughput drops to lower than 20Mbps without recovery
6. WSW-17925: NO_IR flags are not cleared for ch 12,13 and 14 if country is changed to JP in runtime
7. WCSWREL-251: There is "mlan0: Failed to initialize driver interface" during stress load/unload testing
8. WSW-18431: UAP beacon (in 1x1 mode) not reporting correct values for tx_highest_rate and rx_highest_rate in VhtCap IE
9. WSW-18116: TCP Keep alive packets are not seen after starting cloud keep alive
10. WSW-18690: STAUT is advertising support of MCS-0 to 9 only in HE-IE in Probe Request
11. WCSWREL-266: Fix L5.16 build errors and warnings

Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
2022-04-19 14:46:37 +08:00

1907 lines
54 KiB
C

/** @file moal_uap_wext.c
*
* @brief This file contains wireless extension standard ioctl functions
*
*
* Copyright 2010-2021 NXP
*
* This software file (the File) is distributed by NXP
* under the terms of the GNU General Public License Version 2, June 1991
* (the License). You may use, redistribute and/or modify the File in
* accordance with the terms and conditions of the License, a copy of which
* is available by writing to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
* worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*
*/
/************************************************************************
Change log:
08/06/2010: initial version
************************************************************************/
#include "moal_main.h"
#include "moal_uap.h"
#include "moal_wext.h"
#include "moal_uap_priv.h"
/********************************************************
Global Variables
********************************************************/
typedef struct _chan_to_freq_t {
/** Channel */
t_u16 channel;
/** Frequency */
t_u32 freq;
/** Band */
t_u8 band;
} chan_to_freq_t;
static const chan_to_freq_t chan_to_freq[] = {
{1, 2412, 0}, {2, 2417, 0}, {3, 2422, 0}, {4, 2427, 0},
{5, 2432, 0}, {6, 2437, 0}, {7, 2442, 0}, {8, 2447, 0},
{9, 2452, 0}, {10, 2457, 0}, {11, 2462, 0}, {12, 2467, 0},
{13, 2472, 0}, {14, 2484, 0}, {183, 4915, 1}, {184, 4920, 1},
{185, 4925, 1}, {187, 4935, 1}, {188, 4940, 1}, {189, 4945, 1},
{192, 4960, 1}, {196, 4980, 1}, {7, 5035, 1}, {8, 5040, 1},
{9, 5045, 1}, {11, 5055, 1}, {12, 5060, 1}, {16, 5080, 1},
{34, 5170, 1}, {36, 5180, 1}, {38, 5190, 1}, {40, 5200, 1},
{42, 5210, 1}, {44, 5220, 1}, {46, 5230, 1}, {48, 5240, 1},
{52, 5260, 1}, {56, 5280, 1}, {60, 5300, 1}, {64, 5320, 1},
{100, 5500, 1}, {104, 5520, 1}, {108, 5540, 1}, {112, 5560, 1},
{116, 5580, 1}, {120, 5600, 1}, {124, 5620, 1}, {128, 5640, 1},
{132, 5660, 1}, {136, 5680, 1}, {140, 5700, 1}, {144, 5720, 1},
{149, 5745, 1}, {153, 5765, 1}, {157, 5785, 1}, {161, 5805, 1},
{165, 5825, 1}, {169, 5845, 1}, {173, 5865, 1}, {177, 5885, 1},
};
/**
* iwpriv ioctl handlers
*/
static const struct iw_priv_args woal_uap_priv_args[] = {
{WOAL_UAP_SETNONE_GETNONE, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, ""},
{WOAL_UAP_START, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "start"},
{WOAL_UAP_STOP, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "stop"},
{WOAL_AP_BSS_START, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "bssstart"},
{WOAL_AP_BSS_STOP, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "bssstop"},
{WOAL_UAP_SETONEINT_GETWORDCHAR, IW_PRIV_TYPE_INT | 1,
IW_PRIV_TYPE_CHAR | 128, ""},
{WOAL_UAP_VERSION, IW_PRIV_TYPE_INT | 1, IW_PRIV_TYPE_CHAR | 128,
"version"},
{WOAL_UAP_VEREXT, IW_PRIV_TYPE_INT | 1, IW_PRIV_TYPE_CHAR | 128,
"verext"},
{WOAL_UAP_SETONEINT_GETONEINT, IW_PRIV_TYPE_INT | 1,
IW_PRIV_TYPE_INT | 1, ""},
#ifdef WIFI_DIRECT_SUPPORT
#if defined(STA_SUPPORT) && defined(UAP_SUPPORT)
{WOAL_UAP_SET_GET_BSS_ROLE, IW_PRIV_TYPE_INT | 1, IW_PRIV_TYPE_INT | 1,
"bssrole"},
#endif
#endif
{WOAL_UAP_SET_GET_256_CHAR, IW_PRIV_TYPE_CHAR | 256,
IW_PRIV_TYPE_CHAR | 256, ""},
{WOAL_WL_FW_RELOAD, IW_PRIV_TYPE_CHAR | 256, IW_PRIV_TYPE_CHAR | 256,
"fwreload"},
{WOAL_AP_SET_CFG, IW_PRIV_TYPE_CHAR | 256, IW_PRIV_TYPE_CHAR | 256,
"apcfg"},
{WOAL_UAP_HOST_CMD, IW_PRIV_TYPE_BYTE | 2047, IW_PRIV_TYPE_BYTE | 2047,
"hostcmd"},
{WOAL_UAP_FROYO_START, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "START"},
{WOAL_UAP_FROYO_STOP, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE, "STOP"},
{WOAL_UAP_FROYO_AP_BSS_START, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE,
"AP_BSS_START"},
{WOAL_UAP_FROYO_AP_BSS_STOP, IW_PRIV_TYPE_NONE, IW_PRIV_TYPE_NONE,
"AP_BSS_STOP"},
{WOAL_UAP_FROYO_WL_FW_RELOAD, IW_PRIV_TYPE_CHAR | 256,
IW_PRIV_TYPE_CHAR | 256, "WL_FW_RELOAD"},
{WOAL_UAP_FROYO_AP_SET_CFG, IW_PRIV_TYPE_CHAR | 256,
IW_PRIV_TYPE_CHAR | 256, "AP_SET_CFG"},
};
/** Convertion from frequency to channel */
#define freq_to_chan(x) ((((x)-2412) / 5) + 1)
/********************************************************
Local Functions
********************************************************/
/**
* @brief Sort Channels
*
* @param freq A pointer to iw_freq structure
* @param num Number of Channels
*
* @return N/A
*/
static inline void woal_sort_channels(struct iw_freq *freq, int num)
{
int i, j;
struct iw_freq temp;
for (i = 0; i < num; i++)
for (j = i + 1; j < num; j++)
if (freq[i].i > freq[j].i) {
temp.i = freq[i].i;
temp.m = freq[i].m;
freq[i].i = freq[j].i;
freq[i].m = freq[j].m;
freq[j].i = temp.i;
freq[j].m = temp.m;
}
}
/**
* @brief Get frequency for channel in given band
*
* @param channel channel
* @param band band
*
* @return freq
*/
static int channel_to_frequency(t_u16 channel, t_u8 band)
{
int i = 0;
ENTER();
for (i = 0; i < (int)ARRAY_SIZE(chan_to_freq); i++) {
if (channel == chan_to_freq[i].channel &&
band == chan_to_freq[i].band) {
LEAVE();
return chan_to_freq[i].freq;
}
}
LEAVE();
return 0;
}
/**
* @brief Commit handler: called after a bunch of SET operations
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param cwrq A pointer to char buffer
* @param extra A pointer to extra data buf
*
* @return 0 --success
*/
static int woal_config_commit(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *cwrq, char *extra)
{
ENTER();
LEAVE();
return 0;
}
/**
* @brief Get name
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param cwrq A pointer to char buffer
* @param extra A pointer to extra data buf
*
* @return 0 --success
*/
static int woal_get_name(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
char *cwrq = wrqu->name;
ENTER();
strcpy(cwrq, "IEEE 802.11-DS");
LEAVE();
return 0;
}
/**
* @brief Get current BSSID
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param awrq A pointer to sockaddr structure
* @param extra A pointer to extra data buf
*
* @return 0 --success
*/
static int woal_get_wap(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct sockaddr *awrq = &wrqu->addr;
int ret = 0;
ENTER();
if (priv->bss_started)
moal_memcpy_ext(priv->phandle, awrq->sa_data,
priv->current_addr, MLAN_MAC_ADDR_LENGTH,
sizeof(awrq->sa_data));
else
memset(awrq->sa_data, 0, MLAN_MAC_ADDR_LENGTH);
awrq->sa_family = ARPHRD_ETHER;
LEAVE();
return ret;
}
/**
* @brief Change the AP BSSID
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param awrq A pointer to iw_param structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_wap(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
int ret = 0;
moal_private *priv = (moal_private *)netdev_priv(dev);
struct sockaddr *awrq = &wrqu->addr;
const t_u8 zero_mac[MLAN_MAC_ADDR_LENGTH] = {0, 0, 0, 0, 0, 0};
ENTER();
if (awrq->sa_family != ARPHRD_ETHER) {
ret = -EINVAL;
goto done;
}
PRINTM(MINFO, "ASSOC: WAP: uAP bss : " MACSTR "\n",
MAC2STR((t_u8 *)awrq->sa_data));
/*
* Using this ioctl to start/stop the BSS, return if bss
* is already started/stopped.
*/
if (memcmp(zero_mac, awrq->sa_data, MLAN_MAC_ADDR_LENGTH)) {
if (priv->bss_started == MFALSE)
ret = woal_uap_bss_ctrl(priv, MOAL_IOCTL_WAIT,
UAP_BSS_START);
else
PRINTM(MINFO, "BSS is already started.\n");
} else {
/* zero_mac means bss_stop */
if (priv->bss_started == MTRUE)
ret = woal_uap_bss_ctrl(priv, MOAL_IOCTL_WAIT,
UAP_BSS_STOP);
else
PRINTM(MINFO, "BSS is already stopped.\n");
}
done:
LEAVE();
return ret;
}
/**
* @brief Set frequency/channel
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param fwrq A pointer to iw_freq structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_freq(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_freq *fwrq = &wrqu->freq;
mlan_uap_bss_param *sys_cfg = NULL, *ap_cfg = NULL;
int ret = 0, chan = 0, i = 0;
ENTER();
ap_cfg = kmalloc(sizeof(mlan_uap_bss_param), GFP_KERNEL);
if (ap_cfg == NULL) {
ret = -ENOMEM;
goto done;
}
sys_cfg = kmalloc(sizeof(mlan_uap_bss_param), GFP_KERNEL);
if (sys_cfg == NULL) {
ret = -ENOMEM;
goto done;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
ret = -EFAULT;
goto done;
}
i = ap_cfg->num_of_chan;
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
/* If setting by frequency, convert to a channel */
if (fwrq->e == 1)
chan = freq_to_chan(fwrq->m / 100000);
else
chan = fwrq->m;
if (chan > 0 && chan < MLAN_MAX_CHANNEL)
sys_cfg->channel = chan;
else {
ret = -EINVAL;
goto done;
}
for (i = 0; i < (int)ap_cfg->num_of_chan; i++)
if (ap_cfg->chan_list[i].chan_number == chan)
break;
if (i == (int)ap_cfg->num_of_chan) {
PRINTM(MERROR, "Channel %d is not supported\n", chan);
ret = -EINVAL;
goto done;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_SET,
MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP confiruration\n");
ret = -EFAULT;
goto done;
}
done:
kfree(sys_cfg);
kfree(ap_cfg);
LEAVE();
return ret;
}
/**
* @brief Get frequency and channel
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param fwrq A pointer to iw_freq structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_get_freq(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_freq *fwrq = &wrqu->freq;
mlan_uap_bss_param *ap_cfg = NULL;
t_u8 band = 0;
int ret = 0;
ENTER();
ap_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!ap_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
return -EFAULT;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
kfree(ap_cfg);
LEAVE();
return -EFAULT;
}
band = (ap_cfg->bandcfg.chanBand == BAND_5GHZ);
fwrq->m = (long)channel_to_frequency(ap_cfg->channel, band);
fwrq->i = (long)ap_cfg->channel;
fwrq->e = 6;
kfree(ap_cfg);
LEAVE();
return ret;
}
/**
* @brief Set wlan mode
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param uwrq A pointer to t_u32 string
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_bss_mode(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
int ret = 0;
t_u32 *uwrq = &wrqu->mode;
ENTER();
switch (*uwrq) {
case IW_MODE_AUTO:
case IW_MODE_MASTER:
PRINTM(MINFO, "This is correct mode in AP mode\n");
break;
default:
PRINTM(MERROR, "Invalid mode for AP\n");
ret = -EINVAL;
}
LEAVE();
return ret;
}
/**
* @brief Get wlan mode
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param uwrq A pointer to t_u32 string
* @param extra A pointer to extra data buf
*
* @return 0 --success
*/
static int woal_get_bss_mode(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
t_u32 *uwrq = &wrqu->mode;
ENTER();
*uwrq = IW_MODE_MASTER;
LEAVE();
return 0;
}
/**
* @brief Set encryption key
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_encode(struct net_device *dev, struct iw_request_info *info,
struct iw_point *dwrq, char *extra)
{
int ret = 0;
moal_private *priv = (moal_private *)netdev_priv(dev);
mlan_uap_bss_param *sys_cfg = NULL, *ap_cfg = NULL;
wep_key *pkey = NULL;
int key_index = 0;
ENTER();
/* Check index */
key_index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
if (key_index > 3) {
PRINTM(MERROR, "Key index #%d out of range\n", key_index);
ret = -EINVAL;
goto done;
}
ap_cfg = kmalloc(sizeof(mlan_uap_bss_param), GFP_KERNEL);
if (ap_cfg == NULL) {
ret = -ENOMEM;
goto done;
}
sys_cfg = kmalloc(sizeof(mlan_uap_bss_param), GFP_KERNEL);
if (sys_cfg == NULL) {
ret = -ENOMEM;
goto done;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
ret = -EFAULT;
goto done;
}
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
sys_cfg->wep_cfg.key0.key_index = 0;
sys_cfg->wep_cfg.key1.key_index = 1;
sys_cfg->wep_cfg.key2.key_index = 2;
sys_cfg->wep_cfg.key3.key_index = 3;
if (key_index >= 0 && key_index <= 3) {
if (key_index == 0)
pkey = &sys_cfg->wep_cfg.key0;
else if (key_index == 1)
pkey = &sys_cfg->wep_cfg.key1;
else if (key_index == 2)
pkey = &sys_cfg->wep_cfg.key2;
else if (key_index == 3)
pkey = &sys_cfg->wep_cfg.key3;
}
if (!(dwrq->flags & IW_ENCODE_NOKEY) && dwrq->length) {
if (dwrq->length > MAX_WEP_KEY_SIZE) {
PRINTM(MERROR, "Key length (%d) out of range\n",
dwrq->length);
ret = -E2BIG;
goto done;
}
if (key_index < 0) {
/* Get current default key index */
if (ap_cfg->wep_cfg.key0.is_default)
pkey = &sys_cfg->wep_cfg.key0;
if (ap_cfg->wep_cfg.key1.is_default)
pkey = &sys_cfg->wep_cfg.key1;
if (ap_cfg->wep_cfg.key2.is_default)
pkey = &sys_cfg->wep_cfg.key2;
if (ap_cfg->wep_cfg.key3.is_default)
pkey = &sys_cfg->wep_cfg.key3;
else { /* Something wrong, select first key as default
*/
PRINTM(MERROR,
"No default key set! Selecting first key.\n");
pkey = &sys_cfg->wep_cfg.key0;
}
}
sys_cfg->protocol = PROTOCOL_STATIC_WEP;
if (extra)
moal_memcpy_ext(priv->phandle, pkey->key, extra,
dwrq->length, sizeof(pkey->key));
/* Set the length */
if (dwrq->length > MIN_WEP_KEY_SIZE)
pkey->length = MAX_WEP_KEY_SIZE;
else
pkey->length = MIN_WEP_KEY_SIZE;
/* Set current key index as default */
pkey->is_default = MTRUE;
} else {
/*
* No key provided so it is either enable key,
* on or off
*/
if (dwrq->flags & IW_ENCODE_DISABLED) {
PRINTM(MINFO, "*** iwconfig mlanX key off ***\n");
sys_cfg->protocol = PROTOCOL_NO_SECURITY;
} else {
/*
* iwconfig mlanX key [n]
* iwconfig mlanX key on
* Do we want to just set the transmit key index ?
*/
if (key_index < 0) {
PRINTM(MINFO,
"*** iwconfig mlanX key on ***\n");
} else {
/* Get current key configuration at key_index */
if (key_index == 0)
moal_memcpy_ext(priv->phandle, pkey,
&ap_cfg->wep_cfg.key0,
sizeof(wep_key),
sizeof(wep_key));
if (key_index == 1)
moal_memcpy_ext(priv->phandle, pkey,
&ap_cfg->wep_cfg.key1,
sizeof(wep_key),
sizeof(wep_key));
if (key_index == 2)
moal_memcpy_ext(priv->phandle, pkey,
&ap_cfg->wep_cfg.key2,
sizeof(wep_key),
sizeof(wep_key));
if (key_index == 3)
moal_memcpy_ext(priv->phandle, pkey,
&ap_cfg->wep_cfg.key3,
sizeof(wep_key),
sizeof(wep_key));
/* Set current key index as default */
pkey->is_default = MTRUE;
}
}
}
if (dwrq->flags & (IW_ENCODE_RESTRICTED | IW_ENCODE_OPEN)) {
switch (dwrq->flags & 0xf000) {
case IW_ENCODE_RESTRICTED:
/* iwconfig mlanX restricted key [1] */
sys_cfg->auth_mode = MLAN_AUTH_MODE_SHARED;
PRINTM(MINFO, "Auth mode restricted!\n");
break;
case IW_ENCODE_OPEN:
/* iwconfig mlanX key [2] open */
sys_cfg->auth_mode = MLAN_AUTH_MODE_OPEN;
PRINTM(MINFO, "Auth mode open!\n");
break;
case IW_ENCODE_RESTRICTED | IW_ENCODE_OPEN:
default:
/* iwconfig mlanX key [2] open restricted */
PRINTM(MINFO, "Auth mode auto!\n");
break;
}
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_SET,
MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP confiruration\n");
ret = -EFAULT;
goto done;
}
done:
kfree(sys_cfg);
kfree(ap_cfg);
LEAVE();
return ret;
}
/**
* @brief Get encryption key
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_get_encode(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_point *dwrq = &wrqu->data;
int index = (dwrq->flags & IW_ENCODE_INDEX);
wep_key *pkey = NULL;
mlan_uap_bss_param *ap_cfg = NULL;
int ret = 0;
ENTER();
if (index < 0 || index > 4) {
PRINTM(MERROR, "Key index #%d out of range\n", index);
ret = -EINVAL;
goto done;
}
ap_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!ap_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
ret = -EFAULT;
goto done;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
ret = -EFAULT;
goto done;
}
dwrq->flags = 0;
/*
* Check encryption mode
*/
switch (ap_cfg->auth_mode) {
case MLAN_AUTH_MODE_OPEN:
dwrq->flags = IW_ENCODE_OPEN;
break;
case MLAN_AUTH_MODE_SHARED:
case MLAN_AUTH_MODE_NETWORKEAP:
dwrq->flags = IW_ENCODE_RESTRICTED;
break;
default:
dwrq->flags = IW_ENCODE_DISABLED | IW_ENCODE_OPEN;
break;
}
switch (ap_cfg->protocol) {
case PROTOCOL_NO_SECURITY:
dwrq->flags |= IW_ENCODE_DISABLED;
break;
case PROTOCOL_STATIC_WEP:
if (ap_cfg->wep_cfg.key0.is_default)
pkey = &ap_cfg->wep_cfg.key0;
else if (ap_cfg->wep_cfg.key1.is_default)
pkey = &ap_cfg->wep_cfg.key1;
else if (ap_cfg->wep_cfg.key2.is_default)
pkey = &ap_cfg->wep_cfg.key2;
else if (ap_cfg->wep_cfg.key3.is_default)
pkey = &ap_cfg->wep_cfg.key3;
if (pkey) {
dwrq->flags |= (pkey->key_index + 1);
dwrq->length = pkey->length;
moal_memcpy_ext(priv->phandle, extra, pkey->key,
pkey->length, pkey->length);
dwrq->flags &= ~IW_ENCODE_DISABLED;
} else {
ret = -EFAULT;
}
break;
case PROTOCOL_WPA:
case PROTOCOL_WPA2:
case PROTOCOL_WPA2_MIXED:
moal_memcpy_ext(priv->phandle, extra,
ap_cfg->wpa_cfg.passphrase,
ap_cfg->wpa_cfg.length, ap_cfg->wpa_cfg.length);
dwrq->length = ap_cfg->wpa_cfg.length;
dwrq->flags |= 1;
dwrq->flags &= ~IW_ENCODE_DISABLED;
break;
default:
dwrq->flags &= ~IW_ENCODE_DISABLED;
break;
}
dwrq->flags |= IW_ENCODE_NOKEY;
done:
kfree(ap_cfg);
LEAVE();
return ret;
}
#if (WIRELESS_EXT >= 18)
/**
* @brief Get IE
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return -EOPNOTSUPP
*/
static int woal_get_gen_ie(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
ENTER();
LEAVE();
return -EOPNOTSUPP;
}
/**
* @brief Set IE
*
* Pass an opaque block of data, expected to be IEEE IEs, to the driver
* for eventual passthrough to the firmware in an associate/join
* (and potentially start) command.
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_gen_ie(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_point *dwrq = &wrqu->data;
mlan_uap_bss_param *sys_cfg = NULL;
IEEEtypes_Header_t *tlv = NULL;
int tlv_hdr_len = sizeof(IEEEtypes_Header_t), tlv_buf_left = 0;
int ret = 0;
ENTER();
sys_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!sys_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
LEAVE();
return -EFAULT;
}
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
tlv_buf_left = dwrq->length;
tlv = (IEEEtypes_Header_t *)extra;
while (tlv_buf_left >= tlv_hdr_len) {
if (tlv->element_id == WPA_IE) {
sys_cfg->protocol |= PROTOCOL_WPA;
if (priv->pairwise_cipher == CIPHER_TKIP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa =
CIPHER_TKIP;
PRINTM(MINFO, "Set IE Cipher TKIP\n");
}
if (priv->pairwise_cipher == CIPHER_AES_CCMP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa =
CIPHER_AES_CCMP;
PRINTM(MINFO, "Set IE Cipher CCMP\n");
}
if (priv->pairwise_cipher ==
(CIPHER_TKIP | CIPHER_AES_CCMP)) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa =
CIPHER_TKIP | CIPHER_AES_CCMP;
PRINTM(MINFO, "Set IE Cipher TKIP + CCMP\n");
}
moal_memcpy_ext(priv->phandle,
priv->bcn_ie_buf + priv->bcn_ie_len,
((t_u8 *)tlv),
sizeof(IEEEtypes_Header_t) + tlv->len,
sizeof(priv->bcn_ie_buf) -
priv->bcn_ie_len);
priv->bcn_ie_len +=
sizeof(IEEEtypes_Header_t) + tlv->len;
}
if (tlv->element_id == RSN_IE) {
sys_cfg->protocol |= PROTOCOL_WPA2;
if (priv->pairwise_cipher == CIPHER_TKIP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 =
CIPHER_TKIP;
}
if (priv->pairwise_cipher == CIPHER_AES_CCMP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 =
CIPHER_AES_CCMP;
}
if (priv->pairwise_cipher ==
(CIPHER_TKIP | CIPHER_AES_CCMP)) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 =
(CIPHER_TKIP | CIPHER_AES_CCMP);
}
moal_memcpy_ext(priv->phandle,
priv->bcn_ie_buf + priv->bcn_ie_len,
((t_u8 *)tlv),
sizeof(IEEEtypes_Header_t) + tlv->len,
sizeof(priv->bcn_ie_buf) -
priv->bcn_ie_len);
priv->bcn_ie_len +=
sizeof(IEEEtypes_Header_t) + tlv->len;
}
if (priv->group_cipher == CIPHER_TKIP)
sys_cfg->wpa_cfg.group_cipher = CIPHER_TKIP;
if (priv->group_cipher == CIPHER_AES_CCMP)
sys_cfg->wpa_cfg.group_cipher = CIPHER_AES_CCMP;
tlv_buf_left -= (tlv_hdr_len + tlv->len);
tlv = (IEEEtypes_Header_t *)((t_u8 *)tlv + tlv_hdr_len +
tlv->len);
}
sys_cfg->key_mgmt = priv->uap_key_mgmt;
if (sys_cfg->key_mgmt & KEY_MGMT_PSK)
sys_cfg->key_mgmt_operation |= 0x01;
if (sys_cfg->key_mgmt & KEY_MGMT_EAP)
sys_cfg->key_mgmt_operation |= 0x03;
if (sys_cfg->protocol) {
if (MLAN_STATUS_SUCCESS !=
woal_set_get_sys_config(priv, MLAN_ACT_SET, MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP configuration\n");
ret = -EFAULT;
goto done;
}
priv->pairwise_cipher = 0;
priv->group_cipher = 0;
/* custom IE command to set priv->bcn_ie_buf */
if (MLAN_STATUS_SUCCESS !=
#define UAP_RSN_MASK (BIT(8) | BIT(5) | BIT(1) | BIT(3))
woal_set_get_custom_ie(priv, UAP_RSN_MASK, priv->bcn_ie_buf,
priv->bcn_ie_len)) {
PRINTM(MERROR, "Error setting wpa-rsn IE\n");
ret = -EFAULT;
}
} else if (dwrq->length == 0) {
/* custom IE command to re-set priv->bcn_ie_buf */
if (MLAN_STATUS_SUCCESS !=
woal_set_get_custom_ie(priv, 0, priv->bcn_ie_buf,
priv->bcn_ie_len)) {
PRINTM(MERROR, "Error resetting wpa-rsn IE\n");
ret = -EFAULT;
}
priv->bcn_ie_len = 0;
}
done:
kfree(sys_cfg);
LEAVE();
return ret;
}
/**
* @brief Extended version of encoding configuration
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_encode_ext(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
struct iw_encode_ext *ext = (struct iw_encode_ext *)extra;
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_point *dwrq = &wrqu->data;
int key_index;
t_u8 *pkey_material = NULL;
mlan_ioctl_req *req = NULL;
mlan_ds_sec_cfg *sec = NULL;
mlan_uap_bss_param *sys_cfg = NULL;
wep_key *pwep_key = NULL;
int ret = 0;
mlan_status status = MLAN_STATUS_SUCCESS;
ENTER();
key_index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
if (key_index < 0 || key_index > 5) {
ret = -EINVAL;
goto done;
}
if (ext->key_len > (dwrq->length - sizeof(struct iw_encode_ext))) {
ret = -EINVAL;
goto done;
}
sys_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!sys_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
ret = -EFAULT;
goto done;
}
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
pkey_material = (t_u8 *)(ext + 1);
/* Disable Key */
if ((dwrq->flags & IW_ENCODE_DISABLED) && !ext->key_len) {
sys_cfg->protocol = PROTOCOL_NO_SECURITY;
} else if (ext->alg == IW_ENCODE_ALG_WEP) {
sys_cfg->protocol = PROTOCOL_STATIC_WEP;
/* Set WEP key */
switch (key_index) {
case 0:
pwep_key = &sys_cfg->wep_cfg.key0;
break;
case 1:
pwep_key = &sys_cfg->wep_cfg.key1;
break;
case 2:
pwep_key = &sys_cfg->wep_cfg.key2;
break;
case 3:
pwep_key = &sys_cfg->wep_cfg.key3;
break;
}
if (pwep_key) {
pwep_key->key_index = key_index;
pwep_key->is_default = MTRUE;
pwep_key->length = ext->key_len;
moal_memcpy_ext(priv->phandle, pwep_key->key,
pkey_material, ext->key_len,
sizeof(pwep_key->key));
}
} else {
/* Set GTK/PTK key */
req = woal_alloc_mlan_ioctl_req(sizeof(mlan_ds_sec_cfg));
if (req == NULL) {
ret = -ENOMEM;
goto done;
}
sec = (mlan_ds_sec_cfg *)req->pbuf;
sec->sub_command = MLAN_OID_SEC_CFG_ENCRYPT_KEY;
req->req_id = MLAN_IOCTL_SEC_CFG;
req->action = MLAN_ACT_SET;
sec->param.encrypt_key.key_len = ext->key_len;
sec->param.encrypt_key.key_index = key_index;
moal_memcpy_ext(priv->phandle,
sec->param.encrypt_key.key_material,
pkey_material, ext->key_len,
sizeof(sec->param.encrypt_key.key_material));
moal_memcpy_ext(priv->phandle, sec->param.encrypt_key.mac_addr,
ext->addr.sa_data, ETH_ALEN,
sizeof(sec->param.encrypt_key.mac_addr));
sec->param.encrypt_key.key_flags = ext->ext_flags;
if (ext->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) {
moal_memcpy_ext(priv->phandle,
sec->param.encrypt_key.pn,
(t_u8 *)ext->rx_seq, SEQ_MAX_SIZE,
sizeof(sec->param.encrypt_key.pn));
DBG_HEXDUMP(MCMD_D, "Uap Rx PN",
sec->param.encrypt_key.pn, SEQ_MAX_SIZE);
}
if (ext->ext_flags & IW_ENCODE_EXT_TX_SEQ_VALID) {
moal_memcpy_ext(priv->phandle,
sec->param.encrypt_key.pn,
(t_u8 *)ext->tx_seq, SEQ_MAX_SIZE,
sizeof(sec->param.encrypt_key.pn));
DBG_HEXDUMP(MCMD_D, "Uap Tx PN",
sec->param.encrypt_key.pn, SEQ_MAX_SIZE);
}
PRINTM(MIOCTL,
"set uap wpa key key_index=%d, key_len=%d key_flags=0x%x " MACSTR
"\n",
key_index, ext->key_len,
sec->param.encrypt_key.key_flags,
MAC2STR(sec->param.encrypt_key.mac_addr));
DBG_HEXDUMP(MCMD_D, "uap wpa key", pkey_material, ext->key_len);
#define IW_ENCODE_ALG_AES_CMAC 5
if (ext->alg == IW_ENCODE_ALG_AES_CMAC)
sec->param.encrypt_key.key_flags |=
KEY_FLAG_AES_MCAST_IGTK;
status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT);
if (status != MLAN_STATUS_SUCCESS)
ret = -EFAULT;
/* Cipher set will be done in set generic IE */
priv->pairwise_cipher = ext->alg;
priv->group_cipher = ext->alg;
goto done; /* No AP configuration */
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_SET,
MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP confiruration\n");
ret = -EFAULT;
goto done;
}
done:
kfree(sys_cfg);
if (status != MLAN_STATUS_PENDING)
kfree(req);
LEAVE();
return ret;
}
/**
* @brief Extended version of encoding configuration
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return -EOPNOTSUPP
*/
static int woal_get_encode_ext(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
ENTER();
LEAVE();
return -EOPNOTSUPP;
}
/**
* @brief Request MLME operation
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0--success, otherwise fail
*/
static int woal_set_mlme(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
struct iw_mlme *mlme = (struct iw_mlme *)extra;
moal_private *priv = (moal_private *)netdev_priv(dev);
mlan_ds_bss *bss = NULL;
mlan_ds_get_info *pinfo = NULL;
mlan_ioctl_req *req = NULL;
mlan_ds_sta_list *sta_list = NULL;
const t_u8 bc_addr[] = {0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF};
t_u8 sta_addr[ETH_ALEN];
int ret = 0, i;
mlan_status status = MLAN_STATUS_SUCCESS;
ENTER();
memset(sta_addr, 0, ETH_ALEN);
if ((mlme->cmd == IW_MLME_DEAUTH) || (mlme->cmd == IW_MLME_DISASSOC)) {
moal_memcpy_ext(priv->phandle, sta_addr,
(t_u8 *)mlme->addr.sa_data, ETH_ALEN,
sizeof(sta_addr));
PRINTM(MIOCTL, "Deauth station: " MACSTR ", reason=%d\n",
MAC2STR(sta_addr), mlme->reason_code);
/* FIXME: For flushing all stations we need to use zero MAC,
* but right now the FW does not support this. So, manually
* delete each one individually.
*/
/* If deauth all station, get the connected STA list first */
if (!memcmp(bc_addr, sta_addr, ETH_ALEN)) {
PRINTM(MIOCTL, "Deauth all stations\n");
req = woal_alloc_mlan_ioctl_req(
sizeof(mlan_ds_get_info));
if (req == NULL) {
LEAVE();
return -ENOMEM;
}
pinfo = (mlan_ds_get_info *)req->pbuf;
pinfo->sub_command = MLAN_OID_UAP_STA_LIST;
req->req_id = MLAN_IOCTL_GET_INFO;
req->action = MLAN_ACT_GET;
status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT);
if (status != MLAN_STATUS_SUCCESS) {
ret = -EFAULT;
goto done;
}
sta_list =
kmalloc(sizeof(mlan_ds_sta_list), GFP_KERNEL);
if (sta_list == NULL) {
PRINTM(MERROR, "Memory allocation failed!\n");
ret = -ENOMEM;
goto done;
}
moal_memcpy_ext(priv->phandle, sta_list,
&pinfo->param.sta_list,
sizeof(mlan_ds_sta_list),
sizeof(mlan_ds_sta_list));
kfree(req);
req = NULL;
}
req = woal_alloc_mlan_ioctl_req(sizeof(mlan_ds_bss));
if (req == NULL) {
ret = -ENOMEM;
goto done;
}
bss = (mlan_ds_bss *)req->pbuf;
bss->sub_command = MLAN_OID_UAP_DEAUTH_STA;
req->req_id = MLAN_IOCTL_BSS;
req->action = MLAN_ACT_SET;
if (sta_list && !memcmp(bc_addr, sta_addr, ETH_ALEN)) {
for (i = 0; i < sta_list->sta_count; i++) {
moal_memcpy_ext(
priv->phandle,
bss->param.deauth_param.mac_addr,
sta_list->info[i].mac_address, ETH_ALEN,
sizeof(bss->param.deauth_param
.mac_addr));
bss->param.deauth_param.reason_code =
mlme->reason_code;
status = woal_request_ioctl(priv, req,
MOAL_IOCTL_WAIT);
if (status != MLAN_STATUS_SUCCESS) {
ret = -EFAULT;
goto done;
}
}
} else {
moal_memcpy_ext(
priv->phandle, bss->param.deauth_param.mac_addr,
sta_addr, ETH_ALEN,
sizeof(bss->param.deauth_param.mac_addr));
bss->param.deauth_param.reason_code = mlme->reason_code;
status = woal_request_ioctl(priv, req, MOAL_IOCTL_WAIT);
if (status != MLAN_STATUS_SUCCESS) {
ret = -EFAULT;
goto done;
}
}
}
done:
if (status != MLAN_STATUS_PENDING)
kfree(req);
kfree(sta_list);
LEAVE();
return ret;
}
/** @brief Set authentication mode parameters
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param vwrq A pointer to iw_param structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_auth(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_param *vwrq = &wrqu->param;
mlan_uap_bss_param *sys_cfg = NULL;
ENTER();
sys_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!sys_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
LEAVE();
return -EFAULT;
}
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
switch (vwrq->flags & IW_AUTH_INDEX) {
case IW_AUTH_CIPHER_PAIRWISE:
/* Rest are not supported now */
if (vwrq->value & IW_AUTH_CIPHER_NONE)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value & IW_AUTH_CIPHER_WEP40)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value & IW_AUTH_CIPHER_WEP104)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value == IW_AUTH_CIPHER_TKIP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa = CIPHER_TKIP;
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 = CIPHER_TKIP;
priv->pairwise_cipher = CIPHER_TKIP;
if (!priv->uap_key_mgmt)
priv->uap_key_mgmt = KEY_MGMT_PSK;
PRINTM(MINFO, "Set Auth Cipher TKIP\n");
} else if (vwrq->value == IW_AUTH_CIPHER_CCMP) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa = CIPHER_AES_CCMP;
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 = CIPHER_AES_CCMP;
priv->pairwise_cipher = CIPHER_AES_CCMP;
if (!priv->uap_key_mgmt)
priv->uap_key_mgmt = KEY_MGMT_PSK;
PRINTM(MINFO, "Set Auth Cipher CCMP\n");
} else if (vwrq->value ==
(IW_AUTH_CIPHER_TKIP | IW_AUTH_CIPHER_CCMP)) {
sys_cfg->wpa_cfg.pairwise_cipher_wpa =
(CIPHER_TKIP | CIPHER_AES_CCMP);
sys_cfg->wpa_cfg.pairwise_cipher_wpa2 =
(CIPHER_TKIP | CIPHER_AES_CCMP);
priv->pairwise_cipher = (CIPHER_TKIP | CIPHER_AES_CCMP);
if (!priv->uap_key_mgmt)
priv->uap_key_mgmt = KEY_MGMT_PSK;
PRINTM(MINFO, "Set Auth Cipher TKIP + CCMP\n");
}
break;
case IW_AUTH_CIPHER_GROUP:
/* Rest are not supported now */
if (vwrq->value & IW_AUTH_CIPHER_NONE)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value & IW_AUTH_CIPHER_WEP40)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value & IW_AUTH_CIPHER_WEP104)
/* XXX Do not delete no-operation line */
;
else if (vwrq->value & IW_AUTH_CIPHER_TKIP) {
sys_cfg->wpa_cfg.group_cipher = CIPHER_TKIP;
priv->group_cipher = CIPHER_TKIP;
if (!priv->uap_key_mgmt)
priv->uap_key_mgmt = KEY_MGMT_PSK;
PRINTM(MINFO, "Set Auth Cipher TKIP\n");
} else if (vwrq->value & IW_AUTH_CIPHER_CCMP) {
sys_cfg->wpa_cfg.group_cipher = CIPHER_AES_CCMP;
priv->group_cipher = CIPHER_AES_CCMP;
if (!priv->uap_key_mgmt)
priv->uap_key_mgmt = KEY_MGMT_PSK;
PRINTM(MINFO, "Set Auth Cipher CCMP\n");
}
break;
case IW_AUTH_80211_AUTH_ALG:
switch (vwrq->value) {
case IW_AUTH_ALG_SHARED_KEY:
PRINTM(MINFO, "Auth mode shared key!\n");
sys_cfg->auth_mode = MLAN_AUTH_MODE_SHARED;
break;
case IW_AUTH_ALG_LEAP:
break;
case IW_AUTH_ALG_OPEN_SYSTEM:
PRINTM(MINFO, "Auth mode open!\n");
sys_cfg->auth_mode = MLAN_AUTH_MODE_OPEN;
break;
default:
PRINTM(MINFO, "Auth mode auto!\n");
break;
}
break;
case IW_AUTH_WPA_VERSION:
switch (vwrq->value) {
case IW_AUTH_WPA_VERSION_DISABLED:
sys_cfg->protocol = PROTOCOL_NO_SECURITY;
break;
case IW_AUTH_WPA_VERSION_WPA:
sys_cfg->protocol = PROTOCOL_WPA;
break;
case IW_AUTH_WPA_VERSION_WPA2:
sys_cfg->protocol = PROTOCOL_WPA2;
break;
case IW_AUTH_WPA_VERSION_WPA | IW_AUTH_WPA_VERSION_WPA2:
sys_cfg->protocol = PROTOCOL_WPA2_MIXED;
break;
default:
break;
}
priv->uap_protocol = sys_cfg->protocol;
break;
case IW_AUTH_KEY_MGMT:
switch (vwrq->value) {
case IW_AUTH_KEY_MGMT_802_1X:
sys_cfg->key_mgmt |= KEY_MGMT_EAP;
priv->uap_key_mgmt |= KEY_MGMT_EAP;
break;
case IW_AUTH_KEY_MGMT_PSK:
sys_cfg->key_mgmt |= KEY_MGMT_PSK;
priv->uap_key_mgmt |= KEY_MGMT_PSK;
break;
default:
break;
}
break;
case IW_AUTH_WPA_ENABLED:
case IW_AUTH_TKIP_COUNTERMEASURES:
case IW_AUTH_DROP_UNENCRYPTED:
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
case IW_AUTH_ROAMING_CONTROL:
case IW_AUTH_PRIVACY_INVOKED:
default:
kfree(sys_cfg);
LEAVE();
return -EOPNOTSUPP; /* No AP configuration */
}
if (!sys_cfg->key_mgmt)
sys_cfg->key_mgmt = priv->uap_key_mgmt;
if (sys_cfg->key_mgmt & KEY_MGMT_PSK)
sys_cfg->key_mgmt_operation |= 0x01;
if (sys_cfg->key_mgmt & KEY_MGMT_EAP)
sys_cfg->key_mgmt_operation |= 0x03;
if (!sys_cfg->protocol)
sys_cfg->protocol = priv->uap_protocol;
/* Set AP configuration */
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_SET,
MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP confiruration\n");
goto done;
}
done:
kfree(sys_cfg);
LEAVE();
return 0;
}
/**
* @brief Get authentication mode parameters
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param vwrq A pointer to iw_param structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_get_auth(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_param *vwrq = &wrqu->param;
mlan_uap_bss_param *ap_cfg = NULL;
ENTER();
ap_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!ap_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
LEAVE();
return -EFAULT;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
kfree(ap_cfg);
LEAVE();
return -EFAULT;
}
switch (vwrq->flags & IW_AUTH_INDEX) {
case IW_AUTH_CIPHER_PAIRWISE:
if (ap_cfg->wpa_cfg.pairwise_cipher_wpa == CIPHER_TKIP ||
ap_cfg->wpa_cfg.pairwise_cipher_wpa2 == CIPHER_TKIP)
vwrq->value = IW_AUTH_CIPHER_TKIP;
else if (ap_cfg->wpa_cfg.pairwise_cipher_wpa ==
CIPHER_AES_CCMP ||
ap_cfg->wpa_cfg.pairwise_cipher_wpa2 ==
CIPHER_AES_CCMP)
vwrq->value = IW_AUTH_CIPHER_CCMP;
else
vwrq->value = IW_AUTH_CIPHER_NONE;
break;
case IW_AUTH_CIPHER_GROUP:
if (ap_cfg->wpa_cfg.group_cipher == CIPHER_TKIP)
vwrq->value = IW_AUTH_CIPHER_TKIP;
else if (ap_cfg->wpa_cfg.group_cipher == CIPHER_AES_CCMP)
vwrq->value = IW_AUTH_CIPHER_CCMP;
else
vwrq->value = IW_AUTH_CIPHER_NONE;
break;
case IW_AUTH_80211_AUTH_ALG:
if (ap_cfg->auth_mode == MLAN_AUTH_MODE_SHARED)
vwrq->value = IW_AUTH_ALG_SHARED_KEY;
else if (ap_cfg->auth_mode == MLAN_AUTH_MODE_NETWORKEAP)
vwrq->value = IW_AUTH_ALG_LEAP;
else
vwrq->value = IW_AUTH_ALG_OPEN_SYSTEM;
break;
case IW_AUTH_WPA_ENABLED:
if (ap_cfg->protocol == PROTOCOL_WPA ||
ap_cfg->protocol == PROTOCOL_WPA2 ||
ap_cfg->protocol == PROTOCOL_WPA2_MIXED)
vwrq->value = 1;
else
vwrq->value = 0;
break;
case IW_AUTH_KEY_MGMT:
if (ap_cfg->key_mgmt & KEY_MGMT_EAP)
vwrq->value |= IW_AUTH_KEY_MGMT_802_1X;
if (ap_cfg->key_mgmt & KEY_MGMT_PSK)
vwrq->value |= IW_AUTH_KEY_MGMT_PSK;
break;
case IW_AUTH_WPA_VERSION:
case IW_AUTH_TKIP_COUNTERMEASURES:
case IW_AUTH_DROP_UNENCRYPTED:
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
case IW_AUTH_ROAMING_CONTROL:
case IW_AUTH_PRIVACY_INVOKED:
default:
kfree(ap_cfg);
LEAVE();
return -EOPNOTSUPP;
}
kfree(ap_cfg);
LEAVE();
return 0;
}
#endif /* WE >= 18 */
/* Data rate listing
* MULTI_BANDS:
* abg a b b/g
* Infra G(12) A(8) B(4) G(12)
* Adhoc A+B(12) A(8) B(4) B(4)
* non-MULTI_BANDS:
b b/g
* Infra B(4) G(12)
* Adhoc B(4) B(4)
*/
/**
* @brief Get Range Info
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_get_range(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_point *dwrq = &wrqu->data;
mlan_uap_bss_param *ap_cfg = NULL;
struct iw_range *range = (struct iw_range *)extra;
t_u8 band = 0;
int i;
ENTER();
ap_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!ap_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
LEAVE();
return -EFAULT;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
kfree(ap_cfg);
LEAVE();
return -EFAULT;
}
dwrq->length = sizeof(struct iw_range);
memset(range, 0, sizeof(struct iw_range));
range->min_nwid = 0;
range->max_nwid = 0;
range->num_bitrates = MAX_DATA_RATES;
for (i = 0;
i < MIN(MAX_DATA_RATES, IW_MAX_BITRATES) && ap_cfg->rates[i];
i++) {
range->bitrate[i] = (ap_cfg->rates[i] & 0x7f) * 500000;
}
range->num_bitrates = i;
PRINTM(MINFO, "IW_MAX_BITRATES=%d num_bitrates=%d\n", IW_MAX_BITRATES,
range->num_bitrates);
range->num_frequency = MIN(ap_cfg->num_of_chan, IW_MAX_FREQUENCIES);
for (i = 0; i < range->num_frequency; i++) {
range->freq[i].i = (long)ap_cfg->chan_list[i].chan_number;
band = (ap_cfg->chan_list[i].bandcfg.chanBand == BAND_5GHZ);
range->freq[i].m =
(long)channel_to_frequency(
ap_cfg->chan_list[i].chan_number, band) *
100000;
range->freq[i].e = 1;
}
PRINTM(MINFO, "IW_MAX_FREQUENCIES=%d num_frequency=%d\n",
IW_MAX_FREQUENCIES, range->num_frequency);
range->num_channels = range->num_frequency;
woal_sort_channels(&range->freq[0], range->num_frequency);
/*
* Set an indication of the max TCP throughput in bit/s that we can
* expect using this interface
*/
if (i > 2)
range->throughput = 5000 * 1000;
else
range->throughput = 1500 * 1000;
range->min_rts = MLAN_RTS_MIN_VALUE;
range->max_rts = MLAN_RTS_MAX_VALUE;
range->min_frag = MLAN_FRAG_MIN_VALUE;
range->max_frag = MLAN_FRAG_MAX_VALUE;
range->encoding_size[0] = 5;
range->encoding_size[1] = 13;
range->num_encoding_sizes = 2;
range->max_encoding_tokens = 4;
/** Minimum power period */
#define IW_POWER_PERIOD_MIN 1000000 /* 1 sec */
/** Maximum power period */
#define IW_POWER_PERIOD_MAX 120000000 /* 2 min */
/** Minimum power timeout value */
#define IW_POWER_TIMEOUT_MIN 1000 /* 1 ms */
/** Maximim power timeout value */
#define IW_POWER_TIMEOUT_MAX 1000000 /* 1 sec */
/* Power Management duration & timeout */
range->min_pmp = IW_POWER_PERIOD_MIN;
range->max_pmp = IW_POWER_PERIOD_MAX;
range->min_pmt = IW_POWER_TIMEOUT_MIN;
range->max_pmt = IW_POWER_TIMEOUT_MAX;
range->pmp_flags = IW_POWER_PERIOD;
range->pmt_flags = IW_POWER_TIMEOUT;
range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_ALL_R;
/*
* Minimum version we recommend
*/
range->we_version_source = 15;
/*
* Version we are compiled with
*/
range->we_version_compiled = WIRELESS_EXT;
range->retry_capa = IW_RETRY_LIMIT;
range->retry_flags = IW_RETRY_LIMIT | IW_RETRY_MAX;
range->min_retry = MLAN_TX_RETRY_MIN;
range->max_retry = MLAN_TX_RETRY_MAX;
#if (WIRELESS_EXT >= 18)
if (ap_cfg->protocol & PROTOCOL_WPA)
range->enc_capa |= IW_ENC_CAPA_WPA;
if (ap_cfg->protocol & PROTOCOL_WPA2)
range->enc_capa |= IW_ENC_CAPA_WPA2;
if (ap_cfg->wpa_cfg.pairwise_cipher_wpa == CIPHER_TKIP ||
ap_cfg->wpa_cfg.pairwise_cipher_wpa2 == CIPHER_TKIP ||
ap_cfg->wpa_cfg.group_cipher == CIPHER_TKIP)
range->enc_capa |= IW_ENC_CAPA_CIPHER_TKIP;
if (ap_cfg->wpa_cfg.pairwise_cipher_wpa == CIPHER_AES_CCMP ||
ap_cfg->wpa_cfg.pairwise_cipher_wpa2 == CIPHER_AES_CCMP ||
ap_cfg->wpa_cfg.group_cipher == CIPHER_AES_CCMP)
range->enc_capa |= IW_ENC_CAPA_CIPHER_CCMP;
#endif
kfree(ap_cfg);
LEAVE();
return 0;
}
/**
* @brief Set priv command
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0 --success, otherwise fail
*/
static int woal_set_priv(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
ENTER();
LEAVE();
return -EOPNOTSUPP;
}
/**
* @brief Set essid
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0--success, otherwise fail
*/
static int woal_set_essid(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
struct iw_point *dwrq = &wrqu->data;
mlan_uap_bss_param *sys_cfg = NULL;
int ret = 0;
ENTER();
/* Check the size of the string */
if (dwrq->length > IW_ESSID_MAX_SIZE + 1) {
ret = -E2BIG;
goto done;
}
sys_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!sys_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
ret = -EFAULT;
goto done;
}
/* Initialize the invalid values so that the correct values
* below are downloaded to firmware */
woal_set_sys_config_invalid_data(sys_cfg);
/* Set the SSID */
#if WIRELESS_EXT > 20
sys_cfg->ssid.ssid_len = dwrq->length;
#else
sys_cfg->ssid.ssid_len = dwrq->length - 1;
#endif
moal_memcpy_ext(priv->phandle, sys_cfg->ssid.ssid, extra,
sys_cfg->ssid.ssid_len, sizeof(sys_cfg->ssid.ssid));
if (!sys_cfg->ssid.ssid_len || sys_cfg->ssid.ssid[0] < 0x20) {
PRINTM(MERROR, "Invalid SSID - aborting set_essid\n");
ret = -EINVAL;
goto done;
}
PRINTM(MINFO, "Requested new SSID = %s\n",
(sys_cfg->ssid.ssid_len > 0) ? (char *)sys_cfg->ssid.ssid :
"NULL");
/* Set AP configuration */
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_SET,
MOAL_IOCTL_WAIT,
sys_cfg)) {
PRINTM(MERROR, "Error setting AP confiruration\n");
ret = -EFAULT;
goto done;
}
done:
kfree(sys_cfg);
LEAVE();
return ret;
}
/**
* @brief Get current essid
*
* @param dev A pointer to net_device structure
* @param info A pointer to iw_request_info structure
* @param dwrq A pointer to iw_point structure
* @param extra A pointer to extra data buf
*
* @return 0--success, otherwise fail
*/
static int woal_get_essid(struct net_device *dev, struct iw_request_info *info,
struct iw_point *dwrq, char *extra)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
mlan_uap_bss_param *ap_cfg = NULL;
ENTER();
ap_cfg = kzalloc(sizeof(mlan_uap_bss_param), GFP_ATOMIC);
if (!ap_cfg) {
PRINTM(MERROR, "Fail to alloc memory for mlan_uap_bss_param\n");
return -EFAULT;
}
if (MLAN_STATUS_SUCCESS != woal_set_get_sys_config(priv, MLAN_ACT_GET,
MOAL_IOCTL_WAIT,
ap_cfg)) {
PRINTM(MERROR, "Error getting AP confiruration\n");
kfree(ap_cfg);
LEAVE();
return -EFAULT;
}
if (priv->bss_started) {
dwrq->length = MIN(dwrq->length, ap_cfg->ssid.ssid_len);
moal_memcpy_ext(priv->phandle, extra, ap_cfg->ssid.ssid,
dwrq->length, dwrq->length);
} else
dwrq->length = 0;
dwrq->flags = 1;
kfree(ap_cfg);
LEAVE();
return 0;
}
/**
* iwconfig settable callbacks
*/
static const iw_handler woal_handler[] = {
(iw_handler)woal_config_commit, /* SIOCSIWCOMMIT */
(iw_handler)woal_get_name, /* SIOCGIWNAME */
(iw_handler)NULL, /* SIOCSIWNWID */
(iw_handler)NULL, /* SIOCGIWNWID */
(iw_handler)woal_set_freq, /* SIOCSIWFREQ */
(iw_handler)woal_get_freq, /* SIOCGIWFREQ */
(iw_handler)woal_set_bss_mode, /* SIOCSIWMODE */
(iw_handler)woal_get_bss_mode, /* SIOCGIWMODE */
(iw_handler)NULL, /* SIOCSIWSENS */
(iw_handler)NULL, /* SIOCGIWSENS */
(iw_handler)NULL, /* SIOCSIWRANGE */
(iw_handler)woal_get_range, /* SIOCGIWRANGE */
(iw_handler)woal_set_priv, /* SIOCSIWPRIV */
(iw_handler)NULL, /* SIOCGIWPRIV */
(iw_handler)NULL, /* SIOCSIWSTATS */
(iw_handler)NULL, /* SIOCGIWSTATS */
#if WIRELESS_EXT > 15
#ifdef CONFIG_WEXT_SPY
iw_handler_set_spy, /* SIOCSIWSPY */
iw_handler_get_spy, /* SIOCGIWSPY */
iw_handler_set_thrspy, /* SIOCSIWTHRSPY */
iw_handler_get_thrspy, /* SIOCGIWTHRSPY */
#else
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
#endif
#else /* WIRELESS_EXT > 15 */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
#endif /* WIRELESS_EXT > 15 */
(iw_handler)woal_set_wap, /* SIOCSIWAP */
(iw_handler)woal_get_wap, /* SIOCGIWAP */
#if WIRELESS_EXT >= 18
(iw_handler)woal_set_mlme, /* SIOCSIWMLME */
#else
(iw_handler)NULL, /* -- hole -- */
#endif
/* (iw_handler) wlan_get_aplist, */ /* SIOCGIWAPLIST */
NULL, /* SIOCGIWAPLIST */
#if WIRELESS_EXT > 13
(iw_handler)NULL, /* SIOCSIWSCAN */
(iw_handler)NULL, /* SIOCGIWSCAN */
#else /* WIRELESS_EXT > 13 */
(iw_handler)NULL, /* SIOCSIWSCAN */
(iw_handler)NULL, /* SIOCGIWSCAN */
#endif /* WIRELESS_EXT > 13 */
(iw_handler)woal_set_essid, /* SIOCSIWESSID */
(iw_handler)woal_get_essid, /* SIOCGIWESSID */
(iw_handler)NULL, /* SIOCSIWNICKN */
(iw_handler)NULL, /* SIOCGIWNICKN */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* SIOCSIWRATE */
(iw_handler)NULL, /* SIOCGIWRATE */
(iw_handler)NULL, /* SIOCSIWRTS */
(iw_handler)NULL, /* SIOCGIWRTS */
(iw_handler)NULL, /* SIOCSIWFRAG */
(iw_handler)NULL, /* SIOCGIWFRAG */
(iw_handler)NULL, /* SIOCSIWTXPOW */
(iw_handler)NULL, /* SIOCGIWTXPOW */
(iw_handler)NULL, /* SIOCSIWRETRY */
(iw_handler)NULL, /* SIOCGIWRETRY */
(iw_handler)woal_set_encode, /* SIOCSIWENCODE */
(iw_handler)woal_get_encode, /* SIOCGIWENCODE */
(iw_handler)NULL, /* SIOCSIWPOWER */
(iw_handler)NULL, /* SIOCGIWPOWER */
#if (WIRELESS_EXT >= 18)
(iw_handler)NULL, /* -- hole -- */
(iw_handler)NULL, /* -- hole -- */
(iw_handler)woal_set_gen_ie, /* SIOCSIWGENIE */
(iw_handler)woal_get_gen_ie, /* SIOCGIWGENIE */
(iw_handler)woal_set_auth, /* SIOCSIWAUTH */
(iw_handler)woal_get_auth, /* SIOCGIWAUTH */
(iw_handler)woal_set_encode_ext, /* SIOCSIWENCODEEXT */
(iw_handler)woal_get_encode_ext, /* SIOCGIWENCODEEXT */
#endif /* WIRELESSS_EXT >= 18 */
};
/**
* iwpriv settable callbacks
*/
static const iw_handler woal_private_handler[] = {
NULL, /* SIOCIWFIRSTPRIV */
};
/********************************************************
Global Functions
********************************************************/
// clang-format off
/** wlan_handler_def */
struct iw_handler_def woal_uap_handler_def = {
.num_standard = ARRAY_SIZE(woal_handler),
.num_private = ARRAY_SIZE(woal_private_handler),
.num_private_args = ARRAY_SIZE(woal_uap_priv_args),
.standard = (iw_handler *)woal_handler,
.private = (iw_handler *)woal_private_handler,
.private_args = (struct iw_priv_args *)woal_uap_priv_args,
#if WIRELESS_EXT > 20
.get_wireless_stats = woal_get_uap_wireless_stats,
#endif
};
// clang-format on
/**
* @brief Get wireless statistics
*
* @param dev A pointer to net_device structure
*
* @return A pointer to iw_statistics buf
*/
struct iw_statistics *woal_get_uap_wireless_stats(struct net_device *dev)
{
moal_private *priv = (moal_private *)netdev_priv(dev);
t_u16 wait_option = MOAL_IOCTL_WAIT;
ENTER();
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
/*
* Since schedule() is not allowed from an atomic context
* such as when dev_base_lock for netdevices is acquired
* for reading/writing in kernel before this call, HostCmd
* is issued in non-blocking way in such contexts and
* blocking in other cases.
*/
if (in_atomic() || !write_can_lock(&dev_base_lock))
wait_option = MOAL_NO_WAIT;
#endif
priv->w_stats.qual.qual = 0;
priv->w_stats.qual.level = 0;
priv->w_stats.discard.code = 0;
priv->w_stats.status = IW_MODE_MASTER;
woal_uap_get_stats(priv, wait_option, NULL);
LEAVE();
return &priv->w_stats;
}