view usr/src/uts/common/io/arn/arn_main.c @ 9999:d5e89571de4e

6814300 Need support for Atheros AR9280 and AR9281 6834736 Need support for new wireless chipset AR9285
author lin wang - Sun Microsystems - Beijing China <Wang.Lin@Sun.COM>
date Tue, 30 Jun 2009 15:05:56 +0800
parents
children e0a7b374d710
line wrap: on
line source

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

/*
 * Copyright (c) 2008 Atheros Communications Inc.
 *
 * Permission to use, copy, modify, and/or 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.
 */

#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 "arn_ath9k.h"
#include "arn_core.h"
#include "arn_reg.h"
#include "arn_hw.h"

#define	ARN_MAX_RSSI	63	/* max rssi */

/*
 * PIO access attributes for registers
 */
static ddi_device_acc_attr_t arn_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 arn_desc_accattr = {
	DDI_DEVICE_ATTR_V0,
	DDI_STRUCTURE_LE_ACC,
	DDI_STRICTORDER_ACC
};

/*
 * Describes the chip's DMA engine
 */
static ddi_dma_attr_t arn_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 arn_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 */
};

#define	ATH_DEF_CACHE_BYTES	32 /* default cache line size */

static kmutex_t arn_loglock;
static void *arn_soft_state_p = NULL;
/* scan interval, ms? */
static int arn_dwelltime = 200; /* 150 */

static int	arn_m_stat(void *,  uint_t, uint64_t *);
static int	arn_m_start(void *);
static void	arn_m_stop(void *);
static int	arn_m_promisc(void *, boolean_t);
static int	arn_m_multicst(void *, boolean_t, const uint8_t *);
static int	arn_m_unicst(void *, const uint8_t *);
static mblk_t	*arn_m_tx(void *, mblk_t *);
static void	arn_m_ioctl(void *, queue_t *, mblk_t *);
static int	arn_m_setprop(void *, const char *, mac_prop_id_t,
    uint_t, const void *);
static int	arn_m_getprop(void *, const char *, mac_prop_id_t,
    uint_t, uint_t, void *, uint_t *);

/* MAC Callcack Functions */
static mac_callbacks_t arn_m_callbacks = {
	MC_IOCTL | MC_SETPROP | MC_GETPROP,
	arn_m_stat,
	arn_m_start,
	arn_m_stop,
	arn_m_promisc,
	arn_m_multicst,
	arn_m_unicst,
	arn_m_tx,
	arn_m_ioctl,
	NULL,
	NULL,
	NULL,
	arn_m_setprop,
	arn_m_getprop
};

/*
 * ARN_DBG_HW
 * ARN_DBG_REG_IO
 * ARN_DBG_QUEUE
 * ARN_DBG_EEPROM
 * ARN_DBG_XMIT
 * ARN_DBG_RECV
 * ARN_DBG_CALIBRATE
 * ARN_DBG_CHANNEL
 * ARN_DBG_INTERRUPT
 * ARN_DBG_REGULATORY
 * ARN_DBG_ANI
 * ARN_DBG_POWER_MGMT
 * ARN_DBG_KEYCACHE
 * ARN_DBG_BEACON
 * ARN_DBG_RATE
 * ARN_DBG_INIT
 * ARN_DBG_ATTACH
 * ARN_DBG_DEATCH
 * ARN_DBG_AGGR
 * ARN_DBG_RESET
 * ARN_DBG_FATAL
 * ARN_DBG_ANY
 * ARN_DBG_ALL
 */
uint32_t arn_dbg_mask = 0;

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

	mutex_enter(&arn_loglock);

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

	mutex_exit(&arn_loglock);
}

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

	mutex_enter(&arn_loglock);

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

	mutex_exit(&arn_loglock);
}

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

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

/*
 * Read and write, they both share the same lock. We do this to serialize
 * reads and writes on Atheros 802.11n PCI devices only. This is required
 * as the FIFO on these devices can only accept sanely 2 requests. After
 * that the device goes bananas. Serializing the reads/writes prevents this
 * from happening.
 */
void
arn_iowrite32(struct ath_hal *ah, uint32_t reg_offset, uint32_t val)
{
	struct arn_softc *sc = ah->ah_sc;
	if (ah->ah_config.serialize_regmode == SER_REG_MODE_ON) {
		mutex_enter(&sc->sc_serial_rw);
		ddi_put32(sc->sc_io_handle,
		    (uint32_t *)((uintptr_t)(sc->mem) + (reg_offset)), val);
		mutex_exit(&sc->sc_serial_rw);
	} else {
		ddi_put32(sc->sc_io_handle,
		    (uint32_t *)((uintptr_t)(sc->mem) + (reg_offset)), val);
	}
}

unsigned int
arn_ioread32(struct ath_hal *ah, uint32_t reg_offset)
{
	uint32_t val;
	struct arn_softc *sc = ah->ah_sc;
	if (ah->ah_config.serialize_regmode == SER_REG_MODE_ON) {
		mutex_enter(&sc->sc_serial_rw);
		val = ddi_get32(sc->sc_io_handle,
		    (uint32_t *)((uintptr_t)(sc->mem) + (reg_offset)));
		mutex_exit(&sc->sc_serial_rw);
	} else {
		val = ddi_get32(sc->sc_io_handle,
		    (uint32_t *)((uintptr_t)(sc->mem) + (reg_offset)));
	}

	return (val);
}

void
arn_rx_buf_link(struct arn_softc *sc, 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;
	/* virtual addr of the beginning of the buffer. */
	ds->ds_vdata = bf->bf_dma.mem_va;

	/*
	 * setup rx descriptors. The bf_dma.alength here tells the H/W
	 * how much data it can DMA to us and that we are prepared
	 * to process
	 */
	(void) ath9k_hw_setuprxdesc(sc->sc_ah, ds,
	    bf->bf_dma.alength, /* buffer size */
	    0);

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

/*
 * Allocate an area of memory and a DMA handle for accessing it
 */
static int
arn_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
arn_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. or beacon buffer list. Allocate DMA memory for
 * each buffer.
 */
static int
arn_buflist_setup(dev_info_t *devinfo, struct arn_softc *sc, 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 = sc->sc_desc_dma.cookie.dmac_address +
		    ((uintptr_t)ds - (uintptr_t)sc->sc_desc);
		list_insert_tail(bflist, bf);

		/* alloc DMA memory */
		err = arn_alloc_dma_mem(devinfo, &arn_dma_attr,
		    sc->sc_dmabuf_size, &arn_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 or beacon buffer list. Free DMA memory.
 */
static void
arn_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 */
		arn_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
arn_desc_free(struct arn_softc *sc)
{
	arn_buflist_cleanup(&sc->sc_txbuf_list);
	arn_buflist_cleanup(&sc->sc_rxbuf_list);
#ifdef ARN_IBSS
	arn_buflist_cleanup(&sc->sc_bcbuf_list);
#endif

	/* Free descriptor DMA buffer */
	arn_free_dma_mem(&sc->sc_desc_dma);

	kmem_free((void *)sc->sc_vbufptr, sc->sc_vbuflen);
	sc->sc_vbufptr = NULL;
}

static int
arn_desc_alloc(dev_info_t *devinfo, struct arn_softc *sc)
{
	int err;
	size_t size;
	struct ath_desc *ds;
	struct ath_buf *bf;

#ifdef ARN_IBSS
	size = sizeof (struct ath_desc) * (ATH_TXBUF + ATH_RXBUF + ATH_BCBUF);
#else
	size = sizeof (struct ath_desc) * (ATH_TXBUF + ATH_RXBUF);
#endif

	err = arn_alloc_dma_mem(devinfo, &arn_desc_dma_attr, size,
	    &arn_desc_accattr, DDI_DMA_CONSISTENT,
	    DDI_DMA_RDWR | DDI_DMA_CONSISTENT, &sc->sc_desc_dma);

	/* virtual address of the first descriptor */
	sc->sc_desc = (struct ath_desc *)sc->sc_desc_dma.mem_va;

	ds = sc->sc_desc;
	ARN_DBG((ARN_DBG_INIT, "arn: arn_desc_alloc(): DMA map: "
	    "%p (%d) -> %p\n",
	    sc->sc_desc, sc->sc_desc_dma.alength,
	    sc->sc_desc_dma.cookie.dmac_address));

	/* allocate data structures to describe TX/RX DMA buffers */
#ifdef ARN_IBSS
	sc->sc_vbuflen = sizeof (struct ath_buf) * (ATH_TXBUF + ATH_RXBUF +
	    ATH_BCBUF);
#else
	sc->sc_vbuflen = sizeof (struct ath_buf) * (ATH_TXBUF + ATH_RXBUF);
#endif
	bf = (struct ath_buf *)kmem_zalloc(sc->sc_vbuflen, KM_SLEEP);
	sc->sc_vbufptr = bf;

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

	/* create RX buffer list */
	err = arn_buflist_setup(devinfo, sc, &sc->sc_rxbuf_list, &bf, &ds,
	    ATH_RXBUF, DDI_DMA_READ | DDI_DMA_STREAMING);
	if (err != DDI_SUCCESS) {
		arn_desc_free(sc);
		return (err);
	}

	/* create TX buffer list */
	err = arn_buflist_setup(devinfo, sc, &sc->sc_txbuf_list, &bf, &ds,
	    ATH_TXBUF, DDI_DMA_STREAMING);
	if (err != DDI_SUCCESS) {
		arn_desc_free(sc);
		return (err);
	}

	/* create beacon buffer list */
#ifdef ARN_IBSS
	err = arn_buflist_setup(devinfo, sc, &sc->sc_bcbuf_list, &bf, &ds,
	    ATH_BCBUF, DDI_DMA_STREAMING);
	if (err != DDI_SUCCESS) {
		arn_desc_free(sc);
		return (err);
	}
#endif

	return (DDI_SUCCESS);
}

static struct ath_rate_table *
/* LINTED E_STATIC_UNUSED */
arn_get_ratetable(struct arn_softc *sc, uint32_t mode)
{
	struct ath_rate_table *rate_table = NULL;

	switch (mode) {
	case IEEE80211_MODE_11A:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11A];
		break;
	case IEEE80211_MODE_11B:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11B];
		break;
	case IEEE80211_MODE_11G:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11G];
		break;
#ifdef ARB_11N
	case IEEE80211_MODE_11NA_HT20:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT20];
		break;
	case IEEE80211_MODE_11NG_HT20:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT20];
		break;
	case IEEE80211_MODE_11NA_HT40PLUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT40PLUS];
		break;
	case IEEE80211_MODE_11NA_HT40MINUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT40MINUS];
		break;
	case IEEE80211_MODE_11NG_HT40PLUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT40PLUS];
		break;
	case IEEE80211_MODE_11NG_HT40MINUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT40MINUS];
		break;
#endif
	default:
		ARN_DBG((ARN_DBG_FATAL, "arn: arn_get_ratetable(): "
		    "invalid mode %u\n", mode));
		return (NULL);
	}

	return (rate_table);

}

static void
arn_setcurmode(struct arn_softc *sc, enum wireless_mode mode)
{
	struct ath_rate_table *rt;
	int i;

	for (i = 0; i < sizeof (sc->asc_rixmap); i++)
		sc->asc_rixmap[i] = 0xff;

	rt = sc->hw_rate_table[mode];
	ASSERT(rt != NULL);

	for (i = 0; i < rt->rate_cnt; i++)
		sc->asc_rixmap[rt->info[i].dot11rate &
		    IEEE80211_RATE_VAL] = (uint8_t)i; /* LINT */

	sc->sc_currates = rt;
	sc->sc_curmode = mode;

	/*
	 * All protection frames are transmited at 2Mb/s for
	 * 11g, otherwise at 1Mb/s.
	 * XXX select protection rate index from rate table.
	 */
	sc->sc_protrix = (mode == ATH9K_MODE_11G ? 1 : 0);
}

static enum wireless_mode
arn_chan2mode(struct ath9k_channel *chan)
{
	if (chan->chanmode == CHANNEL_A)
		return (ATH9K_MODE_11A);
	else if (chan->chanmode == CHANNEL_G)
		return (ATH9K_MODE_11G);
	else if (chan->chanmode == CHANNEL_B)
		return (ATH9K_MODE_11B);
	else if (chan->chanmode == CHANNEL_A_HT20)
		return (ATH9K_MODE_11NA_HT20);
	else if (chan->chanmode == CHANNEL_G_HT20)
		return (ATH9K_MODE_11NG_HT20);
	else if (chan->chanmode == CHANNEL_A_HT40PLUS)
		return (ATH9K_MODE_11NA_HT40PLUS);
	else if (chan->chanmode == CHANNEL_A_HT40MINUS)
		return (ATH9K_MODE_11NA_HT40MINUS);
	else if (chan->chanmode == CHANNEL_G_HT40PLUS)
		return (ATH9K_MODE_11NG_HT40PLUS);
	else if (chan->chanmode == CHANNEL_G_HT40MINUS)
		return (ATH9K_MODE_11NG_HT40MINUS);

	return (ATH9K_MODE_11B);
}

static void
arn_update_txpow(struct arn_softc *sc)
{
	struct ath_hal 	*ah = sc->sc_ah;
	uint32_t txpow;

	if (sc->sc_curtxpow != sc->sc_config.txpowlimit) {
		(void) ath9k_hw_set_txpowerlimit(ah, sc->sc_config.txpowlimit);
		/* read back in case value is clamped */
		(void) ath9k_hw_getcapability(ah, ATH9K_CAP_TXPOW, 1, &txpow);
		sc->sc_curtxpow = (uint32_t)txpow;
	}
}

static void
arn_setup_rates(struct arn_softc *sc, uint32_t mode)
{
	int i, maxrates;
	struct ath_rate_table *rate_table = NULL;
	struct ieee80211_rateset *rateset;
	ieee80211com_t *ic = (ieee80211com_t *)sc;

	/* rate_table = arn_get_ratetable(sc, mode); */
	switch (mode) {
	case IEEE80211_MODE_11A:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11A];
		break;
	case IEEE80211_MODE_11B:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11B];
		break;
	case IEEE80211_MODE_11G:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11G];
		break;
#ifdef ARN_11N
	case IEEE80211_MODE_11NA_HT20:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT20];
		break;
	case IEEE80211_MODE_11NG_HT20:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT20];
		break;
	case IEEE80211_MODE_11NA_HT40PLUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT40PLUS];
		break;
	case IEEE80211_MODE_11NA_HT40MINUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NA_HT40MINUS];
		break;
	case IEEE80211_MODE_11NG_HT40PLUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT40PLUS];
		break;
	case IEEE80211_MODE_11NG_HT40MINUS:
		rate_table = sc->hw_rate_table[ATH9K_MODE_11NG_HT40MINUS];
		break;
#endif
	default:
		ARN_DBG((ARN_DBG_RATE, "arn: arn_get_ratetable(): "
		    "invalid mode %u\n", mode));
		break;
	}
	if (rate_table == NULL)
		return;
	if (rate_table->rate_cnt > ATH_RATE_MAX) {
		ARN_DBG((ARN_DBG_RATE, "arn: arn_rate_setup(): "
		    "rate table too small (%u > %u)\n",
		    rate_table->rate_cnt, IEEE80211_RATE_MAXSIZE));
		maxrates = ATH_RATE_MAX;
	} else
		maxrates = rate_table->rate_cnt;

	ARN_DBG((ARN_DBG_RATE, "arn: arn_rate_setup(): "
	    "maxrates is %d\n", maxrates));

	rateset = &ic->ic_sup_rates[mode];
	for (i = 0; i < maxrates; i++) {
		rateset->ir_rates[i] = rate_table->info[i].dot11rate;
		ARN_DBG((ARN_DBG_RATE, "arn: arn_rate_setup(): "
		    "%d\n", rate_table->info[i].dot11rate));
	}
	rateset->ir_nrates = (uint8_t)maxrates; /* ??? */
}

static int
arn_setup_channels(struct arn_softc *sc)
{
	struct ath_hal *ah = sc->sc_ah;
	ieee80211com_t *ic = (ieee80211com_t *)sc;
	int nchan, i, index;
	uint8_t regclassids[ATH_REGCLASSIDS_MAX];
	uint32_t nregclass = 0;
	struct ath9k_channel *c;

	/* Fill in ah->ah_channels */
	if (!ath9k_regd_init_channels(ah, ATH_CHAN_MAX, (uint32_t *)&nchan,
	    regclassids, ATH_REGCLASSIDS_MAX, &nregclass, CTRY_DEFAULT,
	    B_FALSE, 1)) {
		uint32_t rd = ah->ah_currentRD;
		ARN_DBG((ARN_DBG_CHANNEL, "arn: arn_setup_channels(): "
		    "unable to collect channel list; "
		    "regdomain likely %u country code %u\n",
		    rd, CTRY_DEFAULT));
		return (EINVAL);
	}

	ARN_DBG((ARN_DBG_CHANNEL, "arn: arn_setup_channels(): "
	    "number of channel is %d\n", nchan));

	for (i = 0; i < nchan; i++) {
		c = &ah->ah_channels[i];
		uint16_t flags;
		index = ath9k_hw_mhz2ieee(ah, c->channel, c->channelFlags);

		if (index > IEEE80211_CHAN_MAX) {
			ARN_DBG((ARN_DBG_CHANNEL,
			    "arn: arn_setup_channels(): "
			    "bad hal channel %d (%u/%x) ignored\n",
			    index, c->channel, c->channelFlags));
			continue;
		}
		/* NB: flags are known to be compatible */
		if (index < 0) {
			/*
			 * can't handle frequency <2400MHz (negative
			 * channels) right now
			 */
			ARN_DBG((ARN_DBG_CHANNEL,
			    "arn: arn_setup_channels(): "
			    "hal channel %d (%u/%x) "
			    "cannot be handled, ignored\n",
			    index, c->channel, c->channelFlags));
			continue;
		}

		/*
		 * Calculate net80211 flags; most are compatible
		 * but some need massaging.  Note the static turbo
		 * conversion can be removed once net80211 is updated
		 * to understand static vs. dynamic turbo.
		 */

		flags = c->channelFlags & (CHANNEL_ALL | CHANNEL_PASSIVE);

		if (ic->ic_sup_channels[index].ich_freq == 0) {
			ic->ic_sup_channels[index].ich_freq = c->channel;
			ic->ic_sup_channels[index].ich_flags = flags;
		} else {
			/* channels overlap; e.g. 11g and 11b */
			ic->ic_sup_channels[index].ich_flags |= flags;
		}
		if ((c->channelFlags & CHANNEL_G) == CHANNEL_G) {
			sc->sc_have11g = 1;
			ic->ic_caps |= IEEE80211_C_SHPREAMBLE |
			    IEEE80211_C_SHSLOT;	/* short slot time */
		}
	}

	return (0);
}

uint32_t
arn_chan2flags(ieee80211com_t *isc, struct ieee80211_channel *chan)
{
	static const uint32_t modeflags[] = {
	    0,				/* IEEE80211_MODE_AUTO */
	    CHANNEL_A,			/* IEEE80211_MODE_11A */
	    CHANNEL_B,			/* IEEE80211_MODE_11B */
	    CHANNEL_G,		/* IEEE80211_MODE_11G */
	    0,				/*  */
	    0,		/*  */
	    0		/*  */
	};
	return (modeflags[ieee80211_chan2mode(isc, chan)]);
}

/*
 * Update internal state after a channel change.
 */
void
arn_chan_change(struct arn_softc *sc, struct ieee80211_channel *chan)
{
	struct ieee80211com *ic = &sc->sc_isc;
	enum ieee80211_phymode mode;
	enum wireless_mode wlmode;

	/*
	 * Change channels and update the h/w rate map
	 * if we're switching; e.g. 11a to 11b/g.
	 */
	mode = ieee80211_chan2mode(ic, chan);
	switch (mode) {
	case IEEE80211_MODE_11A:
		wlmode = ATH9K_MODE_11A;
		break;
	case IEEE80211_MODE_11B:
		wlmode = ATH9K_MODE_11B;
		break;
	case IEEE80211_MODE_11G:
		wlmode = ATH9K_MODE_11B;
		break;
	default:
		break;
	}
	if (wlmode != sc->sc_curmode)
		arn_setcurmode(sc, wlmode);

}

/*
 * Set/change channels.  If the channel is really being changed, it's done
 * by reseting the chip.  To accomplish this we must first cleanup any pending
 * DMA, then restart stuff.
 */
static int
arn_set_channel(struct arn_softc *sc, struct ath9k_channel *hchan)
{
	struct ath_hal *ah = sc->sc_ah;
	ieee80211com_t *ic = &sc->sc_isc;
	boolean_t fastcc = B_TRUE;
	boolean_t  stopped;
	struct ieee80211_channel chan;
	enum wireless_mode curmode;

	if (sc->sc_flags & SC_OP_INVALID)
		return (EIO);

	if (hchan->channel != sc->sc_ah->ah_curchan->channel ||
	    hchan->channelFlags != sc->sc_ah->ah_curchan->channelFlags ||
	    (sc->sc_flags & SC_OP_CHAINMASK_UPDATE) ||
	    (sc->sc_flags & SC_OP_FULL_RESET)) {
		int status;

		/*
		 * This is only performed if the channel settings have
		 * actually changed.
		 *
		 * To switch channels clear any pending DMA operations;
		 * wait long enough for the RX fifo to drain, reset the
		 * hardware at the new frequency, and then re-enable
		 * the relevant bits of the h/w.
		 */
		(void) ath9k_hw_set_interrupts(ah, 0);	/* disable interrupts */
		arn_draintxq(sc, B_FALSE);	/* clear pending tx frames */
		stopped = arn_stoprecv(sc);	/* turn off frame recv */

		/*
		 * XXX: do not flush receive queue here. We don't want
		 * to flush data frames already in queue because of
		 * changing channel.
		 */

		if (!stopped || (sc->sc_flags & SC_OP_FULL_RESET))
			fastcc = B_FALSE;

		ARN_DBG((ARN_DBG_CHANNEL, "arn: arn_set_channel(): "
		    "(%u MHz) -> (%u MHz), cflags:%x, chanwidth: %d\n",
		    sc->sc_ah->ah_curchan->channel,
		    hchan->channel, hchan->channelFlags, sc->tx_chan_width));

		if (!ath9k_hw_reset(ah, hchan, sc->tx_chan_width,
		    sc->sc_tx_chainmask, sc->sc_rx_chainmask,
		    sc->sc_ht_extprotspacing, fastcc, &status)) {
			ARN_DBG((ARN_DBG_FATAL, "arn: arn_set_channel(): "
			    "unable to reset channel %u (%uMhz) "
			    "flags 0x%x hal status %u\n",
			    ath9k_hw_mhz2ieee(ah, hchan->channel,
			    hchan->channelFlags),
			    hchan->channel, hchan->channelFlags, status));
			return (EIO);
		}

		sc->sc_curchan = *hchan;

		sc->sc_flags &= ~SC_OP_CHAINMASK_UPDATE;
		sc->sc_flags &= ~SC_OP_FULL_RESET;

		if (arn_startrecv(sc) != 0) {
			arn_problem("arn: arn_set_channel(): "
			    "unable to restart recv logic\n");
			return (EIO);
		}

		chan.ich_freq = hchan->channel;
		chan.ich_flags = hchan->channelFlags;
		ic->ic_ibss_chan = &chan;

		/*
		 * Change channels and update the h/w rate map
		 * if we're switching; e.g. 11a to 11b/g.
		 */
		curmode = arn_chan2mode(hchan);
		if (curmode != sc->sc_curmode)
			arn_setcurmode(sc, arn_chan2mode(hchan));

		arn_update_txpow(sc);

		(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);
	}

	return (0);
}

/*
 *  This routine performs the periodic noise floor calibration function
 *  that is used to adjust and optimize the chip performance.  This
 *  takes environmental changes (location, temperature) into account.
 *  When the task is complete, it reschedules itself depending on the
 *  appropriate interval that was calculated.
 */
static void
arn_ani_calibrate(void *arg)

{
	ieee80211com_t *ic = (ieee80211com_t *)arg;
	struct arn_softc *sc = (struct arn_softc *)ic;
	struct ath_hal *ah = sc->sc_ah;
	boolean_t longcal = B_FALSE;
	boolean_t shortcal = B_FALSE;
	boolean_t aniflag = B_FALSE;
	unsigned int timestamp = drv_hztousec(ddi_get_lbolt())/1000;
	uint32_t cal_interval;

	/*
	 * don't calibrate when we're scanning.
	 * we are most likely not on our home channel.
	 */
	if (ic->ic_state != IEEE80211_S_RUN)
		goto settimer;

	/* Long calibration runs independently of short calibration. */
	if ((timestamp - sc->sc_ani.sc_longcal_timer) >= ATH_LONG_CALINTERVAL) {
		longcal = B_TRUE;
		ARN_DBG((ARN_DBG_CALIBRATE, "arn: "
		    "%s: longcal @%lu\n", __func__, drv_hztousec));
		sc->sc_ani.sc_longcal_timer = timestamp;
	}

	/* Short calibration applies only while sc_caldone is FALSE */
	if (!sc->sc_ani.sc_caldone) {
		if ((timestamp - sc->sc_ani.sc_shortcal_timer) >=
		    ATH_SHORT_CALINTERVAL) {
			shortcal = B_TRUE;
			ARN_DBG((ARN_DBG_CALIBRATE, "arn: "
			    "%s: shortcal @%lu\n",
			    __func__, drv_hztousec));
			sc->sc_ani.sc_shortcal_timer = timestamp;
			sc->sc_ani.sc_resetcal_timer = timestamp;
		}
	} else {
		if ((timestamp - sc->sc_ani.sc_resetcal_timer) >=
		    ATH_RESTART_CALINTERVAL) {
			ath9k_hw_reset_calvalid(ah, ah->ah_curchan,
						&sc->sc_ani.sc_caldone);
			if (sc->sc_ani.sc_caldone)
				sc->sc_ani.sc_resetcal_timer = timestamp;
		}
	}

	/* Verify whether we must check ANI */
	if ((timestamp - sc->sc_ani.sc_checkani_timer) >=
	    ATH_ANI_POLLINTERVAL) {
		aniflag = B_TRUE;
		sc->sc_ani.sc_checkani_timer = timestamp;
	}

	/* Skip all processing if there's nothing to do. */
	if (longcal || shortcal || aniflag) {
		/* Call ANI routine if necessary */
		if (aniflag)
			ath9k_hw_ani_monitor(ah, &sc->sc_halstats,
			    ah->ah_curchan);

		/* Perform calibration if necessary */
		if (longcal || shortcal) {
			boolean_t iscaldone = B_FALSE;

			if (ath9k_hw_calibrate(ah, ah->ah_curchan,
			    sc->sc_rx_chainmask, longcal, &iscaldone)) {
				if (longcal)
					sc->sc_ani.sc_noise_floor =
					    ath9k_hw_getchan_noise(ah,
					    ah->ah_curchan);

				ARN_DBG((ARN_DBG_CALIBRATE, "arn: "
				    "%s: calibrate chan %u/%x nf: %d\n",
				    __func__,
				    ah->ah_curchan->channel,
				    ah->ah_curchan->channelFlags,
				    sc->sc_ani.sc_noise_floor));
			} else {
				ARN_DBG((ARN_DBG_CALIBRATE, "arn: "
				    "%s: calibrate chan %u/%x failed\n",
				    __func__,
				    ah->ah_curchan->channel,
				    ah->ah_curchan->channelFlags));
			}
			sc->sc_ani.sc_caldone = iscaldone;
		}
	}

settimer:
	/*
	 * Set timer interval based on previous results.
	 * The interval must be the shortest necessary to satisfy ANI,
	 * short calibration and long calibration.
	 */
	cal_interval = ATH_LONG_CALINTERVAL;
	if (sc->sc_ah->ah_config.enable_ani)
		cal_interval =
		    min(cal_interval, (uint32_t)ATH_ANI_POLLINTERVAL);

	if (!sc->sc_ani.sc_caldone)
		cal_interval = min(cal_interval,
		    (uint32_t)ATH_SHORT_CALINTERVAL);

	sc->sc_scan_timer = 0;
	sc->sc_scan_timer = timeout(arn_ani_calibrate, (void *)sc,
	    drv_usectohz(cal_interval * 1000));
}

static void
arn_stop_caltimer(struct arn_softc *sc)
{
	timeout_id_t tmp_id = 0;

	while ((sc->sc_cal_timer != 0) && (tmp_id != sc->sc_cal_timer)) {
		tmp_id = sc->sc_cal_timer;
		(void) untimeout(tmp_id);
	}
	sc->sc_cal_timer = 0;
}

static uint_t
arn_isr(caddr_t arg)
{
	/* LINTED E_BAD_PTR_CAST_ALIGN */
	struct arn_softc *sc = (struct arn_softc *)arg;
	struct ath_hal *ah = sc->sc_ah;
	enum ath9k_int status;
	ieee80211com_t *ic = (ieee80211com_t *)sc;

	ARN_LOCK(sc);

	if (sc->sc_flags & SC_OP_INVALID) {
		/*
		 * The hardware is not ready/present, don't
		 * touch anything. Note this can happen early
		 * on if the IRQ is shared.
		 */
		ARN_UNLOCK(sc);
		return (DDI_INTR_UNCLAIMED);
	}
	if (!ath9k_hw_intrpend(ah)) {	/* shared irq, not for us */
		ARN_UNLOCK(sc);
		return (DDI_INTR_UNCLAIMED);
	}

	/*
	 * Figure out the reason(s) for the interrupt. Note
	 * that the hal returns a pseudo-ISR that may include
	 * bits we haven't explicitly enabled so we mask the
	 * value to insure we only process bits we requested.
	 */
	(void) ath9k_hw_getisr(ah, &status); /* NB: clears ISR too */

	status &= sc->sc_imask; /* discard unasked-for bits */

	/*
	 * If there are no status bits set, then this interrupt was not
	 * for me (should have been caught above).
	 */
	if (!status) {
		ARN_UNLOCK(sc);
		return (DDI_INTR_UNCLAIMED);
	}

	sc->sc_intrstatus = status;

	if (status & ATH9K_INT_FATAL) {
		/* need a chip reset */
		ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
		    "ATH9K_INT_FATAL\n"));
		goto reset;
	} else if (status & ATH9K_INT_RXORN) {
		/* need a chip reset */
		ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
		    "ATH9K_INT_RXORN\n"));
		goto reset;
	} else {
		if (status & ATH9K_INT_RXEOL) {
			/*
			 * NB: the hardware should re-read the link when
			 * RXE bit is written, but it doesn't work
			 * at least on older hardware revs.
			 */
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_RXEOL\n"));
			sc->sc_rxlink = NULL;
		}
		if (status & ATH9K_INT_TXURN) {
			/* bump tx trigger level */
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_TXURN\n"));
			(void) ath9k_hw_updatetxtriglevel(ah, B_TRUE);
		}
		/* XXX: optimize this */
		if (status & ATH9K_INT_RX) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_RX\n"));
			sc->sc_rx_pend = 1;
			ddi_trigger_softintr(sc->sc_softint_id);
		}
		if (status & ATH9K_INT_TX) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_TX\n"));
			if (ddi_taskq_dispatch(sc->sc_tq,
			    arn_tx_int_proc, sc, DDI_NOSLEEP) !=
			    DDI_SUCCESS) {
				arn_problem("arn: arn_isr(): "
				    "No memory for tx taskq\n");
				}
			}
#ifdef ARN_ATH9K_INT_MIB
		if (status & ATH9K_INT_MIB) {
			/*
			 * Disable interrupts until we service the MIB
			 * interrupt; otherwise it will continue to
			 * fire.
			 */
			(void) ath9k_hw_set_interrupts(ah, 0);
			/*
			 * Let the hal handle the event. We assume
			 * it will clear whatever condition caused
			 * the interrupt.
			 */
			ath9k_hw_procmibevent(ah, &sc->sc_halstats);
			(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_MIB\n"));
		}
#endif

#ifdef ARN_ATH9K_INT_TIM_TIMER
		if (status & ATH9K_INT_TIM_TIMER) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_TIM_TIMER\n"));
			if (!(ah->ah_caps.hw_caps &
			    ATH9K_HW_CAP_AUTOSLEEP)) {
				/*
				 * Clear RxAbort bit so that we can
				 * receive frames
				 */
				ath9k_hw_setrxabort(ah, 0);
				goto reset;
			}
		}
#endif

		if (status & ATH9K_INT_BMISS) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_BMISS\n"));

			if (ddi_taskq_dispatch(sc->sc_tq, arn_bmiss_proc,
			    sc, DDI_NOSLEEP) != DDI_SUCCESS) {
				arn_problem("arn: arn_isr(): "
				    "No memory available for bmiss taskq\n");
			}
		}

		ARN_UNLOCK(sc);

#ifdef ARN_ATH9K_INT_CST
		/* carrier sense timeout */
		if (status & ATH9K_INT_CST) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_CST\n"));
			return (DDI_INTR_CLAIMED);
		}
#endif

		if (status & ATH9K_INT_SWBA) {
			ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): "
			    "ATH9K_INT_SWBA\n"));
			/* This will occur only in Host-AP or Ad-Hoc mode */
			return (DDI_INTR_CLAIMED);
		}
	}

	return (DDI_INTR_CLAIMED);
reset:
	ARN_DBG((ARN_DBG_INTERRUPT, "Rset for fatal err\n"));
	(void) arn_reset(ic);
	ARN_UNLOCK(sc);
	return (DDI_INTR_CLAIMED);
}

static int
arn_get_channel(struct arn_softc *sc, struct ieee80211_channel *chan)
{
	int i;

	for (i = 0; i < sc->sc_ah->ah_nchan; i++) {
		if (sc->sc_ah->ah_channels[i].channel == chan->ich_freq)
			return (i);
	}

	return (-1);
}

int
arn_reset(ieee80211com_t *ic)
{
	struct arn_softc *sc = (struct arn_softc *)ic;
	struct ath_hal *ah = sc->sc_ah;
	int status;
	int error = 0;

	(void) ath9k_hw_set_interrupts(ah, 0);
	arn_draintxq(sc, 0);
	(void) arn_stoprecv(sc);

	if (!ath9k_hw_reset(ah, sc->sc_ah->ah_curchan, sc->tx_chan_width,
	    sc->sc_tx_chainmask, sc->sc_rx_chainmask,
	    sc->sc_ht_extprotspacing, B_FALSE, &status)) {
		ARN_DBG((ARN_DBG_RESET, "arn: arn_reset(): "
		    "unable to reset hardware; hal status %u\n", status));
		error = EIO;
	}

	if (arn_startrecv(sc) != 0)
		ARN_DBG((ARN_DBG_RESET, "arn: arn_reset(): "
		    "unable to start recv logic\n"));

	/*
	 * We may be doing a reset in response to a request
	 * that changes the channel so update any state that
	 * might change as a result.
	 */
	arn_setcurmode(sc, arn_chan2mode(sc->sc_ah->ah_curchan));

	arn_update_txpow(sc);

	if (sc->sc_flags & SC_OP_BEACONS)
		arn_beacon_config(sc);	/* restart beacons */

	(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);

	return (error);
}

int
arn_get_hal_qnum(uint16_t queue, struct arn_softc *sc)
{
	int qnum;

	switch (queue) {
	case WME_AC_VO:
		qnum = sc->sc_haltype2q[ATH9K_WME_AC_VO];
		break;
	case WME_AC_VI:
		qnum = sc->sc_haltype2q[ATH9K_WME_AC_VI];
		break;
	case WME_AC_BE:
		qnum = sc->sc_haltype2q[ATH9K_WME_AC_BE];
		break;
	case WME_AC_BK:
		qnum = sc->sc_haltype2q[ATH9K_WME_AC_BK];
		break;
	default:
		qnum = sc->sc_haltype2q[ATH9K_WME_AC_BE];
		break;
	}

	return (qnum);
}

static struct {
	uint32_t version;
	const char *name;
} ath_mac_bb_names[] = {
	{ AR_SREV_VERSION_5416_PCI,	"5416" },
	{ AR_SREV_VERSION_5416_PCIE,	"5418" },
	{ AR_SREV_VERSION_9100,		"9100" },
	{ AR_SREV_VERSION_9160,		"9160" },
	{ AR_SREV_VERSION_9280,		"9280" },
	{ AR_SREV_VERSION_9285,		"9285" }
};

static struct {
	uint16_t version;
	const char *name;
} ath_rf_names[] = {
	{ 0,				"5133" },
	{ AR_RAD5133_SREV_MAJOR,	"5133" },
	{ AR_RAD5122_SREV_MAJOR,	"5122" },
	{ AR_RAD2133_SREV_MAJOR,	"2133" },
	{ AR_RAD2122_SREV_MAJOR,	"2122" }
};

/*
 * Return the MAC/BB name. "????" is returned if the MAC/BB is unknown.
 */

static const char *
arn_mac_bb_name(uint32_t mac_bb_version)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ath_mac_bb_names); i++) {
		if (ath_mac_bb_names[i].version == mac_bb_version) {
			return (ath_mac_bb_names[i].name);
		}
	}

	return ("????");
}

/*
 * Return the RF name. "????" is returned if the RF is unknown.
 */

static const char *
arn_rf_name(uint16_t rf_version)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ath_rf_names); i++) {
		if (ath_rf_names[i].version == rf_version) {
			return (ath_rf_names[i].name);
		}
	}

	return ("????");
}

static void
arn_next_scan(void *arg)
{
	ieee80211com_t *ic = arg;
	struct arn_softc *sc = (struct arn_softc *)ic;

	sc->sc_scan_timer = 0;
	if (ic->ic_state == IEEE80211_S_SCAN) {
		sc->sc_scan_timer = timeout(arn_next_scan, (void *)sc,
		    drv_usectohz(arn_dwelltime * 1000));
		ieee80211_next_scan(ic);
	}
}

static void
arn_stop_scantimer(struct arn_softc *sc)
{
	timeout_id_t tmp_id = 0;

	while ((sc->sc_scan_timer != 0) && (tmp_id != sc->sc_scan_timer)) {
		tmp_id = sc->sc_scan_timer;
		(void) untimeout(tmp_id);
	}
	sc->sc_scan_timer = 0;
}

static int32_t
arn_newstate(ieee80211com_t *ic, enum ieee80211_state nstate, int arg)
{
	struct arn_softc *sc = (struct arn_softc *)ic;
	struct ath_hal *ah = sc->sc_ah;
	struct ieee80211_node *in;
	int32_t i, error;
	uint8_t *bssid;
	uint32_t rfilt;
	enum ieee80211_state ostate;
	struct ath9k_channel *channel;
	int pos;

	/* Should set up & init LED here */

	if (sc->sc_flags & SC_OP_INVALID)
		return (0);

	ostate = ic->ic_state;
	ARN_DBG((ARN_DBG_INIT, "arn: arn_newstate(): "
	    "%x -> %x!\n", ostate, nstate));

	ARN_LOCK(sc);

	if (nstate != IEEE80211_S_SCAN)
		arn_stop_scantimer(sc);
	if (nstate != IEEE80211_S_RUN)
		arn_stop_caltimer(sc);

	/* Should set LED here */

	if (nstate == IEEE80211_S_INIT) {
		sc->sc_imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
		/*
		 * Disable interrupts.
		 */
		(void) ath9k_hw_set_interrupts
		    (ah, sc->sc_imask &~ ATH9K_INT_GLOBAL);

#ifdef ARN_IBSS
		if (ic->ic_opmode == IEEE80211_M_IBSS) {
			(void) ath9k_hw_stoptxdma(ah, sc->sc_beaconq);
			arn_beacon_return(sc);
		}
#endif
		ARN_UNLOCK(sc);
		ieee80211_stop_watchdog(ic);
		goto done;
	}
	in = ic->ic_bss;

	pos = arn_get_channel(sc, ic->ic_curchan);

	if (pos == -1) {
		ARN_DBG((ARN_DBG_FATAL, "arn: "
		    "%s: Invalid channel\n", __func__));
		error = EINVAL;
		ARN_UNLOCK(sc);
		goto bad;
	}
	sc->tx_chan_width = ATH9K_HT_MACMODE_20;
	sc->sc_ah->ah_channels[pos].chanmode =
	    arn_chan2flags(ic, ic->ic_curchan);
	channel = &sc->sc_ah->ah_channels[pos];
	if (channel == NULL) {
		arn_problem("arn_newstate(): channel == NULL");
		ARN_UNLOCK(sc);
		goto bad;
	}
	error = arn_set_channel(sc, channel);
	if (error != 0) {
		if (nstate != IEEE80211_S_SCAN) {
			ARN_UNLOCK(sc);
			ieee80211_reset_chan(ic);
			goto bad;
		}
	}

	/*
	 * Get the receive filter according to the
	 * operating mode and state
	 */
	rfilt = arn_calcrxfilter(sc);

	if (nstate == IEEE80211_S_SCAN)
		bssid = ic->ic_macaddr;
	else
		bssid = in->in_bssid;

	ath9k_hw_setrxfilter(ah, rfilt);

	if (nstate == IEEE80211_S_RUN && ic->ic_opmode != IEEE80211_M_IBSS)
		ath9k_hw_write_associd(ah, bssid, in->in_associd);
	else
		ath9k_hw_write_associd(ah, bssid, 0);

	/* Check for WLAN_CAPABILITY_PRIVACY ? */
	if (ic->ic_flags & IEEE80211_F_PRIVACY) {
		for (i = 0; i < IEEE80211_WEP_NKID; i++) {
			if (ath9k_hw_keyisvalid(ah, (uint16_t)i))
				(void) ath9k_hw_keysetmac(ah, (uint16_t)i,
				    bssid);
		}
	}

	if (nstate == IEEE80211_S_RUN) {
		switch (ic->ic_opmode) {
#ifdef ARN_IBSS
		case IEEE80211_M_IBSS:
			/*
			 * Allocate and setup the beacon frame.
			 * Stop any previous beacon DMA.
			 */
			(void) ath9k_hw_stoptxdma(ah, sc->sc_beaconq);
			arn_beacon_return(sc);
			error = arn_beacon_alloc(sc, in);
			if (error != 0) {
				ARN_UNLOCK(sc);
				goto bad;
			}
			/*
			 * If joining an adhoc network defer beacon timer
			 * configuration to the next beacon frame so we
			 * have a current TSF to use.  Otherwise we're
			 * starting an ibss/bss so there's no need to delay.
			 */
			if (ic->ic_opmode == IEEE80211_M_IBSS &&
			    ic->ic_bss->in_tstamp.tsf != 0) {
				sc->sc_bsync = 1;
			} else {
				arn_beacon_config(sc);
			}
			break;
#endif /* ARN_IBSS */
		case IEEE80211_M_STA:
			if (ostate != IEEE80211_S_RUN) {
				/*
				 * Defer beacon timer configuration to the next
				 * beacon frame so we have a current TSF to use.
				 * Any TSF collected when scanning is likely old
				 */
#ifdef ARN_IBSS
				sc->sc_bsync = 1;
#else
				/* Configure the beacon and sleep timers. */
				arn_beacon_config(sc);
#endif /* ARN_IBSS */
			}
			break;
		default:
			break;
		}
	} else {
		sc->sc_imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
		(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);
	}

	/*
	 * Reset the rate control state.
	 */
	arn_rate_ctl_reset(sc, nstate);

	ARN_UNLOCK(sc);
done:
	/*
	 * Invoke the parent method to complete the work.
	 */
	error = sc->sc_newstate(ic, nstate, arg);

	/*
	 * Finally, start any timers.
	 */
	if (nstate == IEEE80211_S_RUN) {
		ieee80211_start_watchdog(ic, 1);
		ASSERT(sc->sc_cal_timer == 0);
		sc->sc_cal_timer = timeout(arn_ani_calibrate, (void *)sc,
		    drv_usectohz(100 * 1000));
	} else if ((nstate == IEEE80211_S_SCAN) && (ostate != nstate)) {
		/* start ap/neighbor scan timer */
		/* ASSERT(sc->sc_scan_timer == 0); */
		if (sc->sc_scan_timer != 0) {
			(void) untimeout(sc->sc_scan_timer);
			sc->sc_scan_timer = 0;
		}
		sc->sc_scan_timer = timeout(arn_next_scan, (void *)sc,
		    drv_usectohz(arn_dwelltime * 1000));
	}

bad:
	return (error);
}

static void
arn_watchdog(void *arg)
{
	struct arn_softc *sc = arg;
	ieee80211com_t *ic = &sc->sc_isc;
	int ntimer = 0;

	ARN_LOCK(sc);
	ic->ic_watchdog_timer = 0;
	if (sc->sc_flags & SC_OP_INVALID) {
		ARN_UNLOCK(sc);
		return;
	}

	if (ic->ic_state == IEEE80211_S_RUN) {
		/*
		 * 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) {
			sc->sc_stats.ast_rate_calls ++;
			if (ic->ic_opmode == IEEE80211_M_STA)
				arn_rate_ctl(ic, ic->ic_bss);
			else
				ieee80211_iterate_nodes(&ic->ic_sta,
				    arn_rate_ctl, sc);
		}

		ntimer = 1;
	}
	ARN_UNLOCK(sc);

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

static struct ieee80211_node *
arn_node_alloc(ieee80211com_t *ic)
{
	struct ath_node *an;
	struct arn_softc *sc = (struct arn_softc *)ic;

	an = kmem_zalloc(sizeof (struct ath_node), KM_SLEEP);
	arn_rate_update(sc, &an->an_node, 0);

	return ((an != NULL) ? &an->an_node : NULL);
}

static void
arn_node_free(struct ieee80211_node *in)
{
	ieee80211com_t *ic = in->in_ic;
	struct arn_softc *sc = (struct arn_softc *)ic;
	struct ath_buf *bf;
	struct ath_txq *txq;
	int32_t i;

	for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
		if (ARN_TXQ_SETUP(sc, i)) {
			txq = &sc->sc_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));
}

/*
 * Allocate tx/rx key slots for TKIP.  We allocate one slot for
 * each key. MIC is right after the decrypt/encrypt key.
 */
static uint16_t
arn_key_alloc_pair(struct arn_softc *sc, ieee80211_keyix *txkeyix,
    ieee80211_keyix *rxkeyix)
{
	uint16_t i, keyix;

	ASSERT(!sc->sc_splitmic);
	for (i = 0; i < ARRAY_SIZE(sc->sc_keymap)/4; i++) {
		uint8_t b = sc->sc_keymap[i];
		if (b == 0xff)
			continue;
		for (keyix = i * NBBY; keyix < (i + 1) * NBBY;
		    keyix++, b >>= 1) {
			if ((b & 1) || is_set(keyix+64, sc->sc_keymap)) {
				/* full pair unavailable */
				continue;
			}
			set_bit(keyix, sc->sc_keymap);
			set_bit(keyix+64, sc->sc_keymap);
			ARN_DBG((ARN_DBG_KEYCACHE,
			    "arn_key_alloc_pair(): key pair %u,%u\n",
			    keyix, keyix+64));
			*txkeyix = *rxkeyix = keyix;
			return (1);
		}
	}
	ARN_DBG((ARN_DBG_KEYCACHE, "arn_key_alloc_pair():"
	    " out of pair space\n"));

	return (0);
}

/*
 * Allocate tx/rx key slots for TKIP.  We allocate two slots for
 * each key, one for decrypt/encrypt and the other for the MIC.
 */
static int
arn_key_alloc_2pair(struct arn_softc *sc, ieee80211_keyix *txkeyix,
    ieee80211_keyix *rxkeyix)
{
	uint16_t i, keyix;

	ASSERT(sc->sc_splitmic);
	for (i = 0; i < ARRAY_SIZE(sc->sc_keymap)/4; i++) {
		uint8_t b = sc->sc_keymap[i];
		if (b != 0xff) {
			/*
			 * One or more slots in this byte are free.
			 */
			keyix = i*NBBY;
			while (b & 1) {
		again:
				keyix++;
				b >>= 1;
			}
			/* XXX IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV */
			if (is_set(keyix+32, sc->sc_keymap) ||
			    is_set(keyix+64, sc->sc_keymap) ||
			    is_set(keyix+32+64, sc->sc_keymap)) {
				/* full pair unavailable */
				if (keyix == (i+1)*NBBY) {
					/* no slots were appropriate, advance */
					continue;
				}
				goto again;
			}
			set_bit(keyix, sc->sc_keymap);
			set_bit(keyix+64, sc->sc_keymap);
			set_bit(keyix+32, sc->sc_keymap);
			set_bit(keyix+32+64, sc->sc_keymap);
			ARN_DBG((ARN_DBG_KEYCACHE,
			    "arn_key_alloc_2pair(): key pair %u,%u %u,%u\n",
			    keyix, keyix+64,
			    keyix+32, keyix+32+64));
			*txkeyix = *rxkeyix = keyix;
			return (1);
		}
	}
	ARN_DBG((ARN_DBG_KEYCACHE, "arn_key_alloc_2pair(): "
	    " out of pair space\n"));

	return (0);
}
/*
 * Allocate a single key cache slot.
 */
static int
arn_key_alloc_single(struct arn_softc *sc, ieee80211_keyix *txkeyix,
    ieee80211_keyix *rxkeyix)
{
	uint16_t i, keyix;

	/* try i,i+32,i+64,i+32+64 to minimize key pair conflicts */
	for (i = 0; i < ARRAY_SIZE(sc->sc_keymap); i++) {
		uint8_t b = sc->sc_keymap[i];

		if (b != 0xff) {
			/*
			 * One or more slots are free.
			 */
			keyix = i*NBBY;
			while (b & 1)
				keyix++, b >>= 1;
			set_bit(keyix, sc->sc_keymap);
			ARN_DBG((ARN_DBG_KEYCACHE, "arn_key_alloc_single(): "
			    "key %u\n", keyix));
			*txkeyix = *rxkeyix = keyix;
			return (1);
		}
	}
	return (0);
}

/*
 * Allocate one or more key cache slots for a unicast key.  The
 * key itself is needed only to identify the cipher.  For hardware
 * TKIP with split cipher+MIC keys we allocate two key cache slot
 * pairs so that we can setup separate TX and RX MIC keys.  Note
 * that the MIC key for a TKIP key at slot i is assumed by the
 * hardware to be at slot i+64.  This limits TKIP keys to the first
 * 64 entries.
 */
/* ARGSUSED */
int
arn_key_alloc(ieee80211com_t *ic, const struct ieee80211_key *k,
    ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
{
	struct arn_softc *sc = (struct arn_softc *)ic;

	/*
	 * We allocate two pair for TKIP when using the h/w to do
	 * the MIC.  For everything else, including software crypto,
	 * we allocate a single entry.  Note that s/w crypto requires
	 * a pass-through slot on the 5211 and 5212.  The 5210 does
	 * not support pass-through cache entries and we map all
	 * those requests to slot 0.
	 */
	if (k->wk_flags & IEEE80211_KEY_SWCRYPT) {
		return (arn_key_alloc_single(sc, keyix, rxkeyix));
	} else if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP &&
	    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0) {
		if (sc->sc_splitmic)
			return (arn_key_alloc_2pair(sc, keyix, rxkeyix));
		else
			return (arn_key_alloc_pair(sc, keyix, rxkeyix));
	} else {
		return (arn_key_alloc_single(sc, keyix, rxkeyix));
	}
}

/*
 * Delete an entry in the key cache allocated by ath_key_alloc.
 */
int
arn_key_delete(ieee80211com_t *ic, const struct ieee80211_key *k)
{
	struct arn_softc *sc = (struct arn_softc *)ic;
	struct ath_hal *ah = sc->sc_ah;
	const struct ieee80211_cipher *cip = k->wk_cipher;
	ieee80211_keyix keyix = k->wk_keyix;

	ARN_DBG((ARN_DBG_KEYCACHE, "arn_key_delete():"
	    " delete key %u ic_cipher=0x%x\n", keyix, cip->ic_cipher));

	(void) ath9k_hw_keyreset(ah, keyix);
	/*
	 * Handle split tx/rx keying required for TKIP with h/w MIC.
	 */
	if (cip->ic_cipher == IEEE80211_CIPHER_TKIP &&
	    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0 && sc->sc_splitmic)
		(void) ath9k_hw_keyreset(ah, keyix+32);		/* RX key */

	if (keyix >= IEEE80211_WEP_NKID) {
		/*
		 * Don't touch keymap entries for global keys so
		 * they are never considered for dynamic allocation.
		 */
		clr_bit(keyix, sc->sc_keymap);
		if (cip->ic_cipher == IEEE80211_CIPHER_TKIP &&
		    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0) {
			/*
			 * If splitmic is true +64 is TX key MIC,
			 * else +64 is RX key + RX key MIC.
			 */
			clr_bit(keyix+64, sc->sc_keymap);
			if (sc->sc_splitmic) {
				/* Rx key */
				clr_bit(keyix+32, sc->sc_keymap);
				/* RX key MIC */
				clr_bit(keyix+32+64, sc->sc_keymap);
			}
		}
	}
	return (1);
}

/*
 * Set a TKIP key into the hardware.  This handles the
 * potential distribution of key state to multiple key
 * cache slots for TKIP.
 */
static int
arn_keyset_tkip(struct arn_softc *sc, const struct ieee80211_key *k,
    struct ath9k_keyval *hk, const uint8_t mac[IEEE80211_ADDR_LEN])
{
	uint8_t *key_rxmic = NULL;
	uint8_t *key_txmic = NULL;
	uint8_t  *key = (uint8_t *)&(k->wk_key[0]);
	struct ath_hal *ah = sc->sc_ah;

	key_txmic = key + 16;
	key_rxmic = key + 24;

	if (mac == NULL) {
		/* Group key installation */
		(void) memcpy(hk->kv_mic,  key_rxmic, sizeof (hk->kv_mic));
		return (ath9k_hw_set_keycache_entry(ah, k->wk_keyix, hk,
		    mac, B_FALSE));
	}
	if (!sc->sc_splitmic) {
		/*
		 * data key goes at first index,
		 * the hal handles the MIC keys at index+64.
		 */
		(void) memcpy(hk->kv_mic, key_rxmic, sizeof (hk->kv_mic));
		(void) memcpy(hk->kv_txmic, key_txmic, sizeof (hk->kv_txmic));
		return (ath9k_hw_set_keycache_entry(ah, k->wk_keyix, hk,
		    mac, B_FALSE));
	}
	/*
	 * TX key goes at first index, RX key at +32.
	 * The hal handles the MIC keys at index+64.
	 */
	(void) memcpy(hk->kv_mic, key_txmic, sizeof (hk->kv_mic));
	if (!(ath9k_hw_set_keycache_entry(ah, k->wk_keyix, hk, NULL,
	    B_FALSE))) {
		/* Txmic entry failed. No need to proceed further */
		ARN_DBG((ARN_DBG_KEYCACHE,
		    "%s Setting TX MIC Key Failed\n", __func__));
		return (0);
	}

	(void) memcpy(hk->kv_mic, key_rxmic, sizeof (hk->kv_mic));

	/* XXX delete tx key on failure? */
	return (ath9k_hw_set_keycache_entry(ah, k->wk_keyix, hk, mac, B_FALSE));

}

int
arn_key_set(ieee80211com_t *ic, const struct ieee80211_key *k,
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct arn_softc *sc = (struct arn_softc *)ic;
	const struct ieee80211_cipher *cip = k->wk_cipher;
	struct ath9k_keyval hk;

	/* cipher table */
	static const uint8_t ciphermap[] = {
		ATH9K_CIPHER_WEP,		/* IEEE80211_CIPHER_WEP */
		ATH9K_CIPHER_TKIP,		/* IEEE80211_CIPHER_TKIP */
		ATH9K_CIPHER_AES_OCB,	/* IEEE80211_CIPHER_AES_OCB */
		ATH9K_CIPHER_AES_CCM,	/* IEEE80211_CIPHER_AES_CCM */
		ATH9K_CIPHER_CKIP,		/* IEEE80211_CIPHER_CKIP */
		ATH9K_CIPHER_CLR,		/* IEEE80211_CIPHER_NONE */
	};

	bzero(&hk, sizeof (hk));

	/*
	 * Software crypto uses a "clear key" so non-crypto
	 * state kept in the key cache are maintainedd so that
	 * rx frames have an entry to match.
	 */
	if ((k->wk_flags & IEEE80211_KEY_SWCRYPT) == 0) {
		ASSERT(cip->ic_cipher < 6);
		hk.kv_type = ciphermap[cip->ic_cipher];
		hk.kv_len = k->wk_keylen;
		bcopy(k->wk_key, hk.kv_val, k->wk_keylen);
	} else {
		hk.kv_type = ATH9K_CIPHER_CLR;
	}

	if (hk.kv_type == ATH9K_CIPHER_TKIP &&
	    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0) {
		return (arn_keyset_tkip(sc, k, &hk, mac));
	} else {
		return (ath9k_hw_set_keycache_entry(sc->sc_ah,
		    k->wk_keyix, &hk, mac, B_FALSE));
	}
}

/*
 * Enable/Disable short slot timing
 */
void
arn_set_shortslot(ieee80211com_t *ic, int onoff)
{
	struct ath_hal *ah = ((struct arn_softc *)ic)->sc_ah;

	if (onoff)
		(void) ath9k_hw_setslottime(ah, ATH9K_SLOT_TIME_9);
	else
		(void) ath9k_hw_setslottime(ah, ATH9K_SLOT_TIME_20);
}

static int
arn_open(struct arn_softc *sc)
{
	ieee80211com_t *ic = (ieee80211com_t *)sc;
	struct ieee80211_channel *curchan = ic->ic_curchan;
	struct ath9k_channel *init_channel;
	int error = 0, pos, status;

	ARN_LOCK_ASSERT(sc);

	pos = arn_get_channel(sc, curchan);
	if (pos == -1) {
		ARN_DBG((ARN_DBG_FATAL, "arn: "
		    "%s: Invalid channel\n", __func__));
		error = EINVAL;
		goto error;
	}

	sc->tx_chan_width = ATH9K_HT_MACMODE_20;

	if (sc->sc_curmode == ATH9K_MODE_11A) {
		sc->sc_ah->ah_channels[pos].chanmode = CHANNEL_A;
	} else {
		sc->sc_ah->ah_channels[pos].chanmode = CHANNEL_G;
	}

	init_channel = &sc->sc_ah->ah_channels[pos];

	/* Reset SERDES registers */
	ath9k_hw_configpcipowersave(sc->sc_ah, 0);

	/*
	 * 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.
	 */
	if (!ath9k_hw_reset(sc->sc_ah, init_channel,
	    sc->tx_chan_width, sc->sc_tx_chainmask,
	    sc->sc_rx_chainmask, sc->sc_ht_extprotspacing,
	    B_FALSE, &status)) {
		ARN_DBG((ARN_DBG_FATAL, "arn: "
		    "%s: unable to reset hardware; hal status %u "
		    "(freq %u flags 0x%x)\n", __func__, status,
		    init_channel->channel, init_channel->channelFlags));

		error = EIO;
		goto error;
	}

	/*
	 * This is needed only to setup initial state
	 * but it's best done after a reset.
	 */
	arn_update_txpow(sc);

	/*
	 * Setup the hardware after reset:
	 * The receive engine is set going.
	 * Frame transmit is handled entirely
	 * in the frame output path; there's nothing to do
	 * here except setup the interrupt mask.
	 */
	if (arn_startrecv(sc) != 0) {
		ARN_DBG((ARN_DBG_INIT, "arn: "
		    "%s: unable to start recv logic\n", __func__));
		error = EIO;
		goto error;
	}

	/* Setup our intr mask. */
	sc->sc_imask = ATH9K_INT_RX | ATH9K_INT_TX |
	    ATH9K_INT_RXEOL | ATH9K_INT_RXORN |
	    ATH9K_INT_FATAL | ATH9K_INT_GLOBAL;
#ifdef ARN_ATH9K_HW_CAP_GTT
	if (sc->sc_ah->ah_caps.hw_caps & ATH9K_HW_CAP_GTT)
		sc->sc_imask |= ATH9K_INT_GTT;
#endif

#ifdef ARN_ATH9K_HW_CAP_GTT
	if (sc->sc_ah->ah_caps.hw_caps & ATH9K_HW_CAP_HT)
		sc->sc_imask |= ATH9K_INT_CST;
#endif

	/*
	 * Enable MIB interrupts when there are hardware phy counters.
	 * Note we only do this (at the moment) for station mode.
	 */
#ifdef ARN_ATH9K_INT_MIB
	if (ath9k_hw_phycounters(sc->sc_ah) &&
	    ((sc->sc_ah->ah_opmode == ATH9K_M_STA) ||
	    (sc->sc_ah->ah_opmode == ATH9K_M_IBSS)))
		sc->sc_imask |= ATH9K_INT_MIB;
#endif
	/*
	 * Some hardware processes the TIM IE and fires an
	 * interrupt when the TIM bit is set.  For hardware
	 * that does, if not overridden by configuration,
	 * enable the TIM interrupt when operating as station.
	 */
#ifdef ARN_ATH9K_INT_TIM
	if ((sc->sc_ah->ah_caps.hw_caps & ATH9K_HW_CAP_ENHANCEDPM) &&
	    (sc->sc_ah->ah_opmode == ATH9K_M_STA) &&
	    !sc->sc_config.swBeaconProcess)
		sc->sc_imask |= ATH9K_INT_TIM;
#endif
	if (arn_chan2mode(init_channel) != sc->sc_curmode)
		arn_setcurmode(sc, arn_chan2mode(init_channel));
	ARN_DBG((ARN_DBG_INIT, "arn: "
	    "%s: current mode after arn_setcurmode is %d\n",
	    __func__, sc->sc_curmode));

	sc->sc_isrunning = 1;

	/* Disable BMISS interrupt when we're not associated */
	sc->sc_imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
	(void) ath9k_hw_set_interrupts(sc->sc_ah, sc->sc_imask);

	return (0);

error:
	return (error);
}

static void
arn_close(struct arn_softc *sc)
{
	ieee80211com_t *ic = (ieee80211com_t *)sc;
	struct ath_hal *ah = sc->sc_ah;

	ARN_LOCK_ASSERT(sc);

	if (!sc->sc_isrunning)
		return;

	/*
	 * Shutdown the hardware and driver
	 * Note that some of this work is not possible if the
	 * hardware is gone (invalid).
	 */
	ARN_UNLOCK(sc);
	ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
	ieee80211_stop_watchdog(ic);
	ARN_LOCK(sc);

	/*
	 * make sure h/w will not generate any interrupt
	 * before setting the invalid flag.
	 */
	(void) ath9k_hw_set_interrupts(ah, 0);

	if (!(sc->sc_flags & SC_OP_INVALID)) {
		arn_draintxq(sc, 0);
		(void) arn_stoprecv(sc);
		(void) ath9k_hw_phy_disable(ah);
	} else {
		sc->sc_rxlink = NULL;
	}

	sc->sc_isrunning = 0;
}

/*
 * MAC callback functions
 */
static int
arn_m_stat(void *arg, uint_t stat, uint64_t *val)
{
	struct arn_softc *sc = arg;
	ieee80211com_t *ic = (ieee80211com_t *)sc;
	struct ieee80211_node *in;
	struct ieee80211_rateset *rs;

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

	return (0);
}

int
arn_m_start(void *arg)
{
	struct arn_softc *sc = arg;
	int err = 0;

	ARN_LOCK(sc);

	/*
	 * Stop anything previously setup.  This is safe
	 * whether this is the first time through or not.
	 */

	arn_close(sc);

	if ((err = arn_open(sc)) != 0) {
		ARN_UNLOCK(sc);
		return (err);
	}

	/* H/W is reday now */
	sc->sc_flags &= ~SC_OP_INVALID;

	ARN_UNLOCK(sc);

	return (0);
}

static void
arn_m_stop(void *arg)
{
	struct arn_softc *sc = arg;

	ARN_LOCK(sc);
	arn_close(sc);

	/* disable HAL and put h/w to sleep */
	(void) ath9k_hw_disable(sc->sc_ah);
	ath9k_hw_configpcipowersave(sc->sc_ah, 1);

	/* XXX: hardware will not be ready in suspend state */
	sc->sc_flags |= SC_OP_INVALID;
	ARN_UNLOCK(sc);
}

static int
arn_m_promisc(void *arg, boolean_t on)
{
	struct arn_softc *sc = arg;
	struct ath_hal *ah = sc->sc_ah;
	uint32_t rfilt;

	ARN_LOCK(sc);

	rfilt = ath9k_hw_getrxfilter(ah);
	if (on)
		rfilt |= ATH9K_RX_FILTER_PROM;
	else
		rfilt &= ~ATH9K_RX_FILTER_PROM;
	sc->sc_promisc = on;
	ath9k_hw_setrxfilter(ah, rfilt);

	ARN_UNLOCK(sc);

	return (0);
}

static int
arn_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
	struct arn_softc *sc = arg;
	struct ath_hal *ah = sc->sc_ah;
	uint32_t val, index, bit;
	uint8_t pos;
	uint32_t *mfilt = sc->sc_mcast_hash;

	ARN_LOCK(sc);

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

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

	ARN_UNLOCK(sc);
	return (0);
}

static int
arn_m_unicst(void *arg, const uint8_t *macaddr)
{
	struct arn_softc *sc = arg;
	struct ath_hal *ah = sc->sc_ah;
	ieee80211com_t *ic = (ieee80211com_t *)sc;

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

	ARN_LOCK(sc);
	IEEE80211_ADDR_COPY(sc->sc_isc.ic_macaddr, macaddr);
	(void) ath9k_hw_setmac(ah, sc->sc_isc.ic_macaddr);
	(void) arn_reset(ic);
	ARN_UNLOCK(sc);
	return (0);
}

static mblk_t *
arn_m_tx(void *arg, mblk_t *mp)
{
	struct arn_softc *sc = arg;
	int error = 0;
	mblk_t *next;
	ieee80211com_t *ic = (ieee80211com_t *)sc;

	/*
	 * 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) {
		ARN_DBG((ARN_DBG_XMIT, "arn: arn_m_tx(): "
		    "discard, state %u\n", ic->ic_state));
		sc->sc_stats.ast_tx_discard++;
		freemsgchain(mp);
		return (NULL);
	}

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

	return (mp);
}

static void
arn_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
	struct arn_softc *sc = arg;
	int32_t err;

	err = ieee80211_ioctl(&sc->sc_isc, wq, mp);

	ARN_LOCK(sc);
	if (err == ENETRESET) {
		if (!(sc->sc_flags & SC_OP_INVALID)) {
			ARN_UNLOCK(sc);

			(void) arn_m_start(sc);

			(void) ieee80211_new_state(&sc->sc_isc,
			    IEEE80211_S_SCAN, -1);
			ARN_LOCK(sc);
		}
	}
	ARN_UNLOCK(sc);
}

static int
arn_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, const void *wldp_buf)
{
	struct arn_softc *sc = arg;
	int	err;

	err = ieee80211_setprop(&sc->sc_isc, pr_name, wldp_pr_num,
	    wldp_length, wldp_buf);

	ARN_LOCK(sc);

	if (err == ENETRESET) {
		if (!(sc->sc_flags & SC_OP_INVALID)) {
			ARN_UNLOCK(sc);
			(void) arn_m_start(sc);
			(void) ieee80211_new_state(&sc->sc_isc,
			    IEEE80211_S_SCAN, -1);
			ARN_LOCK(sc);
		}
		err = 0;
	}

	ARN_UNLOCK(sc);

	return (err);
}

/* ARGSUSED */
static int
arn_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t pr_flags, uint_t wldp_length, void *wldp_buf, uint_t *perm)
{
	struct arn_softc *sc = arg;
	int	err = 0;

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

	return (err);
}

/* return bus cachesize in 4B word units */
static void
arn_pci_config_cachesize(struct arn_softc *sc)
{
	uint8_t csz;

	/*
	 * Cache line size is used to size and align various
	 * structures used to communicate with the hardware.
	 */
	csz = pci_config_get8(sc->sc_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(sc->sc_cfg_handle, PCI_CONF_CACHE_LINESZ,
		    csz);
	}
	sc->sc_cachelsz = csz << 2;
}

static int
arn_pci_setup(struct arn_softc *sc)
{
	uint16_t command;

	/*
	 * Enable memory mapping and bus mastering
	 */
	ASSERT(sc != NULL);
	command = pci_config_get16(sc->sc_cfg_handle, PCI_CONF_COMM);
	command	|= PCI_COMM_MAE | PCI_COMM_ME;
	pci_config_put16(sc->sc_cfg_handle, PCI_CONF_COMM, command);
	command = pci_config_get16(sc->sc_cfg_handle, PCI_CONF_COMM);
	if ((command & PCI_COMM_MAE) == 0) {
		arn_problem("arn: arn_pci_setup(): "
		    "failed to enable memory mapping\n");
		return (EIO);
	}
	if ((command & PCI_COMM_ME) == 0) {
		arn_problem("arn: arn_pci_setup(): "
		    "failed to enable bus mastering\n");
		return (EIO);
	}
	ARN_DBG((ARN_DBG_INIT, "arn: arn_pci_setup(): "
	    "set command reg to 0x%x \n", command));

	return (0);
}

static void
arn_get_hw_encap(struct arn_softc *sc)
{
	ieee80211com_t *ic;
	struct ath_hal *ah;

	ic = (ieee80211com_t *)sc;
	ah = sc->sc_ah;

	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_AES_CCM, NULL))
		ic->ic_caps |= IEEE80211_C_AES_CCM;
	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_AES_OCB, NULL))
		ic->ic_caps |= IEEE80211_C_AES;
	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_TKIP, NULL))
		ic->ic_caps |= IEEE80211_C_TKIP;
	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_WEP, NULL))
		ic->ic_caps |= IEEE80211_C_WEP;
	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_MIC, NULL))
		ic->ic_caps |= IEEE80211_C_TKIPMIC;
}

static int
arn_resume(dev_info_t *devinfo)
{
	struct arn_softc *sc;
	int ret = DDI_SUCCESS;

	sc = ddi_get_soft_state(arn_soft_state_p, ddi_get_instance(devinfo));
	if (sc == NULL) {
		ARN_DBG((ARN_DBG_INIT, "ath: ath_resume(): "
		    "failed to get soft state\n"));
		return (DDI_FAILURE);
	}

	ARN_LOCK(sc);
	/*
	 * Set up config space command register(s). Refuse
	 * to resume on failure.
	 */
	if (arn_pci_setup(sc) != 0) {
		ARN_DBG((ARN_DBG_INIT, "ath: ath_resume(): "
		    "ath_pci_setup() failed\n"));
		ARN_UNLOCK(sc);
		return (DDI_FAILURE);
	}

	if (!(sc->sc_flags & SC_OP_INVALID))
		ret = arn_open(sc);
	ARN_UNLOCK(sc);

	return (ret);
}

static int
arn_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
{
	struct arn_softc *sc;
	int		instance;
	int		status;
	int32_t		err;
	uint16_t	vendor_id;
	uint16_t	device_id;
	uint32_t	i;
	uint32_t	val;
	char		strbuf[32];
	ieee80211com_t *ic;
	struct ath_hal *ah;
	wifi_data_t wd = { 0 };
	mac_register_t *macp;

	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_RESUME:
		return (arn_resume(devinfo));
	default:
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(devinfo);
	if (ddi_soft_state_zalloc(arn_soft_state_p, instance) != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: "
		    "%s: Unable to alloc softstate\n", __func__));
		return (DDI_FAILURE);
	}

	sc = ddi_get_soft_state(arn_soft_state_p, ddi_get_instance(devinfo));
	ic = (ieee80211com_t *)sc;
	sc->sc_dev = devinfo;

	mutex_init(&sc->sc_genlock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&sc->sc_serial_rw, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&sc->sc_txbuflock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&sc->sc_rxbuflock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&sc->sc_resched_lock, NULL, MUTEX_DRIVER, NULL);
#ifdef ARN_IBSS
	mutex_init(&sc->sc_bcbuflock, NULL, MUTEX_DRIVER, NULL);
#endif

	sc->sc_flags |= SC_OP_INVALID;

	err = pci_config_setup(devinfo, &sc->sc_cfg_handle);
	if (err != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "pci_config_setup() failed"));
		goto attach_fail0;
	}

	if (arn_pci_setup(sc) != 0)
		goto attach_fail1;

	/* Cache line size set up */
	arn_pci_config_cachesize(sc);

	vendor_id = pci_config_get16(sc->sc_cfg_handle, PCI_CONF_VENID);
	device_id = pci_config_get16(sc->sc_cfg_handle, PCI_CONF_DEVID);
	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): vendor 0x%x, "
	    "device id 0x%x, cache size %d\n",
	    vendor_id, device_id,
	    pci_config_get8(sc->sc_cfg_handle, PCI_CONF_CACHE_LINESZ)));

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

	err = ddi_regs_map_setup(devinfo, 1,
	    &sc->mem, 0, 0, &arn_reg_accattr, &sc->sc_io_handle);
	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
	    "regs map1 = %x err=%d\n", sc->mem, err));
	if (err != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "ddi_regs_map_setup() failed"));
		goto attach_fail1;
	}

	ah = ath9k_hw_attach(device_id, sc, sc->mem, &status);
	if (ah == NULL) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to attach hw: H/W status %u\n",
		    status));
		goto attach_fail2;
	}
	sc->sc_ah = ah;

	ath9k_hw_getmac(ah, ic->ic_macaddr);

	/* Get the hardware key cache size. */
	sc->sc_keymax = ah->ah_caps.keycache_size;
	if (sc->sc_keymax > ATH_KEYMAX) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "Warning, using only %u entries in %u key cache\n",
		    ATH_KEYMAX, sc->sc_keymax));
		sc->sc_keymax = ATH_KEYMAX;
	}

	/*
	 * Reset the key cache since some parts do not
	 * reset the contents on initial power up.
	 */
	for (i = 0; i < sc->sc_keymax; i++)
		(void) ath9k_hw_keyreset(ah, (uint16_t)i);
	/*
	 * 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.
	 * XXX only for splitmic.
	 */
	for (i = 0; i < IEEE80211_WEP_NKID; i++) {
		set_bit(i, sc->sc_keymap);
		set_bit(i + 32, sc->sc_keymap);
		set_bit(i + 64, sc->sc_keymap);
		set_bit(i + 32 + 64, sc->sc_keymap);
	}

	/* Collect the channel list using the default country code */
	err = arn_setup_channels(sc);
	if (err == EINVAL) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "ERR:arn_setup_channels\n"));
		goto attach_fail3;
	}

	/* default to STA mode */
	sc->sc_ah->ah_opmode = ATH9K_M_STA;

	/* Setup rate tables */
	arn_rate_attach(sc);
	arn_setup_rates(sc, IEEE80211_MODE_11A);
	arn_setup_rates(sc, IEEE80211_MODE_11B);
	arn_setup_rates(sc, IEEE80211_MODE_11G);

	/* Setup current mode here */
	arn_setcurmode(sc, ATH9K_MODE_11G);

	/* 802.11g features */
	if (sc->sc_have11g)
		ic->ic_caps |= IEEE80211_C_SHPREAMBLE |
		    IEEE80211_C_SHSLOT;		/* short slot time */

	/* temp workaround */
	sc->sc_mrretry = 1;

	/* Setup tx/rx descriptors */
	err = arn_desc_alloc(devinfo, sc);
	if (err != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "failed to allocate descriptors: %d\n", err));
		goto attach_fail3;
	}

	if ((sc->sc_tq = ddi_taskq_create(devinfo, "ath_taskq", 1,
	    TASKQ_DEFAULTPRI, 0)) == NULL) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "ERR:ddi_taskq_create\n"));
		goto attach_fail4;
	}

	/*
	 * Allocate hardware transmit queues: one queue for
	 * beacon frames and one data queue for each QoS
	 * priority.  Note that the hal handles reseting
	 * these queues at the needed time.
	 */
#ifdef ARN_IBSS
	sc->sc_beaconq = arn_beaconq_setup(ah);
	if (sc->sc_beaconq == (-1)) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup a beacon xmit queue\n"));
		goto attach_fail4;
	}
#endif
#ifdef ARN_HOSTAP
	sc->sc_cabq = arn_txq_setup(sc, ATH9K_TX_QUEUE_CAB, 0);
	if (sc->sc_cabq == NULL) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup CAB xmit queue\n"));
		goto attach_fail4;
	}

	sc->sc_config.cabqReadytime = ATH_CABQ_READY_TIME;
	ath_cabq_update(sc);
#endif

	for (i = 0; i < ARRAY_SIZE(sc->sc_haltype2q); i++)
		sc->sc_haltype2q[i] = -1;

	/* Setup data queues */
	/* NB: ensure BK queue is the lowest priority h/w queue */
	if (!arn_tx_setup(sc, ATH9K_WME_AC_BK)) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup xmit queue for BK traffic\n"));
		goto attach_fail4;
	}
	if (!arn_tx_setup(sc, ATH9K_WME_AC_BE)) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup xmit queue for BE traffic\n"));
		goto attach_fail4;
	}
	if (!arn_tx_setup(sc, ATH9K_WME_AC_VI)) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup xmit queue for VI traffic\n"));
		goto attach_fail4;
	}
	if (!arn_tx_setup(sc, ATH9K_WME_AC_VO)) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "unable to setup xmit queue for VO traffic\n"));
		goto attach_fail4;
	}

	/*
	 * Initializes the noise floor to a reasonable default value.
	 * Later on this will be updated during ANI processing.
	 */

	sc->sc_ani.sc_noise_floor = ATH_DEFAULT_NOISE_FLOOR;


	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_TKIP, NULL)) {
		/*
		 * Whether we should enable h/w TKIP MIC.
		 * XXX: if we don't support WME TKIP MIC, then we wouldn't
		 * report WMM capable, so it's always safe to turn on
		 * TKIP MIC in this case.
		 */
		(void) ath9k_hw_setcapability(sc->sc_ah, ATH9K_CAP_TKIP_MIC,
		    0, 1, NULL);
	}

	/* Get cipher releated capability information */
	arn_get_hw_encap(sc);

	/*
	 * Check whether the separate key cache entries
	 * are required to handle both tx+rx MIC keys.
	 * With split mic keys the number of stations is limited
	 * to 27 otherwise 59.
	 */
	if (ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_TKIP, NULL) &&
	    ath9k_hw_getcapability(ah, ATH9K_CAP_CIPHER,
	    ATH9K_CIPHER_MIC, NULL) &&
	    ath9k_hw_getcapability(ah, ATH9K_CAP_TKIP_SPLIT,
	    0, NULL))
		sc->sc_splitmic = 1;

	/* turn on mcast key search if possible */
	if (!ath9k_hw_getcapability(ah, ATH9K_CAP_MCAST_KEYSRCH, 0, NULL))
		(void) ath9k_hw_setcapability(ah, ATH9K_CAP_MCAST_KEYSRCH, 1,
		    1, NULL);

	sc->sc_config.txpowlimit = ATH_TXPOWER_MAX;
	sc->sc_config.txpowlimit_override = 0;

#ifdef ARN_11N
	/* 11n Capabilities */
	if (ah->ah_caps.hw_caps & ATH9K_HW_CAP_HT) {
		sc->sc_flags |= SC_OP_TXAGGR;
		sc->sc_flags |= SC_OP_RXAGGR;
	}
#endif

#ifdef ARN_11N
	sc->sc_tx_chainmask = ah->ah_caps.tx_chainmask;
	sc->sc_rx_chainmask = ah->ah_caps.rx_chainmask;
#else
	sc->sc_tx_chainmask = 1;
	sc->sc_rx_chainmask = 1;
#endif
	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
	    "tx_chainmask = %d, rx_chainmask = %d\n",
	    sc->sc_tx_chainmask, sc->sc_rx_chainmask));

	(void) ath9k_hw_setcapability(ah, ATH9K_CAP_DIVERSITY, 1, B_TRUE, NULL);
	sc->sc_defant = ath9k_hw_getdefantenna(ah);

	ath9k_hw_getmac(ah, sc->sc_myaddr);
	if (ah->ah_caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK) {
		ath9k_hw_getbssidmask(ah, sc->sc_bssidmask);
		ATH_SET_VAP_BSSID_MASK(sc->sc_bssidmask);
		(void) ath9k_hw_setbssidmask(ah, sc->sc_bssidmask);
	}

	/* set default value to short slot time */
	sc->sc_slottime = ATH9K_SLOT_TIME_9;
	(void) ath9k_hw_setslottime(ah, ATH9K_SLOT_TIME_9);

	/* initialize beacon slots */
	for (i = 0; i < ARRAY_SIZE(sc->sc_bslot); i++)
		sc->sc_bslot[i] = ATH_IF_ID_ANY;

	/* save MISC configurations */
	sc->sc_config.swBeaconProcess = 1;


	ic->ic_caps |= IEEE80211_C_WPA;	/* Support WPA/WPA2 */
	ic->ic_phytype = IEEE80211_T_OFDM;
	ic->ic_opmode = IEEE80211_M_STA;
	ic->ic_state = IEEE80211_S_INIT;
	ic->ic_maxrssi = ARN_MAX_RSSI;
	ic->ic_set_shortslot = arn_set_shortslot;
	ic->ic_xmit = arn_tx;
	ieee80211_attach(ic);

	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
	    "ic->ic_curchan->ich_freq: %d\n", ic->ic_curchan->ich_freq));

	/* 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 = arn_reset;
	sc->sc_newstate = ic->ic_newstate;
	ic->ic_newstate = arn_newstate;
#ifdef ARN_IBSS
	sc->sc_recv_mgmt = ic->ic_recv_mgmt;
	ic->ic_recv_mgmt = arn_recv_mgmt;
#endif
	ic->ic_watchdog = arn_watchdog;
	ic->ic_node_alloc = arn_node_alloc;
	ic->ic_node_free = arn_node_free;
	ic->ic_crypto.cs_key_alloc = arn_key_alloc;
	ic->ic_crypto.cs_key_delete = arn_key_delete;
	ic->ic_crypto.cs_key_set = arn_key_set;

	ieee80211_media_init(ic);

	/*
	 * initialize default tx key
	 */
	ic->ic_def_txkey = 0;

	sc->sc_rx_pend = 0;
	(void) ath9k_hw_set_interrupts(sc->sc_ah, 0);
	err = ddi_add_softintr(devinfo, DDI_SOFTINT_LOW,
	    &sc->sc_softint_id, NULL, 0, arn_softint_handler, (caddr_t)sc);
	if (err != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "ddi_add_softintr() failed....\n"));
		goto attach_fail5;
	}

	if (ddi_get_iblock_cookie(devinfo, 0, &sc->sc_iblock)
	    != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "Can not get iblock cookie for INT\n"));
		goto attach_fail6;
	}

	if (ddi_add_intr(devinfo, 0, NULL, NULL, arn_isr,
	    (caddr_t)sc) != DDI_SUCCESS) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "Can not set intr for ARN 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);

	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
	    "IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_bss->in_bssid)"
	    "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
	    wd.wd_bssid[0], wd.wd_bssid[1], wd.wd_bssid[2],
	    wd.wd_bssid[3], wd.wd_bssid[4], wd.wd_bssid[5]));

	if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
		    "MAC version mismatch\n"));
		goto attach_fail7;
	}

	macp->m_type_ident	= MAC_PLUGIN_IDENT_WIFI;
	macp->m_driver		= sc;
	macp->m_dip		= devinfo;
	macp->m_src_addr	= ic->ic_macaddr;
	macp->m_callbacks	= &arn_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) {
		ARN_DBG((ARN_DBG_ATTACH, "arn: arn_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",
	    ARN_NODENAME, instance);
	err = ddi_create_minor_node(devinfo, strbuf, S_IFCHR,
	    instance + 1, DDI_NT_NET_WIFI, 0);
	if (err != DDI_SUCCESS)
		ARN_DBG((ARN_DBG_ATTACH, "WARN: arn: arn_attach(): "
		    "Create minor node failed - %d\n", err));

	mac_link_update(ic->ic_mach, LINK_STATE_DOWN);

	sc->sc_promisc = B_FALSE;
	bzero(sc->sc_mcast_refs, sizeof (sc->sc_mcast_refs));
	bzero(sc->sc_mcast_hash, sizeof (sc->sc_mcast_hash));

	ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): "
	    "Atheros AR%s MAC/BB Rev:%x "
	    "AR%s RF Rev:%x: mem=0x%lx\n",
	    arn_mac_bb_name(ah->ah_macVersion),
	    ah->ah_macRev,
	    arn_rf_name((ah->ah_analog5GhzRev & AR_RADIO_SREV_MAJOR)),
	    ah->ah_phyRev,
	    (unsigned long)sc->mem));

	/* XXX: hardware will not be ready until arn_open() being called */
	sc->sc_flags |= SC_OP_INVALID;
	sc->sc_isrunning = 0;

	return (DDI_SUCCESS);

attach_fail7:
	ddi_remove_intr(devinfo, 0, sc->sc_iblock);
attach_fail6:
	ddi_remove_softintr(sc->sc_softint_id);
attach_fail5:
	(void) ieee80211_detach(ic);
attach_fail4:
	arn_desc_free(sc);
	if (sc->sc_tq)
		ddi_taskq_destroy(sc->sc_tq);
attach_fail3:
	ath9k_hw_detach(ah);
attach_fail2:
	ddi_regs_map_free(&sc->sc_io_handle);
attach_fail1:
	pci_config_teardown(&sc->sc_cfg_handle);
attach_fail0:
	sc->sc_flags |= SC_OP_INVALID;
	/* cleanup tx queues */
	mutex_destroy(&sc->sc_txbuflock);
	for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
		if (ARN_TXQ_SETUP(sc, i)) {
			/* arn_tx_cleanupq(asc, &asc->sc_txq[i]); */
			mutex_destroy(&((&sc->sc_txq[i])->axq_lock));
		}
	}
	mutex_destroy(&sc->sc_rxbuflock);
	mutex_destroy(&sc->sc_serial_rw);
	mutex_destroy(&sc->sc_genlock);
	mutex_destroy(&sc->sc_resched_lock);
#ifdef ARN_IBSS
	mutex_destroy(&sc->sc_bcbuflock);
#endif

	ddi_soft_state_free(arn_soft_state_p, instance);

	return (DDI_FAILURE);

}

/*
 * Suspend transmit/receive for powerdown
 */
static int
arn_suspend(struct arn_softc *sc)
{
	ARN_LOCK(sc);
	arn_close(sc);
	ARN_UNLOCK(sc);

	return (DDI_SUCCESS);
}

static int32_t
arn_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd)
{
	struct arn_softc *sc;
	int i;

	sc = ddi_get_soft_state(arn_soft_state_p, ddi_get_instance(devinfo));
	ASSERT(sc != NULL);

	switch (cmd) {
	case DDI_DETACH:
		break;

	case DDI_SUSPEND:
		return (arn_suspend(sc));

	default:
		return (DDI_FAILURE);
	}

	if (mac_disable(sc->sc_isc.ic_mach) != 0)
		return (DDI_FAILURE);

	arn_stop_scantimer(sc);
	arn_stop_caltimer(sc);

	/* disable interrupts */
	(void) ath9k_hw_set_interrupts(sc->sc_ah, 0);

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

	/* free intterrupt resources */
	ddi_remove_intr(devinfo, 0, sc->sc_iblock);
	ddi_remove_softintr(sc->sc_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(&sc->sc_isc);

	arn_desc_free(sc);

	ddi_taskq_destroy(sc->sc_tq);

	if (!(sc->sc_flags & SC_OP_INVALID))
		(void) ath9k_hw_setpower(sc->sc_ah, ATH9K_PM_AWAKE);

	/* cleanup tx queues */
	mutex_destroy(&sc->sc_txbuflock);
	for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
		if (ARN_TXQ_SETUP(sc, i)) {
			arn_tx_cleanupq(sc, &sc->sc_txq[i]);
			mutex_destroy(&((&sc->sc_txq[i])->axq_lock));
		}
	}

	ath9k_hw_detach(sc->sc_ah);

	/* free io handle */
	ddi_regs_map_free(&sc->sc_io_handle);
	pci_config_teardown(&sc->sc_cfg_handle);

	/* destroy locks */
	mutex_destroy(&sc->sc_genlock);
	mutex_destroy(&sc->sc_serial_rw);
	mutex_destroy(&sc->sc_rxbuflock);
	mutex_destroy(&sc->sc_resched_lock);
#ifdef ARN_IBSS
	mutex_destroy(&sc->sc_bcbuflock);
#endif

	ddi_remove_minor_node(devinfo, NULL);
	ddi_soft_state_free(arn_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
arn_quiesce(dev_info_t *devinfo)
{
	struct arn_softc *sc;
	int i;
	struct ath_hal *ah;

	sc = ddi_get_soft_state(arn_soft_state_p, ddi_get_instance(devinfo));

	if (sc == NULL || (ah = sc->sc_ah) == NULL)
		return (DDI_FAILURE);

	/*
	 * Disable interrupts
	 */
	(void) ath9k_hw_set_interrupts(ah, 0);

	/*
	 * Disable TX HW
	 */
	for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
		if (ARN_TXQ_SETUP(sc, i))
			(void) ath9k_hw_stoptxdma(ah, sc->sc_txq[i].axq_qnum);
	}

	/*
	 * Disable RX HW
	 */
	ath9k_hw_stoppcurecv(ah);
	ath9k_hw_setrxfilter(ah, 0);
	(void) ath9k_hw_stopdmarecv(ah);
	drv_usecwait(3000);

	/*
	 * Power down HW
	 */
	(void) ath9k_hw_phy_disable(ah);

	return (DDI_SUCCESS);
}

DDI_DEFINE_STREAM_OPS(arn_dev_ops, nulldev, nulldev, arn_attach, arn_detach,
    nodev, NULL, D_MP, NULL, arn_quiesce);

static struct modldrv arn_modldrv = {
	&mod_driverops, /* Type of module.  This one is a driver */
	"arn-Atheros 9000 series driver:vertion 1.1", /* short description */
	&arn_dev_ops /* driver specific ops */
};

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

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

int
_init(void)
{
	int status;

	status = ddi_soft_state_init
	    (&arn_soft_state_p, sizeof (struct arn_softc), 1);
	if (status != 0)
		return (status);

	mutex_init(&arn_loglock, NULL, MUTEX_DRIVER, NULL);
	mac_init_ops(&arn_dev_ops, "arn");
	status = mod_install(&modlinkage);
	if (status != 0) {
		mac_fini_ops(&arn_dev_ops);
		mutex_destroy(&arn_loglock);
		ddi_soft_state_fini(&arn_soft_state_p);
	}

	return (status);
}

int
_fini(void)
{
	int status;

	status = mod_remove(&modlinkage);
	if (status == 0) {
		mac_fini_ops(&arn_dev_ops);
		mutex_destroy(&arn_loglock);
		ddi_soft_state_fini(&arn_soft_state_p);
	}
	return (status);
}