diff options
author | xc151355 <none@none> | 2005-11-27 19:11:23 -0800 |
---|---|---|
committer | xc151355 <none@none> | 2005-11-27 19:11:23 -0800 |
commit | 7a1306a70fee0e017a445bde1dcfd1997f691cf4 (patch) | |
tree | 383e9f41f9a495e95fa1376b4b36b3fba7ea3620 /usr/src/uts/common/io/ath/ath_aux.c | |
parent | e23a7e348c32cb3a1a7072dcac6905a150a028d7 (diff) | |
download | illumos-gate-7a1306a70fee0e017a445bde1dcfd1997f691cf4.tar.gz |
PSARC 2005/440 Atheros 802.11b/g driver
5050300 Solaris need Atheros AR5212 WiFi driver
Diffstat (limited to 'usr/src/uts/common/io/ath/ath_aux.c')
-rw-r--r-- | usr/src/uts/common/io/ath/ath_aux.c | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/ath/ath_aux.c b/usr/src/uts/common/io/ath/ath_aux.c new file mode 100644 index 0000000000..fb119a22af --- /dev/null +++ b/usr/src/uts/common/io/ath/ath_aux.c @@ -0,0 +1,601 @@ +/* + * Copyright 2005 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" +#include "ath_ieee80211.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 *isc = (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 (isc->isc_opmode != IEEE80211_M_STA) + rfilt |= HAL_RX_FILTER_PROBEREQ; + if (isc->isc_opmode != IEEE80211_M_HOSTAP && + (asc->asc_promisc & GLD_MAC_PROMISC_PHYS)) /* promiscuous */ + rfilt |= HAL_RX_FILTER_PROM; + if (isc->isc_opmode == IEEE80211_M_STA || + isc->isc_opmode == IEEE80211_M_IBSS || + isc->isc_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 = TXQ_FLAG_TXEOLINT_ENABLE | TXQ_FLAG_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_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 *isc = (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", + isc->isc_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 ieee80211channel *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 */ + CHANNEL_T /* IEEE80211_MODE_TURBO */ + }; + return (modeflags[ieee80211_chan2mode(isc, chan)]); +} + + +int +ath_getchannels(ath_t *asc, uint32_t cc, HAL_BOOL outdoor, HAL_BOOL xchanmode) +{ + ieee80211com_t *isc = (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, + 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]; + ix = ath_hal_mhz2ieee(c->channel, c->channelFlags); + if (ix > IEEE80211_CHAN_MAX) { + ATH_DEBUG((ATH_DBG_AUX, "ath: ath_getchannels(): " + "bad hal channel %u (%u/%x) ignored\n", + ix, c->channel, c->channelFlags)); + continue; + } + /* NB: flags are known to be compatible */ + if (isc->isc_channels[ix].ich_freq == 0) { + isc->isc_channels[ix].ich_freq = c->channel; + isc->isc_channels[ix].ich_flags = c->channelFlags; + } else { + /* channels overlap; e.g. 11g and 11b */ + isc->isc_channels[ix].ich_flags |= c->channelFlags; + } + 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); +} + +/* + * 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 ieee80211channel *chan) +{ + struct ath_hal *ah = asc->asc_ah; + ieee80211com_t *isc = &asc->asc_isc; + + if (chan != isc->isc_ibss_chan) { + HAL_STATUS status; + HAL_CHANNEL hchan; + enum ieee80211_phymode mode; + + /* + * 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(isc, chan); + if (!ATH_HAL_RESET(ah, (HAL_OPMODE)isc->isc_opmode, + &hchan, AH_TRUE, &status)) { + ATH_DEBUG((ATH_DBG_AUX, "ath: ath_chan_set():" + "unable to reset channel %u (%uMhz)\n", + ieee80211_chan2ieee(isc, chan), chan->ich_freq)); + return (EIO); + } + + /* + * 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. + */ + isc->isc_ibss_chan = chan; + mode = ieee80211_chan2mode(isc, chan); + if (mode != asc->asc_curmode) + ath_setcurmode(asc, mode); + /* + * 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 *isc = (ieee80211com_t *)asc; + struct ieee80211_node *in = isc->isc_bss; + uint32_t nexttbtt; + + nexttbtt = (ATH_LE_READ_4(in->in_tstamp + 4) << 22) | + (ATH_LE_READ_4(in->in_tstamp) >> 10); + nexttbtt += in->in_intval; + if (isc->isc_opmode != IEEE80211_M_HOSTAP) { + HAL_BEACON_STATE bs; + uint32_t bmisstime; + + /* 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; + + /* + * Calculate the number of consecutive beacons to miss + * before taking a BMISS interrupt. The configuration + * is specified in ms, so we need to convert that to + * TU's and then calculate based on the beacon interval. + * Note that we clamp the result to at most 10 beacons. + */ + bmisstime = (isc->isc_bmisstimeout * 1000) / 1024; + bs.bs_bmissthreshold = howmany(bmisstime, in->in_intval); + 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); + } +} + + + +/* + * Fill the hardware key cache with key entries. + */ +void +ath_initkeytable(ath_t *asc) +{ + ieee80211com_t *isc = (ieee80211com_t *)asc; + struct ath_hal *ah = asc->asc_ah; + int32_t i; + + for (i = 0; i < IEEE80211_WEP_NKID; i++) { + struct ieee80211_wepkey *k = &isc->isc_nw_keys[i]; + if (k->iwk_len == 0) + ATH_HAL_KEYRESET(ah, i); + else { + HAL_KEYVAL hk; + +#ifdef DEBUG + char tmp[200], stmp[10]; + int j; + bzero(tmp, 200); + bzero(stmp, 10); + for (j = 0; j < k->iwk_len; j++) { + (void) sprintf(stmp, "0x%02x ", k->iwk_key[j]); + (void) strcat(tmp, stmp); + } + ATH_DEBUG((ATH_DBG_AUX, "ath: ath_initkeytable(): " + "key%d val=%s\n", i, tmp)); +#endif /* DEBUG */ + bzero(&hk, sizeof (hk)); + hk.kv_type = HAL_CIPHER_WEP; + hk.kv_len = k->iwk_len; + bcopy(k->iwk_key, hk.kv_val, k->iwk_len); + ATH_HAL_KEYSET(ah, i, &hk); + } + } +} + +void +ath_reset(ath_t *asc) +{ + ieee80211com_t *isc = (ieee80211com_t *)asc; + struct ath_hal *ah = asc->asc_ah; + struct ieee80211channel *ch; + HAL_STATUS status; + HAL_CHANNEL hchan; + + /* + * Convert to a HAL channel description with the flags + * constrained to reflect the current operating mode. + */ + ch = isc->isc_ibss_chan; + hchan.channel = ch->ich_freq; + hchan.channelFlags = ath_chan2flags(isc, ch); + + ATH_HAL_INTRSET(ah, 0); /* disable interrupts */ + ath_draintxq(asc); /* stop xmit side */ + if (asc->asc_invalid == 0) + ath_stoprecv(asc); /* stop recv side */ + /* indicate channel change so we do a full reset */ + if (!ATH_HAL_RESET(ah, (HAL_OPMODE)isc->isc_opmode, &hchan, + AH_TRUE, &status)) { + ath_problem("ath: ath_reset(): " + "reseting hardware failed, HAL status %u\n", status); + } + if (asc->asc_invalid == 0) { + ath_initkeytable(asc); + if (ath_startrecv(asc) != 0) /* restart recv */ + ath_problem("ath: ath_reset(): " + "starting receiving logic failed\n"); + if (isc->isc_state == IEEE80211_S_RUN) { + ath_beacon_config(asc); /* restart beacons */ + } + ATH_HAL_INTRSET(ah, asc->asc_imask); + } +} |