/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2007 by  Lukas Turek <turek@ksvi.mff.cuni.cz>
 * Copyright (c) 2007 by  Jiri Svoboda <jirik.svoboda@seznam.cz>
 * Copyright (c) 2007 by  Martin Krulis <martin.krulis@matfyz.cz>
 * Copyright (c) 2006 by Damien Bergamini <damien.bergamini@free.fr>
 * Copyright (c) 2006 by Florian Stoehr <ich@florian-stoehr.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

/*
 * ZD1211 wLAN driver
 * Driver major routines
 */

#include <sys/byteorder.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/mac_provider.h>
#include <sys/mac_wifi.h>
#include <sys/strsun.h>
#include <sys/ksynch.h>

#include "zyd.h"
#include "zyd_reg.h"

static int zyd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int zyd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

static int zyd_m_stat(void *arg, uint_t stat, uint64_t *val);
static int zyd_m_start(void *arg);
static void zyd_m_stop(void *arg);
static int zyd_m_unicst(void *arg, const uint8_t *macaddr);
static int zyd_m_multicst(void *arg, boolean_t add, const uint8_t *m);
static int zyd_m_promisc(void *arg, boolean_t on);
static void zyd_m_ioctl(void *arg, queue_t *wq, mblk_t *mp);
static mblk_t *zyd_m_tx(void *arg, mblk_t *mp);
static int zyd_m_getprop(void *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);
static int zyd_m_setprop(void *arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf);

static int zyd_newstate(struct ieee80211com *ic,
    enum ieee80211_state state, int arg);

/* Driver identification */
static char zyd_ident[] = ZYD_DRV_DESC " " ZYD_DRV_REV;

/* Global state pointer for managing per-device soft states */
void *zyd_ssp;

/*
 * Mac Call Back entries
 */
static mac_callbacks_t zyd_m_callbacks = {
	MC_IOCTL | MC_SETPROP | MC_GETPROP,
	zyd_m_stat,		/* Get the value of a statistic */
	zyd_m_start,		/* Start the device */
	zyd_m_stop,		/* Stop the device */
	zyd_m_promisc,		/* Enable or disable promiscuous mode */
	zyd_m_multicst,		/* Enable or disable a multicast addr */
	zyd_m_unicst,		/* Set the unicast MAC address */
	zyd_m_tx,		/* Transmit a packet */
	zyd_m_ioctl,		/* Process an unknown ioctl */
	NULL,			/* mc_getcapab */
	NULL,
	NULL,
	zyd_m_setprop,
	zyd_m_getprop
};

/*
 *  Module Loading Data & Entry Points
 */
DDI_DEFINE_STREAM_OPS(zyd_devops,	/* name */
    nulldev,			/* identify */
    nulldev,			/* probe */
    zyd_attach,			/* attach */
    zyd_detach,			/* detach */
    nodev,			/* reset */
    NULL,			/* getinfo */
    D_MP,			/* flag */
    NULL,			/* stream_tab */
    ddi_quiesce_not_needed	/* quiesce */
);

static struct modldrv zyd_modldrv = {
	&mod_driverops,		/* drv_modops */
	zyd_ident,		/* drv_linkinfo */
	&zyd_devops		/* drv_dev_ops */
};

static struct modlinkage zyd_ml = {
	MODREV_1,		/* ml_rev */
	{&zyd_modldrv, NULL}	/* ml_linkage */
};

/*
 * Wireless-specific structures
 */
static const struct ieee80211_rateset zyd_rateset_11b = {
	4, {2, 4, 11, 22}	/* units are 0.5Mbit! */
};

static const struct ieee80211_rateset zyd_rateset_11g = {
	12, {2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108}
};


#ifdef DEBUG
uint32_t zyd_dbg_flags;

void
zyd_dbg(uint32_t dbg_mask, const int8_t *fmt, ...)
{
	va_list args;

	if (dbg_mask & zyd_dbg_flags) {
		va_start(args, fmt);
		vcmn_err(CE_CONT, fmt, args);
		va_end(args);
	}
}
#endif

void
zyd_warn(const int8_t *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vcmn_err(CE_WARN, fmt, args);
	va_end(args);
}

/*
 * Internal functions
 */
static uint8_t
zyd_plcp_signal(uint16_t rate)
{
	switch (rate) {
		/* CCK rates (returned values are device-dependent) */
	case 2:
		return (0x0);
	case 4:
		return (0x1);
	case 11:
		return (0x2);
	case 22:
		return (0x3);

		/* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */
	case 12:
		return (0xb);
	case 18:
		return (0xf);
	case 24:
		return (0xa);
	case 36:
		return (0xe);
	case 48:
		return (0x9);
	case 72:
		return (0xd);
	case 96:
		return (0x8);
	case 108:
		return (0xc);

		/* unsupported rates (should not get there) */
	default:
		return (0xff);
	}
}

/*
 * Timeout function for scanning.
 *
 * Called at the end of each scanning round.
 */
static void
zyd_next_scan(void *arg)
{
	struct zyd_softc *sc = arg;
	struct ieee80211com *ic = &sc->ic;

	ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: fired\n"));

	if (ic->ic_state == IEEE80211_S_SCAN) {
		ieee80211_next_scan(ic);
	} else {
		ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: no work\n"));
	}
}

/*
 * Extract a 802.11 frame from the received packet and forward it to net80211.
 */
void
zyd_receive(struct zyd_softc *sc, const uint8_t *buf, uint16_t len)
{
	const struct zyd_rx_stat *stat;
	struct ieee80211com *ic = &sc->ic;
	struct ieee80211_frame *wh;
	struct ieee80211_node *in;
	int rlen;		/* Actual frame length */
	uint8_t rssi;
	mblk_t *m;

	if (len < ZYD_MIN_FRAGSZ) {
		/* Packet is too short, silently drop it */
		sc->rx_err++;
		return;
	}

	stat = (const struct zyd_rx_stat *)
	    (buf + len - sizeof (struct zyd_rx_stat));
	if (stat->flags & ZYD_RX_ERROR) {
		/* Frame is corrupted, silently drop it */
		sc->rx_err++;
		return;
	}

	/* compute actual frame length */
	rlen = len - sizeof (struct zyd_plcphdr) -
	    sizeof (struct zyd_rx_stat) - IEEE80211_CRC_LEN;

	m = allocb(rlen, BPRI_MED);
	if (m == NULL) {
		sc->rx_nobuf++;
		return;
	}

	/* Copy frame to new buffer */
	bcopy(buf + sizeof (struct zyd_plcphdr), m->b_wptr, rlen);
	m->b_wptr += rlen;

	/* Send frame to net80211 stack */
	wh = (struct ieee80211_frame *)m->b_rptr;
	in = ieee80211_find_rxnode(ic, wh);
	rssi = (stat->rssi < 25) ? 230 : (255 - stat->rssi) / 2;

	(void) ieee80211_input(ic, m, in, (int32_t)rssi, 0);

	ieee80211_free_node(in);
}

/*
 * xxx_send callback for net80211.
 *
 * Transmit a 802.11 frame.
 *
 * Constructs a packet from zyd_tx_header and 802.11 frame data
 * and sends it to the chip.
 */
/*ARGSUSED*/
static int
zyd_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type)
{
	struct zyd_softc *sc = ZYD_IC_TO_SOFTC(ic);
	struct zyd_tx_header *buf_hdr;
	struct ieee80211_frame *wh;
	struct ieee80211_node *in;
	struct ieee80211_key *k;
	mblk_t *m, *m0;
	int len, off, mblen;
	uint16_t frame_size, additional_size, rate;
	uint8_t service;
	int res;

	ASSERT(mp->b_next == NULL);

	/* device not ready, drop all frames */
	if (!sc->usb.connected || sc->suspended || !sc->running) {
		freemsg(mp);
		if (type == IEEE80211_FC0_TYPE_DATA)
			return (DDI_SUCCESS);
		else
			return (DDI_FAILURE);
	}

	/* device queue overrun */
	if (sc->tx_queued >= ZYD_TX_LIST_COUNT) {
		/* drop management frames */
		if (type != IEEE80211_FC0_TYPE_DATA) {
			freemsg(mp);
		} else {
			(void) zyd_serial_enter(sc, ZYD_NO_SIG);
			sc->resched = B_TRUE;
			zyd_serial_exit(sc);
		}
		return (DDI_FAILURE);
	}

	m = allocb(msgdsize(mp) + sizeof (struct zyd_tx_header) + 32,
	    BPRI_MED);
	if (m == NULL) {
		sc->tx_nobuf++;
		(void) zyd_serial_enter(sc, ZYD_NO_SIG);
		sc->resched = B_TRUE;
		zyd_serial_exit(sc);
		return (DDI_FAILURE);
	}
	m->b_rptr += sizeof (struct zyd_tx_header);
	m->b_wptr = m->b_rptr;

	for (off = 0, m0 = mp; m0 != NULL; m0 = m0->b_cont) {
		mblen = MBLKL(m0);
		(void) memcpy(m->b_rptr + off, m0->b_rptr, mblen);
		off += mblen;
	}
	m->b_wptr += off;

	wh = (struct ieee80211_frame *)m->b_rptr;
	in = ieee80211_find_txnode(ic, wh->i_addr1);

	if (in == NULL) {
		freemsg(m);
		sc->tx_err++;
		freemsg(mp);
		return (DDI_SUCCESS);
	}
	in->in_inact = 0;

	if (type == IEEE80211_FC0_TYPE_DATA)
		(void) ieee80211_encap(ic, m, in);

	if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
		k = ieee80211_crypto_encap(ic, m);
		if (k == NULL) {
			sc->tx_err++;
			ieee80211_free_node(in);
			freemsg(m);
			freemsg(mp);
			return (DDI_SUCCESS);
		}
		/* packet header may have moved, reset our local pointer */
		wh = (struct ieee80211_frame *)m->b_rptr;
	}

	/*
	 * pickup a rate. May need work to make adaptive - at present,
	 * picks best rate for mode.
	 */
	if (type == IEEE80211_FC0_TYPE_MGT) {
		/* mgmt frames are sent at 1M */
		rate = (uint16_t)in->in_rates.ir_rates[0];
	} else if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE) {
		rate = (uint16_t)ic->ic_sup_rates[ic->ic_curmode].
		    ir_rates[ic->ic_fixed_rate];
	} else {
		rate = (uint16_t)ic->ic_sup_rates[ic->ic_curmode].
		    ir_rates[in->in_txrate];
	}
	rate &= IEEE80211_RATE_VAL;
	if (rate == 0)		/* should not happen */
		rate = 2;

	/* Get total length of frame */
	len = msgsize(m);

	m->b_rptr -= sizeof (struct zyd_tx_header);
	buf_hdr = (struct zyd_tx_header *)m->b_rptr;

	frame_size = (uint16_t)len + 4;	/* include CRC32 */
	buf_hdr->frame_size = LE_16(frame_size);

	/*
	 * Compute "packet size". What the 10 stands for,
	 * nobody knows.
	 */
	additional_size = sizeof (struct zyd_tx_header) + 10;
	if (sc->mac_rev == ZYD_ZD1211)
		buf_hdr->packet_size = LE_16(frame_size + additional_size);
	else
		buf_hdr->packet_size = LE_16(additional_size);

	buf_hdr->rate_mod_flags = LE_8(zyd_plcp_signal(rate));
	buf_hdr->type_flags = LE_8(ZYD_TX_FLAG_BACKOFF);
	if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
		/* multicast frames are not sent at OFDM rates in 802.11b/g */
		if (frame_size > ic->ic_rtsthreshold) {
			buf_hdr->type_flags |= ZYD_TX_FLAG_RTS;
		} else if (ZYD_RATE_IS_OFDM(rate) &&
		    (ic->ic_flags & IEEE80211_F_USEPROT)) {
			if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
				buf_hdr->type_flags |=
				    ZYD_TX_FLAG_CTS_TO_SELF;
			else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
				buf_hdr->type_flags |= ZYD_TX_FLAG_RTS;
		}
	} else
		buf_hdr->type_flags |= ZYD_TX_FLAG_MULTICAST;

	if ((type == IEEE80211_FC0_TYPE_CTL) &&
	    (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK)
	    == IEEE80211_FC0_SUBTYPE_PS_POLL)
		buf_hdr->type_flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL);

	if (ZYD_RATE_IS_OFDM(rate)) {
		buf_hdr->rate_mod_flags |= ZYD_TX_RMF_OFDM;
		if (ic->ic_curmode == IEEE80211_MODE_11A)
			buf_hdr->rate_mod_flags |= ZYD_TX_RMF_5GHZ;
	} else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
		buf_hdr->rate_mod_flags |= ZYD_TX_RMF_SH_PREAMBLE;

	/*
	 * Compute frame duration and length-extension service flag.
	 */
	service = 0x00;

	buf_hdr->frame_duration = LE_16((16 * frame_size + rate - 1) / rate);
	buf_hdr->service = service;
	buf_hdr->next_frame_duration = LE_16(0);

	if (rate == 22) {
		const int remainder = (16 * frame_size) % 22;
		if (remainder != 0 && remainder < 7)
			buf_hdr->service |= ZYD_TX_SERVICE_LENGTH_EXTENSION;
	}

	res = zyd_usb_send_packet(&sc->usb, m);
	if (res != ZYD_SUCCESS) {
		sc->tx_err++;
	} else {
		(void) zyd_serial_enter(sc, ZYD_NO_SIG);
		sc->tx_queued++;
		zyd_serial_exit(sc);
		freemsg(mp);
		ic->ic_stats.is_tx_frags++;
		ic->ic_stats.is_tx_bytes += len;
	}

	ieee80211_free_node(in);

	return (DDI_SUCCESS);
}

/*
 * Register with the MAC layer.
 */
static zyd_res
zyd_mac_init(struct zyd_softc *sc)
{
	struct ieee80211com *ic = &sc->ic;
	mac_register_t *macp;
	wifi_data_t wd = { 0 };
	int err;

	/*
	 * Initialize mac structure
	 */
	macp = mac_alloc(MAC_VERSION);
	if (macp == NULL) {
		ZYD_WARN("failed to allocate MAC structure\n");
		return (ZYD_FAILURE);
	}

	/*
	 * Initialize pointer to device specific functions
	 */
	wd.wd_secalloc = WIFI_SEC_NONE;
	wd.wd_opmode = sc->ic.ic_opmode;
	IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_macaddr);

	macp->m_type_ident = MAC_PLUGIN_IDENT_WIFI;
	macp->m_driver = sc;
	macp->m_dip = sc->dip;
	macp->m_src_addr = ic->ic_macaddr;
	macp->m_callbacks = &zyd_m_callbacks;
	macp->m_min_sdu = 0;
	macp->m_max_sdu = IEEE80211_MTU;
	macp->m_pdata = &wd;
	macp->m_pdata_size = sizeof (wd);

	/*
	 * Register the macp to mac
	 */
	err = mac_register(macp, &sc->ic.ic_mach);
	mac_free(macp);

	if (err != DDI_SUCCESS) {
		ZYD_WARN("failed to register MAC structure\n");
		return (ZYD_FAILURE);
	}

	return (ZYD_SUCCESS);
}

/*
 * Register with net80211.
 */
static void
zyd_wifi_init(struct zyd_softc *sc)
{
	struct ieee80211com *ic = &sc->ic;
	int i;

	/*
	 * Initialize the WiFi part, which will be used by generic layer
	 */
	ic->ic_phytype = IEEE80211_T_OFDM;
	ic->ic_opmode = IEEE80211_M_STA;
	ic->ic_state = IEEE80211_S_INIT;
	ic->ic_maxrssi = 255;
	ic->ic_xmit = zyd_send;

	/* set device capabilities */
	ic->ic_caps = IEEE80211_C_TXPMGT |	/* tx power management */
	    IEEE80211_C_SHPREAMBLE |		/* short preamble supported */
	    IEEE80211_C_SHSLOT | IEEE80211_C_WPA;	/* Support WPA/WPA2 */

	/* Copy MAC address */
	IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->macaddr);

	/*
	 * set supported .11b and .11g rates
	 */
	ic->ic_sup_rates[IEEE80211_MODE_11B] = zyd_rateset_11b;
	ic->ic_sup_rates[IEEE80211_MODE_11G] = zyd_rateset_11g;

	/*
	 * set supported .11b and .11g channels(1 through 14)
	 */
	for (i = 1; i <= 14; i++) {
		ic->ic_sup_channels[i].ich_freq =
		    ieee80211_ieee2mhz(i, IEEE80211_CHAN_2GHZ);
		ic->ic_sup_channels[i].ich_flags =
		    IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
		    IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
	}

	/*
	 * Init generic layer (it cannot fail)
	 */
	ieee80211_attach(ic);

	/* register WPA door */
	ieee80211_register_door(ic, ddi_driver_name(sc->dip),
	    ddi_get_instance(sc->dip));

	/* Must be after attach! */
	sc->newstate = ic->ic_newstate;
	ic->ic_newstate = zyd_newstate;

	ieee80211_media_init(ic);
	ic->ic_def_txkey = 0;
}

/*
 * Device operations
 */
/*
 * Binding the driver to a device.
 *
 * Concurrency: Until zyd_attach() returns with success,
 * the only other entry point that can be executed is getinfo().
 * Thus no locking here yet.
 */
static int
zyd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	struct zyd_softc *sc;
	char strbuf[32];
	int instance;
	int err;

	switch (cmd) {
	case DDI_ATTACH:
		break;

	case DDI_RESUME:
		sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip));
		ASSERT(sc != NULL);

		(void) zyd_resume(sc);
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(dip);
	err = ddi_soft_state_zalloc(zyd_ssp, instance);

	if (err != DDI_SUCCESS) {
		ZYD_WARN("failed to allocate soft state\n");
		return (DDI_FAILURE);
	}

	sc = ddi_get_soft_state(zyd_ssp, instance);
	sc->dip = dip;
	sc->timeout_id = 0;

	if (zyd_usb_init(sc) != ZYD_SUCCESS) {
		ddi_soft_state_free(zyd_ssp, instance);
		return (DDI_FAILURE);
	}

	if (zyd_hw_init(sc) != ZYD_SUCCESS) {
		zyd_usb_deinit(sc);
		ddi_soft_state_free(zyd_ssp, instance);
		return (DDI_FAILURE);
	}

	zyd_wifi_init(sc);

	if (zyd_mac_init(sc) != DDI_SUCCESS) {
		ieee80211_detach(&sc->ic);
		zyd_usb_deinit(sc);
		ddi_soft_state_free(zyd_ssp, instance);
		return (DDI_FAILURE);
	}

	/*
	 * Create minor node of type DDI_NT_NET_WIFI
	 */
	(void) snprintf(strbuf, sizeof (strbuf), ZYD_DRV_NAME"%d", instance);
	err = ddi_create_minor_node(dip, strbuf, S_IFCHR,
	    instance + 1, DDI_NT_NET_WIFI, 0);
	if (err != DDI_SUCCESS)
		ZYD_WARN("failed to create minor node\n");

	/* initialize locking */
	zyd_serial_init(sc);

	return (DDI_SUCCESS);
}

/*
 * Detach the driver from a device.
 *
 * Concurrency: Will be called only after a successful attach
 * (and not concurrently).
 */
static int
zyd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	struct zyd_softc *sc = NULL;

	switch (cmd) {
	case DDI_DETACH:
		break;

	case DDI_SUSPEND:
		sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip));
		ASSERT(sc != NULL);

		return (zyd_suspend(sc));

	default:
		return (DDI_FAILURE);
	}

	sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip));
	ASSERT(sc != NULL);

	if (mac_disable(sc->ic.ic_mach) != 0)
		return (DDI_FAILURE);
	/*
	 * Unregister from the MAC layer subsystem
	 */
	(void) mac_unregister(sc->ic.ic_mach);

	/*
	 * Detach ieee80211
	 */
	ieee80211_detach(&sc->ic);

	zyd_hw_deinit(sc);
	zyd_usb_deinit(sc);

	/* At this point it should be safe to release & destroy the locks */
	zyd_serial_deinit(sc);

	ddi_remove_minor_node(dip, NULL);
	ddi_soft_state_free(zyd_ssp, ddi_get_instance(dip));
	return (DDI_SUCCESS);
}

/*
 * Mac Call Back functions
 */

/*
 * Read device statistic information.
 */
static int
zyd_m_stat(void *arg, uint_t stat, uint64_t *val)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;
	ieee80211com_t *ic = &sc->ic;
	ieee80211_node_t *in;

	switch (stat) {
	case MAC_STAT_IFSPEED:
		if (!sc->usb.connected || sc->suspended || !sc->running)
			return (ENOTSUP);
		in = ieee80211_ref_node(ic->ic_bss);
		*val = ((ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) ?
		    IEEE80211_RATE(in->in_txrate) :
		    ic->ic_fixed_rate) / 2 * 1000000;
		ieee80211_free_node(in);
		break;
	case MAC_STAT_NOXMTBUF:
		*val = sc->tx_nobuf;
		break;
	case MAC_STAT_NORCVBUF:
		*val = sc->rx_nobuf;
		break;
	case MAC_STAT_IERRORS:
		*val = sc->rx_err;
		break;
	case MAC_STAT_RBYTES:
		*val = ic->ic_stats.is_rx_bytes;
		break;
	case MAC_STAT_IPACKETS:
		*val = ic->ic_stats.is_rx_frags;
		break;
	case MAC_STAT_OBYTES:
		*val = ic->ic_stats.is_tx_bytes;
		break;
	case MAC_STAT_OPACKETS:
		*val = ic->ic_stats.is_tx_frags;
		break;
	case MAC_STAT_OERRORS:
	case WIFI_STAT_TX_FAILED:
		*val = sc->tx_err;
		break;
	case WIFI_STAT_TX_RETRANS:
	case WIFI_STAT_FCS_ERRORS:
	case WIFI_STAT_WEP_ERRORS:
	case WIFI_STAT_TX_FRAGS:
	case WIFI_STAT_MCAST_TX:
	case WIFI_STAT_RTS_SUCCESS:
	case WIFI_STAT_RTS_FAILURE:
	case WIFI_STAT_ACK_FAILURE:
	case WIFI_STAT_RX_FRAGS:
	case WIFI_STAT_MCAST_RX:
	case WIFI_STAT_RX_DUPS:
		return (ieee80211_stat(ic, stat, val));
	default:
		return (ENOTSUP);
	}
	return (0);
}

/*
 * Start the device.
 *
 * Concurrency: Presumably fully concurrent, must lock.
 */
static int
zyd_m_start(void *arg)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;

	(void) zyd_serial_enter(sc, ZYD_NO_SIG);
	if ((!sc->usb.connected) || (zyd_hw_start(sc) != ZYD_SUCCESS)) {
		zyd_serial_exit(sc);
		return (DDI_FAILURE);
	}
	zyd_serial_exit(sc);

	ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1);
	sc->running = B_TRUE;

	return (DDI_SUCCESS);
}

/*
 * Stop the device.
 */
static void
zyd_m_stop(void *arg)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;

	sc->running = B_FALSE;
	ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1);

	(void) zyd_serial_enter(sc, ZYD_NO_SIG);
	sc->resched = B_FALSE;
	zyd_hw_stop(sc);
	zyd_serial_exit(sc);
}

/*
 * Change the MAC address of the device.
 */
/*ARGSUSED*/
static int
zyd_m_unicst(void *arg, const uint8_t *macaddr)
{
	return (DDI_FAILURE);
}

/*
 * Enable/disable multicast.
 */
/*ARGSUSED*/
static int
zyd_m_multicst(void *arg, boolean_t add, const uint8_t *m)
{
	ZYD_DEBUG((ZYD_DBG_GLD, "multicast not implemented\n"));
	return (DDI_SUCCESS);
}

/*
 * Enable/disable promiscuous mode.
 */
/*ARGSUSED*/
static int
zyd_m_promisc(void *arg, boolean_t on)
{
	ZYD_DEBUG((ZYD_DBG_GLD, "promiscuous not implemented\n"));
	return (DDI_SUCCESS);
}

/*
 * IOCTL request.
 */
static void
zyd_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;
	struct ieee80211com *ic = &sc->ic;

	if (!sc->usb.connected || sc->suspended || !sc->running) {
		miocnak(wq, mp, 0, ENXIO);
		return;
	}

	if (ieee80211_ioctl(ic, wq, mp) == ENETRESET) {
		if (sc->running && ic->ic_des_esslen) {
			zyd_m_stop(sc);
			if (zyd_m_start(sc) != DDI_SUCCESS)
				return;
			ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
		}
	}
}

/*
 * callback functions for /get/set properties
 */
static int
zyd_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, const void *wldp_buf)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;
	struct ieee80211com *ic = &sc->ic;
	int err;

	if (!sc->usb.connected || sc->suspended || !sc->running) {
		return (ENXIO);
	}

	err = ieee80211_setprop(ic, pr_name, wldp_pr_num, wldp_length,
	    wldp_buf);
	if (err == ENETRESET) {
		if (sc->running && ic->ic_des_esslen) {
			zyd_m_stop(sc);
			if (zyd_m_start(sc) != DDI_SUCCESS)
				return (DDI_FAILURE);
			ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
		}
		err = 0;
	}

	return (err);
}

static int
zyd_m_getprop(void *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)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;
	int err;

	if (!sc->usb.connected || sc->suspended || !sc->running) {
		return (DDI_FAILURE);
	}

	err = ieee80211_getprop(&sc->ic, pr_name, wldp_pr_num,
	    pr_flags, wldp_length, wldp_buf, perm);

	return (err);
}

/*
 * Transmit a data frame.
 */
static mblk_t *
zyd_m_tx(void *arg, mblk_t *mp)
{
	struct zyd_softc *sc = (struct zyd_softc *)arg;
	struct ieee80211com *ic = &sc->ic;
	mblk_t *next;

	ASSERT(mp != NULL);

	/* not associated, drop data frames */
	if (ic->ic_state != IEEE80211_S_RUN) {
		freemsg(mp);
		return (DDI_SUCCESS);
	}

	while (mp != NULL) {
		next = mp->b_next;
		mp->b_next = NULL;

		if (zyd_send(ic, mp, IEEE80211_FC0_TYPE_DATA) != DDI_SUCCESS) {
			mp->b_next = next;
			break;
		}
		mp = next;
	}

	return (mp);
}

/*
 * xxx_newstate callback for net80211.
 *
 * Called by net80211 whenever the ieee80211 state changes.
 */
static int
zyd_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
	struct zyd_softc *sc = ZYD_IC_TO_SOFTC(ic);
	struct ieee80211_node *in;
	uint_t chan;

	if (sc->timeout_id != 0) {
		(void) untimeout(sc->timeout_id);
		sc->timeout_id = 0;
	}

	if (!sc->usb.connected || sc->suspended || !sc->running) {
		return (sc->newstate(ic, nstate, arg));
	}

	switch (nstate) {
	case IEEE80211_S_SCAN:
		ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: starting next\n"));
		sc->timeout_id = timeout(zyd_next_scan, sc,
		    drv_usectohz(ZYD_DWELL_TIME));
		/*FALLTHRU*/
	case IEEE80211_S_AUTH:
	case IEEE80211_S_ASSOC:
	case IEEE80211_S_RUN:
		chan = ieee80211_chan2ieee(ic, ic->ic_curchan);
		if (chan == 0 || chan == IEEE80211_CHAN_ANY) {
			ZYD_WARN("invalid channel number\n");
			return (0);
		}
		(void) zyd_serial_enter(sc, ZYD_SER_SIG);
		zyd_hw_set_channel(sc, chan);
		zyd_serial_exit(sc);

		in = ic->ic_bss;
		in->in_txrate = in->in_rates.ir_nrates - 1;
	default:
		break;
	}

	return (sc->newstate(ic, nstate, arg));
}

/*
 * USB-safe synchronization.
 * Debugging routines.
 *
 * Kmutexes should never be held when making calls to USBA
 * or when sleeping. Thus, we implement our own "mutex" on top
 * of kmutexes and kcondvars.
 *
 * Usage: Any (possibly concurrent) access to the soft state or device must
 * be serialized with a pair of zyd_serial_enter()/zyd_serial_exit().
 */
/*
 * Initialize the serialization object.
 */
void
zyd_serial_init(struct zyd_softc *sc)
{
	mutex_init(&sc->serial.lock, NULL, MUTEX_DRIVER,
	    sc->usb.cdata->dev_iblock_cookie);
	cv_init(&sc->serial.wait, NULL, CV_DRIVER, NULL);

	sc->serial.held = B_FALSE;
	sc->serial.initialized = B_TRUE;
}

/*
 * Wait for the serialization object.
 *
 * If wait_sig is ZYD_SER_SIG, the function may return
 * a signal is received. In this case, the serialization object
 * is not acquired (but the mutex is) and the return value is ZYD_FAILURE.
 *
 * In any other case the function returns ZYD_SUCCESS and the
 * serialization object is acquired.
 */
zyd_res
zyd_serial_enter(struct zyd_softc *sc, boolean_t wait_sig)
{
	zyd_res res;

	mutex_enter(&sc->serial.lock);

	res = ZYD_SUCCESS;

	while (sc->serial.held != B_FALSE) {
		if (wait_sig == ZYD_SER_SIG) {
			res = cv_wait_sig(&sc->serial.wait, &sc->serial.lock);
		} else {
			cv_wait(&sc->serial.wait, &sc->serial.lock);
		}
	}
	sc->serial.held = B_TRUE;

	mutex_exit(&sc->serial.lock);

	return (res);
}

/*
 * Release the serialization object.
 */
void
zyd_serial_exit(struct zyd_softc *sc)
{
	mutex_enter(&sc->serial.lock);
	sc->serial.held = B_FALSE;
	cv_broadcast(&sc->serial.wait);
	mutex_exit(&sc->serial.lock);
}

/*
 * Destroy the serialization object.
 */
void
zyd_serial_deinit(struct zyd_softc *sc)
{
	cv_destroy(&sc->serial.wait);
	mutex_destroy(&sc->serial.lock);

	sc->serial.initialized = B_FALSE;
}


/*
 * zyd_cb_lock: a special signal structure that is used for notification
 * that a callback function has been called.
 */

/* Initializes the zyd_cb_lock structure. */
void
zyd_cb_lock_init(struct zyd_cb_lock *lock)
{
	ASSERT(lock != NULL);
	mutex_init(&lock->mutex, NULL, MUTEX_DRIVER, NULL);
	cv_init(&lock->cv, NULL, CV_DRIVER, NULL);
	lock->done = B_FALSE;
}

/* Deinitalizes the zyd_cb_lock structure. */
void
zyd_cb_lock_destroy(struct zyd_cb_lock *lock)
{
	ASSERT(lock != NULL);
	mutex_destroy(&lock->mutex);
	cv_destroy(&lock->cv);
}

/*
 * Wait on lock until someone calls the "signal" function or the timeout
 * expires. Note: timeout is in microseconds.
 */
zyd_res
zyd_cb_lock_wait(struct zyd_cb_lock *lock, clock_t timeout)
{
	zyd_res res;
	clock_t etime;
	int cv_res;

	ASSERT(lock != NULL);

	mutex_enter(&lock->mutex);

	if (timeout < 0) {
		/* no timeout - wait as long as needed */
		while (lock->done == B_FALSE)
			(void) cv_wait(&lock->cv, &lock->mutex);
	} else {
		/* wait with timeout (given in usec) */
		etime = ddi_get_lbolt() + drv_usectohz(timeout);
		while (lock->done == B_FALSE) {
			cv_res =
			    cv_timedwait_sig(&lock->cv, &lock->mutex, etime);
			if (cv_res <= 0)
				break;
		}
	}

	res = (lock->done == B_TRUE) ? ZYD_SUCCESS : ZYD_FAILURE;

	mutex_exit(&lock->mutex);

	return (res);
}

/* Signal that the job (eg. callback) is done and unblock anyone who waits. */
void
zyd_cb_lock_signal(struct zyd_cb_lock *lock)
{
	ASSERT(lock != NULL);

	mutex_enter(&lock->mutex);

	lock->done = B_TRUE;
	cv_broadcast(&lock->cv);

	mutex_exit(&lock->mutex);
}

/*
 * Loadable module configuration entry points
 */

/*
 * _init module entry point.
 *
 * Called when the module is being loaded into memory.
 */
int
_init(void)
{
	int err;

	err = ddi_soft_state_init(&zyd_ssp, sizeof (struct zyd_softc), 1);

	if (err != DDI_SUCCESS)
		return (err);

	mac_init_ops(&zyd_devops, ZYD_DRV_NAME);
	err = mod_install(&zyd_ml);

	if (err != DDI_SUCCESS) {
		mac_fini_ops(&zyd_devops);
		ddi_soft_state_fini(&zyd_ssp);
	}

	return (err);
}

/*
 * _info module entry point.
 *
 * Called to obtain information about the module.
 */
int
_info(struct modinfo *modinfop)
{
	return (mod_info(&zyd_ml, modinfop));
}

/*
 * _fini module entry point.
 *
 * Called when the module is being unloaded.
 */
int
_fini(void)
{
	int err;

	err = mod_remove(&zyd_ml);
	if (err == DDI_SUCCESS) {
		mac_fini_ops(&zyd_devops);
		ddi_soft_state_fini(&zyd_ssp);
	}

	return (err);
}