nuttx/drivers/wireless/ieee80211/bcm43xxx/bcmf_driver.c
chao.an e6a23e7b8a wireless/bcm43xxx: sort scan result by rssi
1. Replace SCAN_RESULT_SIZE to SCAN_RESULT_ENTRIES
2. filter scan result with better rssi
3. Sort scan result by rssi

Signed-off-by: chao.an <anchao@xiaomi.com>
2022-06-18 19:42:30 +08:00

1680 lines
40 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <nuttx/compiler.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <net/ethernet.h>
#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/wdog.h>
#include <nuttx/sdio.h>
#include <nuttx/net/arp.h>
#include <nuttx/wireless/ieee80211/ieee80211.h>
#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;
}