blob: 98cd936ef4efce1390575afcd3008912fab6abec [file] [log] [blame]
/*
* Linux cfg80211 driver - Android initial functions
*
* Copyright 1999-2016, Broadcom Corporation
* All rights reserved,
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided by the copyright holder "as is" and any express or
* implied warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose are disclaimed. In no event
* shall copyright holder be liable for any direct, indirect, incidental, special,
* exemplary, or consequential damages (including, but not limited to, procurement
* of substitute goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether in
* contract, strict liability, or tort (including negligence or otherwise) arising
* in any way out of the use of this software, even if advised of the possibility
* of such damage
*
*
* <<Broadcom-WL-IPTag/Open:>>
*
* $Id: wl_android.c 608788 2015-12-29 10:59:33Z $
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <net/netlink.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif
#include <wl_android.h>
#include <wldev_common.h>
#include <wlioctl.h>
#include <bcmutils.h>
#include <linux_osl.h>
#include <dhd_dbg.h>
#include <dngl_stats.h>
#include <dhd.h>
#include <dhd_config.h>
#include <proto/bcmip.h>
#ifdef PNO_SUPPORT
#include <dhd_pno.h>
#endif
#ifdef BCMSDIO
#include <bcmsdbus.h>
#endif
#ifdef WL_CFG80211
#ifdef WL_CFG80211_V1
#include <wl_cfg80211_v1.h>
#else
#include <wl_cfg80211.h>
#endif /* WL_CFG80211_V1 */
#endif
#ifdef WL_NAN
#include <wl_cfgnan.h>
#endif /* WL_NAN */
#ifdef DHDTCPACK_SUPPRESS
#include <dhd_ip.h>
#endif /* DHDTCPACK_SUPPRESS */
#ifndef WL_CFG80211
#define htod32(i) i
#define htod16(i) i
#define dtoh32(i) i
#define dtoh16(i) i
#define htodchanspec(i) i
#define dtohchanspec(i) i
#endif
uint android_msg_level = ANDROID_ERROR_LEVEL;
/**
* Extern function declarations (TODO: move them to dhd_linux.h)
*/
int dhd_net_bus_devreset(struct net_device *dev, uint8 flag);
int dhd_dev_init_ioctl(struct net_device *dev);
#ifdef ENABLE_4335BT_WAR
extern int bcm_bt_lock(int cookie);
extern void bcm_bt_unlock(int cookie);
static int lock_cookie_wifi = 'W' | 'i'<<8 | 'F'<<16 | 'i'<<24; /* cookie is "WiFi" */
#endif /* ENABLE_4335BT_WAR */
extern bool ap_fw_loaded;
extern char iface_name[IFNAMSIZ];
/**
* Local (static) functions and variables
*/
/* Initialize g_wifi_on to 1 so dhd_bus_start will be called for the first
* time (only) in dhd_open, subsequential wifi on will be handled by
* wl_android_wifi_on
*/
int g_wifi_on = TRUE;
#ifdef WL_CFG80211
static const char *
get_string_by_separator(char *result, int result_len, const char *src, char separator)
{
char *end = result + result_len - 1;
while ((result != end) && (*src != separator) && (*src)) {
*result++ = *src++;
}
*result = 0;
if (*src == separator) {
++src;
}
return src;
}
int
wl_android_set_roam_offload_bssid_list(struct net_device *dev, const char *cmd)
{
char sbuf[32];
int i, cnt, size, err, ioctl_buf_len;
roamoffl_bssid_list_t *bssid_list;
const char *str = cmd;
char *ioctl_buf;
#ifdef WL_CFG80211_V1
dhd_pub_t *dhdp = wl_cfg80211_get_dhdp();
#else
dhd_pub_t *dhdp = wl_cfg80211_get_dhdp(dev);
#endif /* WL_CFG80211_V1 */
str = get_string_by_separator(sbuf, 32, str, ',');
cnt = bcm_atoi(sbuf);
cnt = MIN(cnt, MAX_ROAMOFFL_BSSID_NUM);
if ((cnt > 0) &&
(((dhdp->op_mode & DHD_FLAG_STA_MODE) && (dhdp->op_mode & DHD_FLAG_HOSTAP_MODE)) ||
FALSE)) {
ANDROID_ERROR(("Can't set ROAMOFFL_BSSID when enabled STA-SoftAP or WES\n"));
return -EINVAL;
}
size = sizeof(int32) + sizeof(struct ether_addr) * cnt;
ANDROID_ERROR(("ROAM OFFLOAD BSSID LIST %d BSSIDs, size %d\n", cnt, size));
bssid_list = kmalloc(size, GFP_KERNEL);
if (bssid_list == NULL) {
ANDROID_ERROR(("%s: memory alloc for bssid list(%d) failed\n",
__FUNCTION__, size));
return -ENOMEM;
}
ioctl_buf_len = size + 64;
ioctl_buf = kmalloc(ioctl_buf_len, GFP_KERNEL);
if (ioctl_buf == NULL) {
ANDROID_ERROR(("%s: memory alloc for ioctl_buf(%d) failed\n",
__FUNCTION__, ioctl_buf_len));
kfree(bssid_list);
return -ENOMEM;
}
for (i = 0; i < cnt; i++) {
str = get_string_by_separator(sbuf, 32, str, ',');
bcm_ether_atoe(sbuf, &bssid_list->bssid[i]);
}
bssid_list->cnt = (int32)cnt;
err = wldev_iovar_setbuf(dev, "roamoffl_bssid_list",
bssid_list, size, ioctl_buf, ioctl_buf_len, NULL);
kfree(bssid_list);
kfree(ioctl_buf);
return err;
}
int
wl_android_set_ap_mac_list(struct net_device *dev, int macmode, struct maclist *maclist)
{
int i, j, match;
int ret = 0;
char mac_buf[MAX_NUM_OF_ASSOCLIST *
sizeof(struct ether_addr) + sizeof(uint)] = {0};
struct maclist *assoc_maclist = (struct maclist *)mac_buf;
/* set filtering mode */
if ((ret = wldev_ioctl(dev, WLC_SET_MACMODE, &macmode, sizeof(macmode), true)) != 0) {
ANDROID_ERROR(("%s : WLC_SET_MACMODE error=%d\n", __FUNCTION__, ret));
return ret;
}
if (macmode != MACLIST_MODE_DISABLED) {
/* set the MAC filter list */
if ((ret = wldev_ioctl(dev, WLC_SET_MACLIST, maclist,
sizeof(int) + sizeof(struct ether_addr) * maclist->count, true)) != 0) {
ANDROID_ERROR(("%s : WLC_SET_MACLIST error=%d\n", __FUNCTION__, ret));
return ret;
}
/* get the current list of associated STAs */
assoc_maclist->count = MAX_NUM_OF_ASSOCLIST;
if ((ret = wldev_ioctl(dev, WLC_GET_ASSOCLIST, assoc_maclist,
sizeof(mac_buf), false)) != 0) {
ANDROID_ERROR(("%s : WLC_GET_ASSOCLIST error=%d\n", __FUNCTION__, ret));
return ret;
}
/* do we have any STA associated? */
if (assoc_maclist->count) {
/* iterate each associated STA */
for (i = 0; i < assoc_maclist->count; i++) {
match = 0;
/* compare with each entry */
for (j = 0; j < maclist->count; j++) {
ANDROID_INFO(("%s : associated="MACDBG " list="MACDBG "\n",
__FUNCTION__, MAC2STRDBG(assoc_maclist->ea[i].octet),
MAC2STRDBG(maclist->ea[j].octet)));
if (memcmp(assoc_maclist->ea[i].octet,
maclist->ea[j].octet, ETHER_ADDR_LEN) == 0) {
match = 1;
break;
}
}
/* do conditional deauth */
/* "if not in the allow list" or "if in the deny list" */
if ((macmode == MACLIST_MODE_ALLOW && !match) ||
(macmode == MACLIST_MODE_DENY && match)) {
scb_val_t scbval;
scbval.val = htod32(1);
memcpy(&scbval.ea, &assoc_maclist->ea[i],
ETHER_ADDR_LEN);
if ((ret = wldev_ioctl(dev,
WLC_SCB_DEAUTHENTICATE_FOR_REASON,
&scbval, sizeof(scb_val_t), true)) != 0)
ANDROID_ERROR(("%s WLC_SCB_DEAUTHENTICATE error=%d\n",
__FUNCTION__, ret));
}
}
}
}
return ret;
}
#endif
/**
* Global function definitions (declared in wl_android.h)
*/
int wl_android_wifi_on(struct net_device *dev)
{
int ret = 0;
int retry = POWERUP_MAX_RETRY;
#ifdef IAPSTA_PREINIT
int bytes_written = 0;
struct dhd_conf *conf;
#endif
if (!dev) {
ANDROID_ERROR(("%s: dev is null\n", __FUNCTION__));
return -EINVAL;
}
printf("%s in 1\n", __FUNCTION__);
dhd_net_if_lock(dev);
printf("%s in 2: g_wifi_on=%d\n", __FUNCTION__, g_wifi_on);
if (!g_wifi_on) {
do {
dhd_net_wifi_platform_set_power(dev, TRUE, WIFI_TURNON_DELAY);
#ifdef BCMSDIO
ret = dhd_net_bus_resume(dev, 0);
#endif /* BCMSDIO */
#ifdef BCMPCIE
ret = dhd_net_bus_devreset(dev, FALSE);
#endif /* BCMPCIE */
if (ret == 0) {
break;
}
ANDROID_ERROR(("\nfailed to power up wifi chip, retry again (%d left) **\n\n",
retry));
#ifdef BCMPCIE
dhd_net_bus_devreset(dev, TRUE);
#endif /* BCMPCIE */
dhd_net_wifi_platform_set_power(dev, FALSE, WIFI_TURNOFF_DELAY);
} while (retry-- > 0);
if (ret != 0) {
ANDROID_ERROR(("\nfailed to power up wifi chip, max retry reached **\n\n"));
goto exit;
}
#ifdef BCMSDIO
ret = dhd_net_bus_devreset(dev, FALSE);
if (ret)
goto err;
dhd_net_bus_resume(dev, 1);
#endif /* BCMSDIO */
#ifndef BCMPCIE
if (!ret) {
if (dhd_dev_init_ioctl(dev) < 0) {
ret = -EFAULT;
goto err;
}
}
#endif /* !BCMPCIE */
#ifdef IAPSTA_PREINIT
conf = dhd_get_conf(dev);
if (conf) {
wl_android_ext_priv_cmd(dev, conf->iapsta_init, 0, &bytes_written);
wl_android_ext_priv_cmd(dev, conf->iapsta_config, 0, &bytes_written);
wl_android_ext_priv_cmd(dev, conf->iapsta_enable, 0, &bytes_written);
}
#endif
g_wifi_on = TRUE;
}
exit:
printf("%s: Success\n", __FUNCTION__);
dhd_net_if_unlock(dev);
return ret;
#ifdef BCMSDIO
err:
dhd_net_bus_devreset(dev, TRUE);
dhd_net_bus_suspend(dev);
dhd_net_wifi_platform_set_power(dev, FALSE, WIFI_TURNOFF_DELAY);
printf("%s: Failed\n", __FUNCTION__);
dhd_net_if_unlock(dev);
return ret;
#endif
}
int wl_android_wifi_off(struct net_device *dev, bool on_failure)
{
int ret = 0;
if (!dev) {
ANDROID_ERROR(("%s: dev is null\n", __FUNCTION__));
return -EINVAL;
}
printf("%s in 1\n", __FUNCTION__);
dhd_net_if_lock(dev);
printf("%s in 2: g_wifi_on=%d, on_failure=%d\n", __FUNCTION__, g_wifi_on, on_failure);
if (g_wifi_on || on_failure) {
#if defined(BCMSDIO) || defined(BCMPCIE)
ret = dhd_net_bus_devreset(dev, TRUE);
#ifdef BCMSDIO
dhd_net_bus_suspend(dev);
#endif /* BCMSDIO */
#endif /* BCMSDIO || BCMPCIE */
dhd_net_wifi_platform_set_power(dev, FALSE, WIFI_TURNOFF_DELAY);
g_wifi_on = FALSE;
}
printf("%s out\n", __FUNCTION__);
dhd_net_if_unlock(dev);
return ret;
}
#define NETLINK_OXYGEN 30
#define AIBSS_BEACON_TIMEOUT 10
static struct sock *nl_sk = NULL;
static void wl_netlink_recv(struct sk_buff *skb)
{
ANDROID_ERROR(("netlink_recv called\n"));
}
static int wl_netlink_init(void)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0))
struct netlink_kernel_cfg cfg = {
.input = wl_netlink_recv,
};
#endif
if (nl_sk != NULL) {
ANDROID_ERROR(("nl_sk already exist\n"));
return BCME_ERROR;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0))
nl_sk = netlink_kernel_create(&init_net, NETLINK_OXYGEN,
0, wl_netlink_recv, NULL, THIS_MODULE);
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0))
nl_sk = netlink_kernel_create(&init_net, NETLINK_OXYGEN, THIS_MODULE, &cfg);
#else
nl_sk = netlink_kernel_create(&init_net, NETLINK_OXYGEN, &cfg);
#endif
if (nl_sk == NULL) {
ANDROID_ERROR(("nl_sk is not ready\n"));
return BCME_ERROR;
}
return BCME_OK;
}
static void wl_netlink_deinit(void)
{
if (nl_sk) {
netlink_kernel_release(nl_sk);
nl_sk = NULL;
}
}
s32
wl_netlink_send_msg(int pid, int type, int seq, void *data, size_t size)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;
int ret = -1;
if (nl_sk == NULL) {
ANDROID_ERROR(("nl_sk was not initialized\n"));
goto nlmsg_failure;
}
skb = alloc_skb(NLMSG_SPACE(size), GFP_ATOMIC);
if (skb == NULL) {
ANDROID_ERROR(("failed to allocate memory\n"));
goto nlmsg_failure;
}
nlh = nlmsg_put(skb, 0, 0, 0, size, 0);
if (nlh == NULL) {
ANDROID_ERROR(("failed to build nlmsg, skb_tailroom:%d, nlmsg_total_size:%d\n",
skb_tailroom(skb), nlmsg_total_size(size)));
dev_kfree_skb(skb);
goto nlmsg_failure;
}
memcpy(nlmsg_data(nlh), data, size);
nlh->nlmsg_seq = seq;
nlh->nlmsg_type = type;
/* netlink_unicast() takes ownership of the skb and frees it itself. */
ret = netlink_unicast(nl_sk, skb, pid, 0);
ANDROID_TRACE(("netlink_unicast() pid=%d, ret=%d\n", pid, ret));
nlmsg_failure:
return ret;
}
int wl_android_init(void)
{
int ret = 0;
#ifdef ENABLE_INSMOD_NO_FW_LOAD
dhd_download_fw_on_driverload = FALSE;
#endif /* ENABLE_INSMOD_NO_FW_LOAD */
if (!iface_name[0]) {
memset(iface_name, 0, IFNAMSIZ);
bcm_strncpy_s(iface_name, IFNAMSIZ, "wlan", IFNAMSIZ);
}
wl_netlink_init();
return ret;
}
int wl_android_exit(void)
{
int ret = 0;
wl_netlink_deinit();
#ifdef WL_ANDROID_CMD
wl_android_cmd_exit();
#endif
return ret;
}
void wl_android_post_init(void)
{
#ifdef ENABLE_4335BT_WAR
bcm_bt_unlock(lock_cookie_wifi);
printk("%s: btlock released\n", __FUNCTION__);
#endif /* ENABLE_4335BT_WAR */
if (!dhd_download_fw_on_driverload)
g_wifi_on = FALSE;
}