/*
 * Copyright 2008 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 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:
		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_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;

	while ((ic->ic_flags & (IEEE80211_F_SCAN | IEEE80211_F_ASCAN)) != 0) {
		if (cv_timedwait_sig(&im->im_scan_cv, &ic->ic_genlock,
		    ddi_get_lbolt() + drv_usectohz(WAIT_SCAN_MAX * 1000)) !=
		    0) {
			break;
		}
	}
}

#define	WIFI_HAVE_CAP(in, flag)	(((in)->in_capinfo & (flag)) ? 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;

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

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_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;
		}
		if ((ic->ic_flags & IEEE80211_F_IBSSON) == 0) {
			ic->ic_flags |= IEEE80211_F_IBSSON;
			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);
}

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;

	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;
	} 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);
			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 = (wl_key_t *)wldp_buf;

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

}

/*
 * 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_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 pr_flags, uint_t wldp_length, void *wldp_buf, uint_t *perm)
{
	int err = 0;
	struct ieee80211com *ic = ic_arg;

	if (wldp_length == 0) {
		err = EINVAL;
		return (err);
	}
	bzero(wldp_buf, wldp_length);

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

	*perm = MAC_PROP_PERM_RW;

	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:
		*perm = MAC_PROP_PERM_READ;
		wl_get_linkstatus(ic, wldp_buf);
		break;
	case MAC_PROP_WL_ESS_LIST:
		*perm = MAC_PROP_PERM_READ;
		wl_get_esslist(ic, wldp_buf);
		break;
	case MAC_PROP_WL_SUPPORTED_RATES:
		*perm = MAC_PROP_PERM_READ;
		wl_get_suprates(ic, wldp_buf);
		break;
	case MAC_PROP_WL_RSSI:
		*perm = MAC_PROP_PERM_READ;
		wl_get_rssi(ic, wldp_buf);
		break;
	case MAC_PROP_WL_CAPABILITY:
		*perm = MAC_PROP_PERM_READ;
		wl_get_capability(ic, wldp_buf);
		break;
	case MAC_PROP_WL_WPA:
		wl_get_wpa(ic, wldp_buf);
		break;
	case MAC_PROP_WL_SCANRESULTS:
		*perm = MAC_PROP_PERM_READ;
		wl_get_scanresults(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);
}