/* * Linux cfg80211 driver - Android related functions * * $Copyright Open Broadcom Corporation$ * * $Id: wl_android.c 420671 2013-08-28 11:37:19Z $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PNO_SUPPORT #include #endif #include #ifdef WL_CFG80211 #include #endif #if defined(CONFIG_WIFI_CONTROL_FUNC) #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)) #include #else #include #endif #endif /* CONFIG_WIFI_CONTROL_FUNC */ #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 /* message levels */ #define ANDROID_ERROR_LEVEL 0x0001 #define ANDROID_TRACE_LEVEL 0x0002 #define ANDROID_INFO_LEVEL 0x0004 uint android_msg_level = ANDROID_ERROR_LEVEL; #define ANDROID_ERROR(x) \ do { \ if (android_msg_level & ANDROID_ERROR_LEVEL) { \ printk(KERN_ERR "ANDROID-ERROR) "); \ printk x; \ } \ } while (0) #define ANDROID_TRACE(x) \ do { \ if (android_msg_level & ANDROID_TRACE_LEVEL) { \ printk(KERN_ERR "ANDROID-TRACE) "); \ printk x; \ } \ } while (0) #define ANDROID_INFO(x) \ do { \ if (android_msg_level & ANDROID_INFO_LEVEL) { \ printk(KERN_ERR "ANDROID-INFO) "); \ printk x; \ } \ } while (0) /* * Android private command strings, PLEASE define new private commands here * so they can be updated easily in the future (if needed) */ #define CMD_START "START" #define CMD_STOP "STOP" #define CMD_SCAN_ACTIVE "SCAN-ACTIVE" #define CMD_SCAN_PASSIVE "SCAN-PASSIVE" #define CMD_RSSI "RSSI" #define CMD_LINKSPEED "LINKSPEED" #define CMD_RXFILTER_START "RXFILTER-START" #define CMD_RXFILTER_STOP "RXFILTER-STOP" #define CMD_RXFILTER_ADD "RXFILTER-ADD" #define CMD_RXFILTER_REMOVE "RXFILTER-REMOVE" #define CMD_BTCOEXSCAN_START "BTCOEXSCAN-START" #define CMD_BTCOEXSCAN_STOP "BTCOEXSCAN-STOP" #define CMD_BTCOEXMODE "BTCOEXMODE" #define CMD_SETSUSPENDOPT "SETSUSPENDOPT" #define CMD_SETSUSPENDMODE "SETSUSPENDMODE" #define CMD_P2P_DEV_ADDR "P2P_DEV_ADDR" #define CMD_SETFWPATH "SETFWPATH" #define CMD_SETBAND "SETBAND" #define CMD_GETBAND "GETBAND" #define CMD_COUNTRY "COUNTRY" #define CMD_P2P_SET_NOA "P2P_SET_NOA" #if !defined WL_ENABLE_P2P_IF #define CMD_P2P_GET_NOA "P2P_GET_NOA" #endif /* WL_ENABLE_P2P_IF */ #define CMD_P2P_SD_OFFLOAD "P2P_SD_" #define CMD_P2P_SET_PS "P2P_SET_PS" #define CMD_SET_AP_WPS_P2P_IE "SET_AP_WPS_P2P_IE" #define CMD_SETROAMMODE "SETROAMMODE" #define CMD_SETIBSSBEACONOUIDATA "SETIBSSBEACONOUIDATA" #define CMD_MIRACAST "MIRACAST" #define CMD_GET_CHANNEL "GET_CHANNEL" #define CMD_SET_ROAM "SET_ROAM_TRIGGER" #define CMD_GET_ROAM "GET_ROAM_TRIGGER" #define CMD_SET_KEEP_ALIVE "SET_KEEP_ALIVE" #define CMD_GET_KEEP_ALIVE "GET_KEEP_ALIVE" #define CMD_GET_PM "GET_PM" #define CMD_SET_PM "SET_PM" #define CMD_MONITOR "MONITOR" #if defined(WL_SUPPORT_AUTO_CHANNEL) #define CMD_GET_BEST_CHANNELS "GET_BEST_CHANNELS" #endif /* WL_SUPPORT_AUTO_CHANNEL */ /* CCX Private Commands */ #ifdef BCMCCX #define CMD_GETCCKM_RN "get cckm_rn" #define CMD_SETCCKM_KRK "set cckm_krk" #define CMD_GET_ASSOC_RES_IES "get assoc_res_ies" #endif #ifdef PNO_SUPPORT #define CMD_PNOSSIDCLR_SET "PNOSSIDCLR" #define CMD_PNOSETUP_SET "PNOSETUP " #define CMD_PNOENABLE_SET "PNOFORCE" #define CMD_PNODEBUG_SET "PNODEBUG" #define CMD_WLS_BATCHING "WLS_BATCHING" #endif /* PNO_SUPPORT */ #define CMD_OKC_SET_PMK "SET_PMK" #define CMD_OKC_ENABLE "OKC_ENABLE" #define CMD_HAPD_MAC_FILTER "HAPD_MAC_FILTER" /* hostap mac mode */ #define MACLIST_MODE_DISABLED 0 #define MACLIST_MODE_DENY 1 #define MACLIST_MODE_ALLOW 2 /* max number of assoc list */ #define MAX_NUM_OF_ASSOCLIST 64 /* max number of mac filter list * restrict max number to 10 as maximum cmd string size is 255 */ #define MAX_NUM_MAC_FILT 10 /* miracast related definition */ #define MIRACAST_MODE_OFF 0 #define MIRACAST_MODE_SOURCE 1 #define MIRACAST_MODE_SINK 2 #ifndef MIRACAST_AMPDU_SIZE #define MIRACAST_AMPDU_SIZE 8 #endif #ifndef MIRACAST_MCHAN_ALGO #define MIRACAST_MCHAN_ALGO 1 #endif #ifndef MIRACAST_MCHAN_BW #define MIRACAST_MCHAN_BW 25 #endif static LIST_HEAD(miracast_resume_list); static u8 miracast_cur_mode; struct io_cfg { s8 *iovar; s32 param; u32 ioctl; void *arg; u32 len; struct list_head list; }; typedef struct android_wifi_priv_cmd { char *buf; int used_len; int total_len; } android_wifi_priv_cmd; #ifdef WL_GENL static s32 wl_genl_handle_msg(struct sk_buff *skb, struct genl_info *info); static int wl_genl_init(void); static int wl_genl_deinit(void); extern struct net init_net; /* attribute policy: defines which attribute has which type (e.g int, char * etc) * possible values defined in net/netlink.h */ static struct nla_policy wl_genl_policy[BCM_GENL_ATTR_MAX + 1] = { [BCM_GENL_ATTR_STRING] = { .type = NLA_NUL_STRING }, [BCM_GENL_ATTR_MSG] = { .type = NLA_BINARY }, }; #define WL_GENL_VER 1 /* family definition */ static struct genl_family wl_genl_family = { .id = GENL_ID_GENERATE, /* Genetlink would generate the ID */ .hdrsize = 0, .name = "bcm-genl", /* Netlink I/F for Android */ .version = WL_GENL_VER, /* Version Number */ .maxattr = BCM_GENL_ATTR_MAX, }; /* commands: mapping between the command enumeration and the actual function */ struct genl_ops wl_genl_ops = { .cmd = BCM_GENL_CMD_MSG, .flags = 0, .policy = wl_genl_policy, .doit = wl_genl_handle_msg, .dumpit = NULL, }; static struct genl_multicast_group wl_genl_mcast = { .id = GENL_ID_GENERATE, /* Genetlink would generate the ID */ .name = "bcm-genl-mcast", }; #endif /* WL_GENL */ /** * Extern function declarations (TODO: move them to dhd_linux.h) */ void dhd_customer_gpio_wlan_ctrl(int onoff); int dhd_dev_reset(struct net_device *dev, uint8 flag); int dhd_dev_init_ioctl(struct net_device *dev); #ifdef WL_CFG80211 int wl_cfg80211_get_p2p_dev_addr(struct net_device *net, struct ether_addr *p2pdev_addr); int wl_cfg80211_set_btcoex_dhcp(struct net_device *dev, char *command); int wl_cfg80211_get_ioctl_version(void); #else int wl_cfg80211_get_p2p_dev_addr(struct net_device *net, struct ether_addr *p2pdev_addr) { return 0; } int wl_cfg80211_set_p2p_noa(struct net_device *net, char* buf, int len) { return 0; } int wl_cfg80211_get_p2p_noa(struct net_device *net, char* buf, int len) { return 0; } int wl_cfg80211_set_p2p_ps(struct net_device *net, char* buf, int len) { return 0; } #endif /* WL_CFG80211 */ extern int dhd_os_check_if_up(void *dhdp); #ifdef BCMLXSDMMC extern void *bcmsdh_get_drvdata(void); #endif /* BCMLXSDMMC */ #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; /** * Local (static) function definitions */ static int wl_android_get_link_speed(struct net_device *net, char *command, int total_len) { int link_speed; int bytes_written; int error; error = wldev_get_link_speed(net, &link_speed); if (error) return -1; /* Convert Kbps to Android Mbps */ link_speed = link_speed / 1000; bytes_written = snprintf(command, total_len, "LinkSpeed %d", link_speed); ANDROID_INFO(("%s: command result is %s\n", __FUNCTION__, command)); return bytes_written; } static int wl_android_get_rssi(struct net_device *net, char *command, int total_len) { wlc_ssid_t ssid = {0}; int rssi; int bytes_written = 0; int error; error = wldev_get_rssi(net, &rssi); if (error) return -1; #if defined(RSSIOFFSET) rssi = wl_update_rssi_offset(rssi); #endif error = wldev_get_ssid(net, &ssid); if (error) return -1; if ((ssid.SSID_len == 0) || (ssid.SSID_len > DOT11_MAX_SSID_LEN)) { ANDROID_ERROR(("%s: wldev_get_ssid failed\n", __FUNCTION__)); } else { memcpy(command, ssid.SSID, ssid.SSID_len); bytes_written = ssid.SSID_len; } bytes_written += snprintf(&command[bytes_written], total_len, " rssi %d", rssi); ANDROID_INFO(("%s: command result is %s (%d)\n", __FUNCTION__, command, bytes_written)); return bytes_written; } static int wl_android_set_suspendopt(struct net_device *dev, char *command, int total_len) { int suspend_flag; int ret_now; int ret = 0; suspend_flag = *(command + strlen(CMD_SETSUSPENDOPT) + 1) - '0'; if (suspend_flag != 0) suspend_flag = 1; ret_now = net_os_set_suspend_disable(dev, suspend_flag); if (ret_now != suspend_flag) { if (!(ret = net_os_set_suspend(dev, ret_now, 1))) ANDROID_INFO(("%s: Suspend Flag %d -> %d\n", __FUNCTION__, ret_now, suspend_flag)); else ANDROID_ERROR(("%s: failed %d\n", __FUNCTION__, ret)); } return ret; } static int wl_android_set_suspendmode(struct net_device *dev, char *command, int total_len) { int ret = 0; #if !defined(CONFIG_HAS_EARLYSUSPEND) || !defined(DHD_USE_EARLYSUSPEND) int suspend_flag; suspend_flag = *(command + strlen(CMD_SETSUSPENDMODE) + 1) - '0'; if (suspend_flag != 0) suspend_flag = 1; if (!(ret = net_os_set_suspend(dev, suspend_flag, 0))) ANDROID_INFO(("%s: Suspend Mode %d\n", __FUNCTION__, suspend_flag)); else ANDROID_ERROR(("%s: failed %d\n", __FUNCTION__, ret)); #endif return ret; } static int wl_android_get_band(struct net_device *dev, char *command, int total_len) { uint band; int bytes_written; int error; error = wldev_get_band(dev, &band); if (error) return -1; bytes_written = snprintf(command, total_len, "Band %d", band); return bytes_written; } #ifdef PNO_SUPPORT #define PARAM_SIZE 50 #define VALUE_SIZE 50 static int wls_parse_batching_cmd(struct net_device *dev, char *command, int total_len) { int err = BCME_OK; uint i, tokens; char *pos, *pos2, *token, *token2, *delim; char param[PARAM_SIZE], value[VALUE_SIZE]; struct dhd_pno_batch_params batch_params; ANDROID_INFO(("%s: command=%s, len=%d\n", __FUNCTION__, command, total_len)); if (total_len < strlen(CMD_WLS_BATCHING)) { ANDROID_ERROR(("%s argument=%d less min size\n", __FUNCTION__, total_len)); err = BCME_ERROR; goto exit; } pos = command + strlen(CMD_WLS_BATCHING) + 1; memset(&batch_params, 0, sizeof(struct dhd_pno_batch_params)); if (!strncmp(pos, PNO_BATCHING_SET, strlen(PNO_BATCHING_SET))) { pos += strlen(PNO_BATCHING_SET) + 1; while ((token = strsep(&pos, PNO_PARAMS_DELIMETER)) != NULL) { memset(param, 0, sizeof(param)); memset(value, 0, sizeof(value)); if (token == NULL || !*token) break; if (*token == '\0') continue; delim = strchr(token, PNO_PARAM_VALUE_DELLIMETER); if (delim != NULL) *delim = ' '; tokens = sscanf(token, "%s %s", param, value); if (!strncmp(param, PNO_PARAM_SCANFREQ, strlen(PNO_PARAM_MSCAN))) { batch_params.scan_fr = simple_strtol(value, NULL, 0); ANDROID_INFO(("scan_freq : %d\n", batch_params.scan_fr)); } else if (!strncmp(param, PNO_PARAM_BESTN, strlen(PNO_PARAM_MSCAN))) { batch_params.bestn = simple_strtol(value, NULL, 0); ANDROID_INFO(("bestn : %d\n", batch_params.bestn)); } else if (!strncmp(param, PNO_PARAM_MSCAN, strlen(PNO_PARAM_MSCAN))) { batch_params.mscan = simple_strtol(value, NULL, 0); ANDROID_INFO(("mscan : %d\n", batch_params.mscan)); } else if (!strncmp(param, PNO_PARAM_CHANNEL, strlen(PNO_PARAM_MSCAN))) { i = 0; pos2 = value; tokens = sscanf(value, "<%s>", value); if (tokens != 1) { err = BCME_ERROR; ANDROID_ERROR(("%s : invalid format for channel" " <> params\n", __FUNCTION__)); goto exit; } while ((token2 = strsep(&pos2, PNO_PARAM_CHANNEL_DELIMETER)) != NULL) { if (token2 == NULL || !*token2) break; if (*token2 == '\0') continue; if (*token2 == 'A' || *token2 == 'B') { batch_params.band = (*token2 == 'A')? WLC_BAND_5G : WLC_BAND_2G; ANDROID_INFO(("band : %s\n", (*token2 == 'A')? "A" : "B")); } else { batch_params.chan_list[i++] = simple_strtol(token2, NULL, 0); batch_params.nchan++; ANDROID_INFO(("channel :%d\n", batch_params.chan_list[i-1])); } } } else if (!strncmp(param, PNO_PARAM_RTT, strlen(PNO_PARAM_MSCAN))) { batch_params.rtt = simple_strtol(value, NULL, 0); ANDROID_INFO(("rtt : %d\n", batch_params.rtt)); } else { ANDROID_ERROR(("%s : unknown param: %s\n", __FUNCTION__, param)); err = BCME_ERROR; goto exit; } } err = dhd_dev_pno_set_for_batch(dev, &batch_params); if (err < 0) { ANDROID_ERROR(("failed to configure batch scan\n")); } } else if (!strncmp(pos, PNO_BATCHING_GET, strlen(PNO_BATCHING_GET))) { err = dhd_dev_pno_get_for_batch(dev, command, total_len); if (err < 0) { ANDROID_ERROR(("failed to getting batching results\n")); } else { err = strlen(command); } } else if (!strncmp(pos, PNO_BATCHING_STOP, strlen(PNO_BATCHING_STOP))) { err = dhd_dev_pno_stop_for_batch(dev); if (err < 0) { ANDROID_ERROR(("failed to stop batching scan\n")); } } else { ANDROID_ERROR(("%s : unknown command\n", __FUNCTION__)); err = BCME_ERROR; goto exit; } exit: return err; } #ifndef WL_SCHED_SCAN static int wl_android_set_pno_setup(struct net_device *dev, char *command, int total_len) { wlc_ssid_t ssids_local[MAX_PFN_LIST_COUNT]; int res = -1; int nssid = 0; cmd_tlv_t *cmd_tlv_temp; char *str_ptr; int tlv_size_left; int pno_time = 0; int pno_repeat = 0; int pno_freq_expo_max = 0; #ifdef PNO_SET_DEBUG int i; char pno_in_example[] = { 'P', 'N', 'O', 'S', 'E', 'T', 'U', 'P', ' ', 'S', '1', '2', '0', 'S', 0x05, 'd', 'l', 'i', 'n', 'k', 'S', 0x04, 'G', 'O', 'O', 'G', 'T', '0', 'B', 'R', '2', 'M', '2', 0x00 }; #endif /* PNO_SET_DEBUG */ ANDROID_INFO(("%s: command=%s, len=%d\n", __FUNCTION__, command, total_len)); if (total_len < (strlen(CMD_PNOSETUP_SET) + sizeof(cmd_tlv_t))) { ANDROID_ERROR(("%s argument=%d less min size\n", __FUNCTION__, total_len)); goto exit_proc; } #ifdef PNO_SET_DEBUG memcpy(command, pno_in_example, sizeof(pno_in_example)); total_len = sizeof(pno_in_example); #endif str_ptr = command + strlen(CMD_PNOSETUP_SET); tlv_size_left = total_len - strlen(CMD_PNOSETUP_SET); cmd_tlv_temp = (cmd_tlv_t *)str_ptr; memset(ssids_local, 0, sizeof(ssids_local)); if ((cmd_tlv_temp->prefix == PNO_TLV_PREFIX) && (cmd_tlv_temp->version == PNO_TLV_VERSION) && (cmd_tlv_temp->subtype == PNO_TLV_SUBTYPE_LEGACY_PNO)) { str_ptr += sizeof(cmd_tlv_t); tlv_size_left -= sizeof(cmd_tlv_t); if ((nssid = wl_iw_parse_ssid_list_tlv(&str_ptr, ssids_local, MAX_PFN_LIST_COUNT, &tlv_size_left)) <= 0) { ANDROID_ERROR(("SSID is not presented or corrupted ret=%d\n", nssid)); goto exit_proc; } else { if ((str_ptr[0] != PNO_TLV_TYPE_TIME) || (tlv_size_left <= 1)) { ANDROID_ERROR(("%s scan duration corrupted field size %d\n", __FUNCTION__, tlv_size_left)); goto exit_proc; } str_ptr++; pno_time = simple_strtoul(str_ptr, &str_ptr, 16); ANDROID_INFO(("%s: pno_time=%d\n", __FUNCTION__, pno_time)); if (str_ptr[0] != 0) { if ((str_ptr[0] != PNO_TLV_FREQ_REPEAT)) { ANDROID_ERROR(("%s pno repeat : corrupted field\n", __FUNCTION__)); goto exit_proc; } str_ptr++; pno_repeat = simple_strtoul(str_ptr, &str_ptr, 16); ANDROID_INFO(("%s :got pno_repeat=%d\n", __FUNCTION__, pno_repeat)); if (str_ptr[0] != PNO_TLV_FREQ_EXPO_MAX) { ANDROID_ERROR(("%s FREQ_EXPO_MAX corrupted field size\n", __FUNCTION__)); goto exit_proc; } str_ptr++; pno_freq_expo_max = simple_strtoul(str_ptr, &str_ptr, 16); ANDROID_INFO(("%s: pno_freq_expo_max=%d\n", __FUNCTION__, pno_freq_expo_max)); } } } else { ANDROID_ERROR(("%s get wrong TLV command\n", __FUNCTION__)); goto exit_proc; } res = dhd_dev_pno_set_for_ssid(dev, ssids_local, nssid, pno_time, pno_repeat, pno_freq_expo_max, NULL, 0); exit_proc: return res; } #endif /* !WL_SCHED_SCAN */ #endif /* PNO_SUPPORT */ static int wl_android_get_p2p_dev_addr(struct net_device *ndev, char *command, int total_len) { int ret; int bytes_written = 0; ret = wl_cfg80211_get_p2p_dev_addr(ndev, (struct ether_addr*)command); if (ret) return 0; bytes_written = sizeof(struct ether_addr); return bytes_written; } #ifdef BCMCCX static int wl_android_get_cckm_rn(struct net_device *dev, char *command) { int error, rn; ANDROID_TRACE(("%s:wl_android_get_cckm_rn\n", dev->name)); error = wldev_iovar_getint(dev, "cckm_rn", &rn); if (unlikely(error)) { ANDROID_ERROR(("wl_android_get_cckm_rn error (%d)\n", error)); return -1; } memcpy(command, &rn, sizeof(int)); return sizeof(int); } static int wl_android_set_cckm_krk(struct net_device *dev, char *command) { int error; unsigned char key[16]; static char iovar_buf[WLC_IOCTL_MEDLEN]; ANDROID_TRACE(("%s: wl_iw_set_cckm_krk\n", dev->name)); memset(iovar_buf, 0, sizeof(iovar_buf)); memcpy(key, command+strlen("set cckm_krk")+1, 16); error = wldev_iovar_setbuf(dev, "cckm_krk", key, sizeof(key), iovar_buf, WLC_IOCTL_MEDLEN, NULL); if (unlikely(error)) { ANDROID_ERROR((" cckm_krk set error (%d)\n", error)); return -1; } return 0; } static int wl_android_get_assoc_res_ies(struct net_device *dev, char *command) { int error; u8 buf[WL_ASSOC_INFO_MAX]; wl_assoc_info_t assoc_info; u32 resp_ies_len = 0; int bytes_written = 0; ANDROID_TRACE(("%s: wl_iw_get_assoc_res_ies\n", dev->name)); error = wldev_iovar_getbuf(dev, "assoc_info", NULL, 0, buf, WL_ASSOC_INFO_MAX, NULL); if (unlikely(error)) { ANDROID_ERROR(("could not get assoc info (%d)\n", error)); return -1; } memcpy(&assoc_info, buf, sizeof(wl_assoc_info_t)); assoc_info.req_len = htod32(assoc_info.req_len); assoc_info.resp_len = htod32(assoc_info.resp_len); assoc_info.flags = htod32(assoc_info.flags); if (assoc_info.resp_len) { resp_ies_len = assoc_info.resp_len - sizeof(struct dot11_assoc_resp); } /* first 4 bytes are ie len */ memcpy(command, &resp_ies_len, sizeof(u32)); bytes_written = sizeof(u32); /* get the association resp IE's if there are any */ if (resp_ies_len) { error = wldev_iovar_getbuf(dev, "assoc_resp_ies", NULL, 0, buf, WL_ASSOC_INFO_MAX, NULL); if (unlikely(error)) { ANDROID_ERROR(("could not get assoc resp_ies (%d)\n", error)); return -1; } memcpy(command+sizeof(u32), buf, resp_ies_len); bytes_written += resp_ies_len; } return bytes_written; } #endif /* BCMCCX */ static 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; } /* * HAPD_MAC_FILTER mac_mode mac_cnt mac_addr1 mac_addr2 * */ static int wl_android_set_mac_address_filter(struct net_device *dev, const char* str) { int i; int ret = 0; int macnum = 0; int macmode = MACLIST_MODE_DISABLED; struct maclist *list; char eabuf[ETHER_ADDR_STR_LEN]; /* string should look like below (macmode/macnum/maclist) */ /* 1 2 00:11:22:33:44:55 00:11:22:33:44:ff */ /* get the MAC filter mode */ macmode = bcm_atoi(strsep((char**)&str, " ")); if (macmode < MACLIST_MODE_DISABLED || macmode > MACLIST_MODE_ALLOW) { ANDROID_ERROR(("%s : invalid macmode %d\n", __FUNCTION__, macmode)); return -1; } macnum = bcm_atoi(strsep((char**)&str, " ")); if (macnum < 0 || macnum > MAX_NUM_MAC_FILT) { ANDROID_ERROR(("%s : invalid number of MAC address entries %d\n", __FUNCTION__, macnum)); return -1; } /* allocate memory for the MAC list */ list = (struct maclist*)kmalloc(sizeof(int) + sizeof(struct ether_addr) * macnum, GFP_KERNEL); if (!list) { ANDROID_ERROR(("%s : failed to allocate memory\n", __FUNCTION__)); return -1; } /* prepare the MAC list */ list->count = htod32(macnum); bzero((char *)eabuf, ETHER_ADDR_STR_LEN); for (i = 0; i < list->count; i++) { strncpy(eabuf, strsep((char**)&str, " "), ETHER_ADDR_STR_LEN - 1); if (!(ret = bcm_ether_atoe(eabuf, &list->ea[i]))) { ANDROID_ERROR(("%s : mac parsing err index=%d, addr=%s\n", __FUNCTION__, i, eabuf)); list->count--; break; } ANDROID_INFO(("%s : %d/%d MACADDR=%s", __FUNCTION__, i, list->count, eabuf)); } /* set the list */ if ((ret = wl_android_set_ap_mac_list(dev, macmode, list)) != 0) ANDROID_ERROR(("%s : Setting MAC list failed error=%d\n", __FUNCTION__, ret)); kfree(list); return 0; } /** * 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; if (!dev) { ANDROID_ERROR(("%s: dev is null\n", __FUNCTION__)); return -EINVAL; } printk("%s in 1\n", __FUNCTION__); dhd_net_if_lock(dev); printk("%s in 2: g_wifi_on=%d\n", __FUNCTION__, g_wifi_on); if (!g_wifi_on) { #ifdef POWER_OFF_IN_SUSPEND g_netdev = dev; #endif do { dhd_customer_gpio_wlan_ctrl(WLAN_RESET_ON); ret = sdioh_start(NULL, 0); if (ret == 0) break; ANDROID_ERROR(("\nfailed to power up wifi chip, retry again (%d left) **\n\n", retry+1)); dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); } while (retry-- >= 0); if (ret != 0) { ANDROID_ERROR(("\nfailed to power up wifi chip, max retry reached **\n\n")); goto exit; } ret = dhd_dev_reset(dev, FALSE); if (ret) goto err; sdioh_start(NULL, 1); if (!ret) { if (dhd_dev_init_ioctl(dev) < 0) { ret = -EFAULT; goto err; } } g_wifi_on = TRUE; } exit: printk("%s: Success\n", __FUNCTION__); dhd_net_if_unlock(dev); return ret; err: dhd_dev_reset(dev, TRUE); sdioh_stop(NULL); dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); printk("%s: Failed\n", __FUNCTION__); dhd_net_if_unlock(dev); return ret; } int wl_android_wifi_off(struct net_device *dev) { int ret = 0; if (!dev) { ANDROID_ERROR(("%s: dev is null\n", __FUNCTION__)); return -EINVAL; } printk("%s in 1\n", __FUNCTION__); dhd_net_if_lock(dev); printk("%s in 2: g_wifi_on=%d\n", __FUNCTION__, g_wifi_on); if (g_wifi_on) { ret = dhd_dev_reset(dev, TRUE); sdioh_stop(NULL); dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); g_wifi_on = FALSE; } printk("%s out\n", __FUNCTION__); dhd_net_if_unlock(dev); return ret; } static int wl_android_set_fwpath(struct net_device *net, char *command, int total_len) { if ((strlen(command) - strlen(CMD_SETFWPATH)) > MOD_PARAM_PATHLEN) return -1; bcm_strncpy_s(fw_path, sizeof(fw_path), command + strlen(CMD_SETFWPATH) + 1, MOD_PARAM_PATHLEN - 1); if (strstr(fw_path, "apsta") != NULL) { ANDROID_INFO(("GOT APSTA FIRMWARE\n")); ap_fw_loaded = TRUE; } else { ANDROID_INFO(("GOT STA FIRMWARE\n")); ap_fw_loaded = FALSE; } return 0; } static int wl_android_set_pmk(struct net_device *dev, char *command, int total_len) { uchar pmk[33]; int error = 0; char smbuf[WLC_IOCTL_SMLEN]; #ifdef OKC_DEBUG int i = 0; #endif bzero(pmk, sizeof(pmk)); memcpy((char *)pmk, command + strlen("SET_PMK "), 32); error = wldev_iovar_setbuf(dev, "okc_info_pmk", pmk, 32, smbuf, sizeof(smbuf), NULL); if (error) { ANDROID_ERROR(("Failed to set PMK for OKC, error = %d\n", error)); } #ifdef OKC_DEBUG ANDROID_ERROR(("PMK is ")); for (i = 0; i < 32; i++) ANDROID_ERROR(("%02X ", pmk[i])); ANDROID_ERROR(("\n")); #endif return error; } static int wl_android_okc_enable(struct net_device *dev, char *command, int total_len) { int error = 0; char okc_enable = 0; okc_enable = command[strlen(CMD_OKC_ENABLE) + 1] - '0'; error = wldev_iovar_setint(dev, "okc_enable", okc_enable); if (error) { ANDROID_ERROR(("Failed to %s OKC, error = %d\n", okc_enable ? "enable" : "disable", error)); } wldev_iovar_setint(dev, "ccx_enable", 0); return error; } int wl_android_set_roam_mode(struct net_device *dev, char *command, int total_len) { int error = 0; int mode = 0; if (sscanf(command, "%*s %d", &mode) != 1) { ANDROID_ERROR(("%s: Failed to get Parameter\n", __FUNCTION__)); return -1; } error = wldev_iovar_setint(dev, "roam_off", mode); if (error) { ANDROID_ERROR(("%s: Failed to set roaming Mode %d, error = %d\n", __FUNCTION__, mode, error)); return -1; } else ANDROID_ERROR(("%s: succeeded to set roaming Mode %d, error = %d\n", __FUNCTION__, mode, error)); return 0; } int wl_android_set_ibss_beacon_ouidata(struct net_device *dev, char *command, int total_len) { char ie_buf[VNDR_IE_MAX_LEN]; char *ioctl_buf = NULL; char hex[] = "XX"; char *pcmd = NULL; int ielen = 0, datalen = 0, idx = 0, tot_len = 0; vndr_ie_setbuf_t *vndr_ie = NULL; s32 iecount; uint32 pktflag; u16 kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; s32 err = BCME_OK; /* Check the VSIE (Vendor Specific IE) which was added. * If exist then send IOVAR to delete it */ if (wl_cfg80211_ibss_vsie_delete(dev) != BCME_OK) { return -EINVAL; } pcmd = command + strlen(CMD_SETIBSSBEACONOUIDATA) + 1; for (idx = 0; idx < DOT11_OUI_LEN; idx++) { hex[0] = *pcmd++; hex[1] = *pcmd++; ie_buf[idx] = (uint8)simple_strtoul(hex, NULL, 16); } pcmd++; while ((*pcmd != '\0') && (idx < VNDR_IE_MAX_LEN)) { hex[0] = *pcmd++; hex[1] = *pcmd++; ie_buf[idx++] = (uint8)simple_strtoul(hex, NULL, 16); datalen++; } tot_len = sizeof(vndr_ie_setbuf_t) + (datalen - 1); vndr_ie = (vndr_ie_setbuf_t *) kzalloc(tot_len, kflags); if (!vndr_ie) { ANDROID_ERROR(("IE memory alloc failed\n")); return -ENOMEM; } /* Copy the vndr_ie SET command ("add"/"del") to the buffer */ strncpy(vndr_ie->cmd, "add", VNDR_IE_CMD_LEN - 1); vndr_ie->cmd[VNDR_IE_CMD_LEN - 1] = '\0'; /* Set the IE count - the buffer contains only 1 IE */ iecount = htod32(1); memcpy((void *)&vndr_ie->vndr_ie_buffer.iecount, &iecount, sizeof(s32)); /* Set packet flag to indicate that BEACON's will contain this IE */ pktflag = htod32(VNDR_IE_BEACON_FLAG | VNDR_IE_PRBRSP_FLAG); memcpy((void *)&vndr_ie->vndr_ie_buffer.vndr_ie_list[0].pktflag, &pktflag, sizeof(u32)); /* Set the IE ID */ vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.id = (uchar) DOT11_MNG_PROPR_ID; memcpy(&vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.oui, &ie_buf, DOT11_OUI_LEN); memcpy(&vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.data, &ie_buf[DOT11_OUI_LEN], datalen); ielen = DOT11_OUI_LEN + datalen; vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.len = (uchar) ielen; ioctl_buf = kmalloc(WLC_IOCTL_MEDLEN, GFP_KERNEL); if (!ioctl_buf) { ANDROID_ERROR(("ioctl memory alloc failed\n")); if (vndr_ie) { kfree(vndr_ie); } return -ENOMEM; } memset(ioctl_buf, 0, WLC_IOCTL_MEDLEN); /* init the buffer */ err = wldev_iovar_setbuf(dev, "ie", vndr_ie, tot_len, ioctl_buf, WLC_IOCTL_MEDLEN, NULL); if (err != BCME_OK) { err = -EINVAL; if (vndr_ie) { kfree(vndr_ie); } } else { /* do NOT free 'vndr_ie' for the next process */ wl_cfg80211_ibss_vsie_set_buffer(vndr_ie, tot_len); } if (ioctl_buf) { kfree(ioctl_buf); } return err; } static int wl_android_iolist_add(struct net_device *dev, struct list_head *head, struct io_cfg *config) { struct io_cfg *resume_cfg; s32 ret; resume_cfg = kzalloc(sizeof(struct io_cfg), GFP_KERNEL); if (!resume_cfg) return -ENOMEM; if (config->iovar) { ret = wldev_iovar_getint(dev, config->iovar, &resume_cfg->param); if (ret) { ANDROID_ERROR(("%s: Failed to get current %s value\n", __FUNCTION__, config->iovar)); goto error; } ret = wldev_iovar_setint(dev, config->iovar, config->param); if (ret) { ANDROID_ERROR(("%s: Failed to set %s to %d\n", __FUNCTION__, config->iovar, config->param)); goto error; } resume_cfg->iovar = config->iovar; } else { resume_cfg->arg = kzalloc(config->len, GFP_KERNEL); if (!resume_cfg->arg) { ret = -ENOMEM; goto error; } ret = wldev_ioctl(dev, config->ioctl, resume_cfg->arg, config->len, false); if (ret) { ANDROID_ERROR(("%s: Failed to get ioctl %d\n", __FUNCTION__, config->ioctl)); goto error; } ret = wldev_ioctl(dev, config->ioctl + 1, config->arg, config->len, true); if (ret) { ANDROID_ERROR(("%s: Failed to set %s to %d\n", __FUNCTION__, config->iovar, config->param)); goto error; } if (config->ioctl + 1 == WLC_SET_PM) wl_cfg80211_update_power_mode(dev); resume_cfg->ioctl = config->ioctl; resume_cfg->len = config->len; } list_add(&resume_cfg->list, head); return 0; error: kfree(resume_cfg->arg); kfree(resume_cfg); return ret; } static void wl_android_iolist_resume(struct net_device *dev, struct list_head *head) { struct io_cfg *config; struct list_head *cur, *q; s32 ret = 0; list_for_each_safe(cur, q, head) { config = list_entry(cur, struct io_cfg, list); if (config->iovar) { if (!ret) ret = wldev_iovar_setint(dev, config->iovar, config->param); } else { if (!ret) ret = wldev_ioctl(dev, config->ioctl + 1, config->arg, config->len, true); if (config->ioctl + 1 == WLC_SET_PM) wl_cfg80211_update_power_mode(dev); kfree(config->arg); } list_del(cur); kfree(config); } } static int wl_android_set_miracast(struct net_device *dev, char *command, int total_len) { int mode, val; int ret = 0; struct io_cfg config; if (sscanf(command, "%*s %d", &mode) != 1) { ANDROID_ERROR(("%s: Failed to get Parameter\n", __FUNCTION__)); return -1; } ANDROID_INFO(("%s: enter miracast mode %d\n", __FUNCTION__, mode)); if (miracast_cur_mode == mode) return 0; wl_android_iolist_resume(dev, &miracast_resume_list); miracast_cur_mode = MIRACAST_MODE_OFF; switch (mode) { case MIRACAST_MODE_SOURCE: /* setting mchan_algo to platform specific value */ config.iovar = "mchan_algo"; config.param = MIRACAST_MCHAN_ALGO; ret = wl_android_iolist_add(dev, &miracast_resume_list, &config); if (ret) goto resume; /* setting mchan_bw to platform specific value */ config.iovar = "mchan_bw"; config.param = MIRACAST_MCHAN_BW; ret = wl_android_iolist_add(dev, &miracast_resume_list, &config); if (ret) goto resume; /* setting apmdu to platform specific value */ config.iovar = "ampdu_mpdu"; config.param = MIRACAST_AMPDU_SIZE; ret = wl_android_iolist_add(dev, &miracast_resume_list, &config); if (ret) goto resume; /* FALLTROUGH */ /* Source mode shares most configurations with sink mode. * Fall through here to avoid code duplication */ case MIRACAST_MODE_SINK: /* disable internal roaming */ config.iovar = "roam_off"; config.param = 1; ret = wl_android_iolist_add(dev, &miracast_resume_list, &config); if (ret) goto resume; /* tunr off pm */ val = 0; config.iovar = NULL; config.ioctl = WLC_GET_PM; config.arg = &val; config.len = sizeof(int); ret = wl_android_iolist_add(dev, &miracast_resume_list, &config); if (ret) goto resume; break; case MIRACAST_MODE_OFF: default: break; } miracast_cur_mode = mode; return 0; resume: ANDROID_ERROR(("%s: turnoff miracast mode because of err%d\n", __FUNCTION__, ret)); wl_android_iolist_resume(dev, &miracast_resume_list); return ret; } int wl_android_get_channel( struct net_device *dev, char* command, int total_len) { int ret; channel_info_t ci; int bytes_written = 0; if (!(ret = wldev_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(channel_info_t), FALSE))) { ANDROID_TRACE(("hw_channel %d\n", ci.hw_channel)); ANDROID_TRACE(("target_channel %d\n", ci.target_channel)); ANDROID_TRACE(("scan_channel %d\n", ci.scan_channel)); bytes_written = snprintf(command, sizeof(channel_info_t)+2, "channel %d", ci.hw_channel); ANDROID_TRACE(("%s: command result is %s\n", __FUNCTION__, command)); } return bytes_written; } int wl_android_set_roam_trigger( struct net_device *dev, char* command, int total_len) { int ret = 0; int roam_trigger[2]; sscanf(command, "%*s %10d", &roam_trigger[0]); roam_trigger[1] = WLC_BAND_ALL; ret = wldev_ioctl(dev, WLC_SET_ROAM_TRIGGER, roam_trigger, sizeof(roam_trigger), 1); if (ret) ANDROID_ERROR(("WLC_SET_ROAM_TRIGGER ERROR %d ret=%d\n", roam_trigger[0], ret)); return ret; } int wl_android_get_roam_trigger( struct net_device *dev, char *command, int total_len) { int ret; int bytes_written; int roam_trigger[2] = {0, 0}; int trigger[2]= {0, 0}; roam_trigger[1] = WLC_BAND_2G; ret = wldev_ioctl(dev, WLC_GET_ROAM_TRIGGER, roam_trigger, sizeof(roam_trigger), 0); if (!ret) trigger[0] = roam_trigger[0]; else ANDROID_ERROR(("2G WLC_GET_ROAM_TRIGGER ERROR %d ret=%d\n", roam_trigger[0], ret)); roam_trigger[1] = WLC_BAND_5G; ret = wldev_ioctl(dev, WLC_GET_ROAM_TRIGGER, roam_trigger, sizeof(roam_trigger), 0); if (!ret) trigger[1] = roam_trigger[0]; else ANDROID_ERROR(("5G WLC_GET_ROAM_TRIGGER ERROR %d ret=%d\n", roam_trigger[0], ret)); ANDROID_TRACE(("roam_trigger %d %d\n", trigger[0], trigger[1])); bytes_written = snprintf(command, total_len, "%d %d", trigger[0], trigger[1]); return bytes_written; } s32 wl_android_set_keep_alive(struct net_device *dev, char* command, int total_len) { char *buf; const char *str; wl_mkeep_alive_pkt_t mkeep_alive_pkt; wl_mkeep_alive_pkt_t *mkeep_alive_pktp; int buf_len; int str_len; int res = -1; uint period_msec = 0; ANDROID_TRACE(("%s: command = %s\n", __FUNCTION__, command)); buf = kmalloc(WLC_IOCTL_MAXLEN, GFP_KERNEL); sscanf(command, "%d", &period_msec); memset(&mkeep_alive_pkt, 0, sizeof(wl_mkeep_alive_pkt_t)); str = "mkeep_alive"; str_len = strlen(str); strncpy(buf, str, str_len); buf[str_len] = '\0'; mkeep_alive_pktp = (wl_mkeep_alive_pkt_t *) (buf + str_len + 1); mkeep_alive_pkt.period_msec = period_msec; buf_len = str_len + 1; mkeep_alive_pkt.version = htod16(WL_MKEEP_ALIVE_VERSION); mkeep_alive_pkt.length = htod16(WL_MKEEP_ALIVE_FIXED_LEN); mkeep_alive_pkt.keep_alive_id = 0; mkeep_alive_pkt.len_bytes = 0; buf_len += WL_MKEEP_ALIVE_FIXED_LEN; /* Keep-alive attributes are set in local variable (mkeep_alive_pkt), and * then memcpy'ed into buffer (mkeep_alive_pktp) since there is no * guarantee that the buffer is properly aligned. */ memcpy((char *)mkeep_alive_pktp, &mkeep_alive_pkt, WL_MKEEP_ALIVE_FIXED_LEN); if ((res = wldev_ioctl(dev, WLC_SET_VAR, buf, buf_len, TRUE)) != 0) ANDROID_ERROR(("%s: mkeep_alive set failed. res[%d]\n", __FUNCTION__, res)); kfree(buf); return res; } s32 wl_android_get_keep_alive(struct net_device *dev, char *command, int total_len) { wl_mkeep_alive_pkt_t *mkeep_alive_pktp; int bytes_written = -1; int res = -1, len, i = 0; char* str = "mkeep_alive"; ANDROID_TRACE(("%s: command = %s\n", __FUNCTION__, command)); len = WLC_IOCTL_MEDLEN; mkeep_alive_pktp = kmalloc(len, GFP_KERNEL); memset(mkeep_alive_pktp, 0, len); strcpy((char*)mkeep_alive_pktp, str); if ((res = wldev_ioctl(dev, WLC_GET_VAR, mkeep_alive_pktp, len, FALSE))<0) { ANDROID_ERROR(("%s: GET mkeep_alive ERROR %d\n", __FUNCTION__, res)); goto exit; } else { printf("Id :%d\n" "Period (msec) :%d\n" "Length :%d\n" "Packet :0x", mkeep_alive_pktp->keep_alive_id, dtoh32(mkeep_alive_pktp->period_msec), dtoh16(mkeep_alive_pktp->len_bytes)); for (i=0; ilen_bytes; i++) { printf("%02x", mkeep_alive_pktp->data[i]); } printf("\n"); } bytes_written = snprintf(command, total_len, "mkeep_alive_period_msec %d ", dtoh32(mkeep_alive_pktp->period_msec)); bytes_written += snprintf(command+bytes_written, total_len, "0x"); for (i=0; ilen_bytes; i++) { bytes_written += snprintf(command+bytes_written, total_len, "%x", mkeep_alive_pktp->data[i]); } ANDROID_TRACE(("%s: command result is %s\n", __FUNCTION__, command)); exit: kfree(mkeep_alive_pktp); return bytes_written; } int wl_android_set_pm(struct net_device *dev,char *command, int total_len) { int pm, ret = -1; ANDROID_TRACE(("%s: cmd %s\n", __FUNCTION__, command)); sscanf(command, "%*s %d", &pm); ret = wldev_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm), FALSE); if (ret) ANDROID_ERROR(("WLC_SET_PM ERROR %d ret=%d\n", pm, ret)); return ret; } int wl_android_get_pm(struct net_device *dev,char *command, int total_len) { int ret = 0; int pm_local; char *pm; int bytes_written=-1; ret = wldev_ioctl(dev, WLC_GET_PM, &pm_local, sizeof(pm_local),FALSE); if (!ret) { ANDROID_TRACE(("%s: PM = %d\n", __func__, pm_local)); if (pm_local == PM_OFF) pm = "PM_OFF"; else if(pm_local == PM_MAX) pm = "PM_MAX"; else if(pm_local == PM_FAST) pm = "PM_FAST"; else { pm_local = 0; pm = "Invalid"; } bytes_written = snprintf(command, total_len, "PM %s", pm); ANDROID_TRACE(("%s: command result is %s\n", __FUNCTION__, command)); } return bytes_written; } static int wl_android_set_monitor(struct net_device *dev, char *command, int total_len) { int val; int ret = 0; int bytes_written; sscanf(command, "%*s %d", &val); bytes_written = wldev_ioctl(dev, WLC_SET_MONITOR, &val, sizeof(int), 1); if (bytes_written) ANDROID_ERROR(("WLC_SET_MONITOR ERROR %d ret=%d\n", val, ret)); return bytes_written; } int wl_android_priv_cmd(struct net_device *net, struct ifreq *ifr, int cmd) { #define PRIVATE_COMMAND_MAX_LEN 8192 int ret = 0; char *command = NULL; int bytes_written = 0; android_wifi_priv_cmd priv_cmd; net_os_wake_lock(net); if (!ifr->ifr_data) { ret = -EINVAL; goto exit; } if (copy_from_user(&priv_cmd, ifr->ifr_data, sizeof(android_wifi_priv_cmd))) { ret = -EFAULT; goto exit; } if (priv_cmd.total_len > PRIVATE_COMMAND_MAX_LEN) { ANDROID_ERROR(("%s: too long priavte command\n", __FUNCTION__)); ret = -EINVAL; goto exit; } command = kmalloc((priv_cmd.total_len + 1), GFP_KERNEL); if (!command) { ANDROID_ERROR(("%s: failed to allocate memory\n", __FUNCTION__)); ret = -ENOMEM; goto exit; } if (copy_from_user(command, priv_cmd.buf, priv_cmd.total_len)) { ret = -EFAULT; goto exit; } command[priv_cmd.total_len] = '\0'; ANDROID_INFO(("%s: Android private cmd \"%s\" on %s\n", __FUNCTION__, command, ifr->ifr_name)); if (strnicmp(command, CMD_START, strlen(CMD_START)) == 0) { ANDROID_INFO(("%s, Received regular START command\n", __FUNCTION__)); bytes_written = wl_android_wifi_on(net); } else if (strnicmp(command, CMD_SETFWPATH, strlen(CMD_SETFWPATH)) == 0) { bytes_written = wl_android_set_fwpath(net, command, priv_cmd.total_len); } if (!g_wifi_on) { ANDROID_ERROR(("%s: Ignore private cmd \"%s\" - iface %s is down\n", __FUNCTION__, command, ifr->ifr_name)); ret = 0; goto exit; } if (strnicmp(command, CMD_STOP, strlen(CMD_STOP)) == 0) { bytes_written = wl_android_wifi_off(net); } else if (strnicmp(command, CMD_SCAN_ACTIVE, strlen(CMD_SCAN_ACTIVE)) == 0) { /* TBD: SCAN-ACTIVE */ } else if (strnicmp(command, CMD_SCAN_PASSIVE, strlen(CMD_SCAN_PASSIVE)) == 0) { /* TBD: SCAN-PASSIVE */ } else if (strnicmp(command, CMD_RSSI, strlen(CMD_RSSI)) == 0) { bytes_written = wl_android_get_rssi(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_LINKSPEED, strlen(CMD_LINKSPEED)) == 0) { bytes_written = wl_android_get_link_speed(net, command, priv_cmd.total_len); } #ifdef PKT_FILTER_SUPPORT else if (strnicmp(command, CMD_RXFILTER_START, strlen(CMD_RXFILTER_START)) == 0) { bytes_written = net_os_enable_packet_filter(net, 1); } else if (strnicmp(command, CMD_RXFILTER_STOP, strlen(CMD_RXFILTER_STOP)) == 0) { bytes_written = net_os_enable_packet_filter(net, 0); } else if (strnicmp(command, CMD_RXFILTER_ADD, strlen(CMD_RXFILTER_ADD)) == 0) { int filter_num = *(command + strlen(CMD_RXFILTER_ADD) + 1) - '0'; bytes_written = net_os_rxfilter_add_remove(net, TRUE, filter_num); } else if (strnicmp(command, CMD_RXFILTER_REMOVE, strlen(CMD_RXFILTER_REMOVE)) == 0) { int filter_num = *(command + strlen(CMD_RXFILTER_REMOVE) + 1) - '0'; bytes_written = net_os_rxfilter_add_remove(net, FALSE, filter_num); } #endif /* PKT_FILTER_SUPPORT */ else if (strnicmp(command, CMD_BTCOEXSCAN_START, strlen(CMD_BTCOEXSCAN_START)) == 0) { /* TBD: BTCOEXSCAN-START */ } else if (strnicmp(command, CMD_BTCOEXSCAN_STOP, strlen(CMD_BTCOEXSCAN_STOP)) == 0) { /* TBD: BTCOEXSCAN-STOP */ } else if (strnicmp(command, CMD_BTCOEXMODE, strlen(CMD_BTCOEXMODE)) == 0) { #ifdef WL_CFG80211 bytes_written = wl_cfg80211_set_btcoex_dhcp(net, command); #else #ifdef PKT_FILTER_SUPPORT uint mode = *(command + strlen(CMD_BTCOEXMODE) + 1) - '0'; if (mode == 1) net_os_enable_packet_filter(net, 0); /* DHCP starts */ else net_os_enable_packet_filter(net, 1); /* DHCP ends */ #endif /* PKT_FILTER_SUPPORT */ #endif /* WL_CFG80211 */ } else if (strnicmp(command, CMD_SETSUSPENDOPT, strlen(CMD_SETSUSPENDOPT)) == 0) { bytes_written = wl_android_set_suspendopt(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_SETSUSPENDMODE, strlen(CMD_SETSUSPENDMODE)) == 0) { bytes_written = wl_android_set_suspendmode(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_SETBAND, strlen(CMD_SETBAND)) == 0) { uint band = *(command + strlen(CMD_SETBAND) + 1) - '0'; if (dhd_conf_get_band(bcmsdh_get_drvdata()) != WLC_BAND_AUTO) { printf("%s: Band is fixed in config.txt\n", __FUNCTION__); goto exit; } #ifdef WL_HOST_BAND_MGMT s32 ret = 0; if ((ret = wl_cfg80211_set_band(net, band)) < 0) { if (ret == BCME_UNSUPPORTED) { /* If roam_var is unsupported, fallback to the original method */ ANDROID_ERROR(("WL_HOST_BAND_MGMT defined, " "but roam_band iovar unsupported in the firmware\n")); } else { bytes_written = -1; goto exit; } } if ((band == WLC_BAND_AUTO) || (ret == BCME_UNSUPPORTED)) bytes_written = wldev_set_band(net, band); #else bytes_written = wldev_set_band(net, band); #endif /* WL_HOST_BAND_MGMT */ } else if (strnicmp(command, CMD_GETBAND, strlen(CMD_GETBAND)) == 0) { bytes_written = wl_android_get_band(net, command, priv_cmd.total_len); } #ifdef WL_CFG80211 /* CUSTOMER_SET_COUNTRY feature is define for only GGSM model */ else if (strnicmp(command, CMD_COUNTRY, strlen(CMD_COUNTRY)) == 0) { char *country_code = command + strlen(CMD_COUNTRY) + 1; bytes_written = wldev_set_country(net, country_code, true, true); } #endif /* WL_CFG80211 */ #ifdef PNO_SUPPORT else if (strnicmp(command, CMD_PNOSSIDCLR_SET, strlen(CMD_PNOSSIDCLR_SET)) == 0) { bytes_written = dhd_dev_pno_stop_for_ssid(net); } #ifndef WL_SCHED_SCAN else if (strnicmp(command, CMD_PNOSETUP_SET, strlen(CMD_PNOSETUP_SET)) == 0) { bytes_written = wl_android_set_pno_setup(net, command, priv_cmd.total_len); } #endif /* !WL_SCHED_SCAN */ else if (strnicmp(command, CMD_PNOENABLE_SET, strlen(CMD_PNOENABLE_SET)) == 0) { int enable = *(command + strlen(CMD_PNOENABLE_SET) + 1) - '0'; bytes_written = (enable)? 0 : dhd_dev_pno_stop_for_ssid(net); } else if (strnicmp(command, CMD_WLS_BATCHING, strlen(CMD_WLS_BATCHING)) == 0) { bytes_written = wls_parse_batching_cmd(net, command, priv_cmd.total_len); } #endif /* PNO_SUPPORT */ else if (strnicmp(command, CMD_P2P_DEV_ADDR, strlen(CMD_P2P_DEV_ADDR)) == 0) { bytes_written = wl_android_get_p2p_dev_addr(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_P2P_SET_NOA, strlen(CMD_P2P_SET_NOA)) == 0) { int skip = strlen(CMD_P2P_SET_NOA) + 1; bytes_written = wl_cfg80211_set_p2p_noa(net, command + skip, priv_cmd.total_len - skip); } #ifdef WL_SDO else if (strnicmp(command, CMD_P2P_SD_OFFLOAD, strlen(CMD_P2P_SD_OFFLOAD)) == 0) { u8 *buf = command; u8 *cmd_id = NULL; int len; cmd_id = strsep((char **)&buf, " "); /* if buf == NULL, means no arg */ if (buf == NULL) len = 0; else len = strlen(buf); bytes_written = wl_cfg80211_sd_offload(net, cmd_id, buf, len); } #endif /* WL_SDO */ #if !defined WL_ENABLE_P2P_IF else if (strnicmp(command, CMD_P2P_GET_NOA, strlen(CMD_P2P_GET_NOA)) == 0) { bytes_written = wl_cfg80211_get_p2p_noa(net, command, priv_cmd.total_len); } #endif /* WL_ENABLE_P2P_IF */ else if (strnicmp(command, CMD_P2P_SET_PS, strlen(CMD_P2P_SET_PS)) == 0) { int skip = strlen(CMD_P2P_SET_PS) + 1; bytes_written = wl_cfg80211_set_p2p_ps(net, command + skip, priv_cmd.total_len - skip); } #ifdef WL_CFG80211 else if (strnicmp(command, CMD_SET_AP_WPS_P2P_IE, strlen(CMD_SET_AP_WPS_P2P_IE)) == 0) { int skip = strlen(CMD_SET_AP_WPS_P2P_IE) + 3; bytes_written = wl_cfg80211_set_wps_p2p_ie(net, command + skip, priv_cmd.total_len - skip, *(command + skip - 2) - '0'); } #endif /* WL_CFG80211 */ else if (strnicmp(command, CMD_OKC_SET_PMK, strlen(CMD_OKC_SET_PMK)) == 0) bytes_written = wl_android_set_pmk(net, command, priv_cmd.total_len); else if (strnicmp(command, CMD_OKC_ENABLE, strlen(CMD_OKC_ENABLE)) == 0) bytes_written = wl_android_okc_enable(net, command, priv_cmd.total_len); #ifdef BCMCCX else if (strnicmp(command, CMD_GETCCKM_RN, strlen(CMD_GETCCKM_RN)) == 0) { bytes_written = wl_android_get_cckm_rn(net, command); } else if (strnicmp(command, CMD_SETCCKM_KRK, strlen(CMD_SETCCKM_KRK)) == 0) { bytes_written = wl_android_set_cckm_krk(net, command); } else if (strnicmp(command, CMD_GET_ASSOC_RES_IES, strlen(CMD_GET_ASSOC_RES_IES)) == 0) { bytes_written = wl_android_get_assoc_res_ies(net, command); } #endif /* BCMCCX */ #if defined(WL_SUPPORT_AUTO_CHANNEL) else if (strnicmp(command, CMD_GET_BEST_CHANNELS, strlen(CMD_GET_BEST_CHANNELS)) == 0) { bytes_written = wl_cfg80211_get_best_channels(net, command, priv_cmd.total_len); } #endif /* WL_SUPPORT_AUTO_CHANNEL */ else if (strnicmp(command, CMD_HAPD_MAC_FILTER, strlen(CMD_HAPD_MAC_FILTER)) == 0) { int skip = strlen(CMD_HAPD_MAC_FILTER) + 1; wl_android_set_mac_address_filter(net, (const char*)command+skip); } else if (strnicmp(command, CMD_SETROAMMODE, strlen(CMD_SETROAMMODE)) == 0) bytes_written = wl_android_set_roam_mode(net, command, priv_cmd.total_len); else if (strnicmp(command, CMD_MIRACAST, strlen(CMD_MIRACAST)) == 0) bytes_written = wl_android_set_miracast(net, command, priv_cmd.total_len); else if (strnicmp(command, CMD_SETIBSSBEACONOUIDATA, strlen(CMD_SETIBSSBEACONOUIDATA)) == 0) bytes_written = wl_android_set_ibss_beacon_ouidata(net, command, priv_cmd.total_len); else if(strnicmp(command, CMD_GET_CHANNEL, strlen(CMD_GET_CHANNEL)) == 0) { bytes_written = wl_android_get_channel(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_SET_ROAM, strlen(CMD_SET_ROAM)) == 0) { bytes_written = wl_android_set_roam_trigger(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_GET_ROAM, strlen(CMD_GET_ROAM)) == 0) { bytes_written = wl_android_get_roam_trigger(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_SET_KEEP_ALIVE, strlen(CMD_SET_KEEP_ALIVE)) == 0) { int skip = strlen(CMD_SET_KEEP_ALIVE) + 1; bytes_written = wl_android_set_keep_alive(net, command+skip, priv_cmd.total_len-skip); } else if (strnicmp(command, CMD_GET_KEEP_ALIVE, strlen(CMD_GET_KEEP_ALIVE)) == 0) { int skip = strlen(CMD_GET_KEEP_ALIVE) + 1; bytes_written = wl_android_get_keep_alive(net, command+skip, priv_cmd.total_len-skip); } else if (strnicmp(command, CMD_GET_PM, strlen(CMD_GET_PM)) == 0) { bytes_written = wl_android_get_pm(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_SET_PM, strlen(CMD_SET_PM)) == 0) { bytes_written = wl_android_set_pm(net, command, priv_cmd.total_len); } else if (strnicmp(command, CMD_MONITOR, strlen(CMD_MONITOR)) == 0) bytes_written = wl_android_set_monitor(net, command, priv_cmd.total_len); else { ANDROID_ERROR(("Unknown PRIVATE command %s - ignored\n", command)); snprintf(command, 3, "OK"); bytes_written = strlen("OK"); } if (bytes_written >= 0) { if ((bytes_written == 0) && (priv_cmd.total_len > 0)) command[0] = '\0'; if (bytes_written >= priv_cmd.total_len) { ANDROID_ERROR(("%s: bytes_written = %d\n", __FUNCTION__, bytes_written)); bytes_written = priv_cmd.total_len; } else { bytes_written++; } priv_cmd.used_len = bytes_written; if (copy_to_user(priv_cmd.buf, command, bytes_written)) { ANDROID_ERROR(("%s: failed to copy data to user buffer\n", __FUNCTION__)); ret = -EFAULT; } } else { ret = bytes_written; } exit: net_os_wake_unlock(net); if (command) { kfree(command); } return ret; } int wl_android_init(void) { int ret = 0; dhd_msg_level |= DHD_ERROR_VAL; #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); } #ifdef WL_GENL wl_genl_init(); #endif return ret; } int wl_android_exit(void) { int ret = 0; #ifdef WL_GENL wl_genl_deinit(); #endif /* WL_GENL */ 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) { sdioh_stop(NULL); /* Call customer gpio to turn off power with WL_REG_ON signal */ dhd_customer_gpio_wlan_ctrl(WLAN_RESET_OFF); g_wifi_on = FALSE; } } #ifdef WL_GENL /* Generic Netlink Initializaiton */ static int wl_genl_init(void) { int ret; ANDROID_TRACE(("GEN Netlink Init\n\n")); /* register new family */ ret = genl_register_family(&wl_genl_family); if (ret != 0) goto failure; /* register functions (commands) of the new family */ ret = genl_register_ops(&wl_genl_family, &wl_genl_ops); if (ret != 0) { ANDROID_ERROR(("register ops failed: %i\n", ret)); genl_unregister_family(&wl_genl_family); goto failure; } ret = genl_register_mc_group(&wl_genl_family, &wl_genl_mcast); if (ret != 0) { ANDROID_ERROR(("register mc_group failed: %i\n", ret)); genl_unregister_ops(&wl_genl_family, &wl_genl_ops); genl_unregister_family(&wl_genl_family); goto failure; } return 0; failure: ANDROID_ERROR(("Registering Netlink failed!!\n")); return -1; } /* Generic netlink deinit */ static int wl_genl_deinit(void) { if (genl_unregister_ops(&wl_genl_family, &wl_genl_ops) < 0) ANDROID_ERROR(("Unregister wl_genl_ops failed\n")); if (genl_unregister_family(&wl_genl_family) < 0) ANDROID_ERROR(("Unregister wl_genl_ops failed\n")); return 0; } s32 wl_event_to_bcm_event(u16 event_type) { u16 event = -1; switch (event_type) { case WLC_E_SERVICE_FOUND: event = BCM_E_SVC_FOUND; break; case WLC_E_P2PO_ADD_DEVICE: event = BCM_E_DEV_FOUND; break; case WLC_E_P2PO_DEL_DEVICE: event = BCM_E_DEV_LOST; break; /* Above events are supported from BCM Supp ver 47 Onwards */ default: ANDROID_ERROR(("Event not supported\n")); } return event; } s32 wl_genl_send_msg( struct net_device *ndev, u32 event_type, u8 *buf, u16 len, u8 *subhdr, u16 subhdr_len) { int ret = 0; struct sk_buff *skb; void *msg; u32 attr_type = 0; bcm_event_hdr_t *hdr = NULL; int mcast = 1; /* By default sent as mutlicast type */ int pid = 0; u8 *ptr = NULL, *p = NULL; u32 tot_len = sizeof(bcm_event_hdr_t) + subhdr_len + len; u16 kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; ANDROID_TRACE(("Enter \n")); /* Decide between STRING event and Data event */ if (event_type == 0) attr_type = BCM_GENL_ATTR_STRING; else attr_type = BCM_GENL_ATTR_MSG; skb = genlmsg_new(NLMSG_GOODSIZE, kflags); if (skb == NULL) { ret = -ENOMEM; goto out; } msg = genlmsg_put(skb, 0, 0, &wl_genl_family, 0, BCM_GENL_CMD_MSG); if (msg == NULL) { ret = -ENOMEM; goto out; } if (attr_type == BCM_GENL_ATTR_STRING) { /* Add a BCM_GENL_MSG attribute. Since it is specified as a string. * make sure it is null terminated */ if (subhdr || subhdr_len) { ANDROID_ERROR(("No sub hdr support for the ATTR STRING type \n")); ret = -EINVAL; goto out; } ret = nla_put_string(skb, BCM_GENL_ATTR_STRING, buf); if (ret != 0) { ANDROID_ERROR(("nla_put_string failed\n")); goto out; } } else { /* ATTR_MSG */ /* Create a single buffer for all */ p = ptr = kzalloc(tot_len, kflags); if (!ptr) { ret = -ENOMEM; ANDROID_ERROR(("ENOMEM!!\n")); goto out; } /* Include the bcm event header */ hdr = (bcm_event_hdr_t *)ptr; hdr->event_type = wl_event_to_bcm_event(event_type); hdr->len = len + subhdr_len; ptr += sizeof(bcm_event_hdr_t); /* Copy subhdr (if any) */ if (subhdr && subhdr_len) { memcpy(ptr, subhdr, subhdr_len); ptr += subhdr_len; } /* Copy the data */ if (buf && len) { memcpy(ptr, buf, len); } ret = nla_put(skb, BCM_GENL_ATTR_MSG, tot_len, p); if (ret != 0) { ANDROID_ERROR(("nla_put_string failed\n")); goto out; } } if (mcast) { int err = 0; /* finalize the message */ genlmsg_end(skb, msg); /* NETLINK_CB(skb).dst_group = 1; */ if ((err = genlmsg_multicast(skb, 0, wl_genl_mcast.id, GFP_ATOMIC)) < 0) ANDROID_ERROR(("genlmsg_multicast for attr(%d) failed. Error:%d \n", attr_type, err)); else ANDROID_TRACE(("Multicast msg sent successfully. attr_type:%d len:%d \n", attr_type, tot_len)); } else { NETLINK_CB(skb).dst_group = 0; /* Not in multicast group */ /* finalize the message */ genlmsg_end(skb, msg); /* send the message back */ if (genlmsg_unicast(&init_net, skb, pid) < 0) ANDROID_ERROR(("genlmsg_unicast failed\n")); } out: if (p) kfree(p); if (ret) nlmsg_free(skb); return ret; } static s32 wl_genl_handle_msg( struct sk_buff *skb, struct genl_info *info) { struct nlattr *na; u8 *data = NULL; ANDROID_TRACE(("Enter \n")); if (info == NULL) { return -EINVAL; } na = info->attrs[BCM_GENL_ATTR_MSG]; if (!na) { ANDROID_ERROR(("nlattribute NULL\n")); return -EINVAL; } data = (char *)nla_data(na); if (!data) { ANDROID_ERROR(("Invalid data\n")); return -EINVAL; } else { /* Handle the data */ #if !defined(WL_CFG80211_P2P_DEV_IF) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)) ANDROID_TRACE(("%s: Data received from pid (%d) \n", __func__, info->snd_pid)); #else ANDROID_TRACE(("%s: Data received from pid (%d) \n", __func__, info->snd_portid)); #endif /* !WL_CFG80211_P2P_DEV_IF && (LINUX_VERSION < VERSION(3, 6, 0) */ } return 0; } #endif /* WL_GENL */ /** * Functions for Android WiFi card detection */ #if defined(CONFIG_WIFI_CONTROL_FUNC) bool g_wifi_poweron = FALSE; static int g_wifidev_registered = 0; static struct semaphore wifi_control_sem; static struct wifi_platform_data *wifi_control_data = NULL; static struct resource *wifi_irqres = NULL; static struct regulator *wifi_regulator = NULL; static int wifi_add_dev(void); static void wifi_del_dev(void); int wl_android_wifictrl_func_add(void) { int ret = 0; sema_init(&wifi_control_sem, 0); ret = wifi_add_dev(); if (ret) { ANDROID_ERROR(("%s: platform_driver_register failed\n", __FUNCTION__)); return ret; } g_wifidev_registered = 1; /* Waiting callback after platform_driver_register is done or exit with error */ if (down_timeout(&wifi_control_sem, msecs_to_jiffies(1000)) != 0) { ret = -EINVAL; ANDROID_ERROR(("%s: platform_driver_register timeout\n", __FUNCTION__)); } return ret; } void wl_android_wifictrl_func_del(void) { if (g_wifidev_registered) { wifi_del_dev(); g_wifidev_registered = 0; } } void* wl_android_prealloc(int section, unsigned long size) { void *alloc_ptr = NULL; if (wifi_control_data && wifi_control_data->mem_prealloc) { alloc_ptr = wifi_control_data->mem_prealloc(section, size); if (alloc_ptr) { ANDROID_INFO(("success alloc section %d\n", section)); if (size != 0L) bzero(alloc_ptr, size); return alloc_ptr; } } ANDROID_ERROR(("can't alloc section %d\n", section)); return NULL; } int wifi_get_irq_number(unsigned long *irq_flags_ptr) { if (wifi_irqres) { *irq_flags_ptr = wifi_irqres->flags & IRQF_TRIGGER_MASK; return (int)wifi_irqres->start; } #ifdef CUSTOM_OOB_GPIO_NUM return CUSTOM_OOB_GPIO_NUM; #else return -1; #endif } int wifi_set_power(int on, unsigned long msec) { int ret = 0; ANDROID_ERROR(("%s = %d\n", __FUNCTION__, on)); if (wifi_regulator && on) ret = regulator_enable(wifi_regulator); if (wifi_control_data && wifi_control_data->set_power) { #ifdef ENABLE_4335BT_WAR if (on) { printk("WiFi: trying to acquire BT lock\n"); if (bcm_bt_lock(lock_cookie_wifi) != 0) printk("** WiFi: timeout in acquiring bt lock**\n"); printk("%s: btlock acquired\n", __FUNCTION__); } else { /* For a exceptional case, release btlock */ bcm_bt_unlock(lock_cookie_wifi); } #endif /* ENABLE_4335BT_WAR */ ret = wifi_control_data->set_power(on); } if (wifi_regulator && !on) ret = regulator_disable(wifi_regulator); if (msec && !ret) OSL_SLEEP(msec); return ret; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)) int wifi_get_mac_addr(unsigned char *buf) { ANDROID_ERROR(("%s\n", __FUNCTION__)); if (!buf) return -EINVAL; if (wifi_control_data && wifi_control_data->get_mac_addr) { return wifi_control_data->get_mac_addr(buf); } return -EOPNOTSUPP; } #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)) */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39)) void *wifi_get_country_code(char *ccode) { ANDROID_TRACE(("%s\n", __FUNCTION__)); if (!ccode) return NULL; if (wifi_control_data && wifi_control_data->get_country_code) { return wifi_control_data->get_country_code(ccode); } return NULL; } #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39)) */ static int wifi_set_carddetect(int on) { ANDROID_ERROR(("%s = %d\n", __FUNCTION__, on)); if (wifi_control_data && wifi_control_data->set_carddetect) { wifi_control_data->set_carddetect(on); } return 0; } static struct resource *get_wifi_irqres_from_of(struct platform_device *pdev) { static struct resource gpio_wifi_irqres; int irq; int gpio = of_get_gpio(pdev->dev.of_node, 0); if (gpio < 0) return NULL; irq = gpio_to_irq(gpio); if (irq < 0) return NULL; gpio_wifi_irqres.name = "bcmdhd_wlan_irq"; gpio_wifi_irqres.start = irq; gpio_wifi_irqres.end = irq; gpio_wifi_irqres.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE; return &gpio_wifi_irqres; } static int wifi_probe(struct platform_device *pdev) { int err; struct regulator *regulator; struct wifi_platform_data *wifi_ctrl = (struct wifi_platform_data *)(pdev->dev.platform_data); if (!wifi_ctrl) { regulator = regulator_get(&pdev->dev, "wlreg_on"); if (IS_ERR(regulator)) return PTR_ERR(regulator); wifi_regulator = regulator; } wifi_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "bcmdhd_wlan_irq"); if (wifi_irqres == NULL) wifi_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "bcm4329_wlan_irq"); if (wifi_irqres == NULL) wifi_irqres = get_wifi_irqres_from_of(pdev); wifi_control_data = wifi_ctrl; err = wifi_set_power(1, 200); /* Power On */ if (unlikely(err)) { ANDROID_ERROR(("%s: set_power failed. err=%d\n", __FUNCTION__, err)); wifi_set_power(0, WIFI_TURNOFF_DELAY); /* WL_REG_ON state unknown, Power off forcely */ } else { wifi_set_carddetect(1); /* CardDetect (0->1) */ g_wifi_poweron = TRUE; } up(&wifi_control_sem); return 0; } static int wifi_remove(struct platform_device *pdev) { struct wifi_platform_data *wifi_ctrl = (struct wifi_platform_data *)(pdev->dev.platform_data); struct io_cfg *cur, *q; ANDROID_ERROR(("## %s\n", __FUNCTION__)); wifi_control_data = wifi_ctrl; if (g_wifi_poweron) { wifi_set_power(0, WIFI_TURNOFF_DELAY); /* Power Off */ wifi_set_carddetect(0); /* CardDetect (1->0) */ g_wifi_poweron = FALSE; list_for_each_entry_safe(cur, q, &miracast_resume_list, list) { list_del(&cur->list); kfree(cur); } } if (wifi_regulator) { regulator_put(wifi_regulator); wifi_regulator = NULL; } up(&wifi_control_sem); return 0; } static int wifi_suspend(struct platform_device *pdev, pm_message_t state) { ANDROID_TRACE(("##> %s\n", __FUNCTION__)); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 39)) && defined(OOB_INTR_ONLY) && 1 bcmsdh_oob_intr_set(0); #endif /* (OOB_INTR_ONLY) */ return 0; } static int wifi_resume(struct platform_device *pdev) { ANDROID_TRACE(("##> %s\n", __FUNCTION__)); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 39)) && defined(OOB_INTR_ONLY) && 1 if (dhd_os_check_if_up(bcmsdh_get_drvdata())) bcmsdh_oob_intr_set(1); #endif /* (OOB_INTR_ONLY) */ return 0; } static const struct of_device_id wifi_device_dt_match[] = { { .compatible = "android,bcmdhd_wlan", }, {}, }; MODULE_DEVICE_TABLE(of, wifi_device_dt_match); static struct platform_driver wifi_device = { .probe = wifi_probe, .remove = wifi_remove, .suspend = wifi_suspend, .resume = wifi_resume, .driver = { .name = "bcmdhd_wlan", .of_match_table = wifi_device_dt_match, } }; static struct platform_driver wifi_device_legacy = { .probe = wifi_probe, .remove = wifi_remove, .suspend = wifi_suspend, .resume = wifi_resume, .driver = { .name = "bcm4329_wlan", } }; static int wifi_add_dev(void) { int ret = 0; ANDROID_TRACE(("## Calling platform_driver_register\n")); ret = platform_driver_register(&wifi_device); if (ret) return ret; ret = platform_driver_register(&wifi_device_legacy); return ret; } static void wifi_del_dev(void) { ANDROID_TRACE(("## Unregister platform_driver_register\n")); platform_driver_unregister(&wifi_device); platform_driver_unregister(&wifi_device_legacy); } #endif /* defined(CONFIG_WIFI_CONTROL_FUNC) */ #if defined(RSSIAVG) void wl_free_rssi_cache(wl_rssi_cache_ctrl_t *rssi_cache_ctrl) { wl_rssi_cache_t *node, *cur, **rssi_head; int i=0; rssi_head = &rssi_cache_ctrl->m_cache_head; node = *rssi_head; for (;node;) { ANDROID_INFO(("%s: Free %d with BSSID %pM\n", __FUNCTION__, i, &node->BSSID)); cur = node; node = cur->next; kfree(cur); i++; } *rssi_head = NULL; } void wl_delete_dirty_rssi_cache(wl_rssi_cache_ctrl_t *rssi_cache_ctrl) { wl_rssi_cache_t *node, *prev, **rssi_head; int i = -1, tmp = 0; struct timeval now; do_gettimeofday(&now); rssi_head = &rssi_cache_ctrl->m_cache_head; node = *rssi_head; prev = node; for (;node;) { i++; if (now.tv_sec > node->tv.tv_sec) { if (node == *rssi_head) { tmp = 1; *rssi_head = node->next; } else { tmp = 0; prev->next = node->next; } ANDROID_INFO(("%s: Del %d with BSSID %pM\n", __FUNCTION__, i, &node->BSSID)); kfree(node); if (tmp == 1) { node = *rssi_head; prev = node; } else { node = prev->next; } continue; } prev = node; node = node->next; } } void wl_delete_disconnected_rssi_cache(wl_rssi_cache_ctrl_t *rssi_cache_ctrl, u8 *bssid) { wl_rssi_cache_t *node, *prev, **rssi_head; int i = -1, tmp = 0; rssi_head = &rssi_cache_ctrl->m_cache_head; node = *rssi_head; prev = node; for (;node;) { i++; if (!memcmp(&node->BSSID, bssid, ETHER_ADDR_LEN)) { if (node == *rssi_head) { tmp = 1; *rssi_head = node->next; } else { tmp = 0; prev->next = node->next; } ANDROID_INFO(("%s: Del %d with BSSID %pM\n", __FUNCTION__, i, &node->BSSID)); kfree(node); if (tmp == 1) { node = *rssi_head; prev = node; } else { node = prev->next; } continue; } prev = node; node = node->next; } } void wl_reset_rssi_cache(wl_rssi_cache_ctrl_t *rssi_cache_ctrl) { wl_rssi_cache_t *node, **rssi_head; rssi_head = &rssi_cache_ctrl->m_cache_head; /* reset dirty */ node = *rssi_head; for (;node;) { node->dirty += 1; node = node->next; } } int wl_update_connected_rssi_cache(struct net_device *net, wl_rssi_cache_ctrl_t *rssi_cache_ctrl, int *rssi_avg) { wl_rssi_cache_t *node, *prev, *leaf, **rssi_head; int j, k=0; int rssi, error=0; struct ether_addr bssid; struct timeval now, timeout; if (!g_wifi_on) return 0; error = wldev_ioctl(net, WLC_GET_BSSID, &bssid, sizeof(bssid), false); if (error == BCME_NOTASSOCIATED) { ANDROID_INFO(("%s: Not Associated! res:%d\n", __FUNCTION__, error)); return 0; } if (error) { ANDROID_ERROR(("Could not get bssid (%d)\n", error)); } error = wldev_get_rssi(net, &rssi); if (error) { ANDROID_ERROR(("Could not get rssi (%d)\n", error)); return error; } do_gettimeofday(&now); timeout.tv_sec = now.tv_sec + RSSICACHE_TIMEOUT; if (timeout.tv_sec < now.tv_sec) { /* * Integer overflow - assume long enough timeout to be assumed * to be infinite, i.e., the timeout would never happen. */ ANDROID_TRACE(("%s: Too long timeout (secs=%d) to ever happen - now=%lu, timeout=%lu", __FUNCTION__, RSSICACHE_TIMEOUT, now.tv_sec, timeout.tv_sec)); } /* update RSSI */ rssi_head = &rssi_cache_ctrl->m_cache_head; node = *rssi_head; prev = NULL; for (;node;) { if (!memcmp(&node->BSSID, &bssid, ETHER_ADDR_LEN)) { ANDROID_INFO(("%s: Update %d with BSSID %pM, RSSI=%d\n", __FUNCTION__, k, &bssid, rssi)); for(j=0; jRSSI[j] = node->RSSI[j+1]; node->RSSI[j] = rssi; node->dirty = 0; node->tv = timeout; goto exit; } prev = node; node = node->next; k++; } leaf = kmalloc(sizeof(wl_rssi_cache_t), GFP_KERNEL); if (!leaf) { ANDROID_ERROR(("%s: Memory alloc failure %d\n", __FUNCTION__, sizeof(wl_rssi_cache_t))); return 0; } ANDROID_INFO(("%s: Add %d with cached BSSID %pM, RSSI=%d in the leaf\n", __FUNCTION__, k, &bssid, rssi)); leaf->next = NULL; leaf->dirty = 0; leaf->tv = timeout; memcpy(&leaf->BSSID, &bssid, ETHER_ADDR_LEN); for (j=0; jRSSI[j] = rssi; if (!prev) *rssi_head = leaf; else prev->next = leaf; exit: *rssi_avg = (int)wl_get_avg_rssi(rssi_cache_ctrl, &bssid); return error; } void wl_update_rssi_cache(wl_rssi_cache_ctrl_t *rssi_cache_ctrl, wl_scan_results_t *ss_list) { wl_rssi_cache_t *node, *prev, *leaf, **rssi_head; wl_bss_info_t *bi = NULL; int i, j, k; struct timeval now, timeout; if (!ss_list->count) return; do_gettimeofday(&now); timeout.tv_sec = now.tv_sec + RSSICACHE_TIMEOUT; if (timeout.tv_sec < now.tv_sec) { /* * Integer overflow - assume long enough timeout to be assumed * to be infinite, i.e., the timeout would never happen. */ ANDROID_TRACE(("%s: Too long timeout (secs=%d) to ever happen - now=%lu, timeout=%lu", __FUNCTION__, RSSICACHE_TIMEOUT, now.tv_sec, timeout.tv_sec)); } rssi_head = &rssi_cache_ctrl->m_cache_head; /* update RSSI */ for (i = 0; i < ss_list->count; i++) { node = *rssi_head; prev = NULL; k = 0; bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : ss_list->bss_info; for (;node;) { if (!memcmp(&node->BSSID, &bi->BSSID, ETHER_ADDR_LEN)) { ANDROID_INFO(("%s: Update %d with BSSID %pM, RSSI=%d, SSID \"%s\"\n", __FUNCTION__, k, &bi->BSSID, dtoh16(bi->RSSI), bi->SSID)); for(j=0; jRSSI[j] = node->RSSI[j+1]; node->RSSI[j] = dtoh16(bi->RSSI); node->dirty = 0; node->tv = timeout; break; } prev = node; node = node->next; k++; } if (node) continue; leaf = kmalloc(sizeof(wl_rssi_cache_t), GFP_KERNEL); if (!leaf) { ANDROID_ERROR(("%s: Memory alloc failure %d\n", __FUNCTION__, sizeof(wl_rssi_cache_t))); return; } ANDROID_INFO(("%s: Add %d with cached BSSID %pM, RSSI=%d, SSID \"%s\" in the leaf\n", __FUNCTION__, k, &bi->BSSID, dtoh16(bi->RSSI), bi->SSID)); leaf->next = NULL; leaf->dirty = 0; leaf->tv = timeout; memcpy(&leaf->BSSID, &bi->BSSID, ETHER_ADDR_LEN); for (j=0; jRSSI[j] = dtoh16(bi->RSSI); if (!prev) *rssi_head = leaf; else prev->next = leaf; } } int16 wl_get_avg_rssi(wl_rssi_cache_ctrl_t *rssi_cache_ctrl, void *addr) { wl_rssi_cache_t *node, **rssi_head; int j, rssi_sum, rssi=RSSI_MINVAL; rssi_head = &rssi_cache_ctrl->m_cache_head; node = *rssi_head; for (;node;) { if (!memcmp(&node->BSSID, addr, ETHER_ADDR_LEN)) { rssi_sum = 0; rssi = 0; for (j=0; jRSSI[RSSIAVG_LEN-j-1]; rssi = rssi_sum / j; break; } node = node->next; } rssi = MIN(rssi, RSSI_MAXVAL); if (rssi == RSSI_MINVAL) { ANDROID_ERROR(("%s: BSSID %pM does not in RSSI cache\n", __FUNCTION__, addr)); } return (int16)rssi; } #endif #if defined(RSSIOFFSET) int wl_update_rssi_offset(int rssi) { uint chip, chiprev; if (!g_wifi_on) return rssi; chip = dhd_bus_chip_id(bcmsdh_get_drvdata()); chiprev = dhd_bus_chiprev_id(bcmsdh_get_drvdata()); if (chip == BCM4330_CHIP_ID && chiprev == BCM4330B2_CHIP_REV) { #if defined(RSSIOFFSET_NEW) int j; for (j=0; jm_cache_head; node = *bss_head; for (;node;) { ANDROID_TRACE(("%s: Free %d with BSSID %pM\n", __FUNCTION__, i, &node->results.bss_info->BSSID)); cur = node; node = cur->next; kfree(cur); i++; } *bss_head = NULL; } void wl_delete_dirty_bss_cache(wl_bss_cache_ctrl_t *bss_cache_ctrl) { wl_bss_cache_t *node, *prev, **bss_head; int i = -1, tmp = 0; struct timeval now; do_gettimeofday(&now); bss_head = &bss_cache_ctrl->m_cache_head; node = *bss_head; prev = node; for (;node;) { i++; if (now.tv_sec > node->tv.tv_sec) { if (node == *bss_head) { tmp = 1; *bss_head = node->next; } else { tmp = 0; prev->next = node->next; } ANDROID_TRACE(("%s: Del %d with BSSID %pM, RSSI=%d, SSID \"%s\"\n", __FUNCTION__, i, &node->results.bss_info->BSSID, dtoh16(node->results.bss_info->RSSI), node->results.bss_info->SSID)); kfree(node); if (tmp == 1) { node = *bss_head; prev = node; } else { node = prev->next; } continue; } prev = node; node = node->next; } } void wl_delete_disconnected_bss_cache(wl_bss_cache_ctrl_t *bss_cache_ctrl, u8 *bssid) { wl_bss_cache_t *node, *prev, **bss_head; int i = -1, tmp = 0; bss_head = &bss_cache_ctrl->m_cache_head; node = *bss_head; prev = node; for (;node;) { i++; if (!memcmp(&node->results.bss_info->BSSID, bssid, ETHER_ADDR_LEN)) { if (node == *bss_head) { tmp = 1; *bss_head = node->next; } else { tmp = 0; prev->next = node->next; } ANDROID_TRACE(("%s: Del %d with BSSID %pM, RSSI=%d, SSID \"%s\"\n", __FUNCTION__, i, &node->results.bss_info->BSSID, dtoh16(node->results.bss_info->RSSI), node->results.bss_info->SSID)); kfree(node); if (tmp == 1) { node = *bss_head; prev = node; } else { node = prev->next; } continue; } prev = node; node = node->next; } } void wl_reset_bss_cache(wl_bss_cache_ctrl_t *bss_cache_ctrl) { wl_bss_cache_t *node, **bss_head; bss_head = &bss_cache_ctrl->m_cache_head; /* reset dirty */ node = *bss_head; for (;node;) { node->dirty += 1; node = node->next; } } void wl_update_bss_cache(wl_bss_cache_ctrl_t *bss_cache_ctrl, wl_scan_results_t *ss_list) { wl_bss_cache_t *node, *prev, *leaf, *tmp, **bss_head; wl_bss_info_t *bi = NULL; int i, k=0; struct timeval now, timeout; if (!ss_list->count) return; do_gettimeofday(&now); timeout.tv_sec = now.tv_sec + BSSCACHE_TIMEOUT; if (timeout.tv_sec < now.tv_sec) { /* * Integer overflow - assume long enough timeout to be assumed * to be infinite, i.e., the timeout would never happen. */ ANDROID_TRACE(("%s: Too long timeout (secs=%d) to ever happen - now=%lu, timeout=%lu", __FUNCTION__, BSSCACHE_TIMEOUT, now.tv_sec, timeout.tv_sec)); } bss_head = &bss_cache_ctrl->m_cache_head; for (i=0; i < ss_list->count; i++) { node = *bss_head; prev = NULL; bi = bi ? (wl_bss_info_t *)((uintptr)bi + dtoh32(bi->length)) : ss_list->bss_info; for (;node;) { if (!memcmp(&node->results.bss_info->BSSID, &bi->BSSID, ETHER_ADDR_LEN)) { tmp = node; leaf = kmalloc(dtoh32(bi->length) + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN, GFP_KERNEL); if (!leaf) { ANDROID_ERROR(("%s: Memory alloc failure %d and keep old BSS info\n", __FUNCTION__, dtoh32(bi->length) + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN)); break; } memcpy(leaf->results.bss_info, bi, dtoh32(bi->length)); leaf->next = node->next; leaf->dirty = 0; leaf->tv = timeout; leaf->results.count = 1; leaf->results.version = ss_list->version; ANDROID_TRACE(("%s: Update %d with BSSID %pM, RSSI=%d, SSID \"%s\", length=%d\n", __FUNCTION__, k, &bi->BSSID, dtoh16(bi->RSSI), bi->SSID, dtoh32(bi->length))); if (!prev) *bss_head = leaf; else prev->next = leaf; node = leaf; prev = node; kfree(tmp); k++; break; } prev = node; node = node->next; } if (node) continue; leaf = kmalloc(dtoh32(bi->length) + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN, GFP_KERNEL); if (!leaf) { ANDROID_ERROR(("%s: Memory alloc failure %d\n", __FUNCTION__, dtoh32(bi->length) + WLC_IW_SS_CACHE_CTRL_FIELD_MAXLEN)); return; } ANDROID_TRACE(("%s: Add %d with cached BSSID %pM, RSSI=%d, SSID \"%s\" in the leaf\n", __FUNCTION__, k, &bi->BSSID, dtoh16(bi->RSSI), bi->SSID)); memcpy(leaf->results.bss_info, bi, dtoh32(bi->length)); leaf->next = NULL; leaf->dirty = 0; leaf->tv = timeout; leaf->results.count = 1; leaf->results.version = ss_list->version; k++; if (!prev) *bss_head = leaf; else prev->next = leaf; } } void wl_release_bss_cache_ctrl(wl_bss_cache_ctrl_t *bss_cache_ctrl) { ANDROID_TRACE(("%s:\n", __FUNCTION__)); wl_free_bss_cache(bss_cache_ctrl); } #endif