/* * Copyright 2006 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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<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 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) { *keyix = *rxkeyix = 0; return (1); } int ath_key_delete(ieee80211com_t *ic, const struct ieee80211_key *k) { struct ath_hal *ah = ((ath_t *)ic)->asc_ah; ATH_HAL_KEYRESET(ah, k->wk_keyix); return (1); } /* * 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 */ (uint8_t)-1, /* 4 is not allocated */ 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; } 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); }