root/usr/src/uts/common/io/ath/ath_main.c
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2002-2004 Sam Leffler, Errno Consulting
 * All rights reserved.
 *
 * 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,
 * without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 * redistribution must be conditioned upon including a substantially
 * similar Disclaimer requirement for further binary redistribution.
 * 3. Neither the names of the above-listed copyright holders nor the names
 * of any contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
 *
 */

/*
 * Driver for the Atheros Wireless LAN controller.
 *
 * The Atheros driver calls into net80211 module for IEEE80211 protocol
 * management functionalities. The driver includes a LLD(Low Level Driver)
 * part to implement H/W related operations.
 * The following is the high level structure of ath driver.
 * (The arrows between modules indicate function call direction.)
 *
 *
 *                                                  |
 *                                                  | GLD thread
 *                                                  V
 *         ==================  =========================================
 *         |                |  |[1]                                    |
 *         |                |  |  GLDv3 Callback functions registered  |
 *         |   Net80211     |  =========================       by      |
 *         |    module      |          |               |     driver    |
 *         |                |          V               |               |
 *         |                |========================  |               |
 *         |   Functions exported by net80211       |  |               |
 *         |                                        |  |               |
 *         ==========================================  =================
 *                         |                                  |
 *                         V                                  |
 *         +----------------------------------+               |
 *         |[2]                               |               |
 *         |    Net80211 Callback functions   |               |
 *         |      registered by LLD           |               |
 *         +----------------------------------+               |
 *                         |                                  |
 *                         V                                  v
 *         +-----------------------------------------------------------+
 *         |[3]                                                        |
 *         |                LLD Internal functions                     |
 *         |                                                           |
 *         +-----------------------------------------------------------+
 *                                    ^
 *                                    | Software interrupt thread
 *                                    |
 *
 * The short description of each module is as below:
 *      Module 1: GLD callback functions, which are intercepting the calls from
 *                GLD to LLD.
 *      Module 2: Net80211 callback functions registered by LLD, which
 *                calls into LLD for H/W related functions needed by net80211.
 *      Module 3: LLD Internal functions, which are responsible for allocing
 *                descriptor/buffer, handling interrupt and other H/W
 *                operations.
 *
 * All functions are running in 3 types of thread:
 * 1. GLD callbacks threads, such as ioctl, intr, etc.
 * 2. Clock interruptt thread which is responsible for scan, rate control and
 *    calibration.
 * 3. Software Interrupt thread originated in LLD.
 *
 * The lock strategy is as below:
 * There have 4 queues for tx, each queue has one asc_txqlock[i] to
 *      prevent conflicts access to queue resource from different thread.
 *
 * All the transmit buffers are contained in asc_txbuf which are
 *      protected by asc_txbuflock.
 *
 * Each receive buffers are contained in asc_rxbuf which are protected
 *      by asc_rxbuflock.
 *
 * In ath struct, asc_genlock is a general lock, protecting most other
 *      operational data in ath_softc struct and HAL accesses.
 *      It is acquired by the interupt handler and most "mode-ctrl" routines.
 *
 * Any of the locks can be acquired singly, but where multiple
 * locks are acquired, they *must* be in the order:
 *    asc_genlock >> asc_txqlock[i] >> asc_txbuflock >> asc_rxbuflock
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/stream.h>
#include <sys/termio.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/strtty.h>
#include <sys/kbio.h>
#include <sys/cred.h>
#include <sys/stat.h>
#include <sys/consdev.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include <sys/errno.h>
#include <sys/mac_provider.h>
#include <sys/dlpi.h>
#include <sys/ethernet.h>
#include <sys/list.h>
#include <sys/byteorder.h>
#include <sys/strsun.h>
#include <sys/policy.h>
#include <inet/common.h>
#include <inet/nd.h>
#include <inet/mi.h>
#include <inet/wifi_ioctl.h>
#include <sys/mac_wifi.h>
#include "ath_hal.h"
#include "ath_impl.h"
#include "ath_aux.h"
#include "ath_rate.h"

#define ATH_MAX_RSSI    63      /* max rssi */

extern void ath_halfix_init(void);
extern void ath_halfix_finit(void);
extern int32_t ath_getset(ath_t *asc, mblk_t *mp, uint32_t cmd);

/*
 * PIO access attributes for registers
 */
static ddi_device_acc_attr_t ath_reg_accattr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC,
        DDI_STRICTORDER_ACC
};

/*
 * DMA access attributes for descriptors: NOT to be byte swapped.
 */
static ddi_device_acc_attr_t ath_desc_accattr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC,
        DDI_STRICTORDER_ACC
};

/*
 * DMA attributes for rx/tx buffers
 */
static ddi_dma_attr_t ath_dma_attr = {
        DMA_ATTR_V0,            /* version number */
        0,                      /* low address */
        0xffffffffU,            /* high address */
        0x3ffffU,               /* counter register max */
        1,                      /* alignment */
        0xFFF,                  /* burst sizes */
        1,                      /* minimum transfer size */
        0x3ffffU,               /* max transfer size */
        0xffffffffU,            /* address register max */
        1,                      /* no scatter-gather */
        1,                      /* granularity of device */
        0,                      /* DMA flags */
};

static ddi_dma_attr_t ath_desc_dma_attr = {
        DMA_ATTR_V0,            /* version number */
        0,                      /* low address */
        0xffffffffU,            /* high address */
        0xffffffffU,            /* counter register max */
        0x1000,                 /* alignment */
        0xFFF,                  /* burst sizes */
        1,                      /* minimum transfer size */
        0xffffffffU,            /* max transfer size */
        0xffffffffU,            /* address register max */
        1,                      /* no scatter-gather */
        1,                      /* granularity of device */
        0,                      /* DMA flags */
};

static kmutex_t ath_loglock;
static void *ath_soft_state_p = NULL;
static int ath_dwelltime = 150;         /* scan interval, ms */

static int      ath_m_stat(void *,  uint_t, uint64_t *);
static int      ath_m_start(void *);
static void     ath_m_stop(void *);
static int      ath_m_promisc(void *, boolean_t);
static int      ath_m_multicst(void *, boolean_t, const uint8_t *);
static int      ath_m_unicst(void *, const uint8_t *);
static mblk_t   *ath_m_tx(void *, mblk_t *);
static void     ath_m_ioctl(void *, queue_t *, mblk_t *);
static int      ath_m_setprop(void *, const char *, mac_prop_id_t,
    uint_t, const void *);
static int      ath_m_getprop(void *, const char *, mac_prop_id_t,
    uint_t, void *);
static void     ath_m_propinfo(void *, const char *, mac_prop_id_t,
    mac_prop_info_handle_t);

static mac_callbacks_t ath_m_callbacks = {
        MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
        ath_m_stat,
        ath_m_start,
        ath_m_stop,
        ath_m_promisc,
        ath_m_multicst,
        ath_m_unicst,
        ath_m_tx,
        NULL,
        ath_m_ioctl,
        NULL,           /* mc_getcapab */
        NULL,
        NULL,
        ath_m_setprop,
        ath_m_getprop,
        ath_m_propinfo
};

/*
 * Available debug flags:
 * ATH_DBG_INIT, ATH_DBG_GLD, ATH_DBG_HAL, ATH_DBG_INT, ATH_DBG_ATTACH,
 * ATH_DBG_DETACH, ATH_DBG_AUX, ATH_DBG_WIFICFG, ATH_DBG_OSDEP
 */
uint32_t ath_dbg_flags = 0;

/*
 * Exception/warning cases not leading to panic.
 */
void
ath_problem(const int8_t *fmt, ...)
{
        va_list args;

        mutex_enter(&ath_loglock);

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

        mutex_exit(&ath_loglock);
}

/*
 * Normal log information independent of debug.
 */
void
ath_log(const int8_t *fmt, ...)
{
        va_list args;

        mutex_enter(&ath_loglock);

        va_start(args, fmt);
        vcmn_err(CE_CONT, fmt, args);
        va_end(args);

        mutex_exit(&ath_loglock);
}

void
ath_dbg(uint32_t dbg_flags, const int8_t *fmt, ...)
{
        va_list args;

        if (dbg_flags & ath_dbg_flags) {
                mutex_enter(&ath_loglock);
                va_start(args, fmt);
                vcmn_err(CE_CONT, fmt, args);
                va_end(args);
                mutex_exit(&ath_loglock);
        }
}

void
ath_setup_desc(ath_t *asc, struct ath_buf *bf)
{
        struct ath_desc *ds;

        ds = bf->bf_desc;
        ds->ds_link = bf->bf_daddr;
        ds->ds_data = bf->bf_dma.cookie.dmac_address;
        ATH_HAL_SETUPRXDESC(asc->asc_ah, ds,
            bf->bf_dma.alength,         /* buffer size */
            0);

        if (asc->asc_rxlink != NULL)
                *asc->asc_rxlink = bf->bf_daddr;
        asc->asc_rxlink = &ds->ds_link;
}


/*
 * Allocate an area of memory and a DMA handle for accessing it
 */
static int
ath_alloc_dma_mem(dev_info_t *devinfo, ddi_dma_attr_t *dma_attr, size_t memsize,
    ddi_device_acc_attr_t *attr_p, uint_t alloc_flags,
    uint_t bind_flags, dma_area_t *dma_p)
{
        int err;

        /*
         * Allocate handle
         */
        err = ddi_dma_alloc_handle(devinfo, dma_attr,
            DDI_DMA_SLEEP, NULL, &dma_p->dma_hdl);
        if (err != DDI_SUCCESS)
                return (DDI_FAILURE);

        /*
         * Allocate memory
         */
        err = ddi_dma_mem_alloc(dma_p->dma_hdl, memsize, attr_p,
            alloc_flags, DDI_DMA_SLEEP, NULL, &dma_p->mem_va,
            &dma_p->alength, &dma_p->acc_hdl);
        if (err != DDI_SUCCESS)
                return (DDI_FAILURE);

        /*
         * Bind the two together
         */
        err = ddi_dma_addr_bind_handle(dma_p->dma_hdl, NULL,
            dma_p->mem_va, dma_p->alength, bind_flags,
            DDI_DMA_SLEEP, NULL, &dma_p->cookie, &dma_p->ncookies);
        if (err != DDI_DMA_MAPPED)
                return (DDI_FAILURE);

        dma_p->nslots = ~0U;
        dma_p->size = ~0U;
        dma_p->token = ~0U;
        dma_p->offset = 0;
        return (DDI_SUCCESS);
}

/*
 * Free one allocated area of DMAable memory
 */
static void
ath_free_dma_mem(dma_area_t *dma_p)
{
        if (dma_p->dma_hdl != NULL) {
                (void) ddi_dma_unbind_handle(dma_p->dma_hdl);
                if (dma_p->acc_hdl != NULL) {
                        ddi_dma_mem_free(&dma_p->acc_hdl);
                        dma_p->acc_hdl = NULL;
                }
                ddi_dma_free_handle(&dma_p->dma_hdl);
                dma_p->ncookies = 0;
                dma_p->dma_hdl = NULL;
        }
}


/*
 * Initialize tx/rx buffer list. Allocate DMA memory for
 * each buffer.
 */
static int
ath_buflist_setup(dev_info_t *devinfo, ath_t *asc, list_t *bflist,
    struct ath_buf **pbf, struct ath_desc **pds, int nbuf, uint_t dmabflags)
{
        int i, err;
        struct ath_buf *bf = *pbf;
        struct ath_desc *ds = *pds;

        list_create(bflist, sizeof (struct ath_buf),
            offsetof(struct ath_buf, bf_node));
        for (i = 0; i < nbuf; i++, bf++, ds++) {
                bf->bf_desc = ds;
                bf->bf_daddr = asc->asc_desc_dma.cookie.dmac_address +
                    ((uintptr_t)ds - (uintptr_t)asc->asc_desc);
                list_insert_tail(bflist, bf);

                /* alloc DMA memory */
                err = ath_alloc_dma_mem(devinfo, &ath_dma_attr,
                    asc->asc_dmabuf_size, &ath_desc_accattr, DDI_DMA_STREAMING,
                    dmabflags, &bf->bf_dma);
                if (err != DDI_SUCCESS)
                        return (err);
        }
        *pbf = bf;
        *pds = ds;

        return (DDI_SUCCESS);
}

/*
 * Destroy tx/rx buffer list. Free DMA memory.
 */
static void
ath_buflist_cleanup(list_t *buflist)
{
        struct ath_buf *bf;

        if (!buflist)
                return;

        bf = list_head(buflist);
        while (bf != NULL) {
                if (bf->bf_m != NULL) {
                        freemsg(bf->bf_m);
                        bf->bf_m = NULL;
                }
                /* Free DMA buffer */
                ath_free_dma_mem(&bf->bf_dma);
                if (bf->bf_in != NULL) {
                        ieee80211_free_node(bf->bf_in);
                        bf->bf_in = NULL;
                }
                list_remove(buflist, bf);
                bf = list_head(buflist);
        }
        list_destroy(buflist);
}


static void
ath_desc_free(ath_t *asc)
{
        ath_buflist_cleanup(&asc->asc_txbuf_list);
        ath_buflist_cleanup(&asc->asc_rxbuf_list);

        /* Free descriptor DMA buffer */
        ath_free_dma_mem(&asc->asc_desc_dma);

        kmem_free((void *)asc->asc_vbufptr, asc->asc_vbuflen);
        asc->asc_vbufptr = NULL;
}

static int
ath_desc_alloc(dev_info_t *devinfo, ath_t *asc)
{
        int err;
        size_t size;
        struct ath_desc *ds;
        struct ath_buf *bf;

        size = sizeof (struct ath_desc) * (ATH_TXBUF + ATH_RXBUF);

        err = ath_alloc_dma_mem(devinfo, &ath_desc_dma_attr, size,
            &ath_desc_accattr, DDI_DMA_CONSISTENT,
            DDI_DMA_RDWR | DDI_DMA_CONSISTENT, &asc->asc_desc_dma);

        /* virtual address of the first descriptor */
        asc->asc_desc = (struct ath_desc *)asc->asc_desc_dma.mem_va;

        ds = asc->asc_desc;
        ATH_DEBUG((ATH_DBG_INIT, "ath: ath_desc_alloc(): DMA map: "
            "%p (%d) -> %p\n",
            asc->asc_desc, asc->asc_desc_dma.alength,
            asc->asc_desc_dma.cookie.dmac_address));

        /* allocate data structures to describe TX/RX DMA buffers */
        asc->asc_vbuflen = sizeof (struct ath_buf) * (ATH_TXBUF + ATH_RXBUF);
        bf = (struct ath_buf *)kmem_zalloc(asc->asc_vbuflen, KM_SLEEP);
        asc->asc_vbufptr = bf;

        /* DMA buffer size for each TX/RX packet */
        asc->asc_dmabuf_size = roundup(1000 + sizeof (struct ieee80211_frame) +
            IEEE80211_MTU + IEEE80211_CRC_LEN +
            (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN +
            IEEE80211_WEP_CRCLEN), asc->asc_cachelsz);

        /* create RX buffer list */
        err = ath_buflist_setup(devinfo, asc, &asc->asc_rxbuf_list, &bf, &ds,
            ATH_RXBUF, DDI_DMA_READ | DDI_DMA_STREAMING);
        if (err != DDI_SUCCESS) {
                ath_desc_free(asc);
                return (err);
        }

        /* create TX buffer list */
        err = ath_buflist_setup(devinfo, asc, &asc->asc_txbuf_list, &bf, &ds,
            ATH_TXBUF, DDI_DMA_STREAMING);
        if (err != DDI_SUCCESS) {
                ath_desc_free(asc);
                return (err);
        }


        return (DDI_SUCCESS);
}

static void
ath_printrxbuf(struct ath_buf *bf, int32_t done)
{
        struct ath_desc *ds = bf->bf_desc;
        const struct ath_rx_status *rs = &bf->bf_status.ds_rxstat;

        ATH_DEBUG((ATH_DBG_RECV, "ath: R (%p %p) %08x %08x %08x "
            "%08x %08x %08x %c\n",
            ds, bf->bf_daddr,
            ds->ds_link, ds->ds_data,
            ds->ds_ctl0, ds->ds_ctl1,
            ds->ds_hw[0], ds->ds_hw[1],
            !done ? ' ' : (rs->rs_status == 0) ? '*' : '!'));
}

static void
ath_rx_handler(ath_t *asc)
{
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ath_buf *bf;
        struct ath_hal *ah = asc->asc_ah;
        struct ath_desc *ds;
        struct ath_rx_status *rs;
        mblk_t *rx_mp;
        struct ieee80211_frame *wh;
        int32_t len, loop = 1;
        uint8_t phyerr;
        HAL_STATUS status;
        HAL_NODE_STATS hal_node_stats;
        struct ieee80211_node *in;

        do {
                mutex_enter(&asc->asc_rxbuflock);
                bf = list_head(&asc->asc_rxbuf_list);
                if (bf == NULL) {
                        ATH_DEBUG((ATH_DBG_RECV, "ath: ath_rx_handler(): "
                            "no buffer\n"));
                        mutex_exit(&asc->asc_rxbuflock);
                        break;
                }
                ASSERT(bf->bf_dma.cookie.dmac_address != 0);
                ds = bf->bf_desc;
                if (ds->ds_link == bf->bf_daddr) {
                        /*
                         * Never process the self-linked entry at the end,
                         * this may be met at heavy load.
                         */
                        mutex_exit(&asc->asc_rxbuflock);
                        break;
                }

                rs = &bf->bf_status.ds_rxstat;
                status = ATH_HAL_RXPROCDESC(ah, ds,
                    bf->bf_daddr,
                    ATH_PA2DESC(asc, ds->ds_link), rs);
                if (status == HAL_EINPROGRESS) {
                        mutex_exit(&asc->asc_rxbuflock);
                        break;
                }
                list_remove(&asc->asc_rxbuf_list, bf);
                mutex_exit(&asc->asc_rxbuflock);

                if (rs->rs_status != 0) {
                        if (rs->rs_status & HAL_RXERR_CRC)
                                asc->asc_stats.ast_rx_crcerr++;
                        if (rs->rs_status & HAL_RXERR_FIFO)
                                asc->asc_stats.ast_rx_fifoerr++;
                        if (rs->rs_status & HAL_RXERR_DECRYPT)
                                asc->asc_stats.ast_rx_badcrypt++;
                        if (rs->rs_status & HAL_RXERR_PHY) {
                                asc->asc_stats.ast_rx_phyerr++;
                                phyerr = rs->rs_phyerr & 0x1f;
                                asc->asc_stats.ast_rx_phy[phyerr]++;
                        }
                        goto rx_next;
                }
                len = rs->rs_datalen;

                /* less than sizeof(struct ieee80211_frame) */
                if (len < 20) {
                        asc->asc_stats.ast_rx_tooshort++;
                        goto rx_next;
                }

                if ((rx_mp = allocb(asc->asc_dmabuf_size, BPRI_MED)) == NULL) {
                        ath_problem("ath: ath_rx_handler(): "
                            "allocing mblk buffer failed.\n");
                        return;
                }

                ATH_DMA_SYNC(bf->bf_dma, DDI_DMA_SYNC_FORCPU);
                bcopy(bf->bf_dma.mem_va, rx_mp->b_rptr, len);

                rx_mp->b_wptr += len;
                wh = (struct ieee80211_frame *)rx_mp->b_rptr;
                if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) ==
                    IEEE80211_FC0_TYPE_CTL) {
                        /*
                         * Ignore control frame received in promisc mode.
                         */
                        freemsg(rx_mp);
                        goto rx_next;
                }
                /* Remove the CRC at the end of IEEE80211 frame */
                rx_mp->b_wptr -= IEEE80211_CRC_LEN;
#ifdef DEBUG
                ath_printrxbuf(bf, status == HAL_OK);
#endif /* DEBUG */
                /*
                 * Locate the node for sender, track state, and then
                 * pass the (referenced) node up to the 802.11 layer
                 * for its use.
                 */
                in = ieee80211_find_rxnode(ic, wh);

                /*
                 * Send frame up for processing.
                 */
                (void) ieee80211_input(ic, rx_mp, in,
                    rs->rs_rssi, rs->rs_tstamp);

                ieee80211_free_node(in);

rx_next:
                mutex_enter(&asc->asc_rxbuflock);
                list_insert_tail(&asc->asc_rxbuf_list, bf);
                mutex_exit(&asc->asc_rxbuflock);
                ath_setup_desc(asc, bf);
        } while (loop);

        /* rx signal state monitoring */
        ATH_HAL_RXMONITOR(ah, &hal_node_stats, &asc->asc_curchan);
}

static void
ath_printtxbuf(struct ath_buf *bf, int done)
{
        struct ath_desc *ds = bf->bf_desc;
        const struct ath_tx_status *ts = &bf->bf_status.ds_txstat;

        ATH_DEBUG((ATH_DBG_SEND, "ath: T(%p %p) %08x %08x %08x %08x %08x"
            " %08x %08x %08x %c\n",
            ds, bf->bf_daddr,
            ds->ds_link, ds->ds_data,
            ds->ds_ctl0, ds->ds_ctl1,
            ds->ds_hw[0], ds->ds_hw[1], ds->ds_hw[2], ds->ds_hw[3],
            !done ? ' ' : (ts->ts_status == 0) ? '*' : '!'));
}

/*
 * The input parameter mp has following assumption:
 * For data packets, GLDv3 mac_wifi plugin allocates and fills the
 * ieee80211 header. For management packets, net80211 allocates and
 * fills the ieee80211 header. In both cases, enough spaces in the
 * header are left for encryption option.
 */
static int32_t
ath_tx_start(ath_t *asc, struct ieee80211_node *in, struct ath_buf *bf,
    mblk_t *mp)
{
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ieee80211_frame *wh;
        struct ath_hal *ah = asc->asc_ah;
        uint32_t subtype, flags, ctsduration;
        int32_t keyix, iswep, hdrlen, pktlen, mblen, mbslen, try0;
        uint8_t rix, cix, txrate, ctsrate;
        struct ath_desc *ds;
        struct ath_txq *txq;
        HAL_PKT_TYPE atype;
        const HAL_RATE_TABLE *rt;
        HAL_BOOL shortPreamble;
        struct ath_node *an;
        caddr_t dest;

        /*
         * CRC are added by H/W, not encaped by driver,
         * but we must count it in pkt length.
         */
        pktlen = IEEE80211_CRC_LEN;

        wh = (struct ieee80211_frame *)mp->b_rptr;
        iswep = wh->i_fc[1] & IEEE80211_FC1_WEP;
        keyix = HAL_TXKEYIX_INVALID;
        hdrlen = sizeof (struct ieee80211_frame);
        if (iswep != 0) {
                const struct ieee80211_cipher *cip;
                struct ieee80211_key *k;

                /*
                 * Construct the 802.11 header+trailer for an encrypted
                 * frame. The only reason this can fail is because of an
                 * unknown or unsupported cipher/key type.
                 */
                k = ieee80211_crypto_encap(ic, mp);
                if (k == NULL) {
                        ATH_DEBUG((ATH_DBG_AUX, "crypto_encap failed\n"));
                        /*
                         * This can happen when the key is yanked after the
                         * frame was queued.  Just discard the frame; the
                         * 802.11 layer counts failures and provides
                         * debugging/diagnostics.
                         */
                        return (EIO);
                }
                cip = k->wk_cipher;
                /*
                 * Adjust the packet + header lengths for the crypto
                 * additions and calculate the h/w key index.  When
                 * a s/w mic is done the frame will have had any mic
                 * added to it prior to entry so m0->m_pkthdr.len above will
                 * account for it. Otherwise we need to add it to the
                 * packet length.
                 */
                hdrlen += cip->ic_header;
                pktlen += cip->ic_trailer;
                if ((k->wk_flags & IEEE80211_KEY_SWMIC) == 0)
                        pktlen += cip->ic_miclen;
                keyix = k->wk_keyix;

                /* packet header may have moved, reset our local pointer */
                wh = (struct ieee80211_frame *)mp->b_rptr;
        }

        dest = bf->bf_dma.mem_va;
        for (; mp != NULL; mp = mp->b_cont) {
                mblen = MBLKL(mp);
                bcopy(mp->b_rptr, dest, mblen);
                dest += mblen;
        }
        mbslen = (uintptr_t)dest - (uintptr_t)bf->bf_dma.mem_va;
        pktlen += mbslen;

        bf->bf_in = in;

        /* setup descriptors */
        ds = bf->bf_desc;
        rt = asc->asc_currates;
        ASSERT(rt != NULL);

        /*
         * The 802.11 layer marks whether or not we should
         * use short preamble based on the current mode and
         * negotiated parameters.
         */
        if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) &&
            (in->in_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) {
                shortPreamble = AH_TRUE;
                asc->asc_stats.ast_tx_shortpre++;
        } else {
                shortPreamble = AH_FALSE;
        }

        an = ATH_NODE(in);

        /*
         * Calculate Atheros packet type from IEEE80211 packet header
         * and setup for rate calculations.
         */
        switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) {
        case IEEE80211_FC0_TYPE_MGT:
                subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
                if (subtype == IEEE80211_FC0_SUBTYPE_BEACON)
                        atype = HAL_PKT_TYPE_BEACON;
                else if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)
                        atype = HAL_PKT_TYPE_PROBE_RESP;
                else if (subtype == IEEE80211_FC0_SUBTYPE_ATIM)
                        atype = HAL_PKT_TYPE_ATIM;
                else
                        atype = HAL_PKT_TYPE_NORMAL;
                rix = 0;        /* lowest rate */
                try0 = ATH_TXMAXTRY;
                if (shortPreamble)
                        txrate = an->an_tx_mgtratesp;
                else
                        txrate = an->an_tx_mgtrate;
                /* force all ctl frames to highest queue */
                txq = asc->asc_ac2q[WME_AC_VO];
                break;
        case IEEE80211_FC0_TYPE_CTL:
                atype = HAL_PKT_TYPE_PSPOLL;
                subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
                rix = 0;        /* lowest rate */
                try0 = ATH_TXMAXTRY;
                if (shortPreamble)
                        txrate = an->an_tx_mgtratesp;
                else
                        txrate = an->an_tx_mgtrate;
                /* force all ctl frames to highest queue */
                txq = asc->asc_ac2q[WME_AC_VO];
                break;
        case IEEE80211_FC0_TYPE_DATA:
                atype = HAL_PKT_TYPE_NORMAL;
                rix = an->an_tx_rix0;
                try0 = an->an_tx_try0;
                if (shortPreamble)
                        txrate = an->an_tx_rate0sp;
                else
                        txrate = an->an_tx_rate0;
                /* Always use background queue */
                txq = asc->asc_ac2q[WME_AC_BK];
                break;
        default:
                /* Unknown 802.11 frame */
                asc->asc_stats.ast_tx_invalid++;
                return (1);
        }
        /*
         * Calculate miscellaneous flags.
         */
        flags = HAL_TXDESC_CLRDMASK;
        if (IEEE80211_IS_MULTICAST(wh->i_addr1)) {
                flags |= HAL_TXDESC_NOACK;      /* no ack on broad/multicast */
                asc->asc_stats.ast_tx_noack++;
        } else if (pktlen > ic->ic_rtsthreshold) {
                flags |= HAL_TXDESC_RTSENA;     /* RTS based on frame length */
                asc->asc_stats.ast_tx_rts++;
        }

        /*
         * Calculate duration.  This logically belongs in the 802.11
         * layer but it lacks sufficient information to calculate it.
         */
        if ((flags & HAL_TXDESC_NOACK) == 0 &&
            (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) !=
            IEEE80211_FC0_TYPE_CTL) {
                uint16_t dur;
                dur = ath_hal_computetxtime(ah, rt, IEEE80211_ACK_SIZE,
                    rix, shortPreamble);
                /* LINTED E_BAD_PTR_CAST_ALIGN */
                *(uint16_t *)wh->i_dur = LE_16(dur);
        }

        /*
         * Calculate RTS/CTS rate and duration if needed.
         */
        ctsduration = 0;
        if (flags & (HAL_TXDESC_RTSENA|HAL_TXDESC_CTSENA)) {
                /*
                 * CTS transmit rate is derived from the transmit rate
                 * by looking in the h/w rate table.  We must also factor
                 * in whether or not a short preamble is to be used.
                 */
                cix = rt->info[rix].controlRate;
                ctsrate = rt->info[cix].rateCode;
                if (shortPreamble)
                        ctsrate |= rt->info[cix].shortPreamble;
                /*
                 * Compute the transmit duration based on the size
                 * of an ACK frame.  We call into the HAL to do the
                 * computation since it depends on the characteristics
                 * of the actual PHY being used.
                 */
                if (flags & HAL_TXDESC_RTSENA) {        /* SIFS + CTS */
                        ctsduration += ath_hal_computetxtime(ah,
                            rt, IEEE80211_ACK_SIZE, cix, shortPreamble);
                }
                /* SIFS + data */
                ctsduration += ath_hal_computetxtime(ah,
                    rt, pktlen, rix, shortPreamble);
                if ((flags & HAL_TXDESC_NOACK) == 0) {  /* SIFS + ACK */
                        ctsduration += ath_hal_computetxtime(ah,
                            rt, IEEE80211_ACK_SIZE, cix, shortPreamble);
                }
        } else
                ctsrate = 0;

        if (++txq->axq_intrcnt >= ATH_TXINTR_PERIOD) {
                flags |= HAL_TXDESC_INTREQ;
                txq->axq_intrcnt = 0;
        }

        /*
         * Formulate first tx descriptor with tx controls.
         */
        ATH_HAL_SETUPTXDESC(ah, ds,
            pktlen,                     /* packet length */
            hdrlen,                     /* header length */
            atype,                      /* Atheros packet type */
            MIN(in->in_txpower, 60),    /* txpower */
            txrate, try0,               /* series 0 rate/tries */
            keyix,                      /* key cache index */
            an->an_tx_antenna,          /* antenna mode */
            flags,                      /* flags */
            ctsrate,                    /* rts/cts rate */
            ctsduration);               /* rts/cts duration */
        bf->bf_flags = flags;

        /* LINTED E_BAD_PTR_CAST_ALIGN */
        ATH_DEBUG((ATH_DBG_SEND, "ath: ath_xmit(): to %s totlen=%d "
            "an->an_tx_rate1sp=%d tx_rate2sp=%d tx_rate3sp=%d "
            "qnum=%d rix=%d sht=%d dur = %d\n",
            ieee80211_macaddr_sprintf(wh->i_addr1), mbslen, an->an_tx_rate1sp,
            an->an_tx_rate2sp, an->an_tx_rate3sp,
            txq->axq_qnum, rix, shortPreamble, *(uint16_t *)wh->i_dur));

        /*
         * Setup the multi-rate retry state only when we're
         * going to use it.  This assumes ath_hal_setuptxdesc
         * initializes the descriptors (so we don't have to)
         * when the hardware supports multi-rate retry and
         * we don't use it.
         */
        if (try0 != ATH_TXMAXTRY)
                ATH_HAL_SETUPXTXDESC(ah, ds,
                    an->an_tx_rate1sp, 2,       /* series 1 */
                    an->an_tx_rate2sp, 2,       /* series 2 */
                    an->an_tx_rate3sp, 2);      /* series 3 */

        ds->ds_link = 0;
        ds->ds_data = bf->bf_dma.cookie.dmac_address;
        ATH_HAL_FILLTXDESC(ah, ds,
            mbslen,             /* segment length */
            AH_TRUE,            /* first segment */
            AH_TRUE,            /* last segment */
            ds);                /* first descriptor */

        ATH_DMA_SYNC(bf->bf_dma, DDI_DMA_SYNC_FORDEV);

        mutex_enter(&txq->axq_lock);
        list_insert_tail(&txq->axq_list, bf);
        if (txq->axq_link == NULL) {
                ATH_HAL_PUTTXBUF(ah, txq->axq_qnum, bf->bf_daddr);
        } else {
                *txq->axq_link = bf->bf_daddr;
        }
        txq->axq_link = &ds->ds_link;
        mutex_exit(&txq->axq_lock);

        ATH_HAL_TXSTART(ah, txq->axq_qnum);

        ic->ic_stats.is_tx_frags++;
        ic->ic_stats.is_tx_bytes += pktlen;

        return (0);
}

/*
 * Transmit a management frame.  On failure we reclaim the skbuff.
 * Note that management frames come directly from the 802.11 layer
 * and do not honor the send queue flow control.  Need to investigate
 * using priority queueing so management frames can bypass data.
 */
static int
ath_xmit(ieee80211com_t *ic, mblk_t *mp, uint8_t type)
{
        ath_t *asc = (ath_t *)ic;
        struct ath_hal *ah = asc->asc_ah;
        struct ieee80211_node *in = NULL;
        struct ath_buf *bf = NULL;
        struct ieee80211_frame *wh;
        int error = 0;

        ASSERT(mp->b_next == NULL);

        if (!ATH_IS_RUNNING(asc)) {
                if ((type & IEEE80211_FC0_TYPE_MASK) !=
                    IEEE80211_FC0_TYPE_DATA) {
                        freemsg(mp);
                }
                return (ENXIO);
        }

        /* Grab a TX buffer */
        mutex_enter(&asc->asc_txbuflock);
        bf = list_head(&asc->asc_txbuf_list);
        if (bf != NULL)
                list_remove(&asc->asc_txbuf_list, bf);
        if (list_empty(&asc->asc_txbuf_list)) {
                ATH_DEBUG((ATH_DBG_SEND, "ath: ath_mgmt_send(): "
                    "stop queue\n"));
                asc->asc_stats.ast_tx_qstop++;
        }
        mutex_exit(&asc->asc_txbuflock);
        if (bf == NULL) {
                ATH_DEBUG((ATH_DBG_SEND, "ath: ath_mgmt_send(): discard, "
                    "no xmit buf\n"));
                ic->ic_stats.is_tx_nobuf++;
                if ((type & IEEE80211_FC0_TYPE_MASK) ==
                    IEEE80211_FC0_TYPE_DATA) {
                        asc->asc_stats.ast_tx_nobuf++;
                        mutex_enter(&asc->asc_resched_lock);
                        asc->asc_resched_needed = B_TRUE;
                        mutex_exit(&asc->asc_resched_lock);
                } else {
                        asc->asc_stats.ast_tx_nobufmgt++;
                        freemsg(mp);
                }
                return (ENOMEM);
        }

        wh = (struct ieee80211_frame *)mp->b_rptr;

        /* Locate node */
        in = ieee80211_find_txnode(ic,  wh->i_addr1);
        if (in == NULL) {
                error = EIO;
                goto bad;
        }

        in->in_inact = 0;
        switch (type & IEEE80211_FC0_TYPE_MASK) {
        case IEEE80211_FC0_TYPE_DATA:
                (void) ieee80211_encap(ic, mp, in);
                break;
        default:
                if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) ==
                    IEEE80211_FC0_SUBTYPE_PROBE_RESP) {
                        /* fill time stamp */
                        uint64_t tsf;
                        uint32_t *tstamp;

                        tsf = ATH_HAL_GETTSF64(ah);
                        /* adjust 100us delay to xmit */
                        tsf += 100;
                        /* LINTED E_BAD_PTR_CAST_ALIGN */
                        tstamp = (uint32_t *)&wh[1];
                        tstamp[0] = LE_32(tsf & 0xffffffff);
                        tstamp[1] = LE_32(tsf >> 32);
                }
                asc->asc_stats.ast_tx_mgmt++;
                break;
        }

        error = ath_tx_start(asc, in, bf, mp);
        if (error != 0) {
bad:
                ic->ic_stats.is_tx_failed++;
                if (bf != NULL) {
                        mutex_enter(&asc->asc_txbuflock);
                        list_insert_tail(&asc->asc_txbuf_list, bf);
                        mutex_exit(&asc->asc_txbuflock);
                }
        }
        if (in != NULL)
                ieee80211_free_node(in);
        if ((type & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_DATA ||
            error == 0) {
                freemsg(mp);
        }

        return (error);
}

static mblk_t *
ath_m_tx(void *arg, mblk_t *mp)
{
        ath_t *asc = arg;
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        mblk_t *next;
        int error = 0;

        /*
         * No data frames go out unless we're associated; this
         * should not happen as the 802.11 layer does not enable
         * the xmit queue until we enter the RUN state.
         */
        if (ic->ic_state != IEEE80211_S_RUN) {
                ATH_DEBUG((ATH_DBG_SEND, "ath: ath_m_tx(): "
                    "discard, state %u\n", ic->ic_state));
                asc->asc_stats.ast_tx_discard++;
                freemsgchain(mp);
                return (NULL);
        }

        while (mp != NULL) {
                next = mp->b_next;
                mp->b_next = NULL;
                error = ath_xmit(ic, mp, IEEE80211_FC0_TYPE_DATA);
                if (error != 0) {
                        mp->b_next = next;
                        if (error == ENOMEM) {
                                break;
                        } else {
                                freemsgchain(mp);       /* CR6501759 issues */
                                return (NULL);
                        }
                }
                mp = next;
        }

        return (mp);
}

static int
ath_tx_processq(ath_t *asc, struct ath_txq *txq)
{
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ath_hal *ah = asc->asc_ah;
        struct ath_buf *bf;
        struct ath_desc *ds;
        struct ieee80211_node *in;
        int32_t sr, lr, nacked = 0;
        struct ath_tx_status *ts;
        HAL_STATUS status;
        struct ath_node *an;

        for (;;) {
                mutex_enter(&txq->axq_lock);
                bf = list_head(&txq->axq_list);
                if (bf == NULL) {
                        txq->axq_link = NULL;
                        mutex_exit(&txq->axq_lock);
                        break;
                }
                ds = bf->bf_desc;       /* last decriptor */
                ts = &bf->bf_status.ds_txstat;
                status = ATH_HAL_TXPROCDESC(ah, ds, ts);
#ifdef DEBUG
                ath_printtxbuf(bf, status == HAL_OK);
#endif
                if (status == HAL_EINPROGRESS) {
                        mutex_exit(&txq->axq_lock);
                        break;
                }
                list_remove(&txq->axq_list, bf);
                mutex_exit(&txq->axq_lock);
                in = bf->bf_in;
                if (in != NULL) {
                        an = ATH_NODE(in);
                        /* Successful transmition */
                        if (ts->ts_status == 0) {
                                an->an_tx_ok++;
                                an->an_tx_antenna = ts->ts_antenna;
                                if (ts->ts_rate & HAL_TXSTAT_ALTRATE)
                                        asc->asc_stats.ast_tx_altrate++;
                                asc->asc_stats.ast_tx_rssidelta =
                                    ts->ts_rssi - asc->asc_stats.ast_tx_rssi;
                                asc->asc_stats.ast_tx_rssi = ts->ts_rssi;
                        } else {
                                an->an_tx_err++;
                                if (ts->ts_status & HAL_TXERR_XRETRY)
                                        asc->asc_stats.ast_tx_xretries++;
                                if (ts->ts_status & HAL_TXERR_FIFO)
                                        asc->asc_stats.ast_tx_fifoerr++;
                                if (ts->ts_status & HAL_TXERR_FILT)
                                        asc->asc_stats.ast_tx_filtered++;
                                an->an_tx_antenna = 0;  /* invalidate */
                        }
                        sr = ts->ts_shortretry;
                        lr = ts->ts_longretry;
                        asc->asc_stats.ast_tx_shortretry += sr;
                        asc->asc_stats.ast_tx_longretry += lr;
                        /*
                         * Hand the descriptor to the rate control algorithm.
                         */
                        if ((ts->ts_status & HAL_TXERR_FILT) == 0 &&
                            (bf->bf_flags & HAL_TXDESC_NOACK) == 0) {
                                /*
                                 * If frame was ack'd update the last rx time
                                 * used to workaround phantom bmiss interrupts.
                                 */
                                if (ts->ts_status == 0) {
                                        nacked++;
                                        an->an_tx_ok++;
                                } else {
                                        an->an_tx_err++;
                                }
                                an->an_tx_retr += sr + lr;
                        }
                }
                bf->bf_in = NULL;
                mutex_enter(&asc->asc_txbuflock);
                list_insert_tail(&asc->asc_txbuf_list, bf);
                mutex_exit(&asc->asc_txbuflock);
                /*
                 * Reschedule stalled outbound packets
                 */
                mutex_enter(&asc->asc_resched_lock);
                if (asc->asc_resched_needed) {
                        asc->asc_resched_needed = B_FALSE;
                        mac_tx_update(ic->ic_mach);
                }
                mutex_exit(&asc->asc_resched_lock);
        }
        return (nacked);
}


static void
ath_tx_handler(ath_t *asc)
{
        int i;

        /*
         * Process each active queue.
         */
        for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
                if (ATH_TXQ_SETUP(asc, i)) {
                        (void) ath_tx_processq(asc, &asc->asc_txq[i]);
                }
        }
}

static struct ieee80211_node *
ath_node_alloc(ieee80211com_t *ic)
{
        struct ath_node *an;
        ath_t *asc = (ath_t *)ic;

        an = kmem_zalloc(sizeof (struct ath_node), KM_SLEEP);
        ath_rate_update(asc, &an->an_node, 0);
        return (&an->an_node);
}

static void
ath_node_free(struct ieee80211_node *in)
{
        ieee80211com_t *ic = in->in_ic;
        ath_t *asc = (ath_t *)ic;
        struct ath_buf *bf;
        struct ath_txq *txq;
        int32_t i;

        for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
                if (ATH_TXQ_SETUP(asc, i)) {
                        txq = &asc->asc_txq[i];
                        mutex_enter(&txq->axq_lock);
                        bf = list_head(&txq->axq_list);
                        while (bf != NULL) {
                                if (bf->bf_in == in) {
                                        bf->bf_in = NULL;
                                }
                                bf = list_next(&txq->axq_list, bf);
                        }
                        mutex_exit(&txq->axq_lock);
                }
        }
        ic->ic_node_cleanup(in);
        if (in->in_wpa_ie != NULL)
                ieee80211_free(in->in_wpa_ie);
        kmem_free(in, sizeof (struct ath_node));
}

static void
ath_next_scan(void *arg)
{
        ieee80211com_t *ic = arg;
        ath_t *asc = (ath_t *)ic;

        asc->asc_scan_timer = 0;
        if (ic->ic_state == IEEE80211_S_SCAN) {
                asc->asc_scan_timer = timeout(ath_next_scan, (void *)asc,
                    drv_usectohz(ath_dwelltime * 1000));
                ieee80211_next_scan(ic);
        }
}

static void
ath_stop_scantimer(ath_t *asc)
{
        timeout_id_t tmp_id = 0;

        while ((asc->asc_scan_timer != 0) && (tmp_id != asc->asc_scan_timer)) {
                tmp_id = asc->asc_scan_timer;
                (void) untimeout(tmp_id);
        }
        asc->asc_scan_timer = 0;
}

static int32_t
ath_newstate(ieee80211com_t *ic, enum ieee80211_state nstate, int arg)
{
        ath_t *asc = (ath_t *)ic;
        struct ath_hal *ah = asc->asc_ah;
        struct ieee80211_node *in;
        int32_t i, error;
        uint8_t *bssid;
        uint32_t rfilt;
        enum ieee80211_state ostate;

        static const HAL_LED_STATE leds[] = {
            HAL_LED_INIT,       /* IEEE80211_S_INIT */
            HAL_LED_SCAN,       /* IEEE80211_S_SCAN */
            HAL_LED_AUTH,       /* IEEE80211_S_AUTH */
            HAL_LED_ASSOC,      /* IEEE80211_S_ASSOC */
            HAL_LED_RUN,        /* IEEE80211_S_RUN */
        };
        if (!ATH_IS_RUNNING(asc))
                return (0);

        ostate = ic->ic_state;
        if (nstate != IEEE80211_S_SCAN)
                ath_stop_scantimer(asc);

        ATH_LOCK(asc);
        ATH_HAL_SETLEDSTATE(ah, leds[nstate]);  /* set LED */

        if (nstate == IEEE80211_S_INIT) {
                asc->asc_imask &= ~(HAL_INT_SWBA | HAL_INT_BMISS);
                /*
                 * Disable interrupts.
                 */
                ATH_HAL_INTRSET(ah, asc->asc_imask &~ HAL_INT_GLOBAL);
                ATH_UNLOCK(asc);
                goto done;
        }
        in = ic->ic_bss;
        error = ath_chan_set(asc, ic->ic_curchan);
        if (error != 0) {
                if (nstate != IEEE80211_S_SCAN) {
                        ATH_UNLOCK(asc);
                        ieee80211_reset_chan(ic);
                        goto bad;
                }
        }

        rfilt = ath_calcrxfilter(asc);

        if (nstate == IEEE80211_S_SCAN)
                bssid = ic->ic_macaddr;
        else
                bssid = in->in_bssid;
        ATH_HAL_SETRXFILTER(ah, rfilt);

        if (nstate == IEEE80211_S_RUN && ic->ic_opmode != IEEE80211_M_IBSS)
                ATH_HAL_SETASSOCID(ah, bssid, in->in_associd);
        else
                ATH_HAL_SETASSOCID(ah, bssid, 0);
        if (ic->ic_flags & IEEE80211_F_PRIVACY) {
                for (i = 0; i < IEEE80211_WEP_NKID; i++) {
                        if (ATH_HAL_KEYISVALID(ah, i))
                                ATH_HAL_KEYSETMAC(ah, i, bssid);
                }
        }

        if ((nstate == IEEE80211_S_RUN) &&
            (ostate != IEEE80211_S_RUN)) {
                /* Configure the beacon and sleep timers. */
                ath_beacon_config(asc);
        } else {
                asc->asc_imask &= ~(HAL_INT_SWBA | HAL_INT_BMISS);
                ATH_HAL_INTRSET(ah, asc->asc_imask);
        }
        /*
         * Reset the rate control state.
         */
        ath_rate_ctl_reset(asc, nstate);

        ATH_UNLOCK(asc);
done:
        /*
         * Invoke the parent method to complete the work.
         */
        error = asc->asc_newstate(ic, nstate, arg);
        /*
         * Finally, start any timers.
         */
        if (nstate == IEEE80211_S_RUN) {
                ieee80211_start_watchdog(ic, 1);
        } else if ((nstate == IEEE80211_S_SCAN) && (ostate != nstate)) {
                /* start ap/neighbor scan timer */
                ASSERT(asc->asc_scan_timer == 0);
                asc->asc_scan_timer = timeout(ath_next_scan, (void *)asc,
                    drv_usectohz(ath_dwelltime * 1000));
        }
bad:
        return (error);
}

/*
 * Periodically recalibrate the PHY to account
 * for temperature/environment changes.
 */
static void
ath_calibrate(ath_t *asc)
{
        struct ath_hal *ah = asc->asc_ah;
        HAL_BOOL iqcaldone;

        asc->asc_stats.ast_per_cal++;

        if (ATH_HAL_GETRFGAIN(ah) == HAL_RFGAIN_NEED_CHANGE) {
                /*
                 * Rfgain is out of bounds, reset the chip
                 * to load new gain values.
                 */
                ATH_DEBUG((ATH_DBG_HAL, "ath: ath_calibrate(): "
                    "Need change RFgain\n"));
                asc->asc_stats.ast_per_rfgain++;
                (void) ath_reset(&asc->asc_isc);
        }
        if (!ATH_HAL_CALIBRATE(ah, &asc->asc_curchan, &iqcaldone)) {
                ATH_DEBUG((ATH_DBG_HAL, "ath: ath_calibrate(): "
                    "calibration of channel %u failed\n",
                    asc->asc_curchan.channel));
                asc->asc_stats.ast_per_calfail++;
        }
}

static void
ath_watchdog(void *arg)
{
        ath_t *asc = arg;
        ieee80211com_t *ic = &asc->asc_isc;
        int ntimer = 0;

        ATH_LOCK(asc);
        ic->ic_watchdog_timer = 0;
        if (!ATH_IS_RUNNING(asc)) {
                ATH_UNLOCK(asc);
                return;
        }

        if (ic->ic_state == IEEE80211_S_RUN) {
                /* periodic recalibration */
                ath_calibrate(asc);

                /*
                 * Start the background rate control thread if we
                 * are not configured to use a fixed xmit rate.
                 */
                if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) {
                        asc->asc_stats.ast_rate_calls ++;
                        if (ic->ic_opmode == IEEE80211_M_STA)
                                ath_rate_ctl(ic, ic->ic_bss);
                        else
                                ieee80211_iterate_nodes(&ic->ic_sta,
                                    ath_rate_ctl, asc);
                }

                ntimer = 1;
        }
        ATH_UNLOCK(asc);

        ieee80211_watchdog(ic);
        if (ntimer != 0)
                ieee80211_start_watchdog(ic, ntimer);
}

static void
ath_tx_proc(void *arg)
{
        ath_t *asc = arg;
        ath_tx_handler(asc);
}


static uint_t
ath_intr(caddr_t arg)
{
        /* LINTED E_BAD_PTR_CAST_ALIGN */
        ath_t *asc = (ath_t *)arg;
        struct ath_hal *ah = asc->asc_ah;
        HAL_INT status;
        ieee80211com_t *ic = (ieee80211com_t *)asc;

        ATH_LOCK(asc);

        if (!ATH_IS_RUNNING(asc)) {
                /*
                 * The hardware is not ready/present, don't touch anything.
                 * Note this can happen early on if the IRQ is shared.
                 */
                ATH_UNLOCK(asc);
                return (DDI_INTR_UNCLAIMED);
        }

        if (!ATH_HAL_INTRPEND(ah)) {    /* shared irq, not for us */
                ATH_UNLOCK(asc);
                return (DDI_INTR_UNCLAIMED);
        }

        ATH_HAL_GETISR(ah, &status);
        status &= asc->asc_imask;
        if (status & HAL_INT_FATAL) {
                asc->asc_stats.ast_hardware++;
                goto reset;
        } else if (status & HAL_INT_RXORN) {
                asc->asc_stats.ast_rxorn++;
                goto reset;
        } else {
                if (status & HAL_INT_RXEOL) {
                        asc->asc_stats.ast_rxeol++;
                        asc->asc_rxlink = NULL;
                }
                if (status & HAL_INT_TXURN) {
                        asc->asc_stats.ast_txurn++;
                        ATH_HAL_UPDATETXTRIGLEVEL(ah, AH_TRUE);
                }

                if (status & HAL_INT_RX) {
                        asc->asc_rx_pend = 1;
                        ddi_trigger_softintr(asc->asc_softint_id);
                }
                if (status & HAL_INT_TX) {
                        if (ddi_taskq_dispatch(asc->asc_tq, ath_tx_proc,
                            asc, DDI_NOSLEEP) != DDI_SUCCESS) {
                                ath_problem("ath: ath_intr(): "
                                    "No memory available for tx taskq\n");
                        }
                }
                ATH_UNLOCK(asc);

                if (status & HAL_INT_SWBA) {
                        /* This will occur only in Host-AP or Ad-Hoc mode */
                        return (DDI_INTR_CLAIMED);
                }

                if (status & HAL_INT_BMISS) {
                        if (ic->ic_state == IEEE80211_S_RUN) {
                                (void) ieee80211_new_state(ic,
                                    IEEE80211_S_ASSOC, -1);
                        }
                }

        }

        return (DDI_INTR_CLAIMED);
reset:
        (void) ath_reset(ic);
        ATH_UNLOCK(asc);
        return (DDI_INTR_CLAIMED);
}

static uint_t
ath_softint_handler(caddr_t data)
{
        /* LINTED E_BAD_PTR_CAST_ALIGN */
        ath_t *asc = (ath_t *)data;

        /*
         * Check if the soft interrupt is triggered by another
         * driver at the same level.
         */
        ATH_LOCK(asc);
        if (asc->asc_rx_pend) { /* Soft interrupt for this driver */
                asc->asc_rx_pend = 0;
                ATH_UNLOCK(asc);
                ath_rx_handler(asc);
                return (DDI_INTR_CLAIMED);
        }
        ATH_UNLOCK(asc);
        return (DDI_INTR_UNCLAIMED);
}

/*
 * following are gld callback routine
 * ath_gld_send, ath_gld_ioctl, ath_gld_gstat
 * are listed in other corresponding sections.
 * reset the hardware w/o losing operational state.  this is
 * basically a more efficient way of doing ath_gld_stop, ath_gld_start,
 * followed by state transitions to the current 802.11
 * operational state.  used to recover from errors rx overrun
 * and to reset the hardware when rf gain settings must be reset.
 */

static void
ath_stop_locked(ath_t *asc)
{
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ath_hal *ah = asc->asc_ah;

        ATH_LOCK_ASSERT(asc);
        if (!asc->asc_isrunning)
                return;

        /*
         * Shutdown the hardware and driver:
         *    reset 802.11 state machine
         *    turn off timers
         *    disable interrupts
         *    turn off the radio
         *    clear transmit machinery
         *    clear receive machinery
         *    drain and release tx queues
         *    reclaim beacon resources
         *    power down hardware
         *
         * Note that some of this work is not possible if the
         * hardware is gone (invalid).
         */
        ATH_UNLOCK(asc);
        ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
        ieee80211_stop_watchdog(ic);
        ATH_LOCK(asc);
        ATH_HAL_INTRSET(ah, 0);
        ath_draintxq(asc);
        if (!asc->asc_invalid) {
                ath_stoprecv(asc);
                ATH_HAL_PHYDISABLE(ah);
        } else {
                asc->asc_rxlink = NULL;
        }
        asc->asc_isrunning = 0;
}

static void
ath_m_stop(void *arg)
{
        ath_t *asc = arg;
        struct ath_hal *ah = asc->asc_ah;

        ATH_LOCK(asc);
        ath_stop_locked(asc);
        ATH_HAL_SETPOWER(ah, HAL_PM_AWAKE);
        asc->asc_invalid = 1;
        ATH_UNLOCK(asc);
}

static int
ath_start_locked(ath_t *asc)
{
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ath_hal *ah = asc->asc_ah;
        HAL_STATUS status;

        ATH_LOCK_ASSERT(asc);

        /*
         * The basic interface to setting the hardware in a good
         * state is ``reset''.  On return the hardware is known to
         * be powered up and with interrupts disabled.  This must
         * be followed by initialization of the appropriate bits
         * and then setup of the interrupt mask.
         */
        asc->asc_curchan.channel = ic->ic_curchan->ich_freq;
        asc->asc_curchan.channelFlags = ath_chan2flags(ic, ic->ic_curchan);
        if (!ATH_HAL_RESET(ah, (HAL_OPMODE)ic->ic_opmode,
            &asc->asc_curchan, AH_FALSE, &status)) {
                ATH_DEBUG((ATH_DBG_HAL, "ath: ath_m_start(): "
                    "reset hardware failed: '%s' (HAL status %u)\n",
                    ath_get_hal_status_desc(status), status));
                return (ENOTACTIVE);
        }

        (void) ath_startrecv(asc);

        /*
         * Enable interrupts.
         */
        asc->asc_imask = HAL_INT_RX | HAL_INT_TX
            | HAL_INT_RXEOL | HAL_INT_RXORN
            | HAL_INT_FATAL | HAL_INT_GLOBAL;
        ATH_HAL_INTRSET(ah, asc->asc_imask);

        /*
         * The hardware should be ready to go now so it's safe
         * to kick the 802.11 state machine as it's likely to
         * immediately call back to us to send mgmt frames.
         */
        ath_chan_change(asc, ic->ic_curchan);

        asc->asc_isrunning = 1;

        return (0);
}

int
ath_m_start(void *arg)
{
        ath_t *asc = arg;
        int err;

        ATH_LOCK(asc);
        /*
         * Stop anything previously setup.  This is safe
         * whether this is the first time through or not.
         */
        ath_stop_locked(asc);

        if ((err = ath_start_locked(asc)) != 0) {
                ATH_UNLOCK(asc);
                return (err);
        }

        asc->asc_invalid = 0;
        ATH_UNLOCK(asc);

        return (0);
}


static int
ath_m_unicst(void *arg, const uint8_t *macaddr)
{
        ath_t *asc = arg;
        struct ath_hal *ah = asc->asc_ah;

        ATH_DEBUG((ATH_DBG_GLD, "ath: ath_gld_saddr(): "
            "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
            macaddr[0], macaddr[1], macaddr[2],
            macaddr[3], macaddr[4], macaddr[5]));

        ATH_LOCK(asc);
        IEEE80211_ADDR_COPY(asc->asc_isc.ic_macaddr, macaddr);
        ATH_HAL_SETMAC(ah, asc->asc_isc.ic_macaddr);

        (void) ath_reset(&asc->asc_isc);
        ATH_UNLOCK(asc);
        return (0);
}

static int
ath_m_promisc(void *arg, boolean_t on)
{
        ath_t *asc = arg;
        struct ath_hal *ah = asc->asc_ah;
        uint32_t rfilt;

        ATH_LOCK(asc);
        rfilt = ATH_HAL_GETRXFILTER(ah);
        if (on)
                rfilt |= HAL_RX_FILTER_PROM;
        else
                rfilt &= ~HAL_RX_FILTER_PROM;
        asc->asc_promisc = on;
        ATH_HAL_SETRXFILTER(ah, rfilt);
        ATH_UNLOCK(asc);

        return (0);
}

static int
ath_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
        ath_t *asc = arg;
        struct ath_hal *ah = asc->asc_ah;
        uint32_t val, index, bit;
        uint8_t pos;
        uint32_t *mfilt = asc->asc_mcast_hash;

        ATH_LOCK(asc);

        /* calculate XOR of eight 6bit values */
        val = ATH_LE_READ_4(mca + 0);
        pos = (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
        val = ATH_LE_READ_4(mca + 3);
        pos ^= (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
        pos &= 0x3f;
        index = pos / 32;
        bit = 1 << (pos % 32);

        if (add) {      /* enable multicast */
                asc->asc_mcast_refs[pos]++;
                mfilt[index] |= bit;
        } else {        /* disable multicast */
                if (--asc->asc_mcast_refs[pos] == 0)
                        mfilt[index] &= ~bit;
        }
        ATH_HAL_SETMCASTFILTER(ah, mfilt[0], mfilt[1]);

        ATH_UNLOCK(asc);
        return (0);
}
/*
 * callback functions for /get/set properties
 */
static int
ath_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, const void *wldp_buf)
{
        ath_t   *asc = arg;
        int     err;

        err = ieee80211_setprop(&asc->asc_isc, pr_name, wldp_pr_num,
            wldp_length, wldp_buf);

        ATH_LOCK(asc);

        if (err == ENETRESET) {
                if (ATH_IS_RUNNING(asc)) {
                        ATH_UNLOCK(asc);
                        (void) ath_m_start(asc);
                        (void) ieee80211_new_state(&asc->asc_isc,
                            IEEE80211_S_SCAN, -1);
                        ATH_LOCK(asc);
                }
                err = 0;
        }

        ATH_UNLOCK(asc);

        return (err);
}

static int
ath_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, void *wldp_buf)
{
        ath_t   *asc = arg;
        int     err = 0;

        err = ieee80211_getprop(&asc->asc_isc, pr_name, wldp_pr_num,
            wldp_length, wldp_buf);

        return (err);
}

static void
ath_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    mac_prop_info_handle_t mph)
{
        ath_t   *asc = arg;

        ieee80211_propinfo(&asc->asc_isc, pr_name, wldp_pr_num, mph);
}

static void
ath_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
        ath_t *asc = arg;
        int32_t err;

        err = ieee80211_ioctl(&asc->asc_isc, wq, mp);
        ATH_LOCK(asc);
        if (err == ENETRESET) {
                if (ATH_IS_RUNNING(asc)) {
                        ATH_UNLOCK(asc);
                        (void) ath_m_start(asc);
                        (void) ieee80211_new_state(&asc->asc_isc,
                            IEEE80211_S_SCAN, -1);
                        ATH_LOCK(asc);
                }
        }
        ATH_UNLOCK(asc);
}

static int
ath_m_stat(void *arg, uint_t stat, uint64_t *val)
{
        ath_t *asc = arg;
        ieee80211com_t *ic = (ieee80211com_t *)asc;
        struct ieee80211_node *in = ic->ic_bss;
        struct ieee80211_rateset *rs = &in->in_rates;

        ATH_LOCK(asc);
        switch (stat) {
        case MAC_STAT_IFSPEED:
                *val = (rs->ir_rates[in->in_txrate] & IEEE80211_RATE_VAL) / 2 *
                    1000000ull;
                break;
        case MAC_STAT_NOXMTBUF:
                *val = asc->asc_stats.ast_tx_nobuf +
                    asc->asc_stats.ast_tx_nobufmgt;
                break;
        case MAC_STAT_IERRORS:
                *val = asc->asc_stats.ast_rx_tooshort;
                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 = asc->asc_stats.ast_tx_fifoerr +
                    asc->asc_stats.ast_tx_xretries +
                    asc->asc_stats.ast_tx_discard;
                break;
        case WIFI_STAT_TX_RETRANS:
                *val = asc->asc_stats.ast_tx_xretries;
                break;
        case WIFI_STAT_FCS_ERRORS:
                *val = asc->asc_stats.ast_rx_crcerr;
                break;
        case WIFI_STAT_WEP_ERRORS:
                *val = asc->asc_stats.ast_rx_badcrypt;
                break;
        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:
                ATH_UNLOCK(asc);
                return (ieee80211_stat(ic, stat, val));
        default:
                ATH_UNLOCK(asc);
                return (ENOTSUP);
        }
        ATH_UNLOCK(asc);

        return (0);
}

static int
ath_pci_setup(ath_t *asc)
{
        uint16_t command;

        /*
         * Enable memory mapping and bus mastering
         */
        ASSERT(asc != NULL);
        command = pci_config_get16(asc->asc_cfg_handle, PCI_CONF_COMM);
        command |= PCI_COMM_MAE | PCI_COMM_ME;
        pci_config_put16(asc->asc_cfg_handle, PCI_CONF_COMM, command);
        command = pci_config_get16(asc->asc_cfg_handle, PCI_CONF_COMM);
        if ((command & PCI_COMM_MAE) == 0) {
                ath_problem("ath: ath_pci_setup(): "
                    "failed to enable memory mapping\n");
                return (EIO);
        }
        if ((command & PCI_COMM_ME) == 0) {
                ath_problem("ath: ath_pci_setup(): "
                    "failed to enable bus mastering\n");
                return (EIO);
        }
        ATH_DEBUG((ATH_DBG_INIT, "ath: ath_pci_setup(): "
            "set command reg to 0x%x \n", command));

        return (0);
}

static int
ath_resume(dev_info_t *devinfo)
{
        ath_t *asc;
        int ret = DDI_SUCCESS;

        asc = ddi_get_soft_state(ath_soft_state_p, ddi_get_instance(devinfo));
        if (asc == NULL) {
                ATH_DEBUG((ATH_DBG_SUSPEND, "ath: ath_resume(): "
                    "failed to get soft state\n"));
                return (DDI_FAILURE);
        }

        ATH_LOCK(asc);
        /*
         * Set up config space command register(s). Refuse
         * to resume on failure.
         */
        if (ath_pci_setup(asc) != 0) {
                ATH_DEBUG((ATH_DBG_SUSPEND, "ath: ath_resume(): "
                    "ath_pci_setup() failed\n"));
                ATH_UNLOCK(asc);
                return (DDI_FAILURE);
        }

        if (!asc->asc_invalid)
                ret = ath_start_locked(asc);
        ATH_UNLOCK(asc);

        return (ret);
}

static int
ath_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
{
        ath_t *asc;
        ieee80211com_t *ic;
        struct ath_hal *ah;
        uint8_t csz;
        HAL_STATUS status;
        caddr_t regs;
        uint32_t i, val;
        uint16_t vendor_id, device_id;
        const char *athname;
        int32_t ath_countrycode = CTRY_DEFAULT; /* country code */
        int32_t err, ath_regdomain = 0; /* regulatory domain */
        char strbuf[32];
        int instance;
        wifi_data_t wd = { 0 };
        mac_register_t *macp;

        switch (cmd) {
        case DDI_ATTACH:
                break;

        case DDI_RESUME:
                return (ath_resume(devinfo));

        default:
                return (DDI_FAILURE);
        }

        instance = ddi_get_instance(devinfo);
        if (ddi_soft_state_zalloc(ath_soft_state_p, instance) != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "Unable to alloc softstate\n"));
                return (DDI_FAILURE);
        }

        asc = ddi_get_soft_state(ath_soft_state_p, ddi_get_instance(devinfo));
        ic = (ieee80211com_t *)asc;
        asc->asc_dev = devinfo;

        mutex_init(&asc->asc_genlock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&asc->asc_txbuflock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&asc->asc_rxbuflock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&asc->asc_resched_lock, NULL, MUTEX_DRIVER, NULL);

        err = pci_config_setup(devinfo, &asc->asc_cfg_handle);
        if (err != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "pci_config_setup() failed"));
                goto attach_fail0;
        }

        if (ath_pci_setup(asc) != 0)
                goto attach_fail1;

        /*
         * Cache line size is used to size and align various
         * structures used to communicate with the hardware.
         */
        csz = pci_config_get8(asc->asc_cfg_handle, PCI_CONF_CACHE_LINESZ);
        if (csz == 0) {
                /*
                 * We must have this setup properly for rx buffer
                 * DMA to work so force a reasonable value here if it
                 * comes up zero.
                 */
                csz = ATH_DEF_CACHE_BYTES / sizeof (uint32_t);
                pci_config_put8(asc->asc_cfg_handle, PCI_CONF_CACHE_LINESZ,
                    csz);
        }
        asc->asc_cachelsz = csz << 2;
        vendor_id = pci_config_get16(asc->asc_cfg_handle, PCI_CONF_VENID);
        device_id = pci_config_get16(asc->asc_cfg_handle, PCI_CONF_DEVID);
        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): vendor 0x%x, "
            "device id 0x%x, cache size %d\n", vendor_id, device_id, csz));

        athname = ath_hal_probe(vendor_id, device_id);
        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): athname: %s\n",
            athname ? athname : "Atheros ???"));

        pci_config_put8(asc->asc_cfg_handle, PCI_CONF_LATENCY_TIMER, 0xa8);
        val = pci_config_get32(asc->asc_cfg_handle, 0x40);
        if ((val & 0x0000ff00) != 0)
                pci_config_put32(asc->asc_cfg_handle, 0x40, val & 0xffff00ff);

        err = ddi_regs_map_setup(devinfo, 1,
            &regs, 0, 0, &ath_reg_accattr, &asc->asc_io_handle);
        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
            "regs map1 = %x err=%d\n", regs, err));
        if (err != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "ddi_regs_map_setup() failed"));
                goto attach_fail1;
        }

        ah = ath_hal_attach(device_id, asc, 0, regs, &status);
        if (ah == NULL) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "unable to attach hw: '%s' (HAL status %u)\n",
                    ath_get_hal_status_desc(status), status));
                goto attach_fail2;
        }
        ATH_DEBUG((ATH_DBG_ATTACH, "mac %d.%d phy %d.%d",
            ah->ah_macVersion, ah->ah_macRev,
            ah->ah_phyRev >> 4, ah->ah_phyRev & 0xf));
        ATH_HAL_INTRSET(ah, 0);
        asc->asc_ah = ah;

        if (ah->ah_abi != HAL_ABI_VERSION) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "HAL ABI mismatch detected (0x%x != 0x%x)\n",
                    ah->ah_abi, HAL_ABI_VERSION));
                goto attach_fail3;
        }

        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
            "HAL ABI version 0x%x\n", ah->ah_abi));
        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
            "HAL mac version %d.%d, phy version %d.%d\n",
            ah->ah_macVersion, ah->ah_macRev,
            ah->ah_phyRev >> 4, ah->ah_phyRev & 0xf));
        if (ah->ah_analog5GhzRev)
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "HAL 5ghz radio version %d.%d\n",
                    ah->ah_analog5GhzRev >> 4,
                    ah->ah_analog5GhzRev & 0xf));
        if (ah->ah_analog2GhzRev)
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "HAL 2ghz radio version %d.%d\n",
                    ah->ah_analog2GhzRev >> 4,
                    ah->ah_analog2GhzRev & 0xf));

        /*
         * Check if the MAC has multi-rate retry support.
         * We do this by trying to setup a fake extended
         * descriptor.  MAC's that don't have support will
         * return false w/o doing anything.  MAC's that do
         * support it will return true w/o doing anything.
         */
        asc->asc_mrretry = ATH_HAL_SETUPXTXDESC(ah, NULL, 0, 0, 0, 0, 0, 0);
        ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
            "multi rate retry support=%x\n",
            asc->asc_mrretry));

        /*
         * Get the hardware key cache size.
         */
        asc->asc_keymax = ATH_HAL_KEYCACHESIZE(ah);
        if (asc->asc_keymax > sizeof (asc->asc_keymap) * NBBY) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath_attach:"
                    " Warning, using only %u entries in %u key cache\n",
                    sizeof (asc->asc_keymap) * NBBY, asc->asc_keymax));
                asc->asc_keymax = sizeof (asc->asc_keymap) * NBBY;
        }
        /*
         * Reset the key cache since some parts do not
         * reset the contents on initial power up.
         */
        for (i = 0; i < asc->asc_keymax; i++)
                ATH_HAL_KEYRESET(ah, i);

        ATH_HAL_GETREGDOMAIN(ah, (uint32_t *)&ath_regdomain);
        ATH_HAL_GETCOUNTRYCODE(ah, &ath_countrycode);
        /*
         * Collect the channel list using the default country
         * code and including outdoor channels.  The 802.11 layer
         * is resposible for filtering this list to a set of
         * channels that it considers ok to use.
         */
        asc->asc_have11g = 0;

        /* enable outdoor use, enable extended channels */
        err = ath_getchannels(asc, ath_countrycode, AH_FALSE, AH_TRUE);
        if (err != 0)
                goto attach_fail3;

        /*
         * Setup rate tables for all potential media types.
         */
        ath_rate_setup(asc, IEEE80211_MODE_11A);
        ath_rate_setup(asc, IEEE80211_MODE_11B);
        ath_rate_setup(asc, IEEE80211_MODE_11G);
        ath_rate_setup(asc, IEEE80211_MODE_TURBO_A);

        /* Setup here so ath_rate_update is happy */
        ath_setcurmode(asc, IEEE80211_MODE_11A);

        err = ath_desc_alloc(devinfo, asc);
        if (err != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "failed to allocate descriptors: %d\n", err));
                goto attach_fail3;
        }

        if ((asc->asc_tq = ddi_taskq_create(devinfo, "ath_taskq", 1,
            TASKQ_DEFAULTPRI, 0)) == NULL) {
                goto attach_fail4;
        }
        /* Setup transmit queues in the HAL */
        if (ath_txq_setup(asc))
                goto attach_fail4;

        ATH_HAL_GETMAC(ah, ic->ic_macaddr);

        /*
         * Initialize pointers to device specific functions which
         * will be used by the generic layer.
         */
        /* 11g support is identified when we fetch the channel set */
        if (asc->asc_have11g)
                ic->ic_caps |= IEEE80211_C_SHPREAMBLE |
                    IEEE80211_C_SHSLOT;         /* short slot time */
        /*
         * Query the hal to figure out h/w crypto support.
         */
        if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_WEP))
                ic->ic_caps |= IEEE80211_C_WEP;
        if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_AES_OCB))
                ic->ic_caps |= IEEE80211_C_AES;
        if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_AES_CCM)) {
                ATH_DEBUG((ATH_DBG_ATTACH, "Atheros support H/W CCMP\n"));
                ic->ic_caps |= IEEE80211_C_AES_CCM;
        }
        if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_CKIP))
                ic->ic_caps |= IEEE80211_C_CKIP;
        if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_TKIP)) {
                ATH_DEBUG((ATH_DBG_ATTACH, "Atheros support H/W TKIP\n"));
                ic->ic_caps |= IEEE80211_C_TKIP;
                /*
                 * Check if h/w does the MIC and/or whether the
                 * separate key cache entries are required to
                 * handle both tx+rx MIC keys.
                 */
                if (ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_MIC)) {
                        ATH_DEBUG((ATH_DBG_ATTACH, "Support H/W TKIP MIC\n"));
                        ic->ic_caps |= IEEE80211_C_TKIPMIC;
                }

                /*
                 * If the h/w supports storing tx+rx MIC keys
                 * in one cache slot automatically enable use.
                 */
                if (ATH_HAL_HASTKIPSPLIT(ah) ||
                    !ATH_HAL_SETTKIPSPLIT(ah, AH_FALSE)) {
                        asc->asc_splitmic = 1;
                }
        }
        ic->ic_caps |= IEEE80211_C_WPA; /* Support WPA/WPA2 */

        asc->asc_hasclrkey = ATH_HAL_CIPHERSUPPORTED(ah, HAL_CIPHER_CLR);
        /*
         * Mark key cache slots associated with global keys
         * as in use.  If we knew TKIP was not to be used we
         * could leave the +32, +64, and +32+64 slots free.
         */
        for (i = 0; i < IEEE80211_WEP_NKID; i++) {
                setbit(asc->asc_keymap, i);
                setbit(asc->asc_keymap, i+64);
                if (asc->asc_splitmic) {
                        setbit(asc->asc_keymap, i+32);
                        setbit(asc->asc_keymap, i+32+64);
                }
        }

        ic->ic_phytype = IEEE80211_T_OFDM;
        ic->ic_opmode = IEEE80211_M_STA;
        ic->ic_state = IEEE80211_S_INIT;
        ic->ic_maxrssi = ATH_MAX_RSSI;
        ic->ic_set_shortslot = ath_set_shortslot;
        ic->ic_xmit = ath_xmit;
        ieee80211_attach(ic);

        /* different instance has different WPA door */
        (void) snprintf(ic->ic_wpadoor, MAX_IEEE80211STR, "%s_%s%d", WPA_DOOR,
            ddi_driver_name(devinfo),
            ddi_get_instance(devinfo));

        /* Override 80211 default routines */
        ic->ic_reset = ath_reset;
        asc->asc_newstate = ic->ic_newstate;
        ic->ic_newstate = ath_newstate;
        ic->ic_watchdog = ath_watchdog;
        ic->ic_node_alloc = ath_node_alloc;
        ic->ic_node_free = ath_node_free;
        ic->ic_crypto.cs_key_alloc = ath_key_alloc;
        ic->ic_crypto.cs_key_delete = ath_key_delete;
        ic->ic_crypto.cs_key_set = ath_key_set;
        ieee80211_media_init(ic);
        /*
         * initialize default tx key
         */
        ic->ic_def_txkey = 0;

        asc->asc_rx_pend = 0;
        ATH_HAL_INTRSET(ah, 0);
        err = ddi_add_softintr(devinfo, DDI_SOFTINT_LOW,
            &asc->asc_softint_id, NULL, 0, ath_softint_handler, (caddr_t)asc);
        if (err != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "ddi_add_softintr() failed\n"));
                goto attach_fail5;
        }

        if (ddi_get_iblock_cookie(devinfo, 0, &asc->asc_iblock)
            != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "Can not get iblock cookie for INT\n"));
                goto attach_fail6;
        }

        if (ddi_add_intr(devinfo, 0, NULL, NULL, ath_intr,
            (caddr_t)asc) != DDI_SUCCESS) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "Can not set intr for ATH driver\n"));
                goto attach_fail6;
        }

        /*
         * Provide initial settings for the WiFi plugin; whenever this
         * information changes, we need to call mac_plugindata_update()
         */
        wd.wd_opmode = ic->ic_opmode;
        wd.wd_secalloc = WIFI_SEC_NONE;
        IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_bss->in_bssid);

        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "MAC version mismatch\n"));
                goto attach_fail7;
        }

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

        err = mac_register(macp, &ic->ic_mach);
        mac_free(macp);
        if (err != 0) {
                ATH_DEBUG((ATH_DBG_ATTACH, "ath: ath_attach(): "
                    "mac_register err %x\n", err));
                goto attach_fail7;
        }

        /* Create minor node of type DDI_NT_NET_WIFI */
        (void) snprintf(strbuf, sizeof (strbuf), "%s%d",
            ATH_NODENAME, instance);
        err = ddi_create_minor_node(devinfo, strbuf, S_IFCHR,
            instance + 1, DDI_NT_NET_WIFI, 0);
        if (err != DDI_SUCCESS)
                ATH_DEBUG((ATH_DBG_ATTACH, "WARN: ath: ath_attach(): "
                    "Create minor node failed - %d\n", err));

        mac_link_update(ic->ic_mach, LINK_STATE_DOWN);
        asc->asc_invalid = 1;
        asc->asc_isrunning = 0;
        asc->asc_promisc = B_FALSE;
        bzero(asc->asc_mcast_refs, sizeof (asc->asc_mcast_refs));
        bzero(asc->asc_mcast_hash, sizeof (asc->asc_mcast_hash));
        return (DDI_SUCCESS);
attach_fail7:
        ddi_remove_intr(devinfo, 0, asc->asc_iblock);
attach_fail6:
        ddi_remove_softintr(asc->asc_softint_id);
attach_fail5:
        (void) ieee80211_detach(ic);
attach_fail4:
        ath_desc_free(asc);
        if (asc->asc_tq)
                ddi_taskq_destroy(asc->asc_tq);
attach_fail3:
        ah->ah_detach(asc->asc_ah);
attach_fail2:
        ddi_regs_map_free(&asc->asc_io_handle);
attach_fail1:
        pci_config_teardown(&asc->asc_cfg_handle);
attach_fail0:
        asc->asc_invalid = 1;
        mutex_destroy(&asc->asc_txbuflock);
        for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
                if (ATH_TXQ_SETUP(asc, i)) {
                        struct ath_txq *txq = &asc->asc_txq[i];
                        mutex_destroy(&txq->axq_lock);
                }
        }
        mutex_destroy(&asc->asc_rxbuflock);
        mutex_destroy(&asc->asc_genlock);
        mutex_destroy(&asc->asc_resched_lock);
        ddi_soft_state_free(ath_soft_state_p, instance);

        return (DDI_FAILURE);
}

/*
 * Suspend transmit/receive for powerdown
 */
static int
ath_suspend(ath_t *asc)
{
        ATH_LOCK(asc);
        ath_stop_locked(asc);
        ATH_UNLOCK(asc);
        ATH_DEBUG((ATH_DBG_SUSPEND, "ath: suspended.\n"));

        return (DDI_SUCCESS);
}

static int32_t
ath_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd)
{
        ath_t *asc;

        asc = ddi_get_soft_state(ath_soft_state_p, ddi_get_instance(devinfo));
        ASSERT(asc != NULL);

        switch (cmd) {
        case DDI_DETACH:
                break;

        case DDI_SUSPEND:
                return (ath_suspend(asc));

        default:
                return (DDI_FAILURE);
        }

        if (mac_disable(asc->asc_isc.ic_mach) != 0)
                return (DDI_FAILURE);

        ath_stop_scantimer(asc);

        /* disable interrupts */
        ATH_HAL_INTRSET(asc->asc_ah, 0);

        /*
         * Unregister from the MAC layer subsystem
         */
        (void) mac_unregister(asc->asc_isc.ic_mach);

        /* free intterrupt resources */
        ddi_remove_intr(devinfo, 0, asc->asc_iblock);
        ddi_remove_softintr(asc->asc_softint_id);

        /*
         * NB: the order of these is important:
         * o call the 802.11 layer before detaching the hal to
         *   insure callbacks into the driver to delete global
         *   key cache entries can be handled
         * o reclaim the tx queue data structures after calling
         *   the 802.11 layer as we'll get called back to reclaim
         *   node state and potentially want to use them
         * o to cleanup the tx queues the hal is called, so detach
         *   it last
         */
        ieee80211_detach(&asc->asc_isc);
        ath_desc_free(asc);
        ddi_taskq_destroy(asc->asc_tq);
        ath_txq_cleanup(asc);
        asc->asc_ah->ah_detach(asc->asc_ah);

        /* free io handle */
        ddi_regs_map_free(&asc->asc_io_handle);
        pci_config_teardown(&asc->asc_cfg_handle);

        /* destroy locks */
        mutex_destroy(&asc->asc_rxbuflock);
        mutex_destroy(&asc->asc_genlock);
        mutex_destroy(&asc->asc_resched_lock);

        ddi_remove_minor_node(devinfo, NULL);
        ddi_soft_state_free(ath_soft_state_p, ddi_get_instance(devinfo));

        return (DDI_SUCCESS);
}

/*
 * quiesce(9E) entry point.
 *
 * This function is called when the system is single-threaded at high
 * PIL with preemption disabled. Therefore, this function must not be
 * blocked.
 *
 * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
 * DDI_FAILURE indicates an error condition and should almost never happen.
 */
static int32_t
ath_quiesce(dev_info_t *devinfo)
{
        ath_t           *asc;
        struct ath_hal  *ah;
        int             i;

        asc = ddi_get_soft_state(ath_soft_state_p, ddi_get_instance(devinfo));

        if (asc == NULL || (ah = asc->asc_ah) == NULL)
                return (DDI_FAILURE);

        /*
         * Disable interrupts
         */
        ATH_HAL_INTRSET(ah, 0);

        /*
         * Disable TX HW
         */
        for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
                if (ATH_TXQ_SETUP(asc, i)) {
                        ATH_HAL_STOPTXDMA(ah, asc->asc_txq[i].axq_qnum);
                }
        }

        /*
         * Disable RX HW
         */
        ATH_HAL_STOPPCURECV(ah);
        ATH_HAL_SETRXFILTER(ah, 0);
        ATH_HAL_STOPDMARECV(ah);
        drv_usecwait(3000);

        /*
         * Power down HW
         */
        ATH_HAL_PHYDISABLE(ah);

        return (DDI_SUCCESS);
}

DDI_DEFINE_STREAM_OPS(ath_dev_ops, nulldev, nulldev, ath_attach, ath_detach,
    nodev, NULL, D_MP, NULL, ath_quiesce);

static struct modldrv ath_modldrv = {
        &mod_driverops,         /* Type of module.  This one is a driver */
        "ath driver 1.4/HAL 0.10.5.6",          /* short description */
        &ath_dev_ops            /* driver specific ops */
};

static struct modlinkage modlinkage = {
        MODREV_1, (void *)&ath_modldrv, NULL
};


int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int
_init(void)
{
        int status;

        status = ddi_soft_state_init(&ath_soft_state_p, sizeof (ath_t), 1);
        if (status != 0)
                return (status);

        mutex_init(&ath_loglock, NULL, MUTEX_DRIVER, NULL);
        ath_halfix_init();
        mac_init_ops(&ath_dev_ops, "ath");
        status = mod_install(&modlinkage);
        if (status != 0) {
                mac_fini_ops(&ath_dev_ops);
                ath_halfix_finit();
                mutex_destroy(&ath_loglock);
                ddi_soft_state_fini(&ath_soft_state_p);
        }

        return (status);
}

int
_fini(void)
{
        int status;

        status = mod_remove(&modlinkage);
        if (status == 0) {
                mac_fini_ops(&ath_dev_ops);
                ath_halfix_finit();
                mutex_destroy(&ath_loglock);
                ddi_soft_state_fini(&ath_soft_state_p);
        }
        return (status);
}