ath_aux.c revision 22eb7cb54d8a6bcf6fe2674cb4b1f0cf2d85cfb6
/*
* Copyright 2008 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);
const char *
ath_get_hal_status_desc(HAL_STATUS status)
{
static const char *hal_status_desc[] = {
"No error",
"No hardware present or device not yet supported",
"Memory allocation failed",
"Hardware didn't respond as expected",
"EEPROM magic number invalid",
"EEPROM version invalid",
"EEPROM unreadable",
"EEPROM checksum invalid",
"EEPROM read problem",
"EEPROM mac address invalid",
"EEPROM size not supported",
"Attempt to change write-locked EEPROM",
"Invalid parameter to function",
"Hardware revision not supported",
"Hardware self-test failed",
"Operation incomplete"
};
if (status >= 0 && status < sizeof (hal_status_desc)/sizeof (char *))
return (hal_status_desc[status]);
else
return ("");
}
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)
rfilt |= HAL_RX_FILTER_PROM; /* promiscuous */
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_mcast_hash[0],
asc->asc_mcast_hash[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_mcast_hash[0], asc->asc_mcast_hash[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"
"flags 0x%x: '%s' (HAL status %u)\n",
ieee80211_chan2ieee(ic, chan), hchan.channel,
hchan.channelFlags,
ath_get_hal_status_desc(status), status));
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 < 1)
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;
/*
* 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,
&asc->asc_curchan, AH_TRUE, &status)) {
ath_problem("ath: ath_reset(): "
"resetting hardware failed, '%s' (HAL status %u)\n",
ath_get_hal_status_desc(status), 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);
}