/*
 * Copyright 2007 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.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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/gld.h>
#include <sys/dlpi.h>
#include <sys/ethernet.h>
#include <sys/list.h>
#include <sys/byteorder.h>
#include <sys/strsun.h>
#include <inet/common.h>
#include <inet/nd.h>
#include <inet/mi.h>
#include <inet/wifi_ioctl.h>
#include "ath_hal.h"
#include "ath_impl.h"

static const char *acnames[] = {
	"WME_AC_BE",
	"WME_AC_BK",
	"WME_AC_VI",
	"WME_AC_VO",
	"WME_UPSD"
};

extern void ath_setup_desc(ath_t *asc, struct ath_buf *bf);

uint32_t
ath_calcrxfilter(ath_t *asc)
{
	ieee80211com_t *ic = (ieee80211com_t *)asc;
	struct ath_hal *ah = asc->asc_ah;
	uint32_t rfilt;

	rfilt = (ATH_HAL_GETRXFILTER(ah) & HAL_RX_FILTER_PHYERR)
	    | HAL_RX_FILTER_UCAST | HAL_RX_FILTER_BCAST | HAL_RX_FILTER_MCAST;
	if (ic->ic_opmode != IEEE80211_M_STA)
		rfilt |= HAL_RX_FILTER_PROBEREQ;
	if (ic->ic_opmode != IEEE80211_M_HOSTAP &&
	    (asc->asc_promisc & GLD_MAC_PROMISC_PHYS))	/* promiscuous */
		rfilt |= HAL_RX_FILTER_PROM;
	if (ic->ic_opmode == IEEE80211_M_STA ||
	    ic->ic_opmode == IEEE80211_M_IBSS ||
	    ic->ic_state == IEEE80211_S_SCAN)
		rfilt |= HAL_RX_FILTER_BEACON;
	return (rfilt);
}

static int
ath_set_data_queue(ath_t *asc, int ac, int haltype)
{
	HAL_TXQ_INFO qi;
	int qnum;
	struct ath_hal *ah = asc->asc_ah;
	struct ath_txq *txq;

	if (ac >= ATH_N(asc->asc_ac2q)) {
		ATH_DEBUG((ATH_DBG_AUX, "ath: ath_set_data_queue(): "
		    "ac %u out of range, max %u!\n",
		    ac, ATH_N(asc->asc_ac2q)));
		return (1);
	}
	(void) memset(&qi, 0, sizeof (qi));
	qi.tqi_subtype = haltype;
	/*
	 * Enable interrupts only for EOL and DESC conditions.
	 * We mark tx descriptors to receive a DESC interrupt
	 * when a tx queue gets deep; otherwise waiting for the
	 * EOL to reap descriptors.  Note that this is done to
	 * reduce interrupt load and this only defers reaping
	 * descriptors, never transmitting frames.  Aside from
	 * reducing interrupts this also permits more concurrency.
	 * The only potential downside is if the tx queue backs
	 * up in which case the top half of the kernel may backup
	 * due to a lack of tx descriptors.
	 */
	qi.tqi_qflags = HAL_TXQ_TXEOLINT_ENABLE | HAL_TXQ_TXDESCINT_ENABLE;
	qnum = ATH_HAL_SETUPTXQUEUE(ah, HAL_TX_QUEUE_DATA, &qi);
	if (qnum == -1) {
		ATH_DEBUG((ATH_DBG_AUX, "ath: ath_set_data_queue(): "
		    "Unable to setup hardware queue for %s traffic!\n",
		    acnames[ac]));
		return (1);
	}
	if (qnum >= ATH_N(asc->asc_txq)) {
		ATH_DEBUG((ATH_DBG_AUX, "ath: ath_set_data_queue(): "
		    "hal qnum %u out of range, max %u!\n",
		    qnum, ATH_N(asc->asc_txq)));
		return (1);
	}
	if (!ATH_TXQ_SETUP(asc, qnum)) {
		txq = &asc->asc_txq[qnum];
		txq->axq_qnum = qnum;
		txq->axq_depth = 0;
		txq->axq_intrcnt = 0;
		txq->axq_link = NULL;
		list_create(&txq->axq_list, sizeof (struct ath_buf),
		    offsetof(struct ath_buf, bf_node));
		mutex_init(&txq->axq_lock, NULL, MUTEX_DRIVER, NULL);
		asc->asc_txqsetup |= 1<<qnum;
	}
	asc->asc_ac2q[ac] = &asc->asc_txq[qnum];
	return (0);
}

int
ath_txq_setup(ath_t *asc)
{
	if (ath_set_data_queue(asc, WME_AC_BE, HAL_WME_AC_BK) ||
	    ath_set_data_queue(asc, WME_AC_BK, HAL_WME_AC_BE) ||
	    ath_set_data_queue(asc, WME_AC_VI, HAL_WME_AC_VI) ||
	    ath_set_data_queue(asc, WME_AC_VO, HAL_WME_AC_VO)) {
		return (1);
	}

	return (0);
}

void
ath_txq_cleanup(ath_t *asc)
{
	int i;

	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];

			ATH_HAL_RELEASETXQUEUE(asc->asc_ah, txq->axq_qnum);
			mutex_destroy(&txq->axq_lock);
			asc->asc_txqsetup &= ~(1 << txq->axq_qnum);
		}
	}
}

void
ath_setcurmode(ath_t *asc, enum ieee80211_phymode mode)
{
	const HAL_RATE_TABLE *rt;
	int i;

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

	rt = asc->asc_rates[mode];
	ASSERT(rt != NULL);

	for (i = 0; i < rt->rateCount; i++)
		asc->asc_rixmap[rt->info[i].dot11Rate & IEEE80211_RATE_VAL] = i;

	asc->asc_currates = rt;
	asc->asc_curmode = mode;
}

/* Set correct parameters for a certain mode */
void
ath_mode_init(ath_t *asc)
{
	ieee80211com_t *ic = (ieee80211com_t *)asc;
	struct ath_hal *ah = asc->asc_ah;
	uint32_t rfilt;

	/* configure rx filter */
	rfilt = ath_calcrxfilter(asc);
	ATH_HAL_SETRXFILTER(ah, rfilt);
	ATH_HAL_SETOPMODE(ah);
	ATH_HAL_SETMCASTFILTER(ah, asc->asc_mfilt[0], asc->asc_mfilt[1]);
	ATH_DEBUG((ATH_DBG_AUX, "ath: ath_mode_init(): "
	    "mode =%d RX filter 0x%x, MC filter %08x:%08x\n",
	    ic->ic_opmode, rfilt,
	    asc->asc_mfilt[0], asc->asc_mfilt[1]));
}


/*
 * Disable the receive h/w in preparation for a reset.
 */
void
ath_stoprecv(ath_t *asc)
{
	ATH_HAL_STOPPCURECV(asc->asc_ah);	/* disable PCU */
	ATH_HAL_SETRXFILTER(asc->asc_ah, 0);	/* clear recv filter */
	ATH_HAL_STOPDMARECV(asc->asc_ah);	/* disable DMA engine */
	drv_usecwait(3000);

	ATH_DEBUG((ATH_DBG_AUX, "ath: ath_stoprecv(): rx queue %p, link %p\n",
	    ATH_HAL_GETRXBUF(asc->asc_ah), asc->asc_rxlink));
	asc->asc_rxlink = NULL;
}

uint32_t
ath_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_PUREG,		/* IEEE80211_MODE_11G */
	    0,				/* IEEE80211_MODE_FH */
	    CHANNEL_108A,		/* IEEE80211_MODE_TURBO_A */
	    CHANNEL_108G		/* IEEE80211_MODE_TURBO_G */
	};
	return (modeflags[ieee80211_chan2mode(isc, chan)]);
}


int
ath_getchannels(ath_t *asc, uint32_t cc, HAL_BOOL outdoor, HAL_BOOL xchanmode)
{
	ieee80211com_t *ic = (ieee80211com_t *)asc;
	struct ath_hal *ah = asc->asc_ah;
	HAL_CHANNEL *chans;
	int i, ix;
	uint32_t nchan;

	chans = (HAL_CHANNEL *)
	    kmem_zalloc(IEEE80211_CHAN_MAX * sizeof (HAL_CHANNEL), KM_SLEEP);

	if (!ath_hal_init_channels(ah, chans, IEEE80211_CHAN_MAX, &nchan,
	    NULL, 0, NULL, cc, HAL_MODE_ALL, outdoor, xchanmode)) {
		ATH_DEBUG((ATH_DBG_AUX, "ath: ath_getchannels(): "
		    "unable to get channel list\n");
		kmem_free(chans, IEEE80211_CHAN_MAX * sizeof (HAL_CHANNEL)));
		return (EINVAL);
	}

	/*
	 * Convert HAL channels to ieee80211 ones and insert
	 * them in the table according to their channel number.
	 */
	for (i = 0; i < nchan; i++) {
		HAL_CHANNEL *c = &chans[i];
		uint16_t flags;
		ix = ath_hal_mhz2ieee(ah, c->channel, c->channelFlags);
		if (ix > IEEE80211_CHAN_MAX) {
			ATH_DEBUG((ATH_DBG_AUX, "ath: ath_getchannels(): "
			    "bad hal channel %d (%u/%x) ignored\n",
			    ix, c->channel, c->channelFlags));
			continue;
		}
		/* NB: flags are known to be compatible */
		if (ix < 0) {
			/*
			 * can't handle frequency <2400MHz (negative
			 * channels) right now
			 */
			ATH_DEBUG((ATH_DBG_AUX, "ath:ath_getchannels(): "
			    "hal channel %d (%u/%x) "
			    "cannot be handled, ignored\n",
			    ix, 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_COMPAT;
		if (c->channelFlags & CHANNEL_STURBO)
			flags |= IEEE80211_CHAN_TURBO;
		if (ic->ic_sup_channels[ix].ich_freq == 0) {
			ic->ic_sup_channels[ix].ich_freq = c->channel;
			ic->ic_sup_channels[ix].ich_flags = flags;
		} else {
			/* channels overlap; e.g. 11g and 11b */
			ic->ic_sup_channels[ix].ich_flags |= flags;
		}
		if ((c->channelFlags & CHANNEL_G) == CHANNEL_G)
			asc->asc_have11g = 1;
	}
	kmem_free(chans, IEEE80211_CHAN_MAX * sizeof (HAL_CHANNEL));
	return (0);
}

static void
ath_drainq(ath_t *asc, struct ath_txq *txq)
{
	struct ath_buf *bf;

	/*
	 * This assumes output has been stopped.
	 */
	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;
		}
		list_remove(&txq->axq_list, bf);
		mutex_exit(&txq->axq_lock);
		bf->bf_in = NULL;
		mutex_enter(&asc->asc_txbuflock);
		list_insert_tail(&asc->asc_txbuf_list, bf);
		mutex_exit(&asc->asc_txbuflock);
	}
}


/*
 * Drain the transmit queues and reclaim resources.
 */
void
ath_draintxq(ath_t *asc)
{
	struct ath_hal *ah = asc->asc_ah;
	struct ath_txq *txq;
	int i;

	if (!asc->asc_invalid) {
		for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
			if (ATH_TXQ_SETUP(asc, i)) {
				txq = &asc->asc_txq[i];
				(void) ATH_HAL_STOPTXDMA(ah, txq->axq_qnum);
			}
		}
	}
	for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
		if (ATH_TXQ_SETUP(asc, i)) {
			ath_drainq(asc, &asc->asc_txq[i]);
		}
	}
}


/* Enable the receive h/w following a reset */
int
ath_startrecv(ath_t *asc)
{
	struct ath_buf *bf;

	asc->asc_rxlink = NULL;

	bf = list_head(&asc->asc_rxbuf_list);
	while (bf != NULL) {
		ath_setup_desc(asc, bf);
		bf = list_next(&asc->asc_rxbuf_list, bf);
	}

	bf = list_head(&asc->asc_rxbuf_list);
	ATH_HAL_PUTRXBUF(asc->asc_ah, bf->bf_daddr);
	ATH_HAL_RXENA(asc->asc_ah);		/* enable recv descriptors */
	ath_mode_init(asc);			/* set filters, etc. */
	ATH_HAL_STARTPCURECV(asc->asc_ah);	/* re-enable PCU/DMA engine */
	return (0);
}

/*
 * Update internal state after a channel change.
 */
void
ath_chan_change(ath_t *asc, struct ieee80211_channel *chan)
{
	struct ieee80211com *ic = &asc->asc_isc;
	enum ieee80211_phymode mode;

	/*
	 * Change channels and update the h/w rate map
	 * if we're switching; e.g. 11a to 11b/g.
	 */
	mode = ieee80211_chan2mode(ic, chan);
	if (mode != asc->asc_curmode)
		ath_setcurmode(asc, mode);
}

/*
 * Set/change channels.  If the channel is really being changed,
 * it's done by resetting the chip.  To accomplish this we must
 * first cleanup any pending DMA.
 */
int
ath_chan_set(ath_t *asc, struct ieee80211_channel *chan)
{
	struct ath_hal *ah = asc->asc_ah;
	ieee80211com_t *ic = &asc->asc_isc;

	if (chan != ic->ic_ibss_chan) {
		HAL_STATUS status;
		HAL_CHANNEL hchan;

		/*
		 * 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.
		 */
		ATH_HAL_INTRSET(ah, 0);		/* disable interrupts */
		ath_draintxq(asc);		/* clear pending tx frames */
		ath_stoprecv(asc);		/* turn off frame recv */
		/*
		 * Convert to a HAL channel description with
		 * the flags constrained to reflect the current
		 * operating mode.
		 */
		hchan.channel = chan->ich_freq;
		hchan.channelFlags = ath_chan2flags(ic, chan);
		if (!ATH_HAL_RESET(ah, (HAL_OPMODE)ic->ic_opmode,
		    &hchan, AH_TRUE, &status)) {
			ATH_DEBUG((ATH_DBG_AUX, "ath: ath_chan_set():"
			    "unable to reset channel %u (%uMhz)\n",
			    ieee80211_chan2ieee(ic, chan), chan->ich_freq));
			return (EIO);
		}
		asc->asc_curchan = hchan;

		/*
		 * Re-enable rx framework.
		 */
		if (ath_startrecv(asc) != 0) {
			ath_problem("ath: ath_chan_set(): "
			    "restarting receiving logic failed\n");
			return (EIO);
		}

		/*
		 * Change channels and update the h/w rate map
		 * if we're switching; e.g. 11a to 11b/g.
		 */
		ic->ic_ibss_chan = chan;
		ath_chan_change(asc, chan);
		/*
		 * Re-enable interrupts.
		 */
		ATH_HAL_INTRSET(ah, asc->asc_imask);
	}
	return (0);
}


/*
 * Configure the beacon and sleep timers.
 *
 * When operating as an AP this resets the TSF and sets
 * up the hardware to notify us when we need to issue beacons.
 *
 * When operating in station mode this sets up the beacon
 * timers according to the timestamp of the last received
 * beacon and the current TSF, configures PCF and DTIM
 * handling, programs the sleep registers so the hardware
 * will wakeup in time to receive beacons, and configures
 * the beacon miss handling so we'll receive a BMISS
 * interrupt when we stop seeing beacons from the AP
 * we've associated with.
 */
void
ath_beacon_config(ath_t *asc)
{
	struct ath_hal *ah = asc->asc_ah;
	ieee80211com_t *ic = (ieee80211com_t *)asc;
	struct ieee80211_node *in = ic->ic_bss;
	uint32_t nexttbtt;

	nexttbtt = (ATH_LE_READ_4(in->in_tstamp.data + 4) << 22) |
	    (ATH_LE_READ_4(in->in_tstamp.data) >> 10);
	nexttbtt += in->in_intval;
	if (ic->ic_opmode != IEEE80211_M_HOSTAP) {
		HAL_BEACON_STATE bs;

		/* NB: no PCF support right now */
		bzero(&bs, sizeof (bs));
		bs.bs_intval = in->in_intval;
		bs.bs_nexttbtt = nexttbtt;
		bs.bs_dtimperiod = bs.bs_intval;
		bs.bs_nextdtim = nexttbtt;

		/*
		 * Setup the number of consecutive beacons to miss
		 * before taking a BMISS interrupt.
		 * Note that we clamp the result to at most 10 beacons.
		 */
		bs.bs_bmissthreshold = ic->ic_bmissthreshold;
		if (bs.bs_bmissthreshold > 10)
			bs.bs_bmissthreshold = 10;
		else if (bs.bs_bmissthreshold <= 0)
			bs.bs_bmissthreshold = 1;
		/*
		 * Calculate sleep duration.  The configuration is
		 * given in ms.  We insure a multiple of the beacon
		 * period is used.  Also, if the sleep duration is
		 * greater than the DTIM period then it makes senses
		 * to make it a multiple of that.
		 */
		bs.bs_sleepduration =
		    roundup((100 * 1000) / 1024, bs.bs_intval);
		if (bs.bs_sleepduration > bs.bs_dtimperiod)
			bs.bs_sleepduration =
			    roundup(bs.bs_sleepduration, bs.bs_dtimperiod);


		ATH_DEBUG((ATH_DBG_AUX, "ath: ath_beacon_config(): "
		    "intval %u nexttbtt %u dtim %u"
		    " nextdtim %u bmiss %u sleep %u\n",
		    bs.bs_intval,
		    bs.bs_nexttbtt,
		    bs.bs_dtimperiod,
		    bs.bs_nextdtim,
		    bs.bs_bmissthreshold,
		    bs.bs_sleepduration));
		ATH_HAL_INTRSET(ah, 0);
		/*
		 * Reset our tsf so the hardware will update the
		 * tsf register to reflect timestamps found in
		 * received beacons.
		 */
		ATH_HAL_RESETTSF(ah);
		ATH_HAL_BEACONTIMERS(ah, &bs);
		asc->asc_imask |= HAL_INT_BMISS;
		ATH_HAL_INTRSET(ah, asc->asc_imask);
	} else {
		ATH_HAL_INTRSET(ah, 0);
		ATH_HAL_BEACONINIT(ah, nexttbtt, in->in_intval);
		asc->asc_imask |= HAL_INT_SWBA;	/* beacon prepare */
		ATH_HAL_INTRSET(ah, asc->asc_imask);
	}
}

/*
 * 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
key_alloc_2pair(ath_t *asc, ieee80211_keyix *txkeyix, ieee80211_keyix *rxkeyix)
{
	uint16_t i, keyix;

	ASSERT(asc->asc_splitmic);
	for (i = 0; i < ATH_N(asc->asc_keymap)/4; i++) {
		uint8_t b = asc->asc_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 (isset(asc->asc_keymap, keyix+32) ||
			    isset(asc->asc_keymap, keyix+64) ||
			    isset(asc->asc_keymap, keyix+32+64)) {
				/* full pair unavailable */
				if (keyix == (i+1)*NBBY) {
					/* no slots were appropriate, advance */
					continue;
				}
				goto again;
			}
			setbit(asc->asc_keymap, keyix);
			setbit(asc->asc_keymap, keyix+64);
			setbit(asc->asc_keymap, keyix+32);
			setbit(asc->asc_keymap, keyix+32+64);
			ATH_DEBUG((ATH_DBG_AUX,
			    "key_alloc_2pair: key pair %u,%u %u,%u\n",
			    keyix, keyix+64,
			    keyix+32, keyix+32+64));
			*txkeyix = *rxkeyix = keyix;
			return (1);
		}
	}
	ATH_DEBUG((ATH_DBG_AUX, "key_alloc_2pair:"
	    " out of pair space\n"));
	return (0);
}
/*
 * Allocate a single key cache slot.
 */
static int
key_alloc_single(ath_t *asc, 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 < ATH_N(asc->asc_keymap); i++) {
		uint8_t b = asc->asc_keymap[i];

		if (b != 0xff) {
			/*
			 * One or more slots are free.
			 */
			keyix = i*NBBY;
			while (b & 1)
				keyix++, b >>= 1;
			setbit(asc->asc_keymap, keyix);
			ATH_DEBUG((ATH_DBG_AUX, "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
ath_key_alloc(ieee80211com_t *ic, const struct ieee80211_key *k,
    ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
{
	ath_t *asc = (ath_t *)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 (key_alloc_single(asc, keyix, rxkeyix));
	} else if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP &&
	    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0 && asc->asc_splitmic) {
		return (key_alloc_2pair(asc, keyix, rxkeyix));
	} else {
		return (key_alloc_single(asc, keyix, rxkeyix));
	}
}

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

	ATH_DEBUG((ATH_DBG_AUX, "ath_key_delete:"
	    " delete key %u ic_cipher=0x%x\n", keyix, cip->ic_cipher));

	ATH_HAL_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 && asc->asc_splitmic)
		ATH_HAL_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.
		 */
		clrbit(asc->asc_keymap, keyix);
		if (cip->ic_cipher == IEEE80211_CIPHER_TKIP &&
		    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0 &&
		    asc->asc_splitmic) {
			clrbit(asc->asc_keymap, keyix+64);	/* TX key MIC */
			clrbit(asc->asc_keymap, keyix+32);	/* RX key */
			clrbit(asc->asc_keymap, keyix+32+64);	/* RX key MIC */
		}
	}
	return (1);
}

static void
ath_keyprint(const char *tag, uint_t ix,
    const HAL_KEYVAL *hk, const uint8_t mac[IEEE80211_ADDR_LEN])
{
	static const char *ciphers[] = {
		"WEP",
		"AES-OCB",
		"AES-CCM",
		"CKIP",
		"TKIP",
		"CLR",
	};
	int i, n;
	char buf[MAX_IEEE80211STR], buft[32];

	(void) snprintf(buf, sizeof (buf), "%s: [%02u] %s ",
	    tag, ix, ciphers[hk->kv_type]);
	for (i = 0, n = hk->kv_len; i < n; i++) {
		(void) snprintf(buft, sizeof (buft), "%02x", hk->kv_val[i]);
		(void) strlcat(buf, buft, sizeof (buf));
	}
	(void) snprintf(buft, sizeof (buft), " mac %s",
	    ieee80211_macaddr_sprintf(mac));
	(void) strlcat(buf, buft, sizeof (buf));
	if (hk->kv_type == HAL_CIPHER_TKIP) {
		(void) snprintf(buft, sizeof (buft), " mic ");
		(void) strlcat(buf, buft, sizeof (buf));
		for (i = 0; i < sizeof (hk->kv_mic); i++) {
			(void) snprintf(buft, sizeof (buft), "%02x",
			    hk->kv_mic[i]);
			(void) strlcat(buf, buft, sizeof (buf));
		}
	}
	ATH_DEBUG((ATH_DBG_AUX, "%s", buf));
}

/*
 * Set a TKIP key into the hardware.  This handles the
 * potential distribution of key state to multiple key
 * cache slots for TKIP.
 */
static int
ath_keyset_tkip(ath_t *asc, const struct ieee80211_key *k,
	HAL_KEYVAL *hk, const uint8_t mac[IEEE80211_ADDR_LEN])
{
#define	IEEE80211_KEY_XR	(IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV)
	static const uint8_t zerobssid[IEEE80211_ADDR_LEN] = {0, 0, 0, 0, 0, 0};
	struct ath_hal *ah = asc->asc_ah;

	ASSERT(k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP);
	if ((k->wk_flags & IEEE80211_KEY_XR) == IEEE80211_KEY_XR) {
		/*
		 * TX key goes at first index, RX key at +32.
		 * The hal handles the MIC keys at index+64.
		 */
		(void) memcpy(hk->kv_mic, k->wk_txmic, sizeof (hk->kv_mic));
		ath_keyprint("ath_keyset_tkip:", k->wk_keyix, hk, zerobssid);
		if (!ATH_HAL_KEYSET(ah, k->wk_keyix, hk, zerobssid))
			return (0);

		(void) memcpy(hk->kv_mic, k->wk_rxmic, sizeof (hk->kv_mic));
		ath_keyprint("ath_keyset_tkip:", k->wk_keyix+32, hk, mac);
		return (ATH_HAL_KEYSET(ah, k->wk_keyix+32, hk, mac));
	} else if (k->wk_flags & IEEE80211_KEY_XR) {
		/*
		 * TX/RX key goes at first index.
		 * The hal handles the MIC keys are index+64.
		 */
		(void) memcpy(hk->kv_mic, k->wk_flags & IEEE80211_KEY_XMIT ?
		    k->wk_txmic : k->wk_rxmic, sizeof (hk->kv_mic));
		ath_keyprint("ath_keyset_tkip:", k->wk_keyix, hk, zerobssid);
		return (ATH_HAL_KEYSET(ah, k->wk_keyix, hk, zerobssid));
	}
	return (0);
#undef IEEE80211_KEY_XR
}

/*
 * Set the key cache contents for the specified key.  Key cache
 * slot(s) must already have been allocated by ath_key_alloc.
 */
int
ath_key_set(ieee80211com_t *ic, const struct ieee80211_key *k,
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	static const uint8_t ciphermap[] = {
		HAL_CIPHER_WEP,		/* IEEE80211_CIPHER_WEP */
		HAL_CIPHER_TKIP,	/* IEEE80211_CIPHER_TKIP */
		HAL_CIPHER_AES_OCB,	/* IEEE80211_CIPHER_AES_OCB */
		HAL_CIPHER_AES_CCM,	/* IEEE80211_CIPHER_AES_CCM */
		HAL_CIPHER_CKIP,	/* IEEE80211_CIPHER_CKIP */
		HAL_CIPHER_CLR,		/* IEEE80211_CIPHER_NONE */
	};
	ath_t *asc = (ath_t *)ic;
	struct ath_hal *ah = asc->asc_ah;
	const struct ieee80211_cipher *cip = k->wk_cipher;
	HAL_KEYVAL hk;

	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 < ATH_N(ciphermap));
		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 = HAL_CIPHER_CLR;
	}

	if (hk.kv_type == HAL_CIPHER_TKIP &&
	    (k->wk_flags & IEEE80211_KEY_SWMIC) == 0 &&
	    asc->asc_splitmic) {
		return (ath_keyset_tkip(asc, k, &hk, mac));
	} else {
		ath_keyprint("ath_keyset:", k->wk_keyix, &hk, mac);
		return (ATH_HAL_KEYSET(ah, k->wk_keyix, &hk, mac));
	}
}

/*
 * Enable/Disable short slot timing
 */
void
ath_set_shortslot(ieee80211com_t *ic, int onoff)
{
	struct ath_hal *ah = ((ath_t *)ic)->asc_ah;

	if (onoff)
		ATH_HAL_SETSLOTTIME(ah, HAL_SLOT_TIME_9);
	else
		ATH_HAL_SETSLOTTIME(ah, HAL_SLOT_TIME_20);
}

int
ath_reset(ieee80211com_t *ic)
{
	ath_t *asc = (ath_t *)ic;
	struct ath_hal *ah = asc->asc_ah;
	struct ieee80211_channel *ch;
	HAL_STATUS status;
	HAL_CHANNEL hchan;

	/*
	 * Convert to a HAL channel description with the flags
	 * constrained to reflect the current operating mode.
	 */
	ch = ic->ic_curchan;
	asc->asc_curchan.channel = ch->ich_freq;
	asc->asc_curchan.channelFlags = ath_chan2flags(ic, ch);

	ATH_HAL_INTRSET(ah, 0);		/* disable interrupts */
	ath_draintxq(asc);		/* stop xmit side */
	if (ATH_IS_RUNNING(asc)) {
		ath_stoprecv(asc);		/* stop recv side */
		/* indicate channel change so we do a full reset */
		if (!ATH_HAL_RESET(ah, (HAL_OPMODE)ic->ic_opmode, &hchan,
		    AH_TRUE, &status)) {
			ath_problem("ath: ath_reset(): "
			    "resetting hardware failed, HAL status %u\n",
			    status);
		}
		ath_chan_change(asc, ch);
	}
	if (ATH_IS_RUNNING(asc)) {
		if (ath_startrecv(asc) != 0)	/* restart recv */
			ath_problem("ath: ath_reset(): "
			    "starting receiving logic failed\n");
		if (ic->ic_state == IEEE80211_S_RUN) {
			ath_beacon_config(asc);	/* restart beacons */
		}
		ATH_HAL_INTRSET(ah, asc->asc_imask);
	}
	return (0);
}