849 lines
21 KiB
C
849 lines
21 KiB
C
/*
|
|
* Common function shared by Linux WEXT, cfg80211 and p2p drivers
|
|
*
|
|
* Copyright (C) 1999-2015, Broadcom Corporation
|
|
* Copyright (C) 2017 NVIDIA Corporation. All rights reserved.
|
|
*
|
|
* Unless you and Broadcom execute a separate written software license
|
|
* agreement governing use of this software, this software is licensed to you
|
|
* under the terms of the GNU General Public License version 2 (the "GPL"),
|
|
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
|
|
* following added to such license:
|
|
*
|
|
* As a special exception, the copyright holders of this software give you
|
|
* permission to link this software with independent modules, and to copy and
|
|
* distribute the resulting executable under terms of your choice, provided that
|
|
* you also meet, for each linked independent module, the terms and conditions of
|
|
* the license of that module. An independent module is a module which is not
|
|
* derived from this software. The special exception does not apply to any
|
|
* modifications of the software.
|
|
*
|
|
* Notwithstanding the above, under no circumstances may you combine this
|
|
* software in any way with any other Broadcom software provided under a license
|
|
* other than the GPL, without Broadcom's express prior written consent.
|
|
*
|
|
* $Id: wldev_common.c 527441 2015-01-19 04:13:03Z $
|
|
*/
|
|
|
|
#include <osl.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/netdevice.h>
|
|
|
|
#include "dynamic.h"
|
|
|
|
#include <wldev_common.h>
|
|
#include <bcmutils.h>
|
|
|
|
#ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA
|
|
#include "dhd_custom_sysfs_tegra_stat.h"
|
|
#endif /* CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA */
|
|
|
|
#define htod32(i) (i)
|
|
#define htod16(i) (i)
|
|
#define dtoh32(i) (i)
|
|
#define dtoh16(i) (i)
|
|
#define htodchanspec(i) (i)
|
|
#define dtohchanspec(i) (i)
|
|
|
|
#define WLDEV_ERROR(args) \
|
|
do { \
|
|
printk(KERN_ERR "WLDEV-ERROR) %s : ", __func__); \
|
|
printk args; \
|
|
} while (0)
|
|
|
|
extern int dhd_ioctl_entry_local(struct net_device *net, wl_ioctl_t *ioc, int cmd);
|
|
|
|
s32 wldev_ioctl(
|
|
struct net_device *dev, u32 cmd, void *arg, u32 len, u32 set)
|
|
{
|
|
s32 ret = 0;
|
|
struct wl_ioctl ioc;
|
|
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.cmd = cmd;
|
|
ioc.buf = arg;
|
|
ioc.len = len;
|
|
ioc.set = set;
|
|
|
|
ret = dhd_ioctl_entry_local(dev, &ioc, cmd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Format a iovar buffer, not bsscfg indexed. The bsscfg index will be
|
|
* taken care of in dhd_ioctl_entry. Internal use only, not exposed to
|
|
* wl_iw, wl_cfg80211 and wl_cfgp2p
|
|
*/
|
|
static s32 wldev_mkiovar(
|
|
s8 *iovar_name, s8 *param, s32 paramlen,
|
|
s8 *iovar_buf, u32 buflen)
|
|
{
|
|
s32 iolen = 0;
|
|
|
|
iolen = bcm_mkiovar(iovar_name, param, paramlen, iovar_buf, buflen);
|
|
return iolen;
|
|
}
|
|
|
|
s32 wldev_iovar_getbuf(
|
|
struct net_device *dev, s8 *iovar_name,
|
|
void *param, s32 paramlen, void *buf, s32 buflen, struct mutex* buf_sync)
|
|
{
|
|
s32 ret = 0;
|
|
if (buf_sync) {
|
|
mutex_lock(buf_sync);
|
|
}
|
|
wldev_mkiovar(iovar_name, param, paramlen, buf, buflen);
|
|
ret = wldev_ioctl(dev, WLC_GET_VAR, buf, buflen, FALSE);
|
|
if (buf_sync)
|
|
mutex_unlock(buf_sync);
|
|
return ret;
|
|
}
|
|
|
|
|
|
s32 wldev_iovar_setbuf(
|
|
struct net_device *dev, s8 *iovar_name,
|
|
void *param, s32 paramlen, void *buf, s32 buflen, struct mutex* buf_sync)
|
|
{
|
|
s32 ret = 0;
|
|
s32 iovar_len;
|
|
if (buf_sync) {
|
|
mutex_lock(buf_sync);
|
|
}
|
|
iovar_len = wldev_mkiovar(iovar_name, param, paramlen, buf, buflen);
|
|
if (iovar_len > 0)
|
|
ret = wldev_ioctl(dev, WLC_SET_VAR, buf, iovar_len, TRUE);
|
|
else
|
|
ret = BCME_BUFTOOSHORT;
|
|
|
|
if (buf_sync)
|
|
mutex_unlock(buf_sync);
|
|
return ret;
|
|
}
|
|
|
|
s32 wldev_iovar_setint(
|
|
struct net_device *dev, s8 *iovar, s32 val)
|
|
{
|
|
s8 iovar_buf[WLC_IOCTL_SMLEN];
|
|
|
|
val = htod32(val);
|
|
memset(iovar_buf, 0, sizeof(iovar_buf));
|
|
return wldev_iovar_setbuf(dev, iovar, &val, sizeof(val), iovar_buf,
|
|
sizeof(iovar_buf), NULL);
|
|
}
|
|
|
|
|
|
s32 wldev_iovar_getint(
|
|
struct net_device *dev, s8 *iovar, s32 *pval)
|
|
{
|
|
s8 iovar_buf[WLC_IOCTL_SMLEN];
|
|
s32 err;
|
|
|
|
memset(iovar_buf, 0, sizeof(iovar_buf));
|
|
err = wldev_iovar_getbuf(dev, iovar, pval, sizeof(*pval), iovar_buf,
|
|
sizeof(iovar_buf), NULL);
|
|
if (err == 0)
|
|
{
|
|
memcpy(pval, iovar_buf, sizeof(*pval));
|
|
*pval = dtoh32(*pval);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/** Format a bsscfg indexed iovar buffer. The bsscfg index will be
|
|
* taken care of in dhd_ioctl_entry. Internal use only, not exposed to
|
|
* wl_iw, wl_cfg80211 and wl_cfgp2p
|
|
*/
|
|
s32 wldev_mkiovar_bsscfg(
|
|
const s8 *iovar_name, s8 *param, s32 paramlen,
|
|
s8 *iovar_buf, s32 buflen, s32 bssidx)
|
|
{
|
|
const s8 *prefix = "bsscfg:";
|
|
s8 *p;
|
|
u32 prefixlen;
|
|
u32 namelen;
|
|
u32 iolen;
|
|
|
|
if (bssidx == 0) {
|
|
return wldev_mkiovar((s8*)iovar_name, (s8 *)param, paramlen,
|
|
(s8 *) iovar_buf, buflen);
|
|
}
|
|
|
|
prefixlen = (u32) strlen(prefix); /* lengh of bsscfg prefix */
|
|
namelen = (u32) strlen(iovar_name) + 1; /* lengh of iovar name + null */
|
|
iolen = prefixlen + namelen + sizeof(u32) + paramlen;
|
|
|
|
if (buflen < 0 || iolen > (u32)buflen)
|
|
{
|
|
WLDEV_ERROR(("%s: buffer is too short\n", __FUNCTION__));
|
|
return BCME_BUFTOOSHORT;
|
|
}
|
|
if (iovar_buf && buflen != 0)
|
|
memset(iovar_buf, 0, buflen);
|
|
else
|
|
return BCME_BADARG;
|
|
|
|
p = (s8 *)iovar_buf;
|
|
/* copy prefix, no null */
|
|
memcpy(p, prefix, prefixlen);
|
|
p += prefixlen;
|
|
|
|
/* copy iovar name including null */
|
|
memcpy(p, iovar_name, namelen);
|
|
p += namelen;
|
|
|
|
/* bss config index as first param */
|
|
bssidx = htod32(bssidx);
|
|
memcpy(p, &bssidx, sizeof(u32));
|
|
p += sizeof(u32);
|
|
|
|
/* parameter buffer follows */
|
|
if (paramlen)
|
|
memcpy(p, param, paramlen);
|
|
|
|
return iolen;
|
|
|
|
}
|
|
|
|
s32 wldev_iovar_getbuf_bsscfg(
|
|
struct net_device *dev, s8 *iovar_name,
|
|
void *param, s32 paramlen, void *buf, s32 buflen, s32 bsscfg_idx, struct mutex* buf_sync)
|
|
{
|
|
s32 ret = 0;
|
|
if (buf_sync) {
|
|
mutex_lock(buf_sync);
|
|
}
|
|
|
|
wldev_mkiovar_bsscfg(iovar_name, param, paramlen, buf, buflen, bsscfg_idx);
|
|
ret = wldev_ioctl(dev, WLC_GET_VAR, buf, buflen, FALSE);
|
|
if (buf_sync) {
|
|
mutex_unlock(buf_sync);
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
|
|
s32 wldev_iovar_setbuf_bsscfg(
|
|
struct net_device *dev, s8 *iovar_name,
|
|
void *param, s32 paramlen, void *buf, s32 buflen, s32 bsscfg_idx, struct mutex* buf_sync)
|
|
{
|
|
s32 ret = 0;
|
|
s32 iovar_len;
|
|
if (buf_sync) {
|
|
mutex_lock(buf_sync);
|
|
}
|
|
iovar_len = wldev_mkiovar_bsscfg(iovar_name, param, paramlen, buf, buflen, bsscfg_idx);
|
|
if (iovar_len > 0)
|
|
ret = wldev_ioctl(dev, WLC_SET_VAR, buf, iovar_len, TRUE);
|
|
else {
|
|
ret = BCME_BUFTOOSHORT;
|
|
}
|
|
|
|
if (buf_sync) {
|
|
mutex_unlock(buf_sync);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
s32 wldev_iovar_setint_bsscfg(
|
|
struct net_device *dev, s8 *iovar, s32 val, s32 bssidx)
|
|
{
|
|
s8 iovar_buf[WLC_IOCTL_SMLEN];
|
|
|
|
val = htod32(val);
|
|
memset(iovar_buf, 0, sizeof(iovar_buf));
|
|
return wldev_iovar_setbuf_bsscfg(dev, iovar, &val, sizeof(val), iovar_buf,
|
|
sizeof(iovar_buf), bssidx, NULL);
|
|
}
|
|
|
|
|
|
s32 wldev_iovar_getint_bsscfg(
|
|
struct net_device *dev, s8 *iovar, s32 *pval, s32 bssidx)
|
|
{
|
|
s8 iovar_buf[WLC_IOCTL_SMLEN];
|
|
s32 err;
|
|
|
|
memset(iovar_buf, 0, sizeof(iovar_buf));
|
|
err = wldev_iovar_getbuf_bsscfg(dev, iovar, pval, sizeof(*pval), iovar_buf,
|
|
sizeof(iovar_buf), bssidx, NULL);
|
|
if (err == 0)
|
|
{
|
|
memcpy(pval, iovar_buf, sizeof(*pval));
|
|
*pval = dtoh32(*pval);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int wldev_get_link_speed(
|
|
struct net_device *dev, int *plink_speed)
|
|
{
|
|
int error;
|
|
|
|
if (!plink_speed)
|
|
return -ENOMEM;
|
|
*plink_speed = 0;
|
|
error = wldev_ioctl(dev, WLC_GET_RATE, plink_speed, sizeof(int), 0);
|
|
if (unlikely(error))
|
|
return error;
|
|
|
|
/* Convert internal 500Kbps to Kbps */
|
|
*plink_speed *= 500;
|
|
return error;
|
|
}
|
|
|
|
int wldev_get_rssi(
|
|
struct net_device *dev, scb_val_t *scb_val)
|
|
{
|
|
int error;
|
|
|
|
if (!scb_val)
|
|
return -ENOMEM;
|
|
|
|
error = wldev_ioctl(dev, WLC_GET_RSSI, scb_val, sizeof(scb_val_t), 0);
|
|
if (unlikely(error))
|
|
return error;
|
|
|
|
#ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA
|
|
TEGRA_SYSFS_HISTOGRAM_DRIVER_STAT_INC(aggr_num_rssi_ioctl);
|
|
#endif /* CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA */
|
|
|
|
return error;
|
|
}
|
|
|
|
int wldev_get_ssid(
|
|
struct net_device *dev, wlc_ssid_t *pssid)
|
|
{
|
|
int error;
|
|
|
|
if (!pssid)
|
|
return -ENOMEM;
|
|
memset(pssid, 0, sizeof(wlc_ssid_t));
|
|
error = wldev_ioctl(dev, WLC_GET_SSID, pssid, sizeof(wlc_ssid_t), 0);
|
|
if (unlikely(error))
|
|
return error;
|
|
pssid->SSID_len = dtoh32(pssid->SSID_len);
|
|
return error;
|
|
}
|
|
|
|
int wldev_reset_band(
|
|
struct net_device *ndev)
|
|
{
|
|
uint band;
|
|
s32 err = 0;
|
|
|
|
err = wldev_get_band(ndev, &band);
|
|
if (!err) {
|
|
err = wldev_set_band(ndev, band);
|
|
if (err < 0) {
|
|
WLDEV_ERROR(("%s: failed to reset band (%d)\n", __FUNCTION__, err));
|
|
}
|
|
} else {
|
|
WLDEV_ERROR(("%s: failed to get current band (%d)\n", __FUNCTION__, err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int wldev_get_band(
|
|
struct net_device *dev, uint *pband)
|
|
{
|
|
int error;
|
|
|
|
*pband = 0;
|
|
error = wldev_ioctl(dev, WLC_GET_BAND, pband, sizeof(uint), 0);
|
|
return error;
|
|
}
|
|
|
|
int wldev_set_band(
|
|
struct net_device *dev, uint band)
|
|
{
|
|
int error = -1;
|
|
|
|
if ((band == WLC_BAND_AUTO) || (band == WLC_BAND_5G) || (band == WLC_BAND_2G)) {
|
|
error = wldev_ioctl(dev, WLC_SET_BAND, &band, sizeof(band), true);
|
|
if (!error)
|
|
dhd_bus_band_set(dev, band);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
int wldev_get_datarate(struct net_device *dev, int *datarate)
|
|
{
|
|
int error = 0;
|
|
|
|
error = wldev_ioctl(dev, WLC_GET_RATE, datarate, sizeof(int), false);
|
|
if (error) {
|
|
return -1;
|
|
} else {
|
|
*datarate = dtoh32(*datarate);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
extern chanspec_t
|
|
wl_chspec_driver_to_host(chanspec_t chanspec);
|
|
#define WL_EXTRA_BUF_MAX 2048
|
|
int wldev_get_mode(
|
|
struct net_device *dev, uint8 *cap)
|
|
{
|
|
int error = 0;
|
|
int chanspec = 0;
|
|
uint16 band = 0;
|
|
uint16 bandwidth = 0;
|
|
wl_bss_info_t *bss = NULL;
|
|
char* buf = kmalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
*(u32*) buf = htod32(WL_EXTRA_BUF_MAX);
|
|
error = wldev_ioctl(dev, WLC_GET_BSS_INFO, (void*)buf, WL_EXTRA_BUF_MAX, false);
|
|
if (error) {
|
|
WLDEV_ERROR(("%s:failed:%d\n", __FUNCTION__, error));
|
|
kfree(buf);
|
|
return -1;
|
|
}
|
|
bss = (struct wl_bss_info *)(buf + 4);
|
|
chanspec = wl_chspec_driver_to_host(bss->chanspec);
|
|
|
|
band = chanspec & WL_CHANSPEC_BAND_MASK;
|
|
bandwidth = chanspec & WL_CHANSPEC_BW_MASK;
|
|
|
|
if (band == WL_CHANSPEC_BAND_2G) {
|
|
if (bss->n_cap)
|
|
strcpy(cap, "n");
|
|
else
|
|
strcpy(cap, "bg");
|
|
} else if (band == WL_CHANSPEC_BAND_5G) {
|
|
if (bandwidth == WL_CHANSPEC_BW_80)
|
|
strcpy(cap, "ac");
|
|
else if ((bandwidth == WL_CHANSPEC_BW_40) || (bandwidth == WL_CHANSPEC_BW_20)) {
|
|
if ((bss->nbss_cap & 0xf00) && (bss->n_cap))
|
|
strcpy(cap, "n|ac");
|
|
else if (bss->n_cap)
|
|
strcpy(cap, "n");
|
|
else if (bss->vht_cap)
|
|
strcpy(cap, "ac");
|
|
else
|
|
strcpy(cap, "a");
|
|
} else {
|
|
WLDEV_ERROR(("%s:Mode get failed\n", __FUNCTION__));
|
|
kfree(buf);
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
kfree(buf);
|
|
return error;
|
|
}
|
|
int wldev_set_country(
|
|
struct net_device *dev, char *country_code, bool notify, bool user_enforced)
|
|
{
|
|
int error = -1;
|
|
wl_country_t cspec = {{0}, 0, {0}};
|
|
scb_val_t scbval;
|
|
char smbuf[WLC_IOCTL_SMLEN];
|
|
|
|
bzero(&scbval, sizeof(scb_val_t));
|
|
error = wldev_iovar_getbuf(dev, "country", NULL, 0, &cspec, sizeof(cspec), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("%s: get country failed = %d\n", __FUNCTION__, error));
|
|
return error;
|
|
}
|
|
|
|
/* Skip setting same country code provided again */
|
|
if (country_code &&
|
|
(strncmp(country_code, cspec.country_abbrev, WLC_CNTRY_BUF_SZ) == 0))
|
|
return 0;
|
|
|
|
/* Reset existing country code if none provided */
|
|
if (!country_code)
|
|
goto set_cur_ccode;
|
|
|
|
if (user_enforced) {
|
|
bzero(&scbval, sizeof(scb_val_t));
|
|
error = wldev_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t), true);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("%s: set country failed due to Disassoc error %d\n",
|
|
__FUNCTION__, error));
|
|
return error;
|
|
}
|
|
}
|
|
|
|
cspec.rev = -1;
|
|
memcpy(cspec.country_abbrev, country_code, WLC_CNTRY_BUF_SZ);
|
|
memcpy(cspec.ccode, country_code, WLC_CNTRY_BUF_SZ);
|
|
dhd_get_customized_country_code(dev, (char *)&cspec.country_abbrev, &cspec);
|
|
|
|
set_cur_ccode:
|
|
|
|
error = wldev_iovar_setbuf(dev, "country", &cspec, sizeof(cspec),
|
|
smbuf, sizeof(smbuf), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("%s: set country for %s as %s rev %d failed\n",
|
|
__FUNCTION__, country_code, cspec.ccode, cspec.rev));
|
|
return error;
|
|
}
|
|
#ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA
|
|
memcpy(bcmdhd_stat.fw_stat.cur_country_code,
|
|
cspec.country_abbrev, WLC_CNTRY_BUF_SZ);
|
|
#endif /* CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA */
|
|
dhd_bus_country_set(dev, &cspec, notify);
|
|
WLDEV_ERROR(("%s: set country for %s as %s rev %d\n",
|
|
__FUNCTION__, country_code, cspec.ccode, cspec.rev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* tuning performance for miracast */
|
|
|
|
int wldev_miracast_tuning(
|
|
struct net_device *dev, char *command, int total_len)
|
|
{
|
|
int error = 0;
|
|
int mode = 0;
|
|
int ampdu_mpdu;
|
|
int ampdu_rx_tid = -1;
|
|
int disable_interference_mitigation = 0;
|
|
int auto_interference_mitigation = -1;
|
|
#ifdef VSDB_BW_ALLOCATE_ENABLE
|
|
int mchan_algo;
|
|
int mchan_bw;
|
|
#endif /* VSDB_BW_ALLOCATE_ENABLE */
|
|
|
|
if (sscanf(command, "%*s %d", &mode) != 1) {
|
|
WLDEV_ERROR(("Failed to get mode\n"));
|
|
return -1;
|
|
}
|
|
|
|
set_mode:
|
|
|
|
|
|
if (mode == 0) {
|
|
/* Normal mode: restore everything to default */
|
|
#ifdef CUSTOM_AMPDU_MPDU
|
|
ampdu_mpdu = CUSTOM_AMPDU_MPDU;
|
|
#else
|
|
ampdu_mpdu = -1; /* FW default */
|
|
#endif
|
|
#ifdef VSDB_BW_ALLOCATE_ENABLE
|
|
mchan_algo = 0; /* Default */
|
|
mchan_bw = 50; /* 50:50 */
|
|
#endif /* VSDB_BW_ALLOCATE_ENABLE */
|
|
}
|
|
else if (mode == 1) {
|
|
/* Miracast source mode */
|
|
ampdu_mpdu = 8; /* for tx latency */
|
|
#ifdef VSDB_BW_ALLOCATE_ENABLE
|
|
mchan_algo = 1; /* BW based */
|
|
mchan_bw = 25; /* 25:75 */
|
|
#endif /* VSDB_BW_ALLOCATE_ENABLE */
|
|
}
|
|
else if (mode == 2) {
|
|
/* Miracast sink/PC Gaming mode */
|
|
ampdu_mpdu = 8; /* FW default */
|
|
#ifdef VSDB_BW_ALLOCATE_ENABLE
|
|
mchan_algo = 0; /* Default */
|
|
mchan_bw = 50; /* 50:50 */
|
|
#endif /* VSDB_BW_ALLOCATE_ENABLE */
|
|
} else if (mode == 3) {
|
|
ampdu_rx_tid = 0;
|
|
mode = 2;
|
|
goto set_mode;
|
|
} else if (mode == 4) {
|
|
ampdu_rx_tid = 0x7f;
|
|
mode = 0;
|
|
goto set_mode;
|
|
} else if (mode == 5) {
|
|
/* Blake connected mode, disable interference mitigation */
|
|
error = wldev_ioctl(dev, WLC_SET_INTERFERENCE_OVERRIDE_MODE,
|
|
&disable_interference_mitigation, sizeof(int), true);
|
|
if (error) {
|
|
WLDEV_ERROR((
|
|
"Failed to set interference_override: mode:%d, error:%d\n",
|
|
mode, error));
|
|
return -1;
|
|
}
|
|
return error;
|
|
} else if (mode == 6) {
|
|
/* No Blake connected, enable auto interference mitigation */
|
|
error = wldev_ioctl(dev, WLC_SET_INTERFERENCE_OVERRIDE_MODE,
|
|
&auto_interference_mitigation, sizeof(int), true);
|
|
if (error) {
|
|
WLDEV_ERROR((
|
|
"Failed to set interference_override: mode:%d, error:%d\n",
|
|
mode, error));
|
|
return -1;
|
|
}
|
|
return error;
|
|
} else {
|
|
WLDEV_ERROR(("Unknown mode: %d\n", mode));
|
|
return -1;
|
|
}
|
|
|
|
/* Update ampdu_mpdu */
|
|
error = wldev_iovar_setint(dev, "ampdu_mpdu", ampdu_mpdu);
|
|
if (error) {
|
|
WLDEV_ERROR(("Failed to set ampdu_mpdu: mode:%d, error:%d\n",
|
|
mode, error));
|
|
return -1;
|
|
}
|
|
|
|
if (ampdu_rx_tid != -1)
|
|
dhd_set_ampdu_rx_tid(dev, ampdu_rx_tid);
|
|
|
|
#ifdef VSDB_BW_ALLOCATE_ENABLE
|
|
if (bcmdhd_vsdb_bw_allocate_enable) {
|
|
error = wldev_iovar_setint(dev, "mchan_algo", mchan_algo);
|
|
if (error) {
|
|
WLDEV_ERROR(("Failed to set mchan_algo: mode:%d, error:%d\n",
|
|
mode, error));
|
|
return -1;
|
|
}
|
|
|
|
error = wldev_iovar_setint(dev, "mchan_bw", mchan_bw);
|
|
if (error) {
|
|
WLDEV_ERROR(("Failed to set mchan_bw: mode:%d, error:%d\n",
|
|
mode, error));
|
|
return -1;
|
|
}
|
|
}
|
|
#endif /* VSDB_BW_ALLOCATE_ENABLE */
|
|
|
|
return error;
|
|
}
|
|
|
|
int wldev_get_rx_rate_stats(
|
|
struct net_device *dev, char *command, int total_len)
|
|
{
|
|
wl_scb_rx_rate_stats_t *rstats;
|
|
struct ether_addr ea;
|
|
char smbuf[WLC_IOCTL_SMLEN];
|
|
char eabuf[18] = {0, };
|
|
int bytes_written = 0;
|
|
int error;
|
|
|
|
memcpy(eabuf, command+strlen("RXRATESTATS")+1, 17);
|
|
|
|
if (!bcm_ether_atoe(eabuf, &ea)) {
|
|
WLDEV_ERROR(("Invalid MAC Address\n"));
|
|
return -1;
|
|
}
|
|
|
|
error = wldev_iovar_getbuf(dev, "rx_rate_stats",
|
|
&ea, ETHER_ADDR_LEN, smbuf, sizeof(smbuf), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("get rx_rate_stats failed = %d\n", error));
|
|
return -1;
|
|
}
|
|
|
|
rstats = (wl_scb_rx_rate_stats_t *)smbuf;
|
|
bytes_written = sprintf(command, "1/%d/%d,",
|
|
dtoh32(rstats->rx1mbps[0]), dtoh32(rstats->rx1mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "2/%d/%d,",
|
|
dtoh32(rstats->rx2mbps[0]), dtoh32(rstats->rx2mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "5.5/%d/%d,",
|
|
dtoh32(rstats->rx5mbps5[0]), dtoh32(rstats->rx5mbps5[1]));
|
|
bytes_written += sprintf(command+bytes_written, "6/%d/%d,",
|
|
dtoh32(rstats->rx6mbps[0]), dtoh32(rstats->rx6mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "9/%d/%d,",
|
|
dtoh32(rstats->rx9mbps[0]), dtoh32(rstats->rx9mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "11/%d/%d,",
|
|
dtoh32(rstats->rx11mbps[0]), dtoh32(rstats->rx11mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "12/%d/%d,",
|
|
dtoh32(rstats->rx12mbps[0]), dtoh32(rstats->rx12mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "18/%d/%d,",
|
|
dtoh32(rstats->rx18mbps[0]), dtoh32(rstats->rx18mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "24/%d/%d,",
|
|
dtoh32(rstats->rx24mbps[0]), dtoh32(rstats->rx24mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "36/%d/%d,",
|
|
dtoh32(rstats->rx36mbps[0]), dtoh32(rstats->rx36mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "48/%d/%d,",
|
|
dtoh32(rstats->rx48mbps[0]), dtoh32(rstats->rx48mbps[1]));
|
|
bytes_written += sprintf(command+bytes_written, "54/%d/%d",
|
|
dtoh32(rstats->rx54mbps[0]), dtoh32(rstats->rx54mbps[1]));
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
int wldev_get_assoc_resp_ie(
|
|
struct net_device *dev, char *command, int total_len)
|
|
{
|
|
wl_assoc_info_t *assoc_info;
|
|
char smbuf[WLC_IOCTL_SMLEN];
|
|
char bssid[6], null_bssid[6];
|
|
int resp_ies_len = 0;
|
|
int bytes_written = 0;
|
|
int error, i;
|
|
|
|
bzero(bssid, 6);
|
|
bzero(null_bssid, 6);
|
|
|
|
/* Check Association */
|
|
error = wldev_ioctl(dev, WLC_GET_BSSID, &bssid, sizeof(bssid), 0);
|
|
if (error == BCME_NOTASSOCIATED) {
|
|
/* Not associated */
|
|
bytes_written += snprintf(&command[bytes_written], total_len, "NA");
|
|
goto done;
|
|
}
|
|
else if (error < 0) {
|
|
WLDEV_ERROR(("WLC_GET_BSSID failed = %d\n", error));
|
|
return -1;
|
|
}
|
|
else if (memcmp(bssid, null_bssid, ETHER_ADDR_LEN) == 0) {
|
|
/* Zero BSSID: Not associated */
|
|
bytes_written += snprintf(&command[bytes_written], total_len, "NA");
|
|
goto done;
|
|
}
|
|
|
|
/* Get assoc_info */
|
|
bzero(smbuf, sizeof(smbuf));
|
|
error = wldev_iovar_getbuf(dev, "assoc_info", NULL, 0, smbuf, sizeof(smbuf), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("get assoc_info failed = %d\n", error));
|
|
return -1;
|
|
}
|
|
|
|
assoc_info = (wl_assoc_info_t *)smbuf;
|
|
resp_ies_len = dtoh32(assoc_info->resp_len) - sizeof(struct dot11_assoc_resp);
|
|
|
|
/* Retrieve assoc resp IEs */
|
|
if (resp_ies_len) {
|
|
error = wldev_iovar_getbuf(dev, "assoc_resp_ies", NULL, 0, smbuf, sizeof(smbuf),
|
|
NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("get assoc_resp_ies failed = %d\n", error));
|
|
return -1;
|
|
}
|
|
|
|
/* Length */
|
|
bytes_written += snprintf(&command[bytes_written], total_len, "%d,", resp_ies_len);
|
|
|
|
/* IEs */
|
|
if ((total_len - bytes_written) > resp_ies_len) {
|
|
for (i = 0; i < resp_ies_len; i++) {
|
|
bytes_written += sprintf(&command[bytes_written], "%02x", smbuf[i]);
|
|
}
|
|
} else {
|
|
WLDEV_ERROR(("Not enough buffer\n"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
WLDEV_ERROR(("Zero Length assoc resp ies = %d\n", resp_ies_len));
|
|
return -1;
|
|
}
|
|
|
|
done:
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
int wldev_get_max_linkspeed(
|
|
struct net_device *dev, char *command, int total_len)
|
|
{
|
|
wl_assoc_info_t *assoc_info;
|
|
char smbuf[WLC_IOCTL_SMLEN];
|
|
char bssid[6], null_bssid[6];
|
|
int resp_ies_len = 0;
|
|
int bytes_written = 0;
|
|
int error, i;
|
|
|
|
bzero(bssid, 6);
|
|
bzero(null_bssid, 6);
|
|
|
|
/* Check Association */
|
|
error = wldev_ioctl(dev, WLC_GET_BSSID, &bssid, sizeof(bssid), 0);
|
|
if (error == BCME_NOTASSOCIATED) {
|
|
/* Not associated */
|
|
bytes_written += snprintf(&command[bytes_written],
|
|
total_len, "-1");
|
|
goto done;
|
|
} else if (error < 0) {
|
|
WLDEV_ERROR(("WLC_GET_BSSID failed = %d\n", error));
|
|
return -1;
|
|
} else if (memcmp(bssid, null_bssid, ETHER_ADDR_LEN) == 0) {
|
|
/* Zero BSSID: Not associated */
|
|
bytes_written += snprintf(&command[bytes_written],
|
|
total_len, "-1");
|
|
goto done;
|
|
}
|
|
/* Get assoc_info */
|
|
bzero(smbuf, sizeof(smbuf));
|
|
error = wldev_iovar_getbuf(dev, "assoc_info", NULL, 0, smbuf,
|
|
sizeof(smbuf), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("get assoc_info failed = %d\n", error));
|
|
return -1;
|
|
}
|
|
|
|
assoc_info = (wl_assoc_info_t *)smbuf;
|
|
resp_ies_len = dtoh32(assoc_info->resp_len) -
|
|
sizeof(struct dot11_assoc_resp);
|
|
|
|
/* Retrieve assoc resp IEs */
|
|
if (resp_ies_len) {
|
|
error = wldev_iovar_getbuf(dev, "assoc_resp_ies", NULL, 0,
|
|
smbuf, sizeof(smbuf), NULL);
|
|
if (error < 0) {
|
|
WLDEV_ERROR(("get assoc_resp_ies failed = %d\n",
|
|
error));
|
|
return -1;
|
|
}
|
|
|
|
{
|
|
int maxRate = 0;
|
|
struct dot11IE {
|
|
unsigned char ie;
|
|
unsigned char len;
|
|
unsigned char data[0];
|
|
} *dot11IE = (struct dot11IE *)smbuf;
|
|
int remaining = resp_ies_len;
|
|
|
|
while (1) {
|
|
if (remaining < 2)
|
|
break;
|
|
if (remaining < dot11IE->len + 2)
|
|
break;
|
|
switch (dot11IE->ie) {
|
|
case 0x01: /* supported rates */
|
|
case 0x32: /* extended supported rates */
|
|
for (i = 0; i < dot11IE->len; i++) {
|
|
int rate = ((dot11IE->data[i] &
|
|
0x7f) / 2);
|
|
if (rate > maxRate)
|
|
maxRate = rate;
|
|
}
|
|
break;
|
|
case 0x2d: /* HT capabilities */
|
|
case 0x3d: /* HT operation */
|
|
/* 11n supported */
|
|
maxRate = 150; /* Just return an 11n
|
|
rate for now. Could implement detailed
|
|
parser later. */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* next IE */
|
|
dot11IE = (struct dot11IE *)
|
|
((unsigned char *)dot11IE + dot11IE->len + 2);
|
|
remaining -= (dot11IE->len + 2);
|
|
}
|
|
bytes_written += snprintf(&command[bytes_written],
|
|
total_len, "MaxLinkSpeed %d",
|
|
maxRate);
|
|
goto done;
|
|
}
|
|
} else {
|
|
WLDEV_ERROR(("Zero Length assoc resp ies = %d\n",
|
|
resp_ies_len));
|
|
return -1;
|
|
}
|
|
|
|
done:
|
|
|
|
return bytes_written;
|
|
|
|
}
|