/**************************************************************************** * drivers/wireless/ieee80211/bcm43xxx/bcmf_driver.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcmf_driver.h" #include "bcmf_cdc.h" #include "bcmf_ioctl.h" #include "bcmf_utils.h" #include "bcmf_netdev.h" #include "bcmf_sdio.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define DOT11_BSSTYPE_ANY 2 #define BCMF_SCAN_TIMEOUT_TICK (5*CLOCKS_PER_SEC) #define BCMF_AUTH_TIMEOUT_MS 20000 /* was 10000 */ #define BCMF_SCAN_RESULT_ENTRIES CONFIG_IEEE80211_BROADCOM_SCAN_RESULT_ENTRIES /* CLM file is cut into pieces of MAX_CHUNK_LEN. * It is relatively small because dongles (FW) have a small maximum size * input payload restriction for ioctl's ... something like 1900'ish bytes. * So chunk len should not exceed 1400 bytes * * NOTE: CONFIG_NET_ETH_PKTSIZE is the MTU plus the size of the Ethernet * header (14 bytes). */ #ifdef CONFIG_IEEE80211_BROADCOM_FWFILES /* REVISIT */ # define MAX_CHUNK_LEN (100) #else # define MAX_CHUNK_LEN \ (CONFIG_NET_ETH_PKTSIZE > 1514 ? 1400 : CONFIG_NET_ETH_PKTSIZE - 114) #endif /* Helper to get iw_event size */ #define BCMF_IW_EVENT_SIZE(field) \ (offsetof(struct iw_event, u) + sizeof(((union iwreq_data *)0)->field)) /* CLM blob macros */ #define DLOAD_HANDLER_VER 1 /* Downloader version */ #define DLOAD_FLAG_VER_MASK 0xf000 /* Downloader version mask */ #define DLOAD_FLAG_VER_SHIFT 12 /* Downloader version shift */ #define DL_CRC_NOT_INUSE 0x0001 #define DL_BEGIN 0x0002 #define DL_END 0x0004 /**************************************************************************** * Private Types ****************************************************************************/ /* CLM blob download head */ struct wl_dload_data { uint16_t flag; uint16_t dload_type; uint32_t len; uint32_t crc; }; /* AP scan state machine status */ enum { BCMF_SCAN_TIMEOUT = 0, BCMF_SCAN_DISABLED, BCMF_SCAN_RUN, BCMF_SCAN_DONE }; /* Generic download types & flags */ enum { DL_TYPE_UCODE = 1, DL_TYPE_CLM = 2 }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static FAR struct bcmf_dev_s *bcmf_allocate_device(void); static void bcmf_free_device(FAR struct bcmf_dev_s *priv); static int bcmf_driver_initialize(FAR struct bcmf_dev_s *priv); #ifdef CONFIG_IEEE80211_BROADCOM_HAVE_CLM static int bcmf_driver_download_clm(FAR struct bcmf_dev_s *priv); #endif /* FIXME only for debug purpose */ static void bcmf_wl_default_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len); static void bcmf_wl_radio_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len); static void bcmf_wl_scan_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len); static void bcmf_wl_auth_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len); static int bcmf_wl_get_interface(FAR struct bcmf_dev_s *priv, struct iwreq *iwr); /**************************************************************************** * Private Functions ****************************************************************************/ static int bcmf_wl_channel_to_frequency(int chan) { if (chan <= 0) { return 0; } else if (chan < 14) { return 2407 + chan * 5; } else if (chan == 14) { return 2484; } else if ((chan >= 36) && (chan <= 165)) { return 5000 + chan * 5; } else if ((chan >= 182) && (chan <= 196)) { return 4000 + chan * 5; } return 0; /* not supported */ } FAR struct bcmf_dev_s *bcmf_allocate_device(void) { int ret; FAR struct bcmf_dev_s *priv; /* Allocate a bcmf device structure */ priv = (FAR struct bcmf_dev_s *)kmm_malloc(sizeof(*priv)); if (!priv) { return NULL; } /* Initialize bcmf device structure */ memset(priv, 0, sizeof(*priv)); /* Init control frames mutex and timeout signal */ if ((ret = nxsem_init(&priv->control_mutex, 0, 1)) != OK) { goto exit_free_priv; } if ((ret = nxsem_init(&priv->control_timeout, 0, 0)) != OK) { goto exit_free_priv; } if ((ret = nxsem_set_protocol(&priv->control_timeout, SEM_PRIO_NONE)) != OK) { goto exit_free_priv; } /* Init authentication signal semaphore */ if ((ret = nxsem_init(&priv->auth_signal, 0, 0)) != OK) { goto exit_free_priv; } if ((ret = nxsem_set_protocol(&priv->auth_signal, SEM_PRIO_NONE)) != OK) { goto exit_free_priv; } /* Init scan timeout timer */ priv->scan_status = BCMF_SCAN_DISABLED; return priv; exit_free_priv: kmm_free(priv); return NULL; } void bcmf_free_device(FAR struct bcmf_dev_s *priv) { /* TODO deinitialize device structures */ kmm_free(priv); } int bcmf_wl_set_mac_address(FAR struct bcmf_dev_s *priv, struct ifreq *req) { int ret; uint32_t out_len = IFHWADDRLEN; ret = bcmf_cdc_iovar_request(priv, CHIP_STA_INTERFACE, true, IOVAR_STR_CUR_ETHERADDR, (uint8_t *)req->ifr_hwaddr.sa_data, &out_len); if (ret != OK) { return ret; } wlinfo("MAC address updated %02X:%02X:%02X:%02X:%02X:%02X\n", req->ifr_hwaddr.sa_data[0], req->ifr_hwaddr.sa_data[1], req->ifr_hwaddr.sa_data[2], req->ifr_hwaddr.sa_data[3], req->ifr_hwaddr.sa_data[4], req->ifr_hwaddr.sa_data[5]); memcpy(priv->bc_dev.d_mac.ether.ether_addr_octet, req->ifr_hwaddr.sa_data, ETHER_ADDR_LEN); return OK; } #ifdef CONFIG_IEEE80211_BROADCOM_HAVE_CLM #ifdef CONFIG_IEEE80211_BROADCOM_FWFILES int bcmf_driver_download_clm(FAR struct bcmf_dev_s *priv) { FAR uint8_t *downloadbuff; struct file finfo; ssize_t nread; uint16_t dl_flag; unsigned int datalen = 7222; int ret; wlinfo("Download %d bytes\n", datalen); ret = file_open(&finfo, CONFIG_IEEE80211_BROADCOM_FWCLMNAME, O_RDONLY); if (ret < 0) { wlerr("ERROR: Failed to open the FILE MTD file\n", ret); return ret; } /* Divide CLM blob into chunks */ downloadbuff = kmm_malloc(sizeof(struct wl_dload_data) + MAX_CHUNK_LEN); if (downloadbuff == NULL) { wlerr("ERROR: Failed allocate memory for CLM data\n"); ret = -ENOMEM; goto errout_with_file; } dl_flag = DL_BEGIN; do { FAR struct wl_dload_data *dlhead; unsigned int chunk_len; uint32_t out_len; chunk_len = datalen >= MAX_CHUNK_LEN ? MAX_CHUNK_LEN : datalen; nread = file_read(&finfo, downloadbuff + sizeof(struct wl_dload_data), chunk_len); if (nread < 0) { ret = (int)nread; wlerr("ERROR: Failed to read CLM data: %d\n", ret); goto errout_with_buffer; } wlinfo("Read blob %d bytes on %d\n", nread, chunk_len); datalen -= chunk_len; if (datalen <= 0) { dl_flag |= DL_END; } /* CLM header */ dlhead = (struct wl_dload_data *)downloadbuff; dlhead->flag = (DLOAD_HANDLER_VER << DLOAD_FLAG_VER_SHIFT) | dl_flag; dlhead->dload_type = DL_TYPE_CLM; dlhead->len = chunk_len; dlhead->crc = 0; out_len = chunk_len + sizeof(struct wl_dload_data); out_len = (out_len + 7) & ~0x7; ret = bcmf_cdc_iovar_request(priv, CHIP_STA_INTERFACE, true, IOVAR_STR_CLMLOAD, downloadbuff, &out_len); wlinfo("datalen=%d, ret=%d\n", datalen, ret); dl_flag &= (uint16_t)~DL_BEGIN; } while ((datalen > 0) && (ret == OK)); wlinfo("Done writing blob"); errout_with_buffer: kmm_free(downloadbuff); errout_with_file: file_close(&finfo); return ret; } #else int bcmf_driver_download_clm(FAR struct bcmf_dev_s *priv) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; FAR uint8_t *srcbuff = sbus->chip->clm_blob_image; FAR uint8_t *downloadbuff; unsigned int datalen = *sbus->chip->clm_blob_image_size; uint16_t dl_flag; int ret = 0; if (srcbuff == NULL || datalen <= 0) { wlinfo("Skip CLM blob...\n"); return 0; } else { wlinfo("Download %d bytes @ %p\n", datalen, srcbuff); } /* Divide CLM blob into chunks */ downloadbuff = kmm_malloc(sizeof(struct wl_dload_data) + MAX_CHUNK_LEN); if (!downloadbuff) { wlerr("No memory for CLM data\n"); return -ENOMEM; } dl_flag = DL_BEGIN; do { FAR struct wl_dload_data *dlhead; unsigned int chunk_len = datalen; uint32_t out_len; chunk_len = datalen >= MAX_CHUNK_LEN ? MAX_CHUNK_LEN : datalen; memcpy(downloadbuff + sizeof(struct wl_dload_data), srcbuff, chunk_len); datalen -= chunk_len; srcbuff += chunk_len; if (datalen <= 0) { dl_flag |= DL_END; } /* CLM header */ dlhead = (struct wl_dload_data *)downloadbuff; dlhead->flag = (DLOAD_HANDLER_VER << DLOAD_FLAG_VER_SHIFT) | dl_flag; dlhead->dload_type = DL_TYPE_CLM; dlhead->len = chunk_len; dlhead->crc = 0; out_len = chunk_len + sizeof(struct wl_dload_data); out_len = (out_len + 7) & ~0x7; ret = bcmf_cdc_iovar_request(priv, CHIP_STA_INTERFACE, true, IOVAR_STR_CLMLOAD, downloadbuff, &out_len); dl_flag &= (uint16_t)~DL_BEGIN; } while ((datalen > 0) && (ret == OK)); kmm_free(downloadbuff); return ret; } #endif #endif /* CONFIG_IEEE80211_BROADCOM_HAVE_CLM */ int bcmf_driver_initialize(FAR struct bcmf_dev_s *priv) { int ret; uint32_t out_len; uint32_t value; uint8_t tmp_buf[64]; int interface = CHIP_STA_INTERFACE; #ifdef CONFIG_IEEE80211_BROADCOM_HAVE_CLM /* Download CLM blob if needed */ ret = bcmf_driver_download_clm(priv); if (ret != OK) { return -EIO; } #endif /* Disable TX Gloming feature */ out_len = 4; *(FAR uint32_t *)tmp_buf = 0; ret = bcmf_cdc_iovar_request(priv, interface, true, IOVAR_STR_TX_GLOM, tmp_buf, &out_len); if (ret != OK) { return -EIO; } /* FIXME disable power save mode */ out_len = 4; value = 0; ret = bcmf_cdc_ioctl(priv, interface, true, WLC_SET_PM, (uint8_t *)&value, &out_len); if (ret != OK) { return ret; } /* Set the GMode to auto */ out_len = 4; value = GMODE_AUTO; ret = bcmf_cdc_ioctl(priv, interface, true, WLC_SET_GMODE, (uint8_t *)&value, &out_len); if (ret != OK) { return ret; } /* TODO configure roaming if needed. Disable for now */ out_len = 4; value = 1; ret = bcmf_cdc_iovar_request(priv, interface, true, IOVAR_STR_ROAM_OFF, (FAR uint8_t *)&value, &out_len); /* TODO configure EAPOL version to default */ out_len = 8; ((FAR uint32_t *)tmp_buf)[0] = interface; ((FAR uint32_t *)tmp_buf)[1] = (uint32_t)-1; if (bcmf_cdc_iovar_request(priv, interface, true, "bsscfg:"IOVAR_STR_SUP_WPA2_EAPVER, tmp_buf, &out_len)) { return -EIO; } /* Query firmware version string */ out_len = sizeof(tmp_buf); ret = bcmf_cdc_iovar_request(priv, interface, false, IOVAR_STR_VERSION, tmp_buf, &out_len); if (ret != OK) { return -EIO; } tmp_buf[sizeof(tmp_buf)-1] = 0; /* Remove line feed */ out_len = strlen((char *)tmp_buf); if (out_len > 0 && tmp_buf[out_len - 1] == '\n') { tmp_buf[out_len - 1] = 0; } wlinfo("fw version <%s>\n", tmp_buf); /* FIXME Configure event mask to enable all asynchronous events */ for (ret = 0; ret < BCMF_EVENT_COUNT; ret++) { bcmf_event_register(priv, bcmf_wl_default_event_handler, ret); } /* Register radio event */ bcmf_event_register(priv, bcmf_wl_radio_event_handler, WLC_E_RADIO); /* Register AP scan event */ bcmf_event_register(priv, bcmf_wl_scan_event_handler, WLC_E_ESCAN_RESULT); /* Register authentication related events */ bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_ASSOC_IND_NDIS); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_AUTH); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_ASSOC); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_LINK); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_PSK_SUP); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_JOIN); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_SET_SSID); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_DEAUTH_IND); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_DISASSOC); bcmf_event_register(priv, bcmf_wl_auth_event_handler, WLC_E_DISASSOC_IND); if (bcmf_event_push_config(priv)) { return -EIO; } /* Register network driver */ return bcmf_netdev_register(priv); } void bcmf_wl_default_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len) { wlinfo("Got event %" PRId32 " from <%s>\n", bcmf_getle32(&event->type), event->src_name); } void bcmf_wl_radio_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len) { } void bcmf_wl_auth_event_handler(FAR struct bcmf_dev_s *priv, struct bcmf_event_s *event, unsigned int len) { uint32_t type; uint32_t status; type = bcmf_getle32(&event->type); status = bcmf_getle32(&event->status); wlinfo("Got auth event %" PRId32 " status %" PRId32 " from <%s>\n", type, status, event->src_name); bcmf_hexdump((uint8_t *)event, len, (unsigned long)event); if (type == WLC_E_SET_SSID && status == WLC_E_STATUS_SUCCESS) { /* Auth complete */ priv->auth_status = OK; nxsem_post(&priv->auth_signal); } } /* bcmf_wl_scan_event_handler must run at high priority else * race condition may occur on priv->scan_result field */ void bcmf_wl_scan_event_handler(FAR struct bcmf_dev_s *priv, FAR struct bcmf_event_s *event, unsigned int len) { FAR struct wl_escan_result *result; unsigned int escan_result_len; FAR struct wl_bss_info *curr; FAR struct wl_bss_info *bss; uint32_t event_len; int16_t worst_rssi; int worst_entry; uint32_t status; int i; int j; event_len = len; if (priv->scan_status < BCMF_SCAN_RUN) { wlinfo("Got Unexpected scan event\n"); goto exit_invalid_frame; } status = bcmf_getle32(&event->status); escan_result_len = bcmf_getle32(&event->len); len -= sizeof(struct bcmf_event_s); if (len > escan_result_len) { len = escan_result_len; } if (len == sizeof(struct wl_escan_result) - sizeof(struct wl_bss_info)) { /* Nothing to process, may be scan done event */ goto wl_escan_result_processed; } if (len < sizeof(struct wl_escan_result)) { goto exit_invalid_frame; } /* Process escan result payload */ result = (FAR struct wl_escan_result *)&event[1]; if (len < result->buflen || result->buflen < sizeof(struct wl_escan_result)) { goto exit_invalid_frame; } /* Process bss_infos */ bss = result->bss_info; for (i = 0; i < result->bss_count; i++) { worst_entry = -1; worst_rssi = 0; wlinfo("Scan result: <%.32s> " "%02x:%02x:%02x:%02x:%02x:%02x " "signal %d %d %d\n", bss->SSID, bss->BSSID.ether_addr_octet[0], bss->BSSID.ether_addr_octet[1], bss->BSSID.ether_addr_octet[2], bss->BSSID.ether_addr_octet[3], bss->BSSID.ether_addr_octet[4], bss->BSSID.ether_addr_octet[5], bss->RSSI, bss->phy_noise, bss->SNR); for (j = 0; j < priv->scan_result_entries; j++) { curr = &priv->scan_result[j]; /* Check if current bss AP is not already detected */ if (memcmp(&curr->BSSID, &bss[i].BSSID, sizeof(curr->BSSID)) == 0) { /* Replace the duplicate entry if rssi is * better than before */ if (curr->RSSI < bss[i].RSSI) { memcpy(curr, bss, sizeof(*curr)); } goto process_next_bss; } /* Find worst rssi and mark the entry */ if (curr->RSSI < worst_rssi) { worst_entry = j; worst_rssi = curr->RSSI; } } if (priv->scan_result_entries == BCMF_SCAN_RESULT_ENTRIES) { /* Entries full and replace the worst entry */ if (worst_entry >= 0) { curr = &priv->scan_result[worst_entry]; if (curr->RSSI < bss->RSSI) { memcpy(curr, bss, sizeof(*curr)); } } process_next_bss: continue; } curr = &priv->scan_result[priv->scan_result_entries]; memcpy(curr, bss, sizeof(*curr)); priv->scan_result_entries++; } wl_escan_result_processed: if (status == WLC_E_STATUS_PARTIAL) { /* More frames to come */ return; } if (status != WLC_E_STATUS_SUCCESS) { wlerr("Invalid event status %" PRId32 "\n", status); return; } /* Scan done */ wlinfo("escan done event %" PRId32 " %" PRId32 "\n", status, bcmf_getle32(&event->reason)); wd_cancel(&priv->scan_timeout); priv->scan_status = BCMF_SCAN_DONE; nxsem_post(&priv->control_mutex); return; exit_invalid_frame: wlerr("Invalid scan result event\n"); bcmf_hexdump((FAR uint8_t *)event, event_len, (unsigned long)event); } static int bcmf_wl_scan_format_results(FAR struct bcmf_dev_s *priv, FAR struct iwreq *iwr) { FAR wl_bss_info_t *scan_result[BCMF_SCAN_RESULT_ENTRIES]; FAR wl_bss_info_t *info; FAR struct iw_event *iwe; FAR char *pointer; int len; int i; int j; if (priv->scan_result_entries == 0) { iwr->u.data.length = 0; return OK; } len = IW_EV_LEN(ap_addr) + IW_EV_LEN(qual) + IW_EV_LEN(freq) + IW_EV_LEN(data) + IW_EV_LEN(essid); len *= priv->scan_result_entries; for (i = 0; i < priv->scan_result_entries; i++) { scan_result[i] = &priv->scan_result[i]; len += (min(strlen((FAR const char *)scan_result[i]->SSID), 32) + 3) & ~3; } if (iwr->u.data.pointer == NULL || iwr->u.data.length < len) { iwr->u.data.length = len; return -E2BIG; } /* Sort list by RSSI */ for (i = 0; i < priv->scan_result_entries; i++) { for (j = 0; j + 1 < priv->scan_result_entries - i; j++) { if (scan_result[j]->RSSI < scan_result[j + 1]->RSSI) { info = scan_result[j]; scan_result[j] = scan_result[j + 1]; scan_result[j + 1] = info; } } } pointer = iwr->u.data.pointer; /* Copy scan result */ for (i = 0; i < priv->scan_result_entries; i++) { info = scan_result[i]; /* Copy BSSID */ iwe = (FAR struct iw_event *)pointer; iwe->cmd = SIOCGIWAP; iwe->u.ap_addr.sa_family = ARPHRD_ETHER; memcpy(&iwe->u.ap_addr.sa_data, info->BSSID.ether_addr_octet, IFHWADDRLEN); iwe->len = IW_EV_LEN(ap_addr); pointer += iwe->len; /* Copy ESSID */ iwe = (FAR struct iw_event *)pointer; iwe->cmd = SIOCGIWESSID; iwe->u.essid.flags = 0; iwe->u.essid.length = min(strlen((FAR const char *)info->SSID), 32); iwe->u.essid.pointer = (FAR void *)sizeof(iwe->u.essid); memcpy(&iwe->u.essid + 1, info->SSID, iwe->u.essid.length); iwe->len = IW_EV_LEN(essid) + ((iwe->u.essid.length + 3) & ~3); pointer += iwe->len; /* Copy link quality info */ iwe = (FAR struct iw_event *)pointer; iwe->cmd = IWEVQUAL; iwe->u.qual.qual = info->SNR; iwe->u.qual.level = info->RSSI; iwe->u.qual.noise = info->phy_noise; iwe->u.qual.updated = IW_QUAL_DBM | IW_QUAL_ALL_UPDATED; iwe->len = IW_EV_LEN(qual); pointer += iwe->len; /* Copy AP control channel */ iwe = (FAR struct iw_event *)pointer; iwe->cmd = SIOCGIWFREQ; iwe->u.freq.e = 0; iwe->u.freq.m = info->ctl_ch; iwe->len = IW_EV_LEN(freq); pointer += iwe->len; /* Copy AP encryption mode */ iwe = (FAR struct iw_event *)pointer; iwe->cmd = SIOCGIWENCODE; iwe->u.data.flags = info->capability & DOT11_CAP_PRIVACY ? IW_ENCODE_ENABLED | IW_ENCODE_NOKEY : IW_ENCODE_DISABLED; iwe->u.data.length = 0; iwe->u.essid.pointer = NULL; iwe->len = IW_EV_LEN(data); pointer += iwe->len; } iwr->u.data.length = pointer - (FAR char *)iwr->u.data.pointer; return OK; } void bcmf_wl_scan_timeout(wdparm_t arg) { FAR struct bcmf_dev_s *priv = (FAR struct bcmf_dev_s *)arg; if (priv->scan_status < BCMF_SCAN_RUN) { /* Fatal error, invalid scan status */ wlerr("Unexpected scan timeout\n"); return; } wlerr("Scan timeout detected\n"); priv->scan_status = BCMF_SCAN_TIMEOUT; nxsem_post(&priv->control_mutex); } int bcmf_wl_get_interface(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { /* TODO resolve interface using iwr->ifr_name */ return CHIP_STA_INTERFACE; } /**************************************************************************** * Public Functions ****************************************************************************/ int bcmf_sdio_initialize(int minor, FAR struct sdio_dev_s *dev) { int ret; FAR struct bcmf_dev_s *priv; wlinfo("minor: %d\n", minor); priv = bcmf_allocate_device(); if (!priv) { return -ENOMEM; } /* Init sdio bus */ ret = bcmf_bus_sdio_initialize(priv, minor, dev); if (ret != OK) { ret = -EIO; goto exit_free_device; } /* Bus initialized, register network driver */ return bcmf_driver_initialize(priv); exit_free_device: bcmf_free_device(priv); return ret; } int bcmf_wl_enable(FAR struct bcmf_dev_s *priv, bool enable) { int ret; uint32_t out_len; /* TODO check device state */ out_len = 0; ret = bcmf_cdc_ioctl(priv, CHIP_STA_INTERFACE, true, enable ? WLC_UP : WLC_DOWN, NULL, &out_len); /* TODO wait for WLC_E_RADIO event */ nxsig_usleep(3000); if (ret == OK) { /* TODO update device state */ } return ret; } int bcmf_wl_start_scan(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { int ret; uint32_t out_len; uint32_t value; int interface; struct iw_scan_req *req; struct wl_escan_params scan_params; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } memset(&scan_params, 0, sizeof(scan_params)); scan_params.version = ESCAN_REQ_VERSION; scan_params.action = WL_SCAN_ACTION_START; scan_params.sync_id = 0xabcd; /* Not used for now */ memset(&scan_params.params.bssid, 0xff, sizeof(scan_params.params.bssid)); scan_params.params.bss_type = DOT11_BSSTYPE_ANY; scan_params.params.nprobes = -1; scan_params.params.active_time = -1; scan_params.params.passive_time = -1; scan_params.params.home_time = -1; scan_params.params.channel_num = 0; if (iwr->u.data.pointer && iwr->u.data.length >= sizeof(*req)) { req = (struct iw_scan_req *)iwr->u.data.pointer; memcpy(&scan_params.params.bssid, req->bssid.sa_data, sizeof(scan_params.params.bssid)); scan_params.params.scan_type = req->scan_type == IW_SCAN_TYPE_ACTIVE ? 0:1; if (iwr->u.data.flags & IW_SCAN_THIS_ESSID && req->essid_len < sizeof(scan_params.params.ssid.SSID)) { /* Scan specific ESSID */ memcpy(scan_params.params.ssid.SSID, req->essid, req->essid_len); scan_params.params.ssid.ssid_len = req->essid_len; } } else { /* Default scan parameters */ wlinfo("Use default scan parameters\n"); memset(&scan_params.params.bssid, 0xff, sizeof(scan_params.params.bssid)); scan_params.params.scan_type = 0; /* Active scan */ } /* Set active scan mode */ value = scan_params.params.scan_type; out_len = 4; if (bcmf_cdc_ioctl(priv, CHIP_STA_INTERFACE, true, WLC_SET_PASSIVE_SCAN, (uint8_t *)&value, &out_len)) { ret = -EIO; goto exit_failed; } /* Lock control_mutex semaphore */ if ((ret = nxsem_wait_uninterruptible(&priv->control_mutex)) < 0) { goto exit_failed; } /* Allocate buffer to store scan result */ if (priv->scan_result == NULL) { priv->scan_result = kmm_malloc(sizeof(wl_bss_info_t) * BCMF_SCAN_RESULT_ENTRIES); if (priv->scan_result == NULL) { wlerr("Cannot allocate result buffer\n"); ret = -ENOMEM; goto exit_sem_post; } } wlinfo("start scan\n"); priv->scan_result_entries = 0; priv->scan_status = BCMF_SCAN_RUN; out_len = sizeof(scan_params); if (bcmf_cdc_iovar_request_unsafe(priv, CHIP_STA_INTERFACE, true, IOVAR_STR_ESCAN, (uint8_t *)&scan_params, &out_len)) { ret = -EIO; goto exit_sem_post; } /* Start scan_timeout timer */ wd_start(&priv->scan_timeout, BCMF_SCAN_TIMEOUT_TICK, bcmf_wl_scan_timeout, (wdparm_t)priv); return OK; exit_sem_post: priv->scan_status = BCMF_SCAN_DISABLED; nxsem_post(&priv->control_mutex); exit_failed: wlinfo("Failed\n"); return ret; } int bcmf_wl_get_scan_results(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { int ret = OK; if (priv->scan_status == BCMF_SCAN_RUN) { ret = -EAGAIN; goto exit_failed; } if (priv->scan_status != BCMF_SCAN_DONE) { ret = -EINVAL; goto exit_failed; } /* Lock control_mutex semaphore to avoid race condition */ if ((ret = nxsem_wait_uninterruptible(&priv->control_mutex)) < 0) { goto exit_failed; } if (!priv->scan_result) { /* Result have already been requested */ ret = OK; iwr->u.data.length = 0; goto exit_sem_post; } ret = bcmf_wl_scan_format_results(priv, iwr); if (ret == OK) { /* Free scan result buffer */ kmm_free(priv->scan_result); priv->scan_result = NULL; priv->scan_result_entries = 0; } exit_sem_post: nxsem_post(&priv->control_mutex); exit_failed: if (ret < 0) { iwr->u.data.length = 0; } return ret; } int bcmf_wl_set_auth_param(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { int ret = -ENOSYS; int interface; uint32_t out_len; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } switch (iwr->u.param.flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: { uint32_t wpa_version[2]; uint32_t auth_mode; switch (iwr->u.param.value) { case IW_AUTH_WPA_VERSION_DISABLED: wpa_version[1] = 0; auth_mode = WPA_AUTH_DISABLED; break; case IW_AUTH_WPA_VERSION_WPA: wpa_version[1] = 1; auth_mode = WPA_AUTH_PSK; break; case IW_AUTH_WPA_VERSION_WPA2: wpa_version[1] = 1; auth_mode = WPA2_AUTH_PSK; break; default: wlerr("Invalid wpa version %" PRId32 "\n", iwr->u.param.value); return -EINVAL; } out_len = 8; wpa_version[0] = interface; if (bcmf_cdc_iovar_request(priv, interface, true, "bsscfg:"IOVAR_STR_SUP_WPA, (uint8_t *)wpa_version, &out_len)) { return -EIO; } out_len = 4; if (bcmf_cdc_ioctl(priv, interface, true, WLC_SET_WPA_AUTH, (uint8_t *)&auth_mode, &out_len)) { return -EIO; } } return OK; case IW_AUTH_CIPHER_PAIRWISE: case IW_AUTH_CIPHER_GROUP: { uint32_t cipher_mode; uint32_t wep_auth = 0; switch (iwr->u.param.value) { case IW_AUTH_CIPHER_NONE: cipher_mode = OPEN_AUTH; break; case IW_AUTH_CIPHER_WEP40: case IW_AUTH_CIPHER_WEP104: cipher_mode = WEP_ENABLED; wep_auth = 1; break; case IW_AUTH_CIPHER_TKIP: cipher_mode = TKIP_ENABLED; break; case IW_AUTH_CIPHER_CCMP: cipher_mode = AES_ENABLED; break; default: wlerr("Invalid cipher mode %" PRId32 "\n", iwr->u.param.value); return -EINVAL; } out_len = 4; if (bcmf_cdc_ioctl(priv, interface, true, WLC_SET_WSEC, (uint8_t *)&cipher_mode, &out_len)) { return -EIO; } /* Set authentication mode */ out_len = 4; if (bcmf_cdc_ioctl(priv, interface, true, WLC_SET_AUTH, (uint8_t *)&wep_auth, &out_len)) { return -EIO; } } return OK; case IW_AUTH_KEY_MGMT: case IW_AUTH_TKIP_COUNTERMEASURES: case IW_AUTH_DROP_UNENCRYPTED: case IW_AUTH_80211_AUTH_ALG: case IW_AUTH_WPA_ENABLED: case IW_AUTH_RX_UNENCRYPTED_EAPOL: case IW_AUTH_ROAMING_CONTROL: case IW_AUTH_PRIVACY_INVOKED: default: wlerr("Unknown cmd %d\n", iwr->u.param.flags); break; } return ret; } int bcmf_wl_set_mode(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; uint32_t value; int interface; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(value); value = iwr->u.mode == IW_MODE_INFRA ? 1 : 0; return bcmf_cdc_ioctl(priv, interface, true, WLC_SET_INFRA, (uint8_t *)&value, &out_len); } int bcmf_wl_get_mode(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; uint32_t infra; int interface; uint32_t ap; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(infra); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_INFRA, (uint8_t *)&infra, &out_len); if (ret == OK) { out_len = sizeof(ap); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_AP, (uint8_t *)&ap, &out_len); } if (ret == OK) { if (infra == 0) { iwr->u.mode = IW_MODE_ADHOC; } else if (ap) { iwr->u.mode = IW_MODE_MASTER; } else { iwr->u.mode = IW_MODE_INFRA; } } return ret; } int bcmf_wl_set_bssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; int interface; int ap = 0; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(ap); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_AP, (uint8_t *)&ap, &out_len); if (ret == OK) { out_len = sizeof(struct ether_addr); ret = bcmf_cdc_ioctl(priv, interface, true, (ap ? WLC_SET_BSSID : WLC_REASSOC), (uint8_t *)iwr->u.ap_addr.sa_data, &out_len); } return ret; } int bcmf_wl_get_bssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; int interface; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } iwr->u.ap_addr.sa_family = ARPHRD_ETHER; out_len = sizeof(struct ether_addr); return bcmf_cdc_ioctl(priv, interface, false, WLC_GET_BSSID, (uint8_t *)iwr->u.ap_addr.sa_data, &out_len); } int bcmf_wl_get_channel(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { channel_info_t ci; uint32_t out_len; int interface; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(ci); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_CHANNEL, (uint8_t *)&ci, &out_len); if (ret == OK) { iwr->u.freq.m = bcmf_wl_channel_to_frequency(ci.target_channel); } return ret; } int bcmf_wl_get_rate(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; uint32_t rate; int interface; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(rate); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_RATE, (uint8_t *)&rate, &out_len); if (ret == OK) { iwr->u.bitrate.value = ((rate / 2) * 1000) + ((rate & 1) ? 500 : 0); iwr->u.bitrate.fixed = 1; } return ret; } int bcmf_wl_get_txpower(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; int interface; int radio; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } out_len = sizeof(radio); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_RADIO, (uint8_t *)&radio, &out_len); if (ret == OK) { out_len = sizeof(iwr->u.txpower.value); ret = bcmf_cdc_iovar_request(priv, interface, false, IOVAR_STR_QTXPOWER, (uint8_t *)&(iwr->u.txpower.value), &out_len); if (ret == OK) { iwr->u.txpower.value &= ~WL_TXPWR_OVERRIDE; iwr->u.txpower.value /= 4; iwr->u.txpower.fixed = 0; iwr->u.txpower.disabled = radio; iwr->u.txpower.flags = IW_TXPOW_DBM; } } return ret; } int bcmf_wl_get_iwrange(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { struct iw_range *range; channel_info_t ci; uint32_t out_len; int interface; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } if (iwr->u.data.length < sizeof(struct iw_range)) { return -EINVAL; } range = iwr->u.data.pointer; memset(range, 0, sizeof(*range)); out_len = sizeof(ci); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_CHANNEL, (uint8_t *)&ci, &out_len); if (ret == OK) { range->num_frequency = 1; range->freq[0].m = bcmf_wl_channel_to_frequency(ci.target_channel); range->freq[0].i = ci.target_channel; } return OK; } int bcmf_wl_get_rssi(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { wl_sta_rssi_t rssi; uint32_t out_len; int interface; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } memset(&rssi.sta_addr, 0x0, sizeof(rssi.sta_addr)); out_len = sizeof(rssi); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_RSSI, (uint8_t *)&rssi, &out_len); if (ret == OK) { iwr->u.sens.value = -rssi.rssi; } return ret; } int bcmf_wl_set_encode_ext(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { int interface; struct iw_encode_ext *ext; uint32_t out_len; wsec_pmk_t psk; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } ext = (struct iw_encode_ext *)iwr->u.encoding.pointer; switch (ext->alg) { case IW_ENCODE_ALG_TKIP: break; case IW_ENCODE_ALG_CCMP: break; case IW_ENCODE_ALG_NONE: case IW_ENCODE_ALG_WEP: default: wlerr("Unknown algo %d\n", ext->alg); return -EINVAL; } memset(&psk, 0, sizeof(wsec_pmk_t)); memcpy(psk.key, &ext->key, ext->key_len); psk.key_len = ext->key_len; psk.flags = WSEC_PASSPHRASE; out_len = sizeof(psk); return bcmf_cdc_ioctl(priv, interface, true, WLC_SET_WSEC_PMK, (uint8_t *)&psk, &out_len); } int bcmf_wl_set_ssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { int ret; int interface; uint32_t out_len; wlc_ssid_t ssid; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } ssid.ssid_len = iwr->u.essid.length; memcpy(ssid.SSID, iwr->u.essid.pointer, iwr->u.essid.length); /* Configure AP SSID and trig authentication request */ out_len = sizeof(ssid); if (bcmf_cdc_ioctl(priv, interface, true, WLC_SET_SSID, (uint8_t *)&ssid, &out_len)) { return -EIO; } ret = bcmf_sem_wait(&priv->auth_signal, BCMF_AUTH_TIMEOUT_MS); wlinfo("semwait done ! %d\n", ret); if (ret < 0) { wlerr("Associate request timeout\n"); return ret; } switch (priv->auth_status) { case OK: wlinfo("AP Join ok\n"); break; default: wlerr("AP join failed %d\n", priv->auth_status); return -EINVAL; } return OK; } int bcmf_wl_get_ssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr) { uint32_t out_len; wlc_ssid_t ssid; int interface; int ret; interface = bcmf_wl_get_interface(priv, iwr); if (interface < 0) { return -EINVAL; } /* Configure AP SSID and trig authentication request */ out_len = sizeof(ssid); ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_SSID, (uint8_t *)&ssid, &out_len); if (ret == OK) { iwr->u.essid.flags = iwr->u.data.flags = 1; iwr->u.essid.length = iwr->u.data.length = ssid.ssid_len + 1; memcpy(iwr->u.essid.pointer, ssid.SSID, iwr->u.essid.length); } return ret; }