#include <sys/param.h>
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/stropts.h>
#include <sys/door.h>
#include <sys/mac_provider.h>
#include "net80211_impl.h"
uint32_t ieee80211_debug = 0x0;
const char *ieee80211_phymode_name[] = {
"auto",
"11a",
"11b",
"11g",
"FH",
"turboA",
"turboG",
"sturboA",
"11na",
"11ng",
};
#define IEEE80211_DPRINT(_level, _fmt) do { \
_NOTE(CONSTCOND) \
va_list ap; \
va_start(ap, (_fmt)); \
vcmn_err((_level), (_fmt), ap); \
va_end(ap); \
_NOTE(CONSTCOND) \
} while (0)
void
ieee80211_err(const int8_t *fmt, ...)
{
IEEE80211_DPRINT(CE_WARN, fmt);
}
void
ieee80211_dbg(uint32_t flag, const int8_t *fmt, ...)
{
if (flag & ieee80211_debug)
IEEE80211_DPRINT(CE_CONT, fmt);
}
void *
ieee80211_malloc(size_t size)
{
void *p = kmem_zalloc((size + 4), KM_SLEEP);
*(int *)p = size;
p = (char *)p + 4;
return (p);
}
void
ieee80211_free(void *p)
{
void *tp = (char *)p - 4;
kmem_free((char *)p - 4, *(int *)tp + 4);
}
void
ieee80211_mac_update(ieee80211com_t *ic)
{
wifi_data_t wd = { 0 };
ieee80211_node_t *in;
in = ic->ic_bss;
wd.wd_secalloc = ieee80211_crypto_getciphertype(ic);
wd.wd_opmode = ic->ic_opmode;
IEEE80211_ADDR_COPY(wd.wd_bssid, in->in_bssid);
wd.wd_qospad = 0;
if (in->in_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT)) {
wd.wd_qospad = 2;
if (ic->ic_flags & IEEE80211_F_DATAPAD)
wd.wd_qospad = roundup(wd.wd_qospad, sizeof (uint32_t));
}
(void) mac_pdata_update(ic->ic_mach, &wd, sizeof (wd));
mac_tx_update(ic->ic_mach);
ieee80211_dbg(IEEE80211_MSG_ANY, "ieee80211_mac_update"
"(cipher = %d)\n", wd.wd_secalloc);
}
static void
ieee80211_event_thread(void *arg)
{
ieee80211com_t *ic = arg;
door_handle_t event_door = NULL;
wl_events_t ev;
door_arg_t darg;
mutex_enter(&ic->ic_doorlock);
ev.event = ic->ic_eventq[ic->ic_evq_head];
ic->ic_evq_head ++;
if (ic->ic_evq_head >= MAX_EVENT)
ic->ic_evq_head = 0;
ieee80211_dbg(IEEE80211_MSG_DEBUG, "ieee80211_event(%d)\n", ev.event);
if (door_ki_open(ic->ic_wpadoor, &event_door) != 0) {
ieee80211_err("ieee80211_event: door_ki_open(%s) failed\n",
ic->ic_wpadoor);
goto out;
}
darg.data_ptr = (char *)&ev;
darg.data_size = sizeof (wl_events_t);
darg.desc_ptr = NULL;
darg.desc_num = 0;
darg.rbuf = NULL;
darg.rsize = 0;
if (door_ki_upcall_limited(event_door, &darg, NULL, SIZE_MAX, 0) != 0) {
ieee80211_err("ieee80211_event: door_ki_upcall() failed\n");
}
if (event_door) {
door_ki_rele(event_door);
}
out:
mutex_exit(&ic->ic_doorlock);
}
void
ieee80211_notify(ieee80211com_t *ic, wpa_event_type event)
{
if ((ic->ic_flags & IEEE80211_F_WPA) == 0)
return;
ic->ic_eventq[ic->ic_evq_tail] = event;
ic->ic_evq_tail ++;
if (ic->ic_evq_tail >= MAX_EVENT) ic->ic_evq_tail = 0;
(void) timeout(ieee80211_event_thread, (void *)ic, 0);
}
void
ieee80211_register_door(ieee80211com_t *ic, const char *drvname, int inst)
{
(void) snprintf(ic->ic_wpadoor, MAX_IEEE80211STR, "%s_%s%d",
WPA_DOOR, drvname, inst);
}
static int
ieee80211_default_reset(ieee80211com_t *ic)
{
return (ENETRESET);
}
uint32_t
ieee80211_chan2ieee(ieee80211com_t *ic, struct ieee80211_channel *ch)
{
if ((ic->ic_sup_channels <= ch) &&
(ch <= &ic->ic_sup_channels[IEEE80211_CHAN_MAX])) {
return (ch - ic->ic_sup_channels);
} else if (ch == IEEE80211_CHAN_ANYC) {
return (IEEE80211_CHAN_ANY);
} else if (ch != NULL) {
ieee80211_err("invalid channel freq %u flags %x\n",
ch->ich_freq, ch->ich_flags);
return (0);
}
ieee80211_err("invalid channel (NULL)\n");
return (0);
}
uint32_t
ieee80211_ieee2mhz(uint32_t chan, uint32_t flags)
{
if (flags & IEEE80211_CHAN_2GHZ) {
if (chan == 14)
return (2484);
if (chan < 14)
return (2412 + (chan - 1) * 5);
else
return (2512 + ((chan - 15) * 20));
} else if (flags & IEEE80211_CHAN_5GHZ) {
return (5000 + (chan * 5));
} else {
if (chan == 14)
return (2484);
if (chan < 14)
return (2412 + (chan - 1) * 5);
if (chan < 27)
return (2512 + ((chan - 15) * 20));
return (5000 + (chan * 5));
}
}
void
ieee80211_media_init(ieee80211com_t *ic)
{
ieee80211_node_lateattach(ic);
}
void
ieee80211_start_watchdog(ieee80211com_t *ic, uint32_t timer)
{
if (ic->ic_watchdog_timer == 0 && ic->ic_watchdog != NULL) {
ic->ic_watchdog_timer = timeout(ic->ic_watchdog, ic,
drv_usectohz(1000000 * timer));
}
}
void
ieee80211_stop_watchdog(ieee80211com_t *ic)
{
if (ic->ic_watchdog_timer != 0) {
if (ic->ic_watchdog != NULL)
(void) untimeout(ic->ic_watchdog_timer);
ic->ic_watchdog_timer = 0;
}
}
void
ieee80211_watchdog(void *arg)
{
ieee80211com_t *ic = arg;
struct ieee80211_impl *im = ic->ic_private;
ieee80211_node_table_t *nt;
int inact_timer = 0;
if (ic->ic_state == IEEE80211_S_INIT)
return;
IEEE80211_LOCK(ic);
if ((im->im_mgt_timer != 0) && (--im->im_mgt_timer == 0)) {
IEEE80211_UNLOCK(ic);
ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
IEEE80211_LOCK(ic);
}
nt = &ic->ic_scan;
if (nt->nt_inact_timer != 0) {
if (--nt->nt_inact_timer == 0)
nt->nt_timeout(nt);
inact_timer += nt->nt_inact_timer;
}
nt = &ic->ic_sta;
if (nt->nt_inact_timer != 0) {
if (--nt->nt_inact_timer == 0)
nt->nt_timeout(nt);
inact_timer += nt->nt_inact_timer;
}
IEEE80211_UNLOCK(ic);
if (im->im_mgt_timer != 0 || inact_timer > 0)
ieee80211_start_watchdog(ic, 1);
}
static int
ieee80211_setmode(ieee80211com_t *ic, enum ieee80211_phymode mode)
{
static const uint32_t chanflags[] = {
0,
IEEE80211_CHAN_A,
IEEE80211_CHAN_B,
IEEE80211_CHAN_PUREG,
IEEE80211_CHAN_FHSS,
IEEE80211_CHAN_T,
IEEE80211_CHAN_108G,
IEEE80211_CHAN_ST,
IEEE80211_CHAN_A,
IEEE80211_CHAN_G,
};
struct ieee80211_channel *ch;
uint32_t modeflags;
int i;
int achannels = 0;
if ((ic->ic_modecaps & (1 << mode)) == 0) {
ieee80211_err("ieee80211_setmode(): mode %u not supported"
" (caps 0x%x)\n", mode, ic->ic_modecaps);
return (EINVAL);
}
ASSERT(mode < IEEE80211_N(chanflags));
modeflags = chanflags[mode];
bzero(ic->ic_chan_active, sizeof (ic->ic_chan_active));
for (i = 0; i <= IEEE80211_CHAN_MAX; i++) {
ch = &ic->ic_sup_channels[i];
if (ch->ich_flags == 0)
continue;
if (mode == IEEE80211_MODE_AUTO) {
if ((ch->ich_flags & ~IEEE80211_CHAN_TURBO) != 0) {
ieee80211_setbit(ic->ic_chan_active, i);
achannels++;
}
} else {
if ((ch->ich_flags & modeflags) == modeflags) {
ieee80211_setbit(ic->ic_chan_active, i);
achannels++;
}
}
}
if (achannels == 0) {
ieee80211_err("ieee80211_setmode(): "
"no channel found for mode %u\n", mode);
return (EINVAL);
}
if (ic->ic_ibss_chan == NULL ||
ieee80211_isclr(ic->ic_chan_active,
ieee80211_chan2ieee(ic, ic->ic_ibss_chan))) {
for (i = 0; i <= IEEE80211_CHAN_MAX; i++) {
if (ieee80211_isset(ic->ic_chan_active, i)) {
ic->ic_ibss_chan = &ic->ic_sup_channels[i];
break;
}
}
}
if (ic->ic_des_chan != IEEE80211_CHAN_ANYC &&
ieee80211_isclr(ic->ic_chan_active,
ieee80211_chan2ieee(ic, ic->ic_des_chan))) {
ic->ic_des_chan = IEEE80211_CHAN_ANYC;
}
if (mode == IEEE80211_MODE_11G || mode == IEEE80211_MODE_11B)
ieee80211_setbasicrates(&ic->ic_sup_rates[mode], mode);
if (ic->ic_bss != NULL)
ic->ic_bss->in_rates = ic->ic_sup_rates[mode];
ic->ic_curmode = mode;
ieee80211_reset_erp(ic);
ieee80211_wme_initparams(ic);
return (0);
}
enum ieee80211_phymode
ieee80211_chan2mode(ieee80211com_t *ic, struct ieee80211_channel *chan)
{
if (IEEE80211_IS_CHAN_HTA(chan))
return (IEEE80211_MODE_11NA);
else if (IEEE80211_IS_CHAN_HTG(chan))
return (IEEE80211_MODE_11NG);
else if (IEEE80211_IS_CHAN_108G(chan))
return (IEEE80211_MODE_TURBO_G);
else if (IEEE80211_IS_CHAN_ST(chan))
return (IEEE80211_MODE_STURBO_A);
else if (IEEE80211_IS_CHAN_T(chan))
return (IEEE80211_MODE_TURBO_A);
else if (IEEE80211_IS_CHAN_A(chan))
return (IEEE80211_MODE_11A);
else if (IEEE80211_IS_CHAN_ANYG(chan))
return (IEEE80211_MODE_11G);
else if (IEEE80211_IS_CHAN_B(chan))
return (IEEE80211_MODE_11B);
else if (IEEE80211_IS_CHAN_FHSS(chan))
return (IEEE80211_MODE_FH);
ieee80211_err("cannot map channel to mode; freq %u flags 0x%x\n",
chan->ich_freq, chan->ich_flags);
return (IEEE80211_MODE_11B);
}
const struct ieee80211_rateset *
ieee80211_get_suprates(ieee80211com_t *ic, struct ieee80211_channel *c)
{
if (IEEE80211_IS_CHAN_HTA(c))
return (&ic->ic_sup_rates[IEEE80211_MODE_11A]);
if (IEEE80211_IS_CHAN_HTG(c)) {
return (&ic->ic_sup_rates[IEEE80211_MODE_11G]);
}
return (&ic->ic_sup_rates[ieee80211_chan2mode(ic, c)]);
}
struct ieee80211_channel *
ieee80211_find_channel(ieee80211com_t *ic, int freq, int flags)
{
struct ieee80211_channel *c;
int i;
flags &= IEEE80211_CHAN_ALLTURBO;
for (i = 0; i < IEEE80211_CHAN_MAX; i++) {
c = &ic->ic_sup_channels[i];
if (c->ich_freq == freq &&
(c->ich_flags & IEEE80211_CHAN_ALLTURBO) == flags)
return (c);
}
return (NULL);
}
int
ieee80211_hdrsize(const void *data)
{
const struct ieee80211_frame *wh = data;
int size = sizeof (struct ieee80211_frame);
ASSERT((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) !=
IEEE80211_FC0_TYPE_CTL);
if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS)
size += IEEE80211_ADDR_LEN;
if (IEEE80211_QOS_HAS_SEQ(wh))
size += sizeof (uint16_t);
return (size);
}
int
ieee80211_hdrspace(ieee80211com_t *ic, const void *data)
{
int size = ieee80211_hdrsize(data);
if (ic->ic_flags & IEEE80211_F_DATAPAD)
size = roundup(size, sizeof (uint32_t));
return (size);
}
int
ieee80211_anyhdrsize(const void *data)
{
const struct ieee80211_frame *wh = data;
if ((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) {
switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) {
case IEEE80211_FC0_SUBTYPE_CTS:
case IEEE80211_FC0_SUBTYPE_ACK:
return (sizeof (struct ieee80211_frame_ack));
case IEEE80211_FC0_SUBTYPE_BAR:
return (sizeof (struct ieee80211_frame_bar));
}
return (sizeof (struct ieee80211_frame_min));
} else
return (ieee80211_hdrsize(data));
}
int
ieee80211_anyhdrspace(ieee80211com_t *ic, const void *data)
{
int size = ieee80211_anyhdrsize(data);
if (ic->ic_flags & IEEE80211_F_DATAPAD)
size = roundup(size, sizeof (uint32_t));
return (size);
}
mblk_t *
ieee80211_getmgtframe(uint8_t **frm, int pktlen)
{
mblk_t *mp;
int len;
len = sizeof (struct ieee80211_frame) + pktlen;
mp = allocb(len, BPRI_MED);
if (mp != NULL) {
*frm = mp->b_rptr + sizeof (struct ieee80211_frame);
mp->b_wptr = mp->b_rptr + len;
} else {
ieee80211_err("ieee80211_getmgtframe: "
"alloc frame failed, %d\n", len);
}
return (mp);
}
void
ieee80211_notify_node_join(ieee80211com_t *ic, ieee80211_node_t *in)
{
if (in == ic->ic_bss)
mac_link_update(ic->ic_mach, LINK_STATE_UP);
ieee80211_notify(ic, EVENT_ASSOC);
}
void
ieee80211_notify_node_leave(ieee80211com_t *ic, ieee80211_node_t *in)
{
if (in == ic->ic_bss)
mac_link_update(ic->ic_mach, LINK_STATE_DOWN);
ieee80211_notify(ic, EVENT_DISASSOC);
}
int
ieee80211_stat(ieee80211com_t *ic, uint_t stat, uint64_t *val)
{
ASSERT(val != NULL);
IEEE80211_LOCK(ic);
switch (stat) {
case WIFI_STAT_TX_FRAGS:
*val = ic->ic_stats.is_tx_frags;
break;
case WIFI_STAT_MCAST_TX:
*val = ic->ic_stats.is_tx_mcast;
break;
case WIFI_STAT_TX_FAILED:
*val = ic->ic_stats.is_tx_failed;
break;
case WIFI_STAT_TX_RETRANS:
*val = ic->ic_stats.is_tx_retries;
break;
case WIFI_STAT_RTS_SUCCESS:
*val = ic->ic_stats.is_rts_success;
break;
case WIFI_STAT_RTS_FAILURE:
*val = ic->ic_stats.is_rts_failure;
break;
case WIFI_STAT_ACK_FAILURE:
*val = ic->ic_stats.is_ack_failure;
break;
case WIFI_STAT_RX_FRAGS:
*val = ic->ic_stats.is_rx_frags;
break;
case WIFI_STAT_MCAST_RX:
*val = ic->ic_stats.is_rx_mcast;
break;
case WIFI_STAT_RX_DUPS:
*val = ic->ic_stats.is_rx_dups;
break;
case WIFI_STAT_FCS_ERRORS:
*val = ic->ic_stats.is_fcs_errors;
break;
case WIFI_STAT_WEP_ERRORS:
*val = ic->ic_stats.is_wep_errors;
break;
}
IEEE80211_UNLOCK(ic);
return (0);
}
void
ieee80211_attach(ieee80211com_t *ic)
{
struct ieee80211_impl *im;
struct ieee80211_channel *ch;
int i;
ASSERT(ic->ic_xmit != NULL);
mutex_init(&ic->ic_genlock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ic->ic_doorlock, NULL, MUTEX_DRIVER, NULL);
im = kmem_alloc(sizeof (ieee80211_impl_t), KM_SLEEP);
ic->ic_private = im;
cv_init(&im->im_scan_cv, NULL, CV_DRIVER, NULL);
bzero(im->im_chan_avail, sizeof (im->im_chan_avail));
ic->ic_modecaps |= 1 << IEEE80211_MODE_AUTO;
for (i = 0; i <= IEEE80211_CHAN_MAX; i++) {
ch = &ic->ic_sup_channels[i];
if (ch->ich_flags) {
if (i != ieee80211_chan2ieee(ic, ch)) {
ieee80211_err("bad channel ignored: "
"freq %u flags%x number %u\n",
ch->ich_freq, ch->ich_flags, i);
ch->ich_flags = 0;
continue;
}
ieee80211_setbit(im->im_chan_avail, i);
if (IEEE80211_IS_CHAN_A(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_11A;
if (IEEE80211_IS_CHAN_B(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_11B;
if (IEEE80211_IS_CHAN_PUREG(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_11G;
if (IEEE80211_IS_CHAN_FHSS(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_FH;
if (IEEE80211_IS_CHAN_T(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_TURBO_A;
if (IEEE80211_IS_CHAN_108G(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_TURBO_G;
if (IEEE80211_IS_CHAN_ST(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_STURBO_A;
if (IEEE80211_IS_CHAN_HTA(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_11NA;
if (IEEE80211_IS_CHAN_HTG(ch))
ic->ic_modecaps |= 1 << IEEE80211_MODE_11NG;
if (ic->ic_curchan == NULL) {
ic->ic_curchan = &ic->ic_sup_channels[i];
}
}
}
if ((ic->ic_modecaps & (1 << ic->ic_curmode)) == 0)
ic->ic_curmode = IEEE80211_MODE_AUTO;
ic->ic_des_chan = IEEE80211_CHAN_ANYC;
(void) ieee80211_setmode(ic, ic->ic_curmode);
if (ic->ic_caps & IEEE80211_C_WME)
ic->ic_flags |= IEEE80211_F_WME;
if (ic->ic_caps & IEEE80211_C_BURST)
ic->ic_flags |= IEEE80211_F_BURST;
ic->ic_bintval = IEEE80211_BINTVAL_DEFAULT;
ic->ic_lintval = ic->ic_bintval;
ic->ic_txpowlimit = IEEE80211_TXPOWER_MAX;
ic->ic_bmissthreshold = IEEE80211_HWBMISS_DEFAULT;
ic->ic_reset = ieee80211_default_reset;
ieee80211_node_attach(ic);
ieee80211_proto_attach(ic);
ieee80211_crypto_attach(ic);
ieee80211_ht_attach(ic);
ic->ic_watchdog_timer = 0;
}
void
ieee80211_detach(ieee80211com_t *ic)
{
struct ieee80211_impl *im = ic->ic_private;
ieee80211_stop_watchdog(ic);
cv_destroy(&im->im_scan_cv);
kmem_free(im, sizeof (ieee80211_impl_t));
if (ic->ic_opt_ie != NULL)
ieee80211_free(ic->ic_opt_ie);
ieee80211_ht_detach(ic);
ieee80211_node_detach(ic);
ieee80211_crypto_detach(ic);
mutex_destroy(&ic->ic_genlock);
mutex_destroy(&ic->ic_doorlock);
}
static struct modlmisc i_wifi_modlmisc = {
&mod_miscops,
"IEEE80211 Kernel Module v2.0"
};
static struct modlinkage i_wifi_modlinkage = {
MODREV_1,
&i_wifi_modlmisc,
NULL
};
int
_init(void)
{
return (mod_install(&i_wifi_modlinkage));
}
int
_fini(void)
{
return (mod_remove(&i_wifi_modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&i_wifi_modlinkage, modinfop));
}