1208 lines
32 KiB
C
1208 lines
32 KiB
C
/****************************************************************************
|
|
* drivers/wireless/ieee80211/bcmf_driver.c
|
|
*
|
|
* Copyright (C) 2017 Gregory Nutt. All rights reserved.
|
|
* Author: Simon Piriou <spiriou31@gmail.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
#include <nuttx/compiler.h>
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
|
|
#include <net/ethernet.h>
|
|
|
|
#include <nuttx/kmalloc.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 10000
|
|
#define BCMF_SCAN_RESULT_SIZE 1024
|
|
|
|
/* Helper to get iw_event size */
|
|
|
|
#define BCMF_IW_EVENT_SIZE(field) \
|
|
(offsetof(struct iw_event, u)+sizeof(((union iwreq_data*)0)->field))
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* AP scan state machine status */
|
|
|
|
enum
|
|
{
|
|
BCMF_SCAN_TIMEOUT = 0,
|
|
BCMF_SCAN_DISABLED,
|
|
BCMF_SCAN_RUN,
|
|
BCMF_SCAN_DONE
|
|
};
|
|
|
|
/****************************************************************************
|
|
* 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);
|
|
|
|
// 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
|
|
****************************************************************************/
|
|
|
|
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 = sem_init(&priv->control_mutex, 0, 1)) != OK)
|
|
{
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
if ((ret = sem_init(&priv->control_timeout, 0, 0)) != OK)
|
|
{
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
if ((ret = sem_setprotocol(&priv->control_timeout, SEM_PRIO_NONE)) != OK)
|
|
{
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
/* Init authentication signal semaphore */
|
|
|
|
if ((ret = sem_init(&priv->auth_signal, 0, 0)) != OK)
|
|
{
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
if ((ret = sem_setprotocol(&priv->auth_signal, SEM_PRIO_NONE)) != OK)
|
|
{
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
/* Init scan timeout timer */
|
|
|
|
priv->scan_status = BCMF_SCAN_DISABLED;
|
|
priv->scan_timeout = wd_create();
|
|
if (!priv->scan_timeout)
|
|
{
|
|
ret = -ENOMEM;
|
|
goto exit_free_priv;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* Disable TX Gloming feature */
|
|
|
|
out_len = 4;
|
|
*(uint32_t *)tmp_buf = 0;
|
|
ret = bcmf_cdc_iovar_request(priv, interface, false,
|
|
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,
|
|
(uint8_t *)&value,
|
|
&out_len);
|
|
|
|
/* TODO configure EAPOL version to default */
|
|
|
|
out_len = 8;
|
|
((uint32_t *)tmp_buf)[0] = interface;
|
|
((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 %d 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)
|
|
{
|
|
// wlinfo("Got radio event %d from <%s>\n", bcmf_getle32(&event->type),
|
|
// event->src_name);
|
|
}
|
|
|
|
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 %d from <%s>\n", type, 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;
|
|
|
|
sem_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,
|
|
struct bcmf_event_s *event, unsigned int len)
|
|
{
|
|
uint32_t status;
|
|
uint32_t event_len;
|
|
struct wl_escan_result *result;
|
|
struct wl_bss_info *bss;
|
|
unsigned int bss_info_len;
|
|
unsigned int escan_result_len;
|
|
unsigned int bss_count = 0;
|
|
|
|
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 = (struct wl_escan_result *)&event[1];
|
|
|
|
if (len < result->buflen || result->buflen < sizeof(struct wl_escan_result))
|
|
{
|
|
goto exit_invalid_frame;
|
|
}
|
|
|
|
/* wl_escan_result structure cointains a wl_bss_info field */
|
|
|
|
len = result->buflen - sizeof(struct wl_escan_result)
|
|
+ sizeof(struct wl_bss_info);
|
|
|
|
/* Process bss_infos */
|
|
|
|
bss = result->bss_info;
|
|
|
|
while (len > 0 && bss_count < result->bss_count)
|
|
{
|
|
struct iw_event *iwe;
|
|
unsigned int result_size;
|
|
size_t essid_len;
|
|
size_t essid_len_aligned;
|
|
uint8_t *ie_buffer;
|
|
unsigned int ie_offset;
|
|
unsigned int check_offset;
|
|
|
|
result_size = BCMF_SCAN_RESULT_SIZE - priv->scan_result_size;
|
|
bss_info_len = bss->length;
|
|
|
|
if (len < bss_info_len)
|
|
{
|
|
wlerr("bss_len error %d %d\n", len, bss_info_len);
|
|
goto exit_invalid_frame;
|
|
}
|
|
|
|
/* Append current bss_info to priv->scan_results
|
|
* FIXME protect this against race conditions
|
|
*/
|
|
|
|
/* Check if current bss AP is not already detected */
|
|
|
|
check_offset = 0;
|
|
|
|
while (priv->scan_result_size - check_offset
|
|
>= offsetof(struct iw_event, u))
|
|
{
|
|
iwe = (struct iw_event*)&priv->scan_result[check_offset];
|
|
|
|
if (iwe->cmd == SIOCGIWAP)
|
|
{
|
|
if (memcmp(&iwe->u.ap_addr.sa_data, bss->BSSID.ether_addr_octet,
|
|
sizeof(bss->BSSID.ether_addr_octet)) == 0)
|
|
{
|
|
goto process_next_bss;
|
|
}
|
|
}
|
|
|
|
check_offset += iwe->len;
|
|
}
|
|
|
|
wlinfo("Scan result: <%.32s> %02x:%02x:%02x:%02x:%02x:%02x\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]);
|
|
|
|
/* Copy BSSID */
|
|
|
|
if (result_size < BCMF_IW_EVENT_SIZE(ap_addr))
|
|
{
|
|
goto scan_result_full;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(ap_addr);
|
|
iwe->cmd = SIOCGIWAP;
|
|
memcpy(&iwe->u.ap_addr.sa_data, bss->BSSID.ether_addr_octet,
|
|
sizeof(bss->BSSID.ether_addr_octet));
|
|
iwe->u.ap_addr.sa_family = ARPHRD_ETHER;
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(ap_addr);
|
|
result_size -= BCMF_IW_EVENT_SIZE(ap_addr);
|
|
|
|
/* Copy ESSID */
|
|
|
|
essid_len = min(strlen((const char*)bss->SSID), 32);
|
|
essid_len_aligned = (essid_len + 3) & -4;
|
|
|
|
if (result_size < BCMF_IW_EVENT_SIZE(essid)+essid_len_aligned)
|
|
{
|
|
goto scan_result_full;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(essid)+essid_len_aligned;
|
|
iwe->cmd = SIOCGIWESSID;
|
|
iwe->u.essid.flags = 0;
|
|
iwe->u.essid.length = essid_len;
|
|
|
|
/* Special processing for iw_point, set offset in pointer field */
|
|
|
|
iwe->u.essid.pointer = (FAR void*)sizeof(iwe->u.essid);
|
|
memcpy(&iwe->u.essid+1, bss->SSID, essid_len);
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(essid)+essid_len_aligned;
|
|
result_size -= BCMF_IW_EVENT_SIZE(essid)+essid_len_aligned;
|
|
|
|
/* Copy link quality info */
|
|
|
|
if (result_size < BCMF_IW_EVENT_SIZE(qual))
|
|
{
|
|
goto scan_result_full;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(qual);
|
|
iwe->cmd = IWEVQUAL;
|
|
iwe->u.qual.qual = bss->SNR;
|
|
wlinfo("signal %d %d %d\n", bss->RSSI, bss->phy_noise, bss->SNR);
|
|
iwe->u.qual.level = bss->RSSI;
|
|
iwe->u.qual.noise = bss->phy_noise;
|
|
iwe->u.qual.updated = IW_QUAL_DBM | IW_QUAL_ALL_UPDATED;
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(qual);
|
|
result_size -= BCMF_IW_EVENT_SIZE(qual);
|
|
|
|
/* Copy AP mode */
|
|
|
|
if (result_size < BCMF_IW_EVENT_SIZE(mode))
|
|
{
|
|
goto scan_result_full;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(mode);
|
|
iwe->cmd = SIOCGIWMODE;
|
|
if (bss->capability & DOT11_CAP_ESS)
|
|
{
|
|
iwe->u.mode = IW_MODE_INFRA;
|
|
}
|
|
else if (bss->capability & DOT11_CAP_IBSS)
|
|
{
|
|
iwe->u.mode = IW_MODE_ADHOC;
|
|
}
|
|
else
|
|
{
|
|
iwe->u.mode = IW_MODE_AUTO;
|
|
}
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(mode);
|
|
result_size -= BCMF_IW_EVENT_SIZE(mode);
|
|
|
|
/* Copy AP encryption mode */
|
|
|
|
if (result_size < BCMF_IW_EVENT_SIZE(data))
|
|
{
|
|
goto scan_result_full;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(data);
|
|
iwe->cmd = SIOCGIWENCODE;
|
|
iwe->u.data.flags = bss->capability & DOT11_CAP_PRIVACY ?
|
|
IW_ENCODE_ENABLED | IW_ENCODE_NOKEY :
|
|
IW_ENCODE_DISABLED;
|
|
iwe->u.data.length = 0;
|
|
iwe->u.essid.pointer = NULL;
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(data);
|
|
result_size -= BCMF_IW_EVENT_SIZE(data);
|
|
|
|
/* Copy relevant raw IE frame */
|
|
|
|
if (bss->ie_offset >= bss_info_len ||
|
|
bss->ie_length > bss_info_len-bss->ie_offset)
|
|
{
|
|
goto process_next_bss;
|
|
}
|
|
|
|
ie_offset = 0;
|
|
ie_buffer = (uint8_t*)bss + bss->ie_offset;
|
|
|
|
while (1)
|
|
{
|
|
size_t ie_frame_size;
|
|
|
|
if (bss->ie_length - ie_offset < 2)
|
|
{
|
|
/* Minimum Information element size is 2 bytes */
|
|
break;
|
|
}
|
|
|
|
ie_frame_size = ie_buffer[ie_offset+1] + 2;
|
|
|
|
if (ie_frame_size > bss->ie_length - ie_offset)
|
|
{
|
|
/* Entry too big */
|
|
break;
|
|
}
|
|
|
|
switch (ie_buffer[ie_offset])
|
|
{
|
|
case IEEE80211_ELEMID_RSN:
|
|
{
|
|
size_t ie_frame_size_aligned;
|
|
ie_frame_size_aligned = (ie_frame_size + 3) & -4;
|
|
wlinfo("found RSN\n");
|
|
if (result_size < BCMF_IW_EVENT_SIZE(data) + ie_frame_size_aligned)
|
|
{
|
|
break;
|
|
}
|
|
|
|
iwe = (struct iw_event*)&priv->scan_result[priv->scan_result_size];
|
|
iwe->len = BCMF_IW_EVENT_SIZE(data)+ie_frame_size_aligned;
|
|
iwe->cmd = IWEVGENIE;
|
|
iwe->u.data.flags = 0;
|
|
iwe->u.data.length = ie_frame_size;
|
|
iwe->u.data.pointer = (FAR void*)sizeof(iwe->u.data);
|
|
memcpy(&iwe->u.data+1, &ie_buffer[ie_offset], ie_frame_size);
|
|
|
|
priv->scan_result_size += BCMF_IW_EVENT_SIZE(essid)+ie_frame_size_aligned;
|
|
result_size -= BCMF_IW_EVENT_SIZE(essid)+ie_frame_size_aligned;
|
|
break;
|
|
}
|
|
default:
|
|
// wlinfo("unhandled IE entry %d %d\n", ie_buffer[ie_offset],
|
|
// ie_buffer[ie_offset+1]);
|
|
break;
|
|
}
|
|
|
|
ie_offset += ie_buffer[ie_offset+1] + 2;
|
|
}
|
|
|
|
goto process_next_bss;
|
|
|
|
scan_result_full:
|
|
/* Continue instead of break to log dropped AP results */
|
|
|
|
wlerr("No more space in scan_result buffer\n");
|
|
|
|
process_next_bss:
|
|
/* Process next bss_info */
|
|
|
|
len -= bss_info_len;
|
|
bss = (struct wl_bss_info *)((uint8_t *)bss + bss_info_len);
|
|
bss_count += 1;
|
|
}
|
|
|
|
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 %d\n", status);
|
|
return;
|
|
}
|
|
|
|
/* Scan done */
|
|
|
|
wlinfo("escan done event %d %d\n", status, bcmf_getle32(&event->reason));
|
|
|
|
wd_cancel(priv->scan_timeout);
|
|
|
|
priv->scan_status = BCMF_SCAN_DONE;
|
|
sem_post(&priv->control_mutex);
|
|
|
|
return;
|
|
|
|
exit_invalid_frame:
|
|
wlerr("Invalid scan result event\n");
|
|
bcmf_hexdump((uint8_t *)event, event_len, (unsigned long)event);
|
|
}
|
|
|
|
void bcmf_wl_scan_timeout(int argc, wdparm_t arg1, ...)
|
|
{
|
|
FAR struct bcmf_dev_s *priv = (FAR struct bcmf_dev_s *)arg1;
|
|
|
|
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;
|
|
sem_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 chek 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 */
|
|
|
|
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 = sem_wait(&priv->control_mutex)) != OK)
|
|
{
|
|
goto exit_failed;
|
|
}
|
|
|
|
/* Allocate buffer to store scan result */
|
|
|
|
if (priv->scan_result == NULL)
|
|
{
|
|
priv->scan_result = kmm_malloc(BCMF_SCAN_RESULT_SIZE);
|
|
if (priv->scan_result == NULL)
|
|
{
|
|
wlerr("Cannot allocate result buffer\n");
|
|
ret = -ENOMEM;
|
|
goto exit_sem_post;
|
|
}
|
|
}
|
|
|
|
wlinfo("start scan\n");
|
|
|
|
priv->scan_result_size = 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;
|
|
sem_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 = sem_wait(&priv->control_mutex)) != OK)
|
|
{
|
|
ret = -EIO;
|
|
goto exit_failed;
|
|
}
|
|
|
|
if (!priv->scan_result)
|
|
{
|
|
/* Result have already been requested */
|
|
|
|
ret = OK;
|
|
iwr->u.data.length = 0;
|
|
goto exit_sem_post;
|
|
}
|
|
|
|
if (iwr->u.data.pointer == NULL ||
|
|
iwr->u.data.length < priv->scan_result_size)
|
|
{
|
|
/* Stat request, return scan_result_size */
|
|
|
|
ret = -E2BIG;
|
|
iwr->u.data.pointer = NULL;
|
|
iwr->u.data.length = priv->scan_result_size;
|
|
goto exit_sem_post;
|
|
}
|
|
|
|
if (priv->scan_result_size <= 0)
|
|
{
|
|
ret = OK;
|
|
iwr->u.data.length = 0;
|
|
goto exit_free_buffer;
|
|
}
|
|
|
|
/* Copy result to user buffer */
|
|
|
|
if (iwr->u.data.length > priv->scan_result_size)
|
|
{
|
|
iwr->u.data.length = priv->scan_result_size;
|
|
}
|
|
|
|
memcpy(iwr->u.data.pointer, priv->scan_result, iwr->u.data.length);
|
|
|
|
exit_free_buffer:
|
|
/* Free scan result buffer */
|
|
|
|
kmm_free(priv->scan_result);
|
|
priv->scan_result = NULL;
|
|
priv->scan_result_size = 0;
|
|
|
|
exit_sem_post:
|
|
sem_post(&priv->control_mutex);
|
|
|
|
exit_failed:
|
|
if (ret)
|
|
{
|
|
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 %d\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_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 %d\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)
|
|
{
|
|
int interface;
|
|
uint32_t out_len;
|
|
uint32_t value;
|
|
|
|
interface = bcmf_wl_get_interface(priv, iwr);
|
|
|
|
if (interface < 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
out_len = 4;
|
|
value = iwr->u.mode == IW_MODE_INFRA ? 1 : 0;
|
|
if (bcmf_cdc_ioctl(priv, interface, true,
|
|
WLC_SET_INFRA, (uint8_t *)&value, &out_len))
|
|
{
|
|
return -EIO;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
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)
|
|
{
|
|
wlerr("Associate request timeout\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
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;
|
|
}
|