root/usr/src/uts/common/io/zyd/zyd.c
/*
 * Copyright 2010 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 wldp_length, void *wldp_buf);
static void zyd_m_propinfo(void *arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, mac_prop_info_handle_t mph);
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 | MC_PROPINFO,
        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 */
        NULL,
        zyd_m_ioctl,            /* Process an unknown ioctl */
        NULL,                   /* mc_getcapab */
        NULL,
        NULL,
        zyd_m_setprop,
        zyd_m_getprop,
        zyd_m_propinfo
};

/*
 *  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 wldp_length, void *wldp_buf)
{
        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,
            wldp_length, wldp_buf);

        return (err);
}

static void
zyd_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    mac_prop_info_handle_t mph)
{
        struct zyd_softc *sc = (struct zyd_softc *)arg;

        ieee80211_propinfo(&sc->ic, pr_name, wldp_pr_num, mph);
}

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