diff options
Diffstat (limited to 'usr/src/uts/common/io/net80211/net80211_ioctl.c')
-rw-r--r-- | usr/src/uts/common/io/net80211/net80211_ioctl.c | 1359 |
1 files changed, 1359 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/net80211/net80211_ioctl.c b/usr/src/uts/common/io/net80211/net80211_ioctl.c new file mode 100644 index 0000000000..3701baa6f7 --- /dev/null +++ b/usr/src/uts/common/io/net80211/net80211_ioctl.c @@ -0,0 +1,1359 @@ +/* + * Copyright 2006 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. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#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.h> +#include <inet/wifi_ioctl.h> +#include "net80211_impl.h" + +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; + char *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: + essid = (char *)ic->ic_des_essid; + if (essid[0] == '\0') + essid = (char *)ic->ic_bss->in_essid; + 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); + break; + case WLAN_SET_PARAM: + if (iw_essid->wl_essid_length > IEEE80211_NWID_LEN) { + ieee80211_err("wifi_cfg_essid: " + "essid too long, %u, max %u\n", + iw_essid->wl_essid_length, IEEE80211_NWID_LEN); + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + break; + } + essid = iw_essid->wl_essid_essid; + essid[IEEE80211_NWID_LEN] = 0; + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_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; + 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; + uint8_t *bssid; + 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: + if (ic->ic_flags & IEEE80211_F_DESBSSID) + bssid = ic->ic_des_bssid; + else + bssid = ic->ic_bss->in_bssid; + bcopy(bssid, outp->wldp_buf, sizeof (wl_bssid_t)); + break; + case WLAN_SET_PARAM: + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_bssid: " + "set bssid=%s\n", + ieee80211_macaddr_sprintf(inp->wldp_buf)); + bcopy(inp->wldp_buf, ic->ic_des_bssid, sizeof (wl_bssid_t)); + ic->ic_flags |= IEEE80211_F_DESBSSID; + err = ENETRESET; + 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; + struct ieee80211_channel *ch = ic->ic_curchan; + 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: { + /* get current physical (FH, DS, ERP) parameters */ + 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("wifi_cfg_phy: " + "unknown phy type, %x\n", ic->ic_phytype); + outp->wldp_result = WL_HW_ERROR; + err = EIO; + break; + } /* switch (ic->ic_phytype) */ + } + break; + } + + case WLAN_SET_PARAM: { + wl_dsss_t *dsss = (wl_dsss_t *)iw_phy; + int16_t ch = dsss->wl_dsss_channel; + + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_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)) { + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + break; + } 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 the 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; + } + 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; + struct ieee80211_key *k; + uint16_t i; + uint32_t klen; + 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; + } + + /* set all valid keys */ + for (i = 0; i < MAX_NWEPKEYS; i++) { + if (iw_wepkey[i].wl_wep_operation != WL_ADD) + continue; + klen = iw_wepkey[i].wl_wep_length; + if (klen > IEEE80211_KEYBUF_SIZE) { + ieee80211_err("wifi_cfg_wepkey: " + "invalid wepkey length, %u\n", klen); + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + continue; /* continue to set other keys */ + } + if (klen == 0) + continue; + + /* + * Set key contents. Only WEP is supported + */ + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_wepkey: " + "set key %u, len=%u\n", i, klen); + k = &ic->ic_nw_keys[i]; + if (ieee80211_crypto_newkey(ic, IEEE80211_CIPHER_WEP, + IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, k) == 0) { + ieee80211_err("wifi_cfg_wepkey: " + "abort, create key failed. id=%u\n", i); + outp->wldp_result = WL_HW_ERROR; + err = EIO; + continue; + } + k->wk_keyix = i; + k->wk_keylen = (uint8_t)klen; + k->wk_flags |= IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV; + bzero(k->wk_key, IEEE80211_KEYBUF_SIZE); + bcopy(iw_wepkey[i].wl_wep_key, k->wk_key, klen); + if (ieee80211_crypto_setkey(ic, k, ic->ic_macaddr) + == 0) { + ieee80211_err("wifi_cfg_wepkey: " + "set key failed len=%u\n", klen); + outp->wldp_result = WL_HW_ERROR; + err = EIO; + } + } + if (err == 0) + err = ENETRESET; + 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: + *ow_auth = ic->ic_bss->in_authmode; + break; + case WLAN_SET_PARAM: + if (*iw_auth == ic->ic_bss->in_authmode) + break; + + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_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("wifi_cfg_authmode: " + "unknown authmode %u\n", *iw_auth); + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + break; + } + 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; + uint32_t flags; + 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: + *ow_encryp = (ic->ic_flags & IEEE80211_F_PRIVACY) ? 1 : 0; + break; + case WLAN_SET_PARAM: + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_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; + } + 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: + 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; + } + break; + case WLAN_SET_PARAM: + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_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) { + outp->wldp_result = WL_LACK_FEATURE; + 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("wifi_cfg_bsstype: " + "unknown opmode %u\n", *iw_opmode); + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + break; + } + 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: + *ow_linkstat = (ic->ic_state == IEEE80211_S_RUN) ? + WL_CONNECTED : WL_NOTCONNECTED; + 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; + const struct ieee80211_rateset *srs; + uint8_t srates, *drates; + int err, buflen, i, j, k, l; + + 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: + /* all rates supported by the device */ + ow_rates->wl_rates_num = 0; + drates = (uint8_t *)ow_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 and skip duplicated rates */ + for (k = 0; k < ow_rates->wl_rates_num; k++) { + if (srates <= drates[k]) + break; + } + if (srates == drates[k]) + continue; /* duplicate, skip */ + /* sort */ + for (l = ow_rates->wl_rates_num; l > k; l--) + drates[l] = drates[l-1]; + drates[k] = srates; + ow_rates->wl_rates_num++; + } + } + 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; + struct ieee80211_node *in = ic->ic_bss; + struct ieee80211_rateset *rs = &in->in_rates; + uint8_t drate, srate; + int err, i, j; + boolean_t found; + + 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; + + srate = rs->ir_rates[in->in_txrate] & IEEE80211_RATE_VAL; + switch (cmd) { + case WLAN_GET_PARAM: + 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; + break; + case WLAN_SET_PARAM: + drate = iw_rates->wl_rates_rates[0]; + if (ic->ic_fixed_rate == drate) + break; + + ieee80211_dbg(IEEE80211_MSG_CONFIG, "wifi_cfg_desrates: " + "set desired rate=%u\n", drate); + + if (drate == 0) { /* reset */ + 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); + } + break; + } + + /* + * Set desired rate. the desired rate is for data transfer + * and usually is checked and used when driver changes to + * RUN state. + * If the driver is in AUTH | ASSOC | RUN state, desired + * rate is checked against rates supported by current ESS. + * If it's supported and current state is AUTH|ASSOC, nothing + * needs to be doen 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) { + /* check if the rate is supported by current ESS */ + for (i = 0; i < rs->ir_nrates; i++) { + if (drate == IEEE80211_RV(rs->ir_rates[i])) + break; + } + if (i < rs->ir_nrates) { /* supported */ + 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); + } + break; + } + } + + /* check the rate is supported by device */ + found = 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])) { + found = B_TRUE; + break; + } + } + if (found) + break; + } + if (!found) { + ieee80211_err("wifi_cfg_desrates: " + "invalid rate %d\n", drate); + outp->wldp_result = WL_NOTSUPPORTED; + err = EINVAL; + break; + } + ic->ic_fixed_rate = drate; + if (ic->ic_state != IEEE80211_S_SCAN) + err = ENETRESET; /* restart */ + 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); + + /* 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; + + /* 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_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); +} + +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; + 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) { + /* + * Check for specific net_config privilege on Solaris 10+. + */ + err = secpolicy_net_config(iocp->ioc_cr, B_FALSE); + if (err != 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); +} |