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