root/usr/src/uts/common/io/net80211/net80211_ioctl.c
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */


#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/strsun.h>
#include <sys/policy.h>
#include <inet/common.h>
#include <inet/nd.h>
#include <inet/mi.h>
#include <sys/note.h>
#include <sys/mac_provider.h>
#include <inet/wifi_ioctl.h>
#include "net80211_impl.h"

static int wl_set_essid(struct ieee80211com *, const void *);
static void wl_get_essid(struct ieee80211com *, void *);
static int wl_set_bssid(struct ieee80211com *, const void *);
static void wl_get_bssid(struct ieee80211com *, void *);
static int wl_set_bsstype(struct ieee80211com *, const void *);
static void wl_get_bsstype(struct ieee80211com *, void *);
static void wl_get_linkstatus(struct ieee80211com *, void *);
static int wl_set_desrates(struct ieee80211com *, const void *);
static void wl_get_desrates(struct ieee80211com *, void *);
static int wl_set_authmode(struct ieee80211com *, const void *);
static void wl_get_authmode(struct ieee80211com *, void *);
static int wl_set_encrypt(struct ieee80211com *, const void *);
static void wl_get_encrypt(struct ieee80211com *, void *);
static void wl_get_rssi(struct ieee80211com *, void *);
static int wl_set_phy(struct ieee80211com *, const void *);
static int wl_get_phy(struct ieee80211com *, void *);
static void wl_get_capability(struct ieee80211com *, void *);
static int wl_set_wpa(struct ieee80211com *, const void *);
static void wl_get_wpa(struct ieee80211com *, void *);
static void wl_get_scanresults(struct ieee80211com *, void *);
static void wl_get_esslist(struct ieee80211com *, void *);
static int wl_set_wepkey(struct ieee80211com *, const void *);
static int wl_set_optie(struct ieee80211com *, const void *);
static int wl_set_delkey(struct ieee80211com *, const void *);
static int wl_set_mlme(struct ieee80211com *, const void *);
static int wl_set_wpakey(struct ieee80211com *, const void *);
static void wl_get_suprates(struct ieee80211com *, void *);
static int wl_set_createibss(struct ieee80211com *, const void *);
static void wl_get_createibss(struct ieee80211com *, void *);

static size_t
wifi_strnlen(const char *s, size_t n)
{
        size_t i;

        for (i = 0; i < n && s[i] != '\0'; i++)
                /* noop */;
        return (i);
}

/*
 * Initialize an output message block by copying from an
 * input message block. The message is of type wldp_t.
 *    mp     input message block
 *    buflen length of wldp_buf
 */
static void
wifi_setupoutmsg(mblk_t *mp, int buflen)
{
        wldp_t *wp;

        wp = (wldp_t *)mp->b_rptr;
        wp->wldp_length = WIFI_BUF_OFFSET + buflen;
        wp->wldp_result = WL_SUCCESS;
        mp->b_wptr = mp->b_rptr + wp->wldp_length;
}

/*
 * Allocate and initialize an output message.
 */
static mblk_t *
wifi_getoutmsg(mblk_t *mp, uint32_t cmd, int buflen)
{
        mblk_t *mp1;
        int size;

        size = WIFI_BUF_OFFSET;
        if (cmd == WLAN_GET_PARAM)
                size += buflen; /* to hold output parameters */
        mp1 = allocb(size, BPRI_HI);
        if (mp1 == NULL) {
                ieee80211_err("wifi_getoutbuf: allocb %d bytes failed!\n",
                    size);
                return (NULL);
        }

        bzero(mp1->b_rptr, size);
        bcopy(mp->b_rptr, mp1->b_rptr, WIFI_BUF_OFFSET);
        wifi_setupoutmsg(mp1, size - WIFI_BUF_OFFSET);

        return (mp1);
}

static int
wifi_cfg_essid(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_essid_t *iw_essid = (wl_essid_t *)inp->wldp_buf;
        wl_essid_t *ow_essid;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_essid_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_essid = (wl_essid_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_essid(ic, ow_essid);
                break;
        case WLAN_SET_PARAM:
                err = wl_set_essid(ic, iw_essid);
                break;
        default:
                ieee80211_err("wifi_cfg_essid: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_bssid(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_bssid_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case  WLAN_GET_PARAM:
                wl_get_bssid(ic, outp->wldp_buf);
                break;
        case WLAN_SET_PARAM:
                err = wl_set_bssid(ic, inp->wldp_buf);
                ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_bssid: "
                    "set bssid=%s\n",
                    ieee80211_macaddr_sprintf(inp->wldp_buf));
                break;
        default:
                ieee80211_err("wifi_cfg_bssid: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_nodename(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_nodename_t *iw_name = (wl_nodename_t *)inp->wldp_buf;
        wl_nodename_t *ow_name;
        char *nodename;
        int len, err;

        err = 0;
        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_nodename_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_name = (wl_nodename_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                len = wifi_strnlen((const char *)ic->ic_nickname,
                    IEEE80211_NWID_LEN);
                ow_name->wl_nodename_length = len;
                bcopy(ic->ic_nickname, ow_name->wl_nodename_name, len);
                break;
        case WLAN_SET_PARAM:
                if (iw_name->wl_nodename_length > IEEE80211_NWID_LEN) {
                        ieee80211_err("wifi_cfg_nodename: "
                            "node name too long, %u\n",
                            iw_name->wl_nodename_length);
                        outp->wldp_result = WL_NOTSUPPORTED;
                        err = EINVAL;
                        break;
                }
                nodename = iw_name->wl_nodename_name;
                nodename[IEEE80211_NWID_LEN] = 0;
                ieee80211_dbg(IEEE80211_MSG_CONFIG,
                    "wifi_cfg_nodename: set nodename %s, len=%d\n",
                    nodename, iw_name->wl_nodename_length);

                len = iw_name->wl_nodename_length;
                if (len > 0)
                        bcopy(nodename, ic->ic_nickname, len);
                if (len < IEEE80211_NWID_LEN)
                        ic->ic_nickname[len] = 0;
                break;
        default:
                ieee80211_err("wifi_cfg_nodename: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_phy(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_phy_conf_t *iw_phy = (wl_phy_conf_t *)inp->wldp_buf;
        wl_phy_conf_t *ow_phy;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_phy_conf_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_phy = (wl_phy_conf_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                err = wl_get_phy(ic, ow_phy);
                break;

        case WLAN_SET_PARAM:
                err = wl_set_phy(ic, iw_phy);
                break;

        default:
                ieee80211_err("wifi_cfg_phy: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        } /* switch (cmd) */

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_wepkey(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_wep_key_t *iw_wepkey = (wl_wep_key_t *)inp->wldp_buf;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, 0)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case WLAN_GET_PARAM:
                outp->wldp_result = WL_WRITEONLY;
                err = EINVAL;
                break;
        case WLAN_SET_PARAM:
                if (inp->wldp_length < sizeof (wl_wep_key_tab_t)) {
                        ieee80211_err("wifi_cfg_wepkey: "
                            "parameter too short, %d, expected %d\n",
                            inp->wldp_length, sizeof (wl_wep_key_tab_t));
                        outp->wldp_result = WL_NOTSUPPORTED;
                        err = EINVAL;
                        break;
                }

                err = wl_set_wepkey(ic, iw_wepkey);
                break;
        default:
                ieee80211_err("wifi_cfg_wepkey: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_keyid(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_wep_key_id_t *iw_kid = (wl_wep_key_id_t *)inp->wldp_buf;
        wl_wep_key_id_t *ow_kid;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_wep_key_id_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_kid = (wl_wep_key_id_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                *ow_kid = (ic->ic_def_txkey == IEEE80211_KEYIX_NONE) ?
                    0 : ic->ic_def_txkey;
                break;
        case  WLAN_SET_PARAM:
                if (*iw_kid >= MAX_NWEPKEYS) {
                        ieee80211_err("wifi_cfg_keyid: "
                            "keyid too large, %u\n", *iw_kid);
                        outp->wldp_result = WL_NOTSUPPORTED;
                        err = EINVAL;
                } else {
                        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_keyid: "
                            "set keyid=%u\n", *iw_kid);
                        ic->ic_def_txkey = *iw_kid;
                        err = ENETRESET;
                }
                break;
        default:
                ieee80211_err("wifi_cfg_keyid: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_authmode(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_authmode_t *iw_auth = (wl_authmode_t *)inp->wldp_buf;
        wl_authmode_t *ow_auth;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_authmode_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_auth = (wl_authmode_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_authmode(ic, ow_auth);
                break;
        case WLAN_SET_PARAM:
                err = wl_set_authmode(ic, iw_auth);
                break;
        default:
                ieee80211_err("wifi_cfg_authmode: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_encrypt(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_encryption_t *iw_encryp = (wl_encryption_t *)inp->wldp_buf;
        wl_encryption_t *ow_encryp;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_encryption_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_encryp = (wl_encryption_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_encrypt(ic, ow_encryp);
                break;
        case WLAN_SET_PARAM:
                err = wl_set_encrypt(ic, iw_encryp);
                break;
        default:
                ieee80211_err("wifi_cfg_encrypt: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_bsstype(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_bss_type_t *iw_opmode = (wl_bss_type_t *)inp->wldp_buf;
        wl_bss_type_t *ow_opmode;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_bss_type_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_opmode = (wl_bss_type_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_bsstype(ic, ow_opmode);
                break;
        case  WLAN_SET_PARAM:
                if (*iw_opmode == ic->ic_opmode)
                        break;

                err = wl_set_bsstype(ic, iw_opmode);
                break;
        default:
                ieee80211_err("wifi_cfg_bsstype: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_createibss(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wldp_t *outp;
        wl_create_ibss_t *iw_ibss = (wl_create_ibss_t *)inp->wldp_buf;
        wl_create_ibss_t *ow_ibss;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_create_ibss_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_ibss = (wl_create_ibss_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_createibss(ic, ow_ibss);
                break;
        case  WLAN_SET_PARAM:
                err = wl_set_createibss(ic, iw_ibss);
                break;
        default:
                ieee80211_err("wifi_cfg_bsstype: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_linkstatus(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_linkstatus_t *ow_linkstat;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_linkstatus_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_linkstat = (wl_linkstatus_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_linkstatus(ic, ow_linkstat);
                break;
        case WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_linkstatus: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_suprates(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_rates_t *ow_rates;
        int err, buflen;

        err = 0;
        /* rate value (wl_rates_rates) is of type char */
        buflen = offsetof(wl_rates_t, wl_rates_rates) +
            sizeof (char) * IEEE80211_MODE_MAX * IEEE80211_RATE_MAXSIZE;
        if ((omp = wifi_getoutmsg(*mp, cmd, buflen)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_rates = (wl_rates_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                (void) wl_get_suprates(ic, ow_rates);
                break;
        case WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_suprates: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_desrates(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_rates_t *iw_rates = (wl_rates_t *)inp->wldp_buf;
        mblk_t *omp;
        wldp_t *outp;
        wl_rates_t *ow_rates;
        int err;

        err = 0;
        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_rates_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_rates = (wl_rates_t *)outp->wldp_buf;

        switch (cmd) {
        case  WLAN_GET_PARAM:
                wl_get_desrates(ic, ow_rates);
                break;
        case  WLAN_SET_PARAM:
                err = wl_set_desrates(ic, iw_rates);
                break;
        default:
                ieee80211_err("wifi_cfg_desrates: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * Rescale device's RSSI value to (0, 15) as required by WiFi
 * driver IOCTLs (PSARC/2003/722)
 */
static wl_rssi_t
wifi_getrssi(struct ieee80211_node *in)
{
        struct ieee80211com *ic = in->in_ic;
        wl_rssi_t rssi, max_rssi;

        rssi = ic->ic_node_getrssi(in);
        max_rssi = (ic->ic_maxrssi == 0) ? IEEE80211_MAXRSSI : ic->ic_maxrssi;
        if (rssi == 0)
                rssi = 0;
        else if (rssi >= max_rssi)
                rssi = MAX_RSSI;
        else
                rssi = rssi * MAX_RSSI / max_rssi + 1;

        return (rssi);
}

static int
wifi_cfg_rssi(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_rssi_t *ow_rssi;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_rssi_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        ow_rssi = (wl_rssi_t *)outp->wldp_buf;

        switch (cmd) {
        case  WLAN_GET_PARAM:
                *ow_rssi = wifi_getrssi(ic->ic_bss);
                break;
        case  WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_rssi: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                return (EINVAL);
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * maximum scan wait time in second.
 * Time spent on scaning one channel is usually 100~200ms. The maximum
 * number of channels defined in wifi_ioctl.h is 99 (MAX_CHANNEL_NUM).
 * As a result the maximum total scan time is defined as below in ms.
 */
#define WAIT_SCAN_MAX   (200 * MAX_CHANNEL_NUM)

static void
wifi_wait_scan(struct ieee80211com *ic)
{
        ieee80211_impl_t *im = ic->ic_private;
        clock_t delta = drv_usectohz(WAIT_SCAN_MAX * 1000);

        while ((ic->ic_flags & (IEEE80211_F_SCAN | IEEE80211_F_ASCAN)) != 0) {
                if (cv_reltimedwait_sig(&im->im_scan_cv, &ic->ic_genlock,
                    delta, TR_CLOCK_TICK) != 0) {
                        break;
                }
        }
}

#define WIFI_HAVE_CAP(in, flag) (((in)->in_capinfo & (flag)) ? 1 : 0)
#define WIFI_HAVE_HTCAP(in)     (((in)->in_htcap != 0) ? 1 : 0)

/*
 * Callback function used by ieee80211_iterate_nodes() in
 * wifi_cfg_esslist() to get info of each node in a node table
 *    arg  output buffer, pointer to wl_ess_list_t
 *    in   each node in the node table
 */
static void
wifi_read_ap(void *arg, struct ieee80211_node *in)
{
        wl_ess_list_t *aps = arg;
        ieee80211com_t *ic = in->in_ic;
        struct ieee80211_channel *chan = in->in_chan;
        struct ieee80211_rateset *rates = &(in->in_rates);
        wl_ess_conf_t *conf;
        uint8_t *end;
        uint_t i, nrates;

        end = (uint8_t *)aps - WIFI_BUF_OFFSET + MAX_BUF_LEN -
            sizeof (wl_ess_list_t);
        conf = &aps->wl_ess_list_ess[aps->wl_ess_list_num];
        if ((uint8_t *)conf > end)
                return;

        conf->wl_ess_conf_length = sizeof (struct wl_ess_conf);

        /* skip newly allocated NULL bss node */
        if (IEEE80211_ADDR_EQ(in->in_macaddr, ic->ic_macaddr))
                return;

        conf->wl_ess_conf_essid.wl_essid_length = in->in_esslen;
        bcopy(in->in_essid, conf->wl_ess_conf_essid.wl_essid_essid,
            in->in_esslen);
        bcopy(in->in_bssid, conf->wl_ess_conf_bssid, IEEE80211_ADDR_LEN);
        conf->wl_ess_conf_wepenabled =
            (in->in_capinfo & IEEE80211_CAPINFO_PRIVACY ?
            WL_ENC_WEP : WL_NOENCRYPTION);
        conf->wl_ess_conf_bsstype =
            (in->in_capinfo & IEEE80211_CAPINFO_ESS ?
            WL_BSS_BSS : WL_BSS_IBSS);
        conf->wl_ess_conf_sl = wifi_getrssi(in);
        conf->wl_ess_conf_reserved[0] = (in->in_wpa_ie == NULL? 0 : 1);

        /* physical (FH, DS, ERP) parameters */
        if (IEEE80211_IS_CHAN_A(chan) || IEEE80211_IS_CHAN_T(chan)) {
                wl_ofdm_t *ofdm =
                    (wl_ofdm_t *)&((conf->wl_phy_conf).wl_phy_ofdm_conf);
                ofdm->wl_ofdm_subtype = WL_OFDM;
                ofdm->wl_ofdm_frequency = chan->ich_freq;
                ofdm->wl_ofdm_ht_enabled = WIFI_HAVE_HTCAP(in);
        } else {
                switch (in->in_phytype) {
                case IEEE80211_T_FH: {
                        wl_fhss_t *fhss = (wl_fhss_t *)
                            &((conf->wl_phy_conf).wl_phy_fhss_conf);

                        fhss->wl_fhss_subtype = WL_FHSS;
                        fhss->wl_fhss_channel = ieee80211_chan2ieee(ic, chan);
                        fhss->wl_fhss_dwelltime = in->in_fhdwell;
                        break;
                }
                case IEEE80211_T_DS: {
                        wl_dsss_t *dsss = (wl_dsss_t *)
                            &((conf->wl_phy_conf).wl_phy_dsss_conf);

                        dsss->wl_dsss_subtype = WL_DSSS;
                        dsss->wl_dsss_channel = ieee80211_chan2ieee(ic, chan);
                        dsss->wl_dsss_have_short_preamble = WIFI_HAVE_CAP(in,
                            IEEE80211_CAPINFO_SHORT_PREAMBLE);
                        dsss->wl_dsss_agility_enabled = WIFI_HAVE_CAP(in,
                            IEEE80211_CAPINFO_CHNL_AGILITY);
                        dsss->wl_dsss_have_pbcc = dsss->wl_dsss_pbcc_enable =
                            WIFI_HAVE_CAP(in, IEEE80211_CAPINFO_PBCC);
                        break;
                }
                case IEEE80211_T_OFDM: {
                        wl_erp_t *erp = (wl_erp_t *)
                            &((conf->wl_phy_conf).wl_phy_erp_conf);

                        erp->wl_erp_subtype = WL_ERP;
                        erp->wl_erp_channel = ieee80211_chan2ieee(ic, chan);
                        erp->wl_erp_have_short_preamble = WIFI_HAVE_CAP(in,
                            IEEE80211_CAPINFO_SHORT_PREAMBLE);
                        erp->wl_erp_have_agility = erp->wl_erp_agility_enabled =
                            WIFI_HAVE_CAP(in, IEEE80211_CAPINFO_CHNL_AGILITY);
                        erp->wl_erp_have_pbcc = erp->wl_erp_pbcc_enabled =
                            WIFI_HAVE_CAP(in, IEEE80211_CAPINFO_PBCC);
                        erp->wl_erp_dsss_ofdm_enabled =
                            WIFI_HAVE_CAP(in, IEEE80211_CAPINFO_DSSSOFDM);
                        erp->wl_erp_sst_enabled = WIFI_HAVE_CAP(in,
                            IEEE80211_CAPINFO_SHORT_SLOTTIME);
                        erp->wl_erp_ht_enabled = WIFI_HAVE_HTCAP(in);
                        break;
                } /* case IEEE80211_T_OFDM */
                } /* switch in->in_phytype */
        }

        /* supported rates */
        nrates = MIN(rates->ir_nrates, MAX_SCAN_SUPPORT_RATES);
        /*
         * The number of supported rates might exceed
         * MAX_SCAN_SUPPORT_RATES. Fill in highest rates
         * first so userland command could properly show
         * maximum speed of AP
         */
        for (i = 0; i < nrates; i++) {
                conf->wl_supported_rates[i] =
                    rates->ir_rates[rates->ir_nrates - i - 1];
        }

        aps->wl_ess_list_num++;
}

static int
wifi_cfg_esslist(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_ess_list_t *ow_aps;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, MAX_BUF_LEN - WIFI_BUF_OFFSET)) ==
            NULL) {
                return (ENOMEM);
        }
        outp = (wldp_t *)omp->b_rptr;
        ow_aps = (wl_ess_list_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                ow_aps->wl_ess_list_num = 0;
                ieee80211_iterate_nodes(&ic->ic_scan, wifi_read_ap, ow_aps);
                outp->wldp_length = WIFI_BUF_OFFSET +
                    offsetof(wl_ess_list_t, wl_ess_list_ess) +
                    ow_aps->wl_ess_list_num * sizeof (wl_ess_conf_t);
                omp->b_wptr = omp->b_rptr + outp->wldp_length;
                break;
        case WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_esslist: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * Scan the network for all available ESSs.
 * IEEE80211_F_SCANONLY is set when current state is INIT. And
 * with this flag, after scan the state will be changed back to
 * INIT. The reason is at the end of SCAN stage, the STA will
 * consequently connect to an AP. Then it looks unreasonable that
 * for a disconnected device, A SCAN command causes it connected.
 * So the state is changed back to INIT.
 */
static int
wifi_cmd_scan(struct ieee80211com *ic, mblk_t *mp)
{
        int ostate = ic->ic_state;

        /*
         * Do not scan when current state is RUN. The reason is
         * when connected, STA is on the same channel as AP. But
         * to do scan, STA have to switch to each available channel,
         * send probe request and wait certian time for probe
         * response/beacon. Then when the STA switches to a channel
         * different than AP's, as a result it cannot send/receive
         * data packets to/from the connected WLAN. This eventually
         * will cause data loss.
         */
        if (ostate == IEEE80211_S_RUN)
                return (0);

        IEEE80211_UNLOCK(ic);

        ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
        IEEE80211_LOCK(ic);
        if (ostate == IEEE80211_S_INIT)
                ic->ic_flags |= IEEE80211_F_SCANONLY;

        /* Don't wait on WPA mode */
        if ((ic->ic_flags & IEEE80211_F_WPA) == 0) {
                /* wait scan complete */
                wifi_wait_scan(ic);
        }

        wifi_setupoutmsg(mp, 0);
        return (0);
}

static void
wifi_loaddefdata(struct ieee80211com *ic)
{
        struct ieee80211_node *in = ic->ic_bss;
        int i;

        ic->ic_des_esslen = 0;
        bzero(ic->ic_des_essid, IEEE80211_NWID_LEN);
        ic->ic_flags &= ~IEEE80211_F_DESBSSID;
        bzero(ic->ic_des_bssid, IEEE80211_ADDR_LEN);
        bzero(ic->ic_bss->in_bssid, IEEE80211_ADDR_LEN);
        ic->ic_des_chan = IEEE80211_CHAN_ANYC;
        ic->ic_fixed_rate = IEEE80211_FIXED_RATE_NONE;
        bzero(ic->ic_nickname, IEEE80211_NWID_LEN);
        in->in_authmode = IEEE80211_AUTH_OPEN;
        ic->ic_flags &= ~IEEE80211_F_PRIVACY;
        ic->ic_flags &= ~IEEE80211_F_WPA;       /* mask WPA mode */
        ic->ic_evq_head = ic->ic_evq_tail = 0;  /* reset Queue */
        ic->ic_def_txkey = 0;
        for (i = 0; i < MAX_NWEPKEYS; i++) {
                ic->ic_nw_keys[i].wk_keylen = 0;
                bzero(ic->ic_nw_keys[i].wk_key, IEEE80211_KEYBUF_SIZE);
        }
        ic->ic_curmode = IEEE80211_MODE_AUTO;
        ic->ic_flags &= ~IEEE80211_F_IBSSON;
        ic->ic_opmode = IEEE80211_M_STA;
}

static int
wifi_cmd_loaddefaults(struct ieee80211com *ic, mblk_t *mp)
{
        wifi_loaddefdata(ic);
        wifi_setupoutmsg(mp, 0);
        return (ENETRESET);
}

static int
wifi_cmd_disassoc(struct ieee80211com *ic, mblk_t *mp)
{
        if (ic->ic_state != IEEE80211_S_INIT) {
                IEEE80211_UNLOCK(ic);
                (void) ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
                IEEE80211_LOCK(ic);
        }
        wifi_loaddefdata(ic);
        wifi_setupoutmsg(mp, 0);
        return (0);
}

/*
 * Get the capabilities of drivers.
 */
static int
wifi_cfg_caps(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_capability_t *o_caps;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_capability_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        o_caps = (wl_capability_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_capability(ic, o_caps);
                break;
        case WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_caps: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * Operating on WPA mode.
 */
static int
wifi_cfg_wpa(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_wpa_t *wpa = (wl_wpa_t *)inp->wldp_buf;
        wl_wpa_t *o_wpa;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, sizeof (wl_wpa_t))) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;
        o_wpa = (wl_wpa_t *)outp->wldp_buf;

        switch (cmd) {
        case WLAN_GET_PARAM:
                wl_get_wpa(ic, o_wpa);
                break;
        case WLAN_SET_PARAM:
                err = wl_set_wpa(ic, wpa);
                break;
        default:
                ieee80211_err("wifi_cfg_wpa: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * WPA daemon set the WPA keys.
 * The WPA keys are negotiated with APs through wpa service.
 */
static int
wifi_cfg_wpakey(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_key_t *ik = (wl_key_t *)(inp->wldp_buf);
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, 0)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case WLAN_GET_PARAM:
                outp->wldp_result = WL_WRITEONLY;
                err = EINVAL;
                break;
        case WLAN_SET_PARAM:
                err = wl_set_wpakey(ic, ik);
                break;
        default:
                ieee80211_err("wifi_cfg_wpakey: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * Delete obsolete keys - keys are dynamically exchanged between APs
 * and wpa daemon.
 */
static int
wifi_cfg_delkey(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_del_key_t *dk = (wl_del_key_t *)inp->wldp_buf;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, 0)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case WLAN_GET_PARAM:
                outp->wldp_result = WL_WRITEONLY;
                err = EINVAL;
                break;
        case WLAN_SET_PARAM:
                err = wl_set_delkey(ic, dk);
                break;
        default:
                ieee80211_err("wifi_cfg_delkey: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * The OPTIE will be used in the association request.
 */
static int
wifi_cfg_setoptie(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_wpa_ie_t *ie_in = (wl_wpa_ie_t *)inp->wldp_buf;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, 0)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case WLAN_GET_PARAM:
                outp->wldp_result = WL_WRITEONLY;
                err = EINVAL;
                break;
        case WLAN_SET_PARAM:
                if ((err = wl_set_optie(ic, ie_in)) == EINVAL)
                        outp->wldp_result = WL_NOTSUPPORTED;
                break;
        default:
                ieee80211_err("wifi_cfg_setoptie: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * To be compatible with drivers/tools of OpenSolaris.org,
 * we use a different ID to filter out those APs of WPA mode.
 */
static int
wifi_cfg_scanresults(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wl_wpa_ess_t *sr;
        ieee80211_node_t *in;
        ieee80211_node_table_t *nt;
        int len, ap_num = 0;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, MAX_BUF_LEN - WIFI_BUF_OFFSET)) ==
            NULL) {
                return (ENOMEM);
        }
        outp = (wldp_t *)omp->b_rptr;
        sr = (wl_wpa_ess_t *)outp->wldp_buf;
        sr->count = 0;

        switch (cmd) {
        case WLAN_GET_PARAM:
                ieee80211_dbg(IEEE80211_MSG_WPA, "wifi_cfg_scanresults\n");
                nt = &ic->ic_scan;
                IEEE80211_NODE_LOCK(nt);
                in = list_head(&nt->nt_node);
                while (in != NULL) {
                        /* filter out non-WPA APs */
                        if (in->in_wpa_ie == NULL) {
                                in = list_next(&nt->nt_node, in);
                                continue;
                        }
                        bcopy(in->in_bssid, sr->ess[ap_num].bssid,
                            IEEE80211_ADDR_LEN);
                        sr->ess[ap_num].ssid_len = in->in_esslen;
                        bcopy(in->in_essid, sr->ess[ap_num].ssid,
                            in->in_esslen);
                        sr->ess[ap_num].freq = in->in_chan->ich_freq;

                        len = in->in_wpa_ie[1] + 2;
                        bcopy(in->in_wpa_ie, sr->ess[ap_num].wpa_ie, len);
                        sr->ess[ap_num].wpa_ie_len = len;

                        ap_num ++;
                        in = list_next(&nt->nt_node, in);
                }
                IEEE80211_NODE_UNLOCK(nt);
                sr->count = ap_num;
                outp->wldp_length = WIFI_BUF_OFFSET +
                    offsetof(wl_wpa_ess_t, ess) +
                    sr->count * sizeof (struct wpa_ess);
                omp->b_wptr = omp->b_rptr + outp->wldp_length;
                break;
        case WLAN_SET_PARAM:
                outp->wldp_result = WL_READONLY;
                err = EINVAL;
                break;
        default:
                ieee80211_err("wifi_cfg_scanresults: unknown cmmand %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

/*
 * Manually control the state of AUTH | DEAUTH | DEASSOC | ASSOC
 */
static int
wifi_cfg_setmlme(struct ieee80211com *ic, uint32_t cmd, mblk_t **mp)
{
        mblk_t *omp;
        wldp_t *outp;
        wldp_t *inp = (wldp_t *)(*mp)->b_rptr;
        wl_mlme_t *mlme = (wl_mlme_t *)inp->wldp_buf;
        int err = 0;

        if ((omp = wifi_getoutmsg(*mp, cmd, 0)) == NULL)
                return (ENOMEM);
        outp = (wldp_t *)omp->b_rptr;

        switch (cmd) {
        case WLAN_GET_PARAM:
                outp->wldp_result = WL_WRITEONLY;
                err = EINVAL;
                break;
        case WLAN_SET_PARAM:
                err = wl_set_mlme(ic, mlme);
                break;
        default:
                ieee80211_err("wifi_cfg_delkey: unknown command %x\n", cmd);
                outp->wldp_result = WL_NOTSUPPORTED;
                err = EINVAL;
                break;
        }

        freemsg(*mp);
        *mp = omp;
        return (err);
}

static int
wifi_cfg_getset(struct ieee80211com *ic, mblk_t **mp, uint32_t cmd)
{
        mblk_t *mp1 = *mp;
        wldp_t *wp = (wldp_t *)mp1->b_rptr;
        int err = 0;

        ASSERT(ic != NULL && mp1 != NULL);
        IEEE80211_LOCK_ASSERT(ic);
        if (MBLKL(mp1) < WIFI_BUF_OFFSET) {
                ieee80211_err("wifi_cfg_getset: "
                    "invalid input buffer, size=%d\n", MBLKL(mp1));
                return (EINVAL);
        }

        switch (wp->wldp_id) {
        /* Commands */
        case WL_SCAN:
                err = wifi_cmd_scan(ic, mp1);
                break;
        case WL_LOAD_DEFAULTS:
                err = wifi_cmd_loaddefaults(ic, mp1);
                break;
        case WL_DISASSOCIATE:
                err = wifi_cmd_disassoc(ic, mp1);
                break;
        /* Parameters */
        case WL_ESSID:
                err = wifi_cfg_essid(ic, cmd, mp);
                break;
        case WL_BSSID:
                err = wifi_cfg_bssid(ic, cmd, mp);
                break;
        case WL_NODE_NAME:
                err = wifi_cfg_nodename(ic, cmd, mp);
                break;
        case WL_PHY_CONFIG:
                err = wifi_cfg_phy(ic, cmd, mp);
                break;
        case WL_WEP_KEY_TAB:
                err = wifi_cfg_wepkey(ic, cmd, mp);
                break;
        case WL_WEP_KEY_ID:
                err = wifi_cfg_keyid(ic, cmd, mp);
                break;
        case WL_AUTH_MODE:
                err = wifi_cfg_authmode(ic, cmd, mp);
                break;
        case WL_ENCRYPTION:
                err = wifi_cfg_encrypt(ic, cmd, mp);
                break;
        case WL_BSS_TYPE:
                err = wifi_cfg_bsstype(ic, cmd, mp);
                break;
        case WL_CREATE_IBSS:
                err = wifi_cfg_createibss(ic, cmd, mp);
                break;
        case WL_DESIRED_RATES:
                err = wifi_cfg_desrates(ic, cmd, mp);
                break;
        case WL_LINKSTATUS:
                err = wifi_cfg_linkstatus(ic, cmd, mp);
                break;
        case WL_ESS_LIST:
                err = wifi_cfg_esslist(ic, cmd, mp);
                break;
        case WL_SUPPORTED_RATES:
                err = wifi_cfg_suprates(ic, cmd, mp);
                break;
        case WL_RSSI:
                err = wifi_cfg_rssi(ic, cmd, mp);
                break;
        /*
         * WPA IOCTLs
         */
        case WL_CAPABILITY:
                err = wifi_cfg_caps(ic, cmd, mp);
                break;
        case WL_WPA:
                err = wifi_cfg_wpa(ic, cmd, mp);
                break;
        case WL_KEY:
                err = wifi_cfg_wpakey(ic, cmd, mp);
                break;
        case WL_DELKEY:
                err = wifi_cfg_delkey(ic, cmd, mp);
                break;
        case WL_SETOPTIE:
                err = wifi_cfg_setoptie(ic, cmd, mp);
                break;
        case WL_SCANRESULTS:
                err = wifi_cfg_scanresults(ic, cmd, mp);
                break;
        case WL_MLME:
                err = wifi_cfg_setmlme(ic, cmd, mp);
                break;
        default:
                wifi_setupoutmsg(mp1, 0);
                wp->wldp_result = WL_LACK_FEATURE;
                err = ENOTSUP;
                break;
        }

        return (err);
}

/*
 * Typically invoked by drivers in response to requests for
 * information or to change settings from the userland.
 *
 * Return value should be checked by WiFi drivers. Return 0
 * on success. Otherwise, return non-zero value to indicate
 * the error. Driver should operate as below when the return
 * error is:
 * ENETRESET    Reset wireless network and re-start to join a
 *              WLAN. ENETRESET is returned when a configuration
 *              parameter has been changed.
 *              When acknowledge a M_IOCTL message, thie error
 *              is ignored.
 */
int
ieee80211_ioctl(struct ieee80211com *ic, queue_t *wq, mblk_t *mp)
{
        struct iocblk *iocp;
        int32_t cmd, err, len;
        boolean_t need_privilege;
        mblk_t *mp1;

        if (MBLKL(mp) < sizeof (struct iocblk)) {
                ieee80211_err("ieee80211_ioctl: ioctl buffer too short, %u\n",
                    MBLKL(mp));
                miocnak(wq, mp, 0, EINVAL);
                return (EINVAL);
        }

        /*
         * Validate the command
         */
        iocp = (struct iocblk *)mp->b_rptr;
        iocp->ioc_error = 0;
        cmd = iocp->ioc_cmd;
        need_privilege = B_TRUE;
        switch (cmd) {
        case WLAN_SET_PARAM:
        case WLAN_COMMAND:
                break;
        case WLAN_GET_PARAM:
                need_privilege = B_FALSE;
                break;
        default:
                ieee80211_dbg(IEEE80211_MSG_ANY, "ieee80211_ioctl(): "
                    "unknown cmd 0x%x\n", cmd);
                miocnak(wq, mp, 0, EINVAL);
                return (EINVAL);
        }

        if (need_privilege && (err = secpolicy_dl_config(iocp->ioc_cr)) != 0) {
                miocnak(wq, mp, 0, err);
                return (err);
        }

        IEEE80211_LOCK(ic);

        /* sanity check */
        mp1 = mp->b_cont;
        if (iocp->ioc_count == 0 || iocp->ioc_count < sizeof (wldp_t) ||
            mp1 == NULL) {
                miocnak(wq, mp, 0, EINVAL);
                IEEE80211_UNLOCK(ic);
                return (EINVAL);
        }

        /* assuming single data block */
        if (mp1->b_cont != NULL) {
                freemsg(mp1->b_cont);
                mp1->b_cont = NULL;
        }

        err = wifi_cfg_getset(ic, &mp1, cmd);
        mp->b_cont = mp1;
        IEEE80211_UNLOCK(ic);

        len = msgdsize(mp1);
        /* ignore ENETRESET when acknowledge the M_IOCTL message */
        if (err == 0 || err == ENETRESET)
                miocack(wq, mp, len, 0);
        else
                miocack(wq, mp, len, err);

        return (err);
}

/*
 * The following routines are for brussels support
 */

/*
 * MAC_PROP_WL_ESSID
 */
static int
wl_set_essid(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        char *essid;
        wl_essid_t *iw_essid = (wl_essid_t *)wldp_buf;

        if (iw_essid->wl_essid_length > IEEE80211_NWID_LEN) {
                ieee80211_err("wl_set_essid: "
                    "essid too long, %u, max %u\n",
                    iw_essid->wl_essid_length, IEEE80211_NWID_LEN);

                err = EINVAL;
                return (err);
        }

        essid = iw_essid->wl_essid_essid;
        essid[IEEE80211_NWID_LEN] = 0;

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_essid: "
            "set essid=%s length=%d\n",
            essid, iw_essid->wl_essid_length);

        ic->ic_des_esslen = iw_essid->wl_essid_length;
        if (ic->ic_des_esslen != 0)
                bcopy(essid, ic->ic_des_essid, ic->ic_des_esslen);
        if (ic->ic_des_esslen < IEEE80211_NWID_LEN)
                ic->ic_des_essid[ic->ic_des_esslen] = 0;

        err = ENETRESET;

        return (err);
}

static void
wl_get_essid(struct ieee80211com *ic, void *wldp_buf)
{
        char *essid;
        wl_essid_t ow_essid;

        essid = (char *)ic->ic_des_essid;
        if (essid[0] == '\0')
                essid = (char *)ic->ic_bss->in_essid;

        bzero(&ow_essid, sizeof (wl_essid_t));
        ow_essid.wl_essid_length = wifi_strnlen((const char *)essid,
            IEEE80211_NWID_LEN);
        bcopy(essid, ow_essid.wl_essid_essid,
            ow_essid.wl_essid_length);
        bcopy(&ow_essid, wldp_buf, sizeof (wl_essid_t));

}

/*
 * MAC_PROP_WL_BSSID
 */
static int
wl_set_bssid(struct ieee80211com *ic, const void* wldp_buf)
{

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_bssid: "
            "set bssid=%s\n",
            ieee80211_macaddr_sprintf(wldp_buf));

        bcopy(wldp_buf, ic->ic_des_bssid, sizeof (wl_bssid_t));
        ic->ic_flags |= IEEE80211_F_DESBSSID;

        return (ENETRESET);
}

static void
wl_get_bssid(struct ieee80211com *ic, void *wldp_buf)
{
        uint8_t *bssid;

        if (ic->ic_flags & IEEE80211_F_DESBSSID)
                bssid = ic->ic_des_bssid;
        else
                bssid = ic->ic_bss->in_bssid;
        bcopy(bssid, wldp_buf, sizeof (wl_bssid_t));

}

/*
 * MAC_PROP_WL_BSSTYP
 */
static int
wl_set_bsstype(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        wl_bss_type_t *iw_opmode = (wl_bss_type_t *)wldp_buf;

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_bsstype: "
            "set bsstype=%u\n", *iw_opmode);

        switch (*iw_opmode) {
        case WL_BSS_BSS:
                ic->ic_flags &= ~IEEE80211_F_IBSSON;
                ic->ic_opmode = IEEE80211_M_STA;
                err = ENETRESET;
                break;
        case WL_BSS_IBSS:
                if ((ic->ic_caps & IEEE80211_C_IBSS) == 0) {
                        err = ENOTSUP;
                        break;
                }

                ic->ic_opmode = IEEE80211_M_IBSS;
                err = ENETRESET;
                break;
        default:
                ieee80211_err("wl_set_bsstype: "
                    "unknown opmode\n");
                err = EINVAL;
                break;
        }
        return (err);
}

static void
wl_get_bsstype(struct ieee80211com *ic, void *wldp_buf)
{
        wl_bss_type_t ow_opmode;

        switch (ic->ic_opmode) {
        case IEEE80211_M_STA:
                ow_opmode = WL_BSS_BSS;
                break;
        case IEEE80211_M_IBSS:
                ow_opmode = WL_BSS_IBSS;
                break;
        default:
                ow_opmode = WL_BSS_ANY;
                break;
        }

        bcopy(&ow_opmode, wldp_buf, sizeof (wl_bss_type_t));
}

/*
 * MAC_PROP_WL_LINKSTATUS
 */
static void
wl_get_linkstatus(struct ieee80211com *ic, void *wldp_buf)
{
        wl_linkstatus_t ow_linkstat;

        ow_linkstat = (ic->ic_state == IEEE80211_S_RUN) ?
            WL_CONNECTED : WL_NOTCONNECTED;
        if ((ic->ic_flags & IEEE80211_F_WPA) &&
            (ieee80211_crypto_getciphertype(ic) != WIFI_SEC_WPA)) {
                ow_linkstat = WL_NOTCONNECTED;
        }

        bcopy(&ow_linkstat, wldp_buf, sizeof (wl_linkstatus_t));
}

/*
 * MAC_PROP_WL_DESIRED_RATESa
 */
static int
wl_set_desrates(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        int i, j;
        uint8_t drate;
        boolean_t isfound;
        wl_rates_t *iw_rates = (wl_rates_t *)wldp_buf;
        struct ieee80211_node *in = ic->ic_bss;
        struct ieee80211_rateset *rs = &in->in_rates;

        drate = iw_rates->wl_rates_rates[0];
        if (ic->ic_fixed_rate == drate)
                return (err);

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_desrates: "
            "set desired rate=%u\n", drate);

        if (drate == 0) {
                ic->ic_fixed_rate = IEEE80211_FIXED_RATE_NONE;
                if (ic->ic_state == IEEE80211_S_RUN) {
                        IEEE80211_UNLOCK(ic);
                        ieee80211_new_state(ic, IEEE80211_S_ASSOC, 0);
                        IEEE80211_LOCK(ic);
                }
                return (err);
        }

        /*
         * Set desired rate. The desired rate is for data transfer
         * and usally is checked and used when driver changes to
         * RUN state.
         * If the driver is in AUTH | ASSOC | RUN state, desired
         * rate is checked anainst rates supported by current ESS.
         * If it's supported and current state is AUTH|ASSOC, nothing
         * needs to be done by driver since the desired rate will
         * be enabled when the device changes to RUN state. And
         * when current state is RUN, Re-associate with the ESS to
         * enable the desired rate.
         */

        if (ic->ic_state != IEEE80211_S_INIT &&
            ic->ic_state != IEEE80211_S_SCAN) {
                for (i = 0; i < rs->ir_nrates; i++) {
                        if (drate == IEEE80211_RV(rs->ir_rates[i]))
                                break;
                }
                /* supported */
                if (i < rs->ir_nrates) {
                        ic->ic_fixed_rate = drate;
                        if (ic->ic_state == IEEE80211_S_RUN) {
                                IEEE80211_UNLOCK(ic);
                                ieee80211_new_state(ic,
                                    IEEE80211_S_ASSOC, 0);
                                IEEE80211_LOCK(ic);
                        }
                        return (err);
                }
        }

        /*
         * In INIT or SCAN state
         * check if the desired rate is supported by device
         */
        isfound = B_FALSE;
        for (i = 0; i < IEEE80211_MODE_MAX; i++) {
                rs = &ic->ic_sup_rates[i];
                for (j = 0; j < rs->ir_nrates; j++) {
                        if (drate ==  IEEE80211_RV(rs->ir_rates[j])) {
                                isfound = B_TRUE;
                                break;
                        }
                }
                if (isfound)
                        break;
        }
        if (!isfound) {
                ieee80211_err("wl_set_desrates: "
                    "invald rate %d\n", drate);
                err = EINVAL;
                return (err);
        }
        ic->ic_fixed_rate = drate;
        if (ic->ic_state != IEEE80211_S_SCAN)
                err = ENETRESET;

        return (err);
}

static void
wl_get_desrates(struct ieee80211com *ic, void *wldp_buf)
{
        uint8_t srate;
        wl_rates_t ow_rates;
        struct ieee80211_node *in = ic->ic_bss;
        struct ieee80211_rateset *rs = &in->in_rates;

        srate = rs->ir_rates[in->in_txrate] & IEEE80211_RATE_VAL;
        ow_rates.wl_rates_num = 1;
        ow_rates.wl_rates_rates[0] =
            (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) ?
            srate : ic->ic_fixed_rate;
        bcopy(&ow_rates, wldp_buf, sizeof (wl_rates_t));

}

/*
 * MAC_PROP_AUTH_MODE
 */
static int
wl_set_authmode(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        wl_authmode_t *iw_auth = (wl_authmode_t *)wldp_buf;

        if (*iw_auth == ic->ic_bss->in_authmode)
                return (err);

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_authmode: "
            "set authmode=%u\n", *iw_auth);

        switch (*iw_auth) {
        case WL_OPENSYSTEM:
        case WL_SHAREDKEY:
                ic->ic_bss->in_authmode = *iw_auth;
                err = ENETRESET;
                break;
        default:
                ieee80211_err("wl_set_authmode: "
                    "unknown authmode %u\n", *iw_auth);
                err = EINVAL;
                break;
        }

        return (err);
}

static void
wl_get_authmode(struct ieee80211com *ic, void *wldp_buf)
{
        wl_authmode_t ow_auth;

        ow_auth = ic->ic_bss->in_authmode;
        bcopy(&ow_auth, wldp_buf, sizeof (wl_authmode_t));

}

/*
 * MAC_PROP_WL_ENCRYPTION
 */
static int
wl_set_encrypt(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        uint32_t flags;
        wl_encryption_t *iw_encryp = (wl_encryption_t *)wldp_buf;

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_encrypt: "
            "set encryption=%u\n", *iw_encryp);

        flags = ic->ic_flags;
        if (*iw_encryp == WL_NOENCRYPTION)
                flags &= ~IEEE80211_F_PRIVACY;
        else
                flags |= IEEE80211_F_PRIVACY;

        if (ic->ic_flags != flags) {
                ic->ic_flags = flags;
                err = ENETRESET;
        }

        return (err);
}

static void
wl_get_encrypt(struct ieee80211com *ic, void *wldp_buf)
{
        wl_encryption_t *ow_encryp;

        ow_encryp = (wl_encryption_t *)wldp_buf;
        *ow_encryp = (ic->ic_flags & IEEE80211_F_PRIVACY) ? 1 : 0;
        if (ic->ic_flags & IEEE80211_F_WPA)
                *ow_encryp = WL_ENC_WPA;

}

/*
 * MAC_PROP_WL_RSSI
 */
static void
wl_get_rssi(struct ieee80211com *ic, void *wldp_buf)
{
        wl_rssi_t *ow_rssi;

        ow_rssi = (wl_rssi_t *)wldp_buf;
        *ow_rssi = wifi_getrssi(ic->ic_bss);

}

/*
 * MAC_PROP_WL_PHY_CONFIG
 */

static int
wl_set_phy(struct ieee80211com *ic, const void* wldp_buf)
{
        int err = 0;
        int16_t ch;
        wl_dsss_t *dsss;
        wl_phy_conf_t *iw_phy = (wl_phy_conf_t *)wldp_buf;

        dsss = (wl_dsss_t *)iw_phy;
        ch = dsss->wl_dsss_channel;

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_phy: "
            "set channel=%d\n", ch);

        if (ch == 0 || ch == (int16_t)IEEE80211_CHAN_ANY) {
                ic->ic_des_chan = IEEE80211_CHAN_ANYC;
        } else if ((uint_t)ch > IEEE80211_CHAN_MAX ||
            ieee80211_isclr(ic->ic_chan_active, ch)) {
                err = EINVAL;
                return (err);
        } else {
                ic->ic_des_chan = ic->ic_ibss_chan =
                    &ic->ic_sup_channels[ch];
        }

        switch (ic->ic_state) {
        case IEEE80211_S_INIT:
        case IEEE80211_S_SCAN:
                err = ENETRESET;
                break;
        default:
                /*
                 * If hte desired channel has changed (to something
                 * other than any) and we're not already scanning,
                 * then kick the state machine.
                 */
                if (ic->ic_des_chan != IEEE80211_CHAN_ANYC &&
                    ic->ic_bss->in_chan != ic->ic_des_chan &&
                    (ic->ic_flags & IEEE80211_F_SCAN) == 0)
                        err = ENETRESET;
                break;
        }

        return (err);
}

#define WIFI_HT_MODE(in)        (((in)->in_flags & IEEE80211_NODE_HT) ? 1 : 0)

static int
wl_get_phy(struct ieee80211com *ic, void *wldp_buf)
{
        int err = 0;
        wl_phy_conf_t *ow_phy;
        struct ieee80211_channel *ch = ic->ic_curchan;
        struct ieee80211_node *in = ic->ic_bss;

        ow_phy = (wl_phy_conf_t *)wldp_buf;
        bzero(wldp_buf, sizeof (wl_phy_conf_t));

        /* get current phy parameters: FH|DS|ERP */
        if (IEEE80211_IS_CHAN_A(ch) || IEEE80211_IS_CHAN_T(ch)) {
                wl_ofdm_t *ofdm = (wl_ofdm_t *)ow_phy;
                ofdm->wl_ofdm_subtype = WL_OFDM;
                ofdm->wl_ofdm_frequency = ch->ich_freq;
                ofdm->wl_ofdm_ht_enabled = WIFI_HT_MODE(in);
        } else {
                switch (ic->ic_phytype) {
                case IEEE80211_T_FH: {
                        wl_fhss_t *fhss = (wl_fhss_t *)ow_phy;
                        fhss->wl_fhss_subtype = WL_FHSS;
                        fhss->wl_fhss_channel =
                            ieee80211_chan2ieee(ic, ch);
                        break;
                }
                case IEEE80211_T_DS: {
                        wl_dsss_t *dsss = (wl_dsss_t *)ow_phy;
                        dsss->wl_dsss_subtype = WL_DSSS;
                        dsss->wl_dsss_channel =
                            ieee80211_chan2ieee(ic, ch);
                        break;
                }
                case IEEE80211_T_OFDM: {
                        wl_erp_t *erp = (wl_erp_t *)ow_phy;
                        erp->wl_erp_subtype = WL_ERP;
                        erp->wl_erp_channel =
                            ieee80211_chan2ieee(ic, ch);
                        erp->wl_erp_ht_enabled = WIFI_HT_MODE(in);
                        break;
                }
                default:
                        ieee80211_err("wl_get_phy: "
                            "unknown phy type, %x\n", ic->ic_phytype);
                        err = EIO;
                        break;
                }
        }

        return (err);
}

/*
 * MAC_PROP_WL_CAPABILITY
 */
static void
wl_get_capability(struct ieee80211com *ic, void *wldp_buf)
{
        wl_capability_t ow_caps;

        ow_caps.caps = ic->ic_caps;
        bcopy(&ow_caps, wldp_buf, sizeof (wl_capability_t));

}

/*
 * MAC_PROP_WL_WPA
 */
static int
wl_set_wpa(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        wl_wpa_t *wpa = (wl_wpa_t *)wldp_buf;

        ieee80211_dbg(IEEE80211_MSG_BRUSSELS, "wl_set_wpa: "
            "set wpa=%u\n", wpa->wpa_flag);

        if (wpa->wpa_flag > 0) {
                /* enable wpa mode */
                ic->ic_flags |= IEEE80211_F_PRIVACY;
                ic->ic_flags |= IEEE80211_F_WPA;
        } else {
                ic->ic_flags &= ~IEEE80211_F_PRIVACY;
                ic->ic_flags &= ~IEEE80211_F_WPA;
        }

        return (err);
}

static void
wl_get_wpa(struct ieee80211com *ic, void *wldp_buf)
{
        wl_wpa_t *wpa;

        wpa = (wl_wpa_t *)wldp_buf;
        wpa->wpa_flag = ((ic->ic_flags & IEEE80211_F_WPA) ? 1 : 0);

        ieee80211_dbg(IEEE80211_MSG_BRUSSELS, "wl_get_wpa: "
            "get wpa=%u\n", wpa->wpa_flag);

}

/*
 * MAC_PROP_WL_SCANRESULTS
 */

static void
wl_get_scanresults(struct ieee80211com *ic, void *wldp_buf)
{
        wl_wpa_ess_t *sr;
        ieee80211_node_t *in;
        ieee80211_node_table_t *nt;
        int ap_num;
        int len;

        sr = (wl_wpa_ess_t *)wldp_buf;
        sr->count = 0;
        ap_num = 0;

        ieee80211_dbg(IEEE80211_MSG_WPA, "wl_get_scanrelults\n");

        nt = &ic->ic_scan;
        IEEE80211_NODE_LOCK(nt);
        in = list_head(&nt->nt_node);

        while (in != NULL) {
                /* filter out non-wpa APs */
                if (in->in_wpa_ie == NULL) {
                        in = list_next(&nt->nt_node, in);
                        continue;
                }
                bcopy(in->in_bssid, sr->ess[ap_num].bssid,
                    IEEE80211_ADDR_LEN);
                sr->ess[ap_num].ssid_len = in->in_esslen;
                bcopy(in->in_essid, sr->ess[ap_num].ssid,
                    in->in_esslen);
                sr->ess[ap_num].freq = in->in_chan->ich_freq;

                len = in->in_wpa_ie[1] + 2;
                bcopy(in->in_wpa_ie, sr->ess[ap_num].wpa_ie, len);
                sr->ess[ap_num].wpa_ie_len = len;

                ap_num++;
                in = list_next(&nt->nt_node, in);
        }
        IEEE80211_NODE_UNLOCK(nt);
        sr->count = ap_num;

}

/*
 * MAC_PROP_WL_ESS_LIST
 */
static void
wl_get_esslist(struct ieee80211com *ic, void *wldp_buf)
{
        wl_ess_list_t *ess_list;

        ess_list = (wl_ess_list_t *)wldp_buf;

        ess_list->wl_ess_list_num = 0;
        ieee80211_iterate_nodes(&ic->ic_scan, wifi_read_ap, ess_list);

}

/*
 * MAC_PROP_WL_WEP_KEY
 */
static int
wl_set_wepkey(struct ieee80211com *ic, const void *wldp_buf)
{
        int      err = 0;
        uint16_t i;
        uint32_t klen;
        struct ieee80211_key *key;
        wl_wep_key_t *wepkey = (wl_wep_key_t *)wldp_buf;

        /* set all valid keys */
        for (i = 0; i < MAX_NWEPKEYS; i++) {
                if (wepkey[i].wl_wep_operation != WL_ADD)
                        continue;
                klen = wepkey[i].wl_wep_length;
                if (klen > IEEE80211_KEYBUF_SIZE) {
                        ieee80211_err("wl_set_wepkey: "
                            "invalid wepkey length, %u\n", klen);
                        err = EINVAL;
                        continue;  /* continue to set other keys */
                }
                if (klen == 0)
                        continue;

                /*
                 *  Set key contents. Only WEP is supported
                 */
                ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_wepkey: "
                    "set key %u, len=%u\n", i, klen);
                key = &ic->ic_nw_keys[i];
                if (ieee80211_crypto_newkey(ic, IEEE80211_CIPHER_WEP,
                    IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, key) == 0) {
                        ieee80211_err("wl_set_wepkey: "
                            "abort, create key failed. id=%u\n", i);
                        err = EIO;
                        continue;
                }

                key->wk_keyix = i;
                key->wk_keylen = (uint8_t)klen;
                key->wk_flags |= IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV;
                bzero(key->wk_key, IEEE80211_KEYBUF_SIZE);
                bcopy(wepkey[i].wl_wep_key, key->wk_key, klen);
                if (ieee80211_crypto_setkey(ic, key, ic->ic_macaddr)
                    == 0) {
                        ieee80211_err("wl_set_wepkey: "
                            "set key failed len=%u\n", klen);
                        err = EIO;
                }
        }
        if (err == 0)
                err = ENETRESET;

        return (err);
}

/*
 * MAC_PROP_WL_SETOPTIE
 */
static int
wl_set_optie(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        char *ie;
        wl_wpa_ie_t *ie_in = (wl_wpa_ie_t *)wldp_buf;

        if (ic->ic_opmode != IEEE80211_M_STA) {
                ieee80211_err("wl_set_optie: opmode err\n");
                err = EINVAL;
                return (err);
        }
        if (ie_in->wpa_ie_len > IEEE80211_MAX_OPT_IE) {

                ieee80211_err("wl_set_optie: optie is too long\n");

                err = EINVAL;
                return (err);
        }

        ie = ieee80211_malloc(ie_in->wpa_ie_len);
        (void) memcpy(ie, ie_in->wpa_ie, ie_in->wpa_ie_len);
        if (ic->ic_opt_ie != NULL) {
                ieee80211_dbg(IEEE80211_MSG_BRUSSELS,
                    "wl_set_optie:ic_opt_ie!=NULL\n");
                ieee80211_free(ic->ic_opt_ie);
        }
        ic->ic_opt_ie = ie;
        ic->ic_opt_ie_len = ie_in->wpa_ie_len;

        return (err);
}

/*
 * MAC_PROP_WL_DELKEY
 */
static int
wl_set_delkey(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        int kid;
        wl_del_key_t *dk = (wl_del_key_t *)wldp_buf;

        ieee80211_dbg(IEEE80211_MSG_BRUSSELS, "wl_set_delkey(): "
            "keyix=%d\n", dk->idk_keyix);

        kid = dk->idk_keyix;

        if (kid == IEEE80211_KEYIX_NONE ||
            kid >= IEEE80211_WEP_NKID) {
                ieee80211_err("wl_set_delkey: incorrect keyix\n");
                err = EINVAL;
                return (err);
        } else {
                (void) ieee80211_crypto_delkey(ic,
                    &ic->ic_nw_keys[kid]);
                ieee80211_mac_update(ic);
        }

        return (err);
}

/*
 * MAC_PROP_WL_MLME
 */

static int
wl_set_mlme(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        uint32_t flags;
        ieee80211_node_t *in;
        wl_mlme_t *mlme = (wl_mlme_t *)wldp_buf;

        ieee80211_dbg(IEEE80211_MSG_WPA, "wl_set_mlme: "
            "op=%d\n", mlme->im_op);

        switch (mlme->im_op) {
        case IEEE80211_MLME_DISASSOC:
        case IEEE80211_MLME_DEAUTH:
                if (ic->ic_opmode == IEEE80211_M_STA) {
                        /*
                         * Mask ic_flags of IEEE80211_F_WPA to disable
                         * ieee80211_notify temporarily.
                         */
                        flags = ic->ic_flags;
                        ic->ic_flags &= ~IEEE80211_F_WPA;

                        IEEE80211_UNLOCK(ic);
                        ieee80211_new_state(ic, IEEE80211_S_INIT,
                            mlme->im_reason);
                        IEEE80211_LOCK(ic);

                        ic->ic_flags = flags;
                }
                break;
        case IEEE80211_MLME_ASSOC:
                if (ic->ic_opmode != IEEE80211_M_STA) {
                        ieee80211_err("wifi_cfg_setmlme: opmode err\n");
                        err = EINVAL;
                        break;
                }
                if (ic->ic_des_esslen != 0) {
                /*
                 * Desired ssid specified; must match both bssid and
                 * ssid to distinguish ap advertising multiple ssid's.
                 */
                        in = ieee80211_find_node_with_ssid(&ic->ic_scan,
                            mlme->im_macaddr,
                            ic->ic_des_esslen,
                            ic->ic_des_essid);
                } else {
                /*
                 * Normal case; just match bssid.
                 */
                        in = ieee80211_find_node(&ic->ic_scan,
                            mlme->im_macaddr);
                }
                if (in == NULL) {
                        ieee80211_err("wifi_cfg_setmlme: "
                            "no matched node\n");
                        err = EINVAL;
                        break;
                }
                IEEE80211_UNLOCK(ic);
                ieee80211_sta_join(ic, in);
                IEEE80211_LOCK(ic);
                break;
        default:
                err = EINVAL;
                break;
        }

        return (err);
}

/*
 * MAC_PROP_WL_WPA_KEY
 */
static int
wl_set_wpakey(struct ieee80211com *ic, const void *wldp_buf)
{
        int err = 0;
        uint16_t kid;
        struct ieee80211_node *in;
        struct ieee80211_key *wk;
        wl_key_t ik;

        bcopy(wldp_buf, &ik, sizeof (wl_key_t));

        ieee80211_dbg(IEEE80211_MSG_BRUSSELS, "wl_set_wpakey: "
            "idx=%d\n", ik.ik_keyix);

        /*
         * cipher support is verified by ieee80211_crypt_newkey
         * this also checks ik.ik_keylen > sizeof(wk->wk_key)
         */
        if (ik.ik_keylen > sizeof (ik.ik_keydata)) {
                ieee80211_err("wl_set_wpakey: key is too long\n");
                err = EINVAL;
                return (err);
        }
        kid = ik.ik_keyix;
        if (kid == IEEE80211_KEYIX_NONE || kid >= IEEE80211_WEP_NKID) {
                ieee80211_err("wl_set_wpakey: incorrect keyix\n");
                err = EINVAL;
                return (err);
        } else {
                wk = &ic->ic_nw_keys[kid];
                /*
                 * Globle slots start off w/o any assigned key index.
                 * Force one here for consistency with WEPKEY.
                 */
                if (wk->wk_keyix == IEEE80211_KEYIX_NONE)
                        wk->wk_keyix = kid;
                in = NULL;
        }

        KEY_UPDATE_BEGIN(ic);
        if (ieee80211_crypto_newkey(ic, ik.ik_type,
            ik.ik_flags, wk)) {
                wk->wk_keylen = ik.ik_keylen;
                /* MIC presence is implied by cipher type */
                if (wk->wk_keylen > IEEE80211_KEYBUF_SIZE)
                        wk->wk_keylen = IEEE80211_KEYBUF_SIZE;
                wk->wk_keyrsc = ik.ik_keyrsc;
                wk->wk_keytsc = 0;
                wk->wk_flags |= ik.ik_flags &
                    (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV);
                (void) memset(wk->wk_key, 0, sizeof (wk->wk_key));
                (void) memcpy(wk->wk_key, ik.ik_keydata,
                    ik.ik_keylen);
                if (!ieee80211_crypto_setkey(ic, wk,
                    in != NULL ? in->in_macaddr : ik.ik_macaddr)) {
                        err = EIO;
                } else if ((ik.ik_flags & IEEE80211_KEY_DEFAULT)) {
                        ic->ic_def_txkey = kid;
                        ieee80211_mac_update(ic);
                }
        } else {
                err = EIO;
        }
        KEY_UPDATE_END(ic);

        return (err);
}

/*
 * MAC_PROP_WL_SUP_RATE
 */
static void
wl_get_suprates(struct ieee80211com *ic, void *wldp_buf)
{
        int i, j, k, l;
        uint8_t srates;
        uint8_t *drates;
        wl_rates_t *wl_rates;
        const struct ieee80211_rateset *srs;

        wl_rates = (wl_rates_t *)wldp_buf;

        wl_rates->wl_rates_num = 0;
        drates = (uint8_t *)wl_rates->wl_rates_rates;
        for (i = 0; i < IEEE80211_MODE_MAX; i++) {
                srs = &ic->ic_sup_rates[i];
                if (srs->ir_nrates == 0)
                        continue;
                for (j = 0; j < srs->ir_nrates; j++) {
                        srates = IEEE80211_RV(srs->ir_rates[j]);
                        /* sort & skip duplicated rates */
                        for (k = 0; k < wl_rates->wl_rates_num; k++) {
                                if (srates <= drates[k])
                                        break;
                        }
                        if (srates == drates[k])
                                /* skip duplicated rates */
                                continue;
                        /* sort */
                        for (l = wl_rates->wl_rates_num; l > k; l--)
                                drates[l] = drates[l-1];
                        drates[k] = srates;
                        wl_rates->wl_rates_num++;
                }
        }

}

/*
 * MAC_PROP_WL_CREATE_IBSS
 */
static int
wl_set_createibss(struct ieee80211com *ic, const void *wldp_buf)
{
        wl_create_ibss_t *iw_ibss = (wl_create_ibss_t *)wldp_buf;
        int err = 0;

        ieee80211_dbg(IEEE80211_MSG_CONFIG, "wl_set_ibss: "
            "set createibss=%u\n", *iw_ibss);

        if ((ic->ic_caps & IEEE80211_C_IBSS) == 0) {
                err = ENOTSUP;
                return (err);
        }
        if (*iw_ibss) {
                if ((ic->ic_flags & IEEE80211_F_IBSSON) == 0) {
                        ic->ic_flags |= IEEE80211_F_IBSSON;
                        ic->ic_opmode = IEEE80211_M_IBSS;
                        /*
                         * Yech, slot time may change depending on the
                         * operating mode so reset it to be sure
                         * everything is setup appropriately.
                         */
                        ieee80211_reset_erp(ic);
                        err = ENETRESET;
                }
        } else {
                if (ic->ic_flags & IEEE80211_F_IBSSON) {
                        ic->ic_flags &= ~IEEE80211_F_IBSSON;
                        err = ENETRESET;
                }
        }

        return (err);
}

static void
wl_get_createibss(struct ieee80211com *ic, void *wldp_buf)
{
        wl_create_ibss_t *ow_ibss = (wl_create_ibss_t *)wldp_buf;

        *ow_ibss = (ic->ic_flags & IEEE80211_F_IBSSON)? 1 : 0;
}

/*
 * Typically invoked by drivers in response to request for
 * information or to change settings from the userland.
 *
 * Return value should be checked by WiFI drivers. Return 0
 * on success. Otherwise, return non-zero value to indicate
 * the error. Driver should operate as below when the return
 * error is:
 * ENETRESET    Reset wireless network and re-start to join a
 *              WLAN, ENETRESET is returned when a configuration
 *              parameter has been changed.
 *              When acknowledge a M_IOCTL message, this error
 *              is ignored
 */
/* ARGSUSED */
int
ieee80211_setprop(void *ic_arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, const void *wldp_buf)
{
        int err = 0;
        struct ieee80211com *ic = ic_arg;

        ASSERT(ic != NULL);
        IEEE80211_LOCK(ic);

        switch (wldp_pr_num) {
        /* mac_prop_id */
        case MAC_PROP_WL_ESSID:
                err = wl_set_essid(ic, wldp_buf);
                break;
        case MAC_PROP_WL_BSSID:
                err = wl_set_bssid(ic, wldp_buf);
                break;
        case MAC_PROP_WL_PHY_CONFIG:
                err = wl_set_phy(ic, wldp_buf);
                break;
        case MAC_PROP_WL_KEY_TAB:
                err = wl_set_wepkey(ic, wldp_buf);
                break;
        case MAC_PROP_WL_AUTH_MODE:
                err = wl_set_authmode(ic, wldp_buf);
                break;
        case MAC_PROP_WL_ENCRYPTION:
                err = wl_set_encrypt(ic, wldp_buf);
                break;
        case MAC_PROP_WL_BSSTYPE:
                err = wl_set_bsstype(ic, wldp_buf);
                break;
        case MAC_PROP_WL_DESIRED_RATES:
                err = wl_set_desrates(ic, wldp_buf);
                break;
        case MAC_PROP_WL_WPA:
                err = wl_set_wpa(ic, wldp_buf);
                break;
        case MAC_PROP_WL_KEY:
                err = wl_set_wpakey(ic, wldp_buf);
                break;
        case MAC_PROP_WL_DELKEY:
                err = wl_set_delkey(ic, wldp_buf);
                break;
        case MAC_PROP_WL_SETOPTIE:
                err = wl_set_optie(ic, wldp_buf);
                break;
        case MAC_PROP_WL_MLME:
                err = wl_set_mlme(ic, wldp_buf);
                break;
        case MAC_PROP_WL_CREATE_IBSS:
                err = wl_set_createibss(ic, wldp_buf);
                break;
        case MAC_PROP_WL_LINKSTATUS:
        case MAC_PROP_WL_ESS_LIST:
        case MAC_PROP_WL_SUPPORTED_RATES:
        case MAC_PROP_WL_RSSI:
        case MAC_PROP_WL_CAPABILITY:
        case MAC_PROP_WL_SCANRESULTS:
                ieee80211_err("ieee80211_setprop: opmode err\n");
                err = EINVAL;
                break;
        default:
                ieee80211_err("ieee80211_setprop: opmode not support\n");
                err = ENOTSUP;
                break;
        }

        IEEE80211_UNLOCK(ic);

        return (err);
}

/* ARGSUSED */
int
ieee80211_getprop(void *ic_arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, void *wldp_buf)
{
        int err = 0;
        struct ieee80211com *ic = ic_arg;

        ASSERT(ic != NULL);
        IEEE80211_LOCK(ic);

        switch (wldp_pr_num) {
        /* mac_prop_id */
        case MAC_PROP_WL_ESSID:
                wl_get_essid(ic, wldp_buf);
                break;
        case MAC_PROP_WL_BSSID:
                wl_get_bssid(ic, wldp_buf);
                break;
        case MAC_PROP_WL_PHY_CONFIG:
                err = wl_get_phy(ic, wldp_buf);
                break;
        case MAC_PROP_WL_AUTH_MODE:
                wl_get_authmode(ic, wldp_buf);
                break;
        case MAC_PROP_WL_ENCRYPTION:
                wl_get_encrypt(ic, wldp_buf);
                break;
        case MAC_PROP_WL_BSSTYPE:
                wl_get_bsstype(ic, wldp_buf);
                break;
        case MAC_PROP_WL_DESIRED_RATES:
                wl_get_desrates(ic, wldp_buf);
                break;
        case MAC_PROP_WL_LINKSTATUS:
                wl_get_linkstatus(ic, wldp_buf);
                break;
        case MAC_PROP_WL_ESS_LIST:
                wl_get_esslist(ic, wldp_buf);
                break;
        case MAC_PROP_WL_SUPPORTED_RATES:
                wl_get_suprates(ic, wldp_buf);
                break;
        case MAC_PROP_WL_RSSI:
                wl_get_rssi(ic, wldp_buf);
                break;
        case MAC_PROP_WL_CAPABILITY:
                wl_get_capability(ic, wldp_buf);
                break;
        case MAC_PROP_WL_WPA:
                wl_get_wpa(ic, wldp_buf);
                break;
        case MAC_PROP_WL_SCANRESULTS:
                wl_get_scanresults(ic, wldp_buf);
                break;
        case MAC_PROP_WL_CREATE_IBSS:
                wl_get_createibss(ic, wldp_buf);
                break;
        case MAC_PROP_WL_KEY_TAB:
        case MAC_PROP_WL_KEY:
        case MAC_PROP_WL_DELKEY:
        case MAC_PROP_WL_SETOPTIE:
        case MAC_PROP_WL_MLME:
                ieee80211_err("ieee80211_setprop: opmode err\n");
                err = EINVAL;
                break;
        default:
                ieee80211_err("ieee80211_setprop: opmode not support\n");
                err = ENOTSUP;
                break;
        }

        IEEE80211_UNLOCK(ic);

        return (err);
}

void ieee80211_propinfo(void *ic_arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, mac_prop_info_handle_t prh)
{
        _NOTE(ARGUNUSED(pr_name, ic_arg));

        /*
         * By default permissions are read/write unless specified
         * otherwise by the driver.
         */

        switch (wldp_pr_num) {
        case MAC_PROP_WL_LINKSTATUS:
        case MAC_PROP_WL_ESS_LIST:
        case MAC_PROP_WL_SUPPORTED_RATES:
        case MAC_PROP_WL_RSSI:
        case MAC_PROP_WL_CAPABILITY:
        case MAC_PROP_WL_SCANRESULTS:
        case MAC_PROP_WL_CREATE_IBSS:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
        }
}