/****************************************************************************
 * 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_core.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define DOT11_BSSTYPE_ANY         2
#define BCMF_SCAN_TIMEOUT_TICK    (5*CLOCKS_PER_SEC)
#define BCMF_AUTH_TIMEOUT_MS      15000  /* was 10000 */
#define BCMF_SCAN_RESULT_ENTRIES  CONFIG_IEEE80211_BROADCOM_SCAN_RESULT_ENTRIES

/* CLM file is cut into pieces of MAX_CHUNK_LEN.
 *
 * NOTE:  CONFIG_NET_ETH_PKTSIZE is the MTU plus the size of the Ethernet
 * header (14 bytes).
 */

#define MAX_CHUNK_LEN \
  (CONFIG_NET_ETH_PKTSIZE > 1514 ? 1400 : CONFIG_NET_ETH_PKTSIZE - 114)

/* 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

#define WPA_OUI_LEN           3               /* WPA OUI length */
#define WPA_VERSION_LEN       2               /* WPA version length */
#define WLAN_WPA_OUI          0xf25000
#define WLAN_WPA_OUI_TYPE     0x01
#define WLAN_WPA_SEL(x)       (((x) << 24) | WLAN_WPA_OUI)
#define WLAN_AKM_PSK          0x02
#define SUITE(oui, id)        (((oui) << 8) | (id))
#define WLAN_AKM_SUITE_PSK    SUITE(0x000FAC, WLAN_AKM_PSK)

/****************************************************************************
 * 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
};

begin_packed_struct struct wpa_cipher_suite
{
  uint8_t oui[3];
  uint8_t type;
} end_packed_struct;

typedef struct wpa_cipher_suite wpa_cipher_suite_t;

begin_packed_struct struct wpa_rsn
{
  uint16_t            version;
  wpa_cipher_suite_t  group;
  uint16_t            scount;
  wpa_cipher_suite_t  pairwise[0];
} end_packed_struct;

typedef struct wpa_rsn wpa_rsn_t;

begin_packed_struct struct wpa_akm
{
  uint16_t            scount;
  wpa_cipher_suite_t  suite[0];
} end_packed_struct;

typedef struct wpa_akm wpa_akm_t;

begin_packed_struct struct wpa_ie_fixed
{
  uint8_t tag;                  /* TAG */
  uint8_t length;               /* TAG length */
  uint8_t oui[3];               /* IE OUI */
  uint8_t oui_type;             /* OUI type */
  begin_packed_struct struct
    {
      uint8_t low;
      uint8_t high;
    }
  end_packed_struct version;    /* IE version */
} end_packed_struct;

typedef struct wpa_ie_fixed wpa_ie_fixed_t;

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

#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_radio_event_handler(FAR struct bcmf_dev_s *priv,
                                        FAR struct bcmf_event_s *event,
                                        unsigned int len);

static void bcmf_wl_scan_event_handler(FAR struct bcmf_dev_s *priv,
                                       FAR struct bcmf_event_s *event,
                                       unsigned int len);

static void bcmf_wl_auth_event_handler(FAR struct bcmf_dev_s *priv,
                                       FAR struct bcmf_event_s *event,
                                       unsigned int len);

static int bcmf_wl_get_interface(FAR struct bcmf_dev_s *priv,
                                 FAR struct iwreq *iwr);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: bcmf_wl_channel_to_frequency
 ****************************************************************************/

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 */
}

/****************************************************************************
 * Name: bcmf_wl_set_mac_address
 ****************************************************************************/

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,
                              (FAR 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;
}

/****************************************************************************
 * Name: bcmf_driver_download_clm
 ****************************************************************************/

#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             = (FAR 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 bcmf_interface_dev_t *ibus = (FAR bcmf_interface_dev_t *)priv->bus;
  FAR uint8_t *srcbuff = ibus->chip->clm_blob_image;
  FAR uint8_t *downloadbuff;
  unsigned int datalen = *ibus->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 */

/****************************************************************************
 * Name: bcmf_wl_active
 ****************************************************************************/

int bcmf_wl_active(FAR struct bcmf_dev_s *priv, bool active)
{
  int interface = CHIP_STA_INTERFACE;
  uint8_t tmp_buf[64];
  uint32_t out_len;
  uint32_t value;
  int ret;

  ninfo("Entered\n");

  ret = bcmf_bus_interface_active(priv, active);
  if (ret != OK || !active)
    {
      return ret;
    }

  ninfo("Interface activated\n");

#ifdef CONFIG_IEEE80211_BROADCOM_HAVE_CLM
  /* Download CLM blob if needed */

  ret = bcmf_driver_download_clm(priv);
  if (ret != OK)
    {
      goto errout_in_sdio_active;
    }
#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)
    {
      goto errout_in_sdio_active;
    }

  /* Set default power save mode */

  out_len = 4;
  value   = PM_OFF;
  ret     = bcmf_cdc_ioctl(priv, interface, true, WLC_SET_PM,
                           (FAR uint8_t *)&value, &out_len);
  if (ret != OK)
    {
      goto errout_in_sdio_active;
    }

  /* Set the GMode to auto */

  out_len = 4;
  value = GMODE_AUTO;
  ret = bcmf_cdc_ioctl(priv, interface, true, WLC_SET_GMODE,
                       (FAR uint8_t *)&value, &out_len);
  if (ret != OK)
    {
      goto errout_in_sdio_active;
    }

  /* 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);
  if (ret != OK)
    {
      goto errout_in_sdio_active;
    }

  /* 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;

  ret = bcmf_cdc_iovar_request(priv, interface, true,
                               "bsscfg:"IOVAR_STR_SUP_WPA2_EAPVER,
                               tmp_buf, &out_len);
  if (ret != OK)
    {
      goto errout_in_sdio_active;
    }

  /* 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)
    {
      goto errout_in_sdio_active;
    }

  tmp_buf[sizeof(tmp_buf)-1] = 0;

  /* Remove line feed */

  out_len = strlen((FAR 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);

  ret = bcmf_event_push_config(priv);

errout_in_sdio_active:
  if (ret != OK)
    {
      bcmf_bus_interface_active(priv, false);
    }

  return ret;
}

/****************************************************************************
 * Name: bcmf_driver_initialize
 ****************************************************************************/

int bcmf_driver_initialize(FAR struct bcmf_dev_s *priv)
{
  /*  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);

  /* Register network driver */

  return bcmf_netdev_register(priv);
}

/****************************************************************************
 * Name: bcmf_wl_radio_event_handler
 ****************************************************************************/

void bcmf_wl_radio_event_handler(FAR struct bcmf_dev_s *priv,
                                 FAR struct bcmf_event_s *event,
                                 unsigned int len)
{
  wlinfo("Unhandled radio event from <%s>\n", event->src_name);
}

/****************************************************************************
 * Name: bcmf_wl_auth_event_handler
 ****************************************************************************/

void bcmf_wl_auth_event_handler(FAR struct bcmf_dev_s *priv,
                                FAR struct bcmf_event_s *event,
                                unsigned int len)
{
  bool auth = false;
  int carrier = -1;
  uint32_t reason;
  uint32_t status;
  uint32_t type;

  type = bcmf_getle32(&event->type);
  status = bcmf_getle32(&event->status);
  reason = bcmf_getle32(&event->reason);

  wlinfo("Got auth event %" PRId32 " status %" PRId32 " from <%s>\n",
         type, status, event->src_name);

  if (!priv->bc_bifup)
    {
      return;
    }

  if (!priv->bc_bifup)
    {
      return;
    }

  bcmf_hexdump((uint8_t *)event, len, (unsigned long)event);

  if (type == WLC_E_PSK_SUP)
    {
      carrier = (reason == WLC_E_SUP_OTHER) ? 1 : 0;
      if (priv->auth_pending)
        {
          priv->auth_status = reason;
          auth = true;
        }
    }
  else if (type == WLC_E_SET_SSID)
    {
      carrier = (status == WLC_E_STATUS_SUCCESS) ? 1 : 0;
      if (!priv->auth_pending || !carrier)
        {
          priv->auth_status = status;
          auth = true;
        }
    }
  else if (type == WLC_E_DISASSOC)
    {
      carrier = 0;
    }

  if (carrier >= 0)
    {
      if (carrier)
        {
          netdev_carrier_on(&priv->bc_dev);
        }
      else
        {
          netdev_carrier_off(&priv->bc_dev);
        }
    }

  if (auth && priv->auth_signal)
    {
      nxsem_post(priv->auth_signal);
    }
}

/****************************************************************************
 * Name: bcmf_wl_scan_event_handler
 ****************************************************************************/

/* 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 wl_bss_info_t *curr;
  FAR wl_bss_info_t *bss;
  unsigned int ie_offset;
  FAR uint8_t *ie_buffer;
  uint32_t event_len;
  int16_t worst_rssi;
  int worst_entry;
  uint32_t status;
  bool vaild_bss;
  int suitelen;
  int i;
  int j;

  event_len = len;

  if (priv->scan_status < BCMF_SCAN_RUN)
    {
      wlinfo("Got Unexpected scan event\n");
      goto exit_invalid_frame;
    }

  wlinfo("Scan event from <%s>\n", event->src_name);

  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 */

  for (i = 0; i < result->bss_count; i++)
    {
      bss = &result->bss_info[i];

      worst_entry = -1;
      worst_rssi = 0;
      vaild_bss = true;

      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);

      if (strnlen((FAR const char *)bss->SSID,
                   sizeof(bss->SSID)) == 0)
        {
          continue;
        }

      if (bss->ctl_ch == 0)
        {
          continue;
        }

      ie_offset = 0;
      ie_buffer = (FAR 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:
                {
                  FAR wpa_rsn_t *rsn = (FAR wpa_rsn_t *)
                                       &ie_buffer[ie_offset + 2];
                  FAR wpa_akm_t *akm;

                  if (rsn->version != WPA_VERSION)
                    {
                      goto process_next_bss;
                    }

                  vaild_bss = false;

                  suitelen = sizeof(*rsn) + rsn->scount *
                             sizeof(wpa_cipher_suite_t);

                  if (ie_buffer[ie_offset + 1] > suitelen + 2)
                    {
                      akm = (FAR wpa_akm_t *)
                            &ie_buffer[ie_offset + suitelen + 2];
                      for (j = 0; j < akm->scount; j++)
                        {
                          uint32_t suite = ntohl
                                   (*(FAR uint32_t *)&akm->suite[j]);
                          if (suite == WLAN_AKM_SUITE_PSK)
                            {
                              goto vaild_bss;
                            }
                        }
                    }
                  break;
                }

              case IEEE80211_ELEMID_VENDOR:
                {
                  FAR wpa_ie_fixed_t *ie = (FAR wpa_ie_fixed_t *)
                                           &ie_buffer[ie_offset];
                  FAR wpa_akm_t *akm;
                  FAR wpa_rsn_t *rsn;

                  /* WPA_OUI */

                  if (memcmp(&ie->oui[0], "\x00\x50\xf2\x01", 4))
                    {
                      break;
                    }

                  vaild_bss = false;

                  rsn = (FAR wpa_rsn_t *)&ie_buffer[ie_offset +
                        sizeof(wpa_ie_fixed_t) - 2];
                  suitelen = sizeof(wpa_ie_fixed_t) +
                             sizeof(*rsn) + rsn->scount *
                             sizeof(wpa_cipher_suite_t) - 2;
                  if (ie_buffer[ie_offset + 1] + 2 > suitelen)
                    {
                      akm = (FAR wpa_akm_t *)&ie_buffer[ie_offset +
                                                        suitelen];
                      for (j = 0; j < akm->scount; j++)
                        {
                          if (*(FAR uint32_t *)&akm->suite[j] ==
                              WLAN_WPA_SEL(WLAN_AKM_PSK))
                            {
                              goto vaild_bss;
                            }
                        }
                    }
                  break;
                }

              default:
                break;
            }

          ie_offset += ie_buffer[ie_offset + 1] + 2;
        }

      /* Check if AP is configured for WEP or unsupport privacy */

      if ((vaild_bss && (bss->capability & DOT11_CAP_PRIVACY)) || !vaild_bss)
        {
          goto process_next_bss;
        }

vaild_bss:

      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 ||
              memcmp(&curr->SSID, &bss[i].SSID,
                     sizeof(curr->SSID)) == 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);
}

/****************************************************************************
 * Name: bcmf_wl_scan_format_results
 ****************************************************************************/

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;
}

/****************************************************************************
 * Name: bcmf_wl_scan_timeout
 ****************************************************************************/

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);
}

/****************************************************************************
 * Name: bcmf_wl_get_interface
 ****************************************************************************/

int bcmf_wl_get_interface(FAR struct bcmf_dev_s *priv, FAR struct iwreq *iwr)
{
  /* TODO resolve interface using iwr->ifr_name */

  return CHIP_STA_INTERFACE;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: bcmf_allocate_device
 ****************************************************************************/

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;
    }

  /* Init scan timeout timer */

  priv->scan_status = BCMF_SCAN_DISABLED;

  return priv;

exit_free_priv:
  kmm_free(priv);
  return NULL;
}

/****************************************************************************
 * Name: bcmf_free_device
 ****************************************************************************/

void bcmf_free_device(FAR struct bcmf_dev_s *priv)
{
  /* ## TODO ## deinitialize device structures */

  kmm_free(priv);
}

/****************************************************************************
 * Name: bcmf_wl_enable
 ****************************************************************************/

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;
}

/****************************************************************************
 * Name: bcmf_wl_start_scan
 *
 * Description:
 *   Start a WiFi scan.
 ****************************************************************************/

int bcmf_wl_start_scan(FAR struct bcmf_dev_s *priv, FAR struct iwreq *iwr)
{
  int ret;
  uint32_t out_len;
  uint32_t value;
  int interface;
  FAR 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 = (FAR 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, (FAR 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,
                                    (FAR 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);

  wlinfo("scan complete\n");

exit_failed:
  wlinfo("Failed\n");
  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_get_scan_results
 *
 * Description:
 *   Get the results of a WiFi scan.
 ****************************************************************************/

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;
}

/****************************************************************************
 * Name: bcmf_wl_set_auth_param
 *
 * Description:
 *   Set the authorization parameters for the device
 ****************************************************************************/

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,
                                     (FAR uint8_t *)wpa_version,
                                     &out_len))
            {
              return -EIO;
            }

          out_len = 4;
          if (bcmf_cdc_ioctl(priv, interface, true, WLC_SET_WPA_AUTH,
                             (FAR 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,
                             (FAR uint8_t *)&cipher_mode,
                             &out_len))
            {
              return -EIO;
            }

          /* Set authentication mode */

          out_len = 4;
          if (bcmf_cdc_ioctl(priv, interface, true,
                             WLC_SET_AUTH,
                             (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_set_mode
 *
 * Description:
 *   Set the mode for the device
 ****************************************************************************/

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, (FAR uint8_t *)&value, &out_len);
}

/****************************************************************************
 * Name: bcmf_wl_get_mode
 *
 * Description:
 *   Get the mode for the device
 ****************************************************************************/

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, (FAR uint8_t *)&infra, &out_len);
  if (ret == OK)
    {
      out_len = sizeof(ap);
      ret = bcmf_cdc_ioctl(priv, interface, false,
                           WLC_GET_AP, (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_set_bssid
 *
 * Description:
 *   Set the bssid for the device
 ****************************************************************************/

int bcmf_wl_set_bssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr)
{
  uint32_t out_len;
  int interface;
  int infra = 0;
  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,
                       (FAR uint8_t *)&ap, &out_len);
  if (ret == OK)
    {
      out_len = sizeof(infra);
      ret = bcmf_cdc_ioctl(priv, interface, false, WLC_GET_INFRA,
                           (FAR uint8_t *)&infra, &out_len);

      if (ret == OK)
        {
          out_len = sizeof(struct ether_addr);
          ret = bcmf_cdc_ioctl(priv, interface, true,
                               ((ap || !infra) ? WLC_SET_BSSID :
                                                 WLC_REASSOC),
                               (FAR uint8_t *)iwr->u.ap_addr.sa_data,
                               &out_len);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_get_bssid
 *
 * Description:
 *   Get the bssid for the device
 ****************************************************************************/

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,
                        (FAR uint8_t *)iwr->u.ap_addr.sa_data, &out_len);
}

/****************************************************************************
 * Name: bcmf_wl_get_channel
 *
 * Description:
 *   Get the channel for the device
 ****************************************************************************/

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, (FAR uint8_t *)&ci, &out_len);
  if (ret == OK)
    {
      iwr->u.freq.m = bcmf_wl_channel_to_frequency(ci.target_channel);
    }

  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_get_rate
 *
 * Description:
 *   Get the data rate for the device
 ****************************************************************************/

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, (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_get_txpower
 *
 * Description:
 *   Get the tranmit power for the device
 ****************************************************************************/

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, (FAR 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,
                                   (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_get_iwrange
 *
 * Description:
 *   Get the iwrange for the device
 ****************************************************************************/

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, (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_get_rssi
 *
 * Description:
 *   Get the RSSI for the device
 ****************************************************************************/

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, (FAR uint8_t *)&rssi, &out_len);
  if (ret == OK)
    {
      iwr->u.sens.value = -rssi.rssi;
    }

  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_set_encode_ext
 *
 * Description:
 *   Set the encoding scheme for a device based on iwreq structure.
 ****************************************************************************/

int bcmf_wl_set_encode_ext(FAR struct bcmf_dev_s *priv, struct iwreq *iwr)
{
  struct iw_encode_ext *ext;
  wsec_pmk_t psk;
  int interface;

  interface = bcmf_wl_get_interface(priv, iwr);

  if (interface < 0)
    {
      return -EINVAL;
    }

  ext = (FAR 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;

  priv->auth_pending = true;
  memcpy(&priv->auth_pmk, &psk, sizeof(psk));

  return OK;
}

/****************************************************************************
 * Name: bcmf_wl_set_ssid
 *
 * Description:
 *   Get the SSID for a device based on iwreq structure.
 ****************************************************************************/

int bcmf_wl_set_ssid(FAR struct bcmf_dev_s *priv, struct iwreq *iwr)
{
  sem_t auth_signal;
  uint32_t out_len;
  scb_val_t scbval;
  wlc_ssid_t ssid;
  int interface;
  int ret;

  interface = bcmf_wl_get_interface(priv, iwr);

  if (interface < 0)
    {
      return -EINVAL;
    }

  out_len = sizeof(scbval);
  memset(&scbval, 0x0, out_len);
  ret = bcmf_cdc_ioctl(priv, interface, true,
                       WLC_DISASSOC, (FAR uint8_t *)&scbval, &out_len);
  if (ret < 0 || !iwr->u.essid.flags)
    {
      goto errout_with_auth;
    }

  if (priv->auth_pending)
    {
      out_len = sizeof(priv->auth_pmk);
      ret = bcmf_cdc_ioctl(priv, interface, true,
                           WLC_SET_WSEC_PMK,
                           (FAR uint8_t *)&priv->auth_pmk, &out_len);
      if (ret < 0)
        {
          goto errout_with_auth;
        }
    }

  /* Init authentication signal semaphore */

  nxsem_init(&auth_signal, 0, 0);
  priv->auth_signal = &auth_signal;

  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);
  ret = bcmf_cdc_ioctl(priv, interface, true,
                       WLC_SET_SSID,
                       (FAR uint8_t *)&ssid, &out_len);
  if (ret == OK)
    {
      ret = nxsem_tickwait_uninterruptible(priv->auth_signal,
                                     MSEC2TICK(BCMF_AUTH_TIMEOUT_MS));
    }

  priv->auth_signal = NULL;
  nxsem_destroy(&auth_signal);

  if (ret < 0)
    {
      wlerr("Associate request timeout\n");
      goto errout_with_auth;
    }

  switch (priv->auth_status)
    {
      case OK:
        wlinfo("AP Join ok\n");
        break;

      default:
        wlerr("AP join failed %" PRIu32 "\n", priv->auth_status);
        ret = -EINVAL;
        break;
    }

errout_with_auth:
  priv->auth_pending = false;
  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_get_ssid
 *
 * Description:
 *   Get the SSID for a device.
 ****************************************************************************/

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, (FAR 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;
}

/****************************************************************************
 * Name: bcmf_wl_set_country_code
 *
 * Description:
 *   Set a new country code.
 ****************************************************************************/

int bcmf_wl_set_country_code(FAR struct bcmf_dev_s *priv,
                             int interface, FAR void *code)
{
  uint8_t country[4] =
    {
      0
    };

  uint32_t out_len;

  memcpy(country, code, 2);

  /* Why out_len = 4 ? Padding bytes to ensure array is
   * terminating with null byte
   */

  out_len = sizeof(country);

  return bcmf_cdc_iovar_request(priv, interface, true,
                                IOVAR_STR_COUNTRY, country,
                                &out_len);
}

/****************************************************************************
 * Name: bcmf_wl_set_country
 *
 * Description:
 *   Set a new country code based on data in an iwreq structure.
 ****************************************************************************/

int bcmf_wl_set_country(FAR struct bcmf_dev_s *priv, FAR struct iwreq *iwr)
{
  int interface;

  interface = bcmf_wl_get_interface(priv, iwr);

  if (interface < 0)
    {
      return -EINVAL;
    }

  return bcmf_wl_set_country_code(priv, interface, iwr->u.data.pointer);
}

/****************************************************************************
 * Name: bcmf_wl_get_country
 *
 * Description:
 *   Get the current country code.
 ****************************************************************************/

int bcmf_wl_get_country(FAR struct bcmf_dev_s *priv, FAR struct iwreq *iwr)
{
  uint8_t country[4] =
    {
      0
    };

  uint32_t out_len;
  int interface;
  int ret;

  interface = bcmf_wl_get_interface(priv, iwr);

  if (interface < 0 || iwr->u.data.pointer == NULL)
    {
      return -EINVAL;
    }

  out_len = sizeof(country);
  ret = bcmf_cdc_iovar_request(priv, interface, false,
                               IOVAR_STR_COUNTRY, country,
                               &out_len);
  if (ret == OK)
    {
      memcpy(iwr->u.data.pointer, country, 2);
      ((uint8_t *)iwr->u.data.pointer)[2] = '\0';
    }

  return ret;
}

/****************************************************************************
 * Name: bcmf_wl_set_dtim
 ****************************************************************************/

#ifdef CONFIG_IEEE80211_BROADCOM_LOWPOWER

int bcmf_wl_set_dtim(FAR struct bcmf_dev_s *priv,
                     uint32_t interval_ms)
{
  uint32_t value = interval_ms / 100;
  uint32_t out_len;
  int ret;

  out_len = sizeof(interval_ms);

  if (value == 0)
    {
      return -EINVAL;
    }

  if (priv->lp_dtim == interval_ms)
    {
      return OK;
    }

  ret = bcmf_cdc_iovar_request(priv, CHIP_STA_INTERFACE, true,
                               IOVAR_STR_LISTEN_INTERVAL_DTIM,
                               (FAR uint8_t *)&value, &out_len);
  if (ret == OK)
    {
      priv->lp_dtim = interval_ms;
    }

  return ret;
}

#endif
