| /* |
| * Copyright 2010 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/sysmacros.h> |
| #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 <sys/net80211.h> |
| #include <sys/net80211_proto.h> |
| #include <sys/net80211_ht.h> |
| |
| |
| #include "arn_ath9k.h" |
| #include "arn_core.h" |
| #include "arn_reg.h" |
| #include "arn_hw.h" |
| |
| #define ARN_MAX_RSSI 45 /* max rssi */ |
| |
| /* |
| * Default 11n reates supported by this station. |
| */ |
| extern struct ieee80211_htrateset ieee80211_rateset_11n; |
| |
| /* |
| * 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, |
| DDI_DEFAULT_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, |
| DDI_DEFAULT_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; |
| static int arn_dwelltime = 200; /* scan interval */ |
| |
| 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, void *); |
| static void arn_m_propinfo(void *, const char *, mac_prop_id_t, |
| mac_prop_info_handle_t); |
| |
| /* MAC Callcack Functions */ |
| static mac_callbacks_t arn_m_callbacks = { |
| MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO, |
| arn_m_stat, |
| arn_m_start, |
| arn_m_stop, |
| arn_m_promisc, |
| arn_m_multicst, |
| arn_m_unicst, |
| arn_m_tx, |
| NULL, |
| arn_m_ioctl, |
| NULL, |
| NULL, |
| NULL, |
| arn_m_setprop, |
| arn_m_getprop, |
| arn_m_propinfo |
| }; |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * 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, |
| uint32_t buflen) |
| { |
| 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, |
| buflen, &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 */ |
| #ifdef ARN_TX_AGGREGRATION |
| sc->tx_dmabuf_size = |
| roundup((IEEE80211_MAX_MPDU_LEN + 3840 * 2), |
| min(sc->sc_cachelsz, (uint16_t)64)); |
| #else |
| sc->tx_dmabuf_size = |
| roundup(IEEE80211_MAX_MPDU_LEN, min(sc->sc_cachelsz, (uint16_t)64)); |
| #endif |
| sc->rx_dmabuf_size = |
| roundup(IEEE80211_MAX_MPDU_LEN, min(sc->sc_cachelsz, (uint16_t)64)); |
| |
| /* create RX buffer list */ |
| err = arn_buflist_setup(devinfo, sc, &sc->sc_rxbuf_list, &bf, &ds, |
| ATH_RXBUF, DDI_DMA_READ | DDI_DMA_STREAMING, sc->rx_dmabuf_size); |
| 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, sc->tx_dmabuf_size); |
| 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 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; |
| } |
| } |
| |
| uint8_t |
| parse_mpdudensity(uint8_t mpdudensity) |
| { |
| /* |
| * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing": |
| * 0 for no restriction |
| * 1 for 1/4 us |
| * 2 for 1/2 us |
| * 3 for 1 us |
| * 4 for 2 us |
| * 5 for 4 us |
| * 6 for 8 us |
| * 7 for 16 us |
| */ |
| switch (mpdudensity) { |
| case 0: |
| return (0); |
| case 1: |
| case 2: |
| case 3: |
| /* |
| * Our lower layer calculations limit our |
| * precision to 1 microsecond |
| */ |
| return (1); |
| case 4: |
| return (2); |
| case 5: |
| return (4); |
| case 6: |
| return (8); |
| case 7: |
| return (16); |
| default: |
| return (0); |
| } |
| } |
| |
| 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]; |
| uint32_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) |
| { |
| uint32_t channel_mode; |
| switch (ieee80211_chan2mode(isc, chan)) { |
| case IEEE80211_MODE_11NA: |
| if (chan->ich_flags & IEEE80211_CHAN_HT40U) |
| channel_mode = CHANNEL_A_HT40PLUS; |
| else if (chan->ich_flags & IEEE80211_CHAN_HT40D) |
| channel_mode = CHANNEL_A_HT40MINUS; |
| else |
| channel_mode = CHANNEL_A_HT20; |
| break; |
| case IEEE80211_MODE_11NG: |
| if (chan->ich_flags & IEEE80211_CHAN_HT40U) |
| channel_mode = CHANNEL_G_HT40PLUS; |
| else if (chan->ich_flags & IEEE80211_CHAN_HT40D) |
| channel_mode = CHANNEL_G_HT40MINUS; |
| else |
| channel_mode = CHANNEL_G_HT20; |
| break; |
| case IEEE80211_MODE_TURBO_G: |
| case IEEE80211_MODE_STURBO_A: |
| case IEEE80211_MODE_TURBO_A: |
| channel_mode = 0; |
| break; |
| case IEEE80211_MODE_11A: |
| channel_mode = CHANNEL_A; |
| break; |
| case IEEE80211_MODE_11G: |
| channel_mode = CHANNEL_B; |
| break; |
| case IEEE80211_MODE_11B: |
| channel_mode = CHANNEL_G; |
| break; |
| case IEEE80211_MODE_FH: |
| channel_mode = 0; |
| break; |
| default: |
| break; |
| } |
| |
| return (channel_mode); |
| } |
| |
| /* |
| * 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")); |
| #ifdef ARN_HW_BEACON_MISS_HANDLE |
| ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): " |
| "handle beacon mmiss by H/W mechanism\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"); |
| } |
| #else |
| ARN_DBG((ARN_DBG_INTERRUPT, "arn: arn_isr(): " |
| "handle beacon mmiss by S/W mechanism\n")); |
| #endif /* ARN_HW_BEACON_MISS_HANDLE */ |
| } |
| |
| 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; |
| } |
| |
| if (in->in_htcap & IEEE80211_HTCAP_CHWIDTH40) { |
| arn_update_chainmask(sc); |
| sc->tx_chan_width = ATH9K_HT_MACMODE_2040; |
| } else |
| 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); |
| /* Reset rssi stats */ |
| sc->sc_halstats.ns_avgbrssi = |
| ATH_RSSI_DUMMY_MARKER; |
| sc->sc_halstats.ns_avgrssi = |
| ATH_RSSI_DUMMY_MARKER; |
| sc->sc_halstats.ns_avgtxrssi = |
| ATH_RSSI_DUMMY_MARKER; |
| sc->sc_halstats.ns_avgtxrate = |
| ATH_RATE_DUMMY_MARKER; |
| /* end */ |
| |
| #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. |
| */ |
| #ifdef ARN_LEGACY_RC |
| 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); |
| } |
| #endif /* ARN_LEGACY_RC */ |
| |
| #ifdef ARN_HW_BEACON_MISS_HANDLE |
| /* nothing to do here */ |
| #else |
| /* currently set 10 seconds as beacon miss threshold */ |
| if (ic->ic_beaconmiss++ > 100) { |
| ARN_DBG((ARN_DBG_BEACON, "arn_watchdog():" |
| "Beacon missed for 10 seconds, run" |
| "ieee80211_new_state(ic, IEEE80211_S_INIT, -1)\n")); |
| ARN_UNLOCK(sc); |
| (void) ieee80211_new_state(ic, IEEE80211_S_INIT, -1); |
| return; |
| } |
| #endif /* ARN_HW_BEACON_MISS_HANDLE */ |
| |
| ntimer = 1; |
| } |
| ARN_UNLOCK(sc); |
| |
| ieee80211_watchdog(ic); |
| if (ntimer != 0) |
| ieee80211_start_watchdog(ic, ntimer); |
| } |
| |
| /* ARGSUSED */ |
| static struct ieee80211_node * |
| arn_node_alloc(ieee80211com_t *ic) |
| { |
| struct ath_node *an; |
| #ifdef ARN_TX_AGGREGATION |
| struct arn_softc *sc = (struct arn_softc *)ic; |
| #endif |
| |
| an = kmem_zalloc(sizeof (struct ath_node), KM_SLEEP); |
| |
| /* legacy rate control */ |
| #ifdef ARN_LEGACY_RC |
| arn_rate_update(sc, &an->an_node, 0); |
| #endif |
| |
| #ifdef ARN_TX_AGGREGATION |
| if (sc->sc_flags & SC_OP_TXAGGR) { |
| arn_tx_node_init(sc, an); |
| } |
| #endif /* ARN_TX_AGGREGATION */ |
| |
| an->last_rssi = ATH_RSSI_DUMMY_MARKER; |
| |
| 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; |
| |
| #ifdef ARN_TX_AGGREGATION |
| if (sc->sc_flags & SC_OP_TXAGGR) |
| arn_tx_node_cleanup(sc, in); |
| #endif /* TX_AGGREGATION */ |
| |
| 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); |
| |
| if (in->in_wme_ie != NULL) |
| ieee80211_free(in->in_wme_ie); |
| |
| if (in->in_htcap_ie != NULL) |
| ieee80211_free(in->in_htcap_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 wldp_length, void *wldp_buf) |
| { |
| struct arn_softc *sc = arg; |
| int err = 0; |
| |
| err = ieee80211_getprop(&sc->sc_isc, pr_name, wldp_pr_num, |
| wldp_length, wldp_buf); |
| |
| return (err); |
| } |
| |
| static void |
| arn_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, |
| mac_prop_info_handle_t prh) |
| { |
| struct arn_softc *sc = arg; |
| |
| ieee80211_propinfo(&sc->sc_isc, pr_name, wldp_pr_num, prh); |
| } |
| |
| /* 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 void |
| arn_setup_ht_cap(struct arn_softc *sc) |
| { |
| #define ATH9K_HT_CAP_MAXRXAMPDU_65536 0x3 /* 2 ^ 16 */ |
| #define ATH9K_HT_CAP_MPDUDENSITY_8 0x6 /* 8 usec */ |
| |
| uint8_t rx_streams; |
| |
| arn_ht_conf *ht_info = &sc->sc_ht_conf; |
| |
| ht_info->ht_supported = B_TRUE; |
| |
| /* Todo: IEEE80211_HTCAP_SMPS */ |
| ht_info->cap = IEEE80211_HTCAP_CHWIDTH40| |
| IEEE80211_HTCAP_SHORTGI40 | |
| IEEE80211_HTCAP_DSSSCCK40; |
| |
| ht_info->ampdu_factor = ATH9K_HT_CAP_MAXRXAMPDU_65536; |
| ht_info->ampdu_density = ATH9K_HT_CAP_MPDUDENSITY_8; |
| |
| /* set up supported mcs set */ |
| (void) memset(&ht_info->rx_mcs_mask, 0, sizeof (ht_info->rx_mcs_mask)); |
| rx_streams = ISP2(sc->sc_ah->ah_caps.rx_chainmask) ? 1 : 2; |
| |
| ht_info->rx_mcs_mask[0] = 0xff; |
| if (rx_streams >= 2) |
| ht_info->rx_mcs_mask[1] = 0xff; |
| } |
| |
| /* xxx should be used for ht rate set negotiating ? */ |
| static void |
| arn_overwrite_11n_rateset(struct arn_softc *sc) |
| { |
| uint8_t *ht_rs = sc->sc_ht_conf.rx_mcs_mask; |
| int mcs_idx, mcs_count = 0; |
| int i, j; |
| |
| (void) memset(&ieee80211_rateset_11n, 0, |
| sizeof (ieee80211_rateset_11n)); |
| for (i = 0; i < 10; i++) { |
| for (j = 0; j < 8; j++) { |
| if (ht_rs[i] & (1 << j)) { |
| mcs_idx = i * 8 + j; |
| if (mcs_idx >= IEEE80211_HTRATE_MAXSIZE) { |
| break; |
| } |
| |
| ieee80211_rateset_11n.rs_rates[mcs_idx] = |
| (uint8_t)mcs_idx; |
| mcs_count++; |
| } |
| } |
| } |
| |
| ieee80211_rateset_11n.rs_nrates = (uint8_t)mcs_count; |
| |
| ARN_DBG((ARN_DBG_RATE, "arn_overwrite_11n_rateset(): " |
| "MCS rate set supported by this station is as follows:\n")); |
| |
| for (i = 0; i < ieee80211_rateset_11n.rs_nrates; i++) { |
| ARN_DBG((ARN_DBG_RATE, "MCS rate %d is %d\n", |
| i, ieee80211_rateset_11n.rs_rates[i])); |
| } |
| |
| } |
| |
| /* |
| * Update WME parameters for a transmit queue. |
| */ |
| static int |
| arn_tx_queue_update(struct arn_softc *sc, int ac) |
| { |
| #define ATH_EXPONENT_TO_VALUE(v) ((1<<v)-1) |
| #define ATH_TXOP_TO_US(v) (v<<5) |
| ieee80211com_t *ic = (ieee80211com_t *)sc; |
| struct ath_txq *txq; |
| struct wmeParams *wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[ac]; |
| struct ath_hal *ah = sc->sc_ah; |
| struct ath9k_tx_queue_info qi; |
| |
| txq = &sc->sc_txq[arn_get_hal_qnum(ac, sc)]; |
| (void) ath9k_hw_get_txq_props(ah, txq->axq_qnum, &qi); |
| |
| /* |
| * TXQ_FLAG_TXOKINT_ENABLE = 0x0001 |
| * TXQ_FLAG_TXERRINT_ENABLE = 0x0001 |
| * TXQ_FLAG_TXDESCINT_ENABLE = 0x0002 |
| * TXQ_FLAG_TXEOLINT_ENABLE = 0x0004 |
| * TXQ_FLAG_TXURNINT_ENABLE = 0x0008 |
| * TXQ_FLAG_BACKOFF_DISABLE = 0x0010 |
| * TXQ_FLAG_COMPRESSION_ENABLE = 0x0020 |
| * TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE = 0x0040 |
| * TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE = 0x0080 |
| */ |
| |
| /* xxx should update these flags here? */ |
| #if 0 |
| qi.tqi_qflags = TXQ_FLAG_TXOKINT_ENABLE | |
| TXQ_FLAG_TXERRINT_ENABLE | |
| TXQ_FLAG_TXDESCINT_ENABLE | |
| TXQ_FLAG_TXURNINT_ENABLE; |
| #endif |
| |
| qi.tqi_aifs = wmep->wmep_aifsn; |
| qi.tqi_cwmin = ATH_EXPONENT_TO_VALUE(wmep->wmep_logcwmin); |
| qi.tqi_cwmax = ATH_EXPONENT_TO_VALUE(wmep->wmep_logcwmax); |
| qi.tqi_readyTime = 0; |
| qi.tqi_burstTime = ATH_TXOP_TO_US(wmep->wmep_txopLimit); |
| |
| ARN_DBG((ARN_DBG_INIT, |
| "%s:" |
| "Q%u" |
| "qflags 0x%x" |
| "aifs %u" |
| "cwmin %u" |
| "cwmax %u" |
| "burstTime %u\n", |
| __func__, |
| txq->axq_qnum, |
| qi.tqi_qflags, |
| qi.tqi_aifs, |
| qi.tqi_cwmin, |
| qi.tqi_cwmax, |
| qi.tqi_burstTime)); |
| |
| if (!ath9k_hw_set_txq_props(ah, txq->axq_qnum, &qi)) { |
| arn_problem("unable to update hardware queue " |
| "parameters for %s traffic!\n", |
| ieee80211_wme_acnames[ac]); |
| return (0); |
| } else { |
| /* push to H/W */ |
| (void) ath9k_hw_resettxqueue(ah, txq->axq_qnum); |
| return (1); |
| } |
| |
| #undef ATH_TXOP_TO_US |
| #undef ATH_EXPONENT_TO_VALUE |
| } |
| |
| /* Update WME parameters */ |
| static int |
| arn_wme_update(ieee80211com_t *ic) |
| { |
| struct arn_softc *sc = (struct arn_softc *)ic; |
| |
| /* updateing */ |
| return (!arn_tx_queue_update(sc, WME_AC_BE) || |
| !arn_tx_queue_update(sc, WME_AC_BK) || |
| !arn_tx_queue_update(sc, WME_AC_VI) || |
| !arn_tx_queue_update(sc, WME_AC_VO) ? EIO : 0); |
| } |
| |
| /* |
| * Update tx/rx chainmask. For legacy association, |
| * hard code chainmask to 1x1, for 11n association, use |
| * the chainmask configuration. |
| */ |
| void |
| arn_update_chainmask(struct arn_softc *sc) |
| { |
| boolean_t is_ht = B_FALSE; |
| sc->sc_flags |= SC_OP_CHAINMASK_UPDATE; |
| |
| is_ht = sc->sc_ht_conf.ht_supported; |
| if (is_ht) { |
| sc->sc_tx_chainmask = sc->sc_ah->ah_caps.tx_chainmask; |
| sc->sc_rx_chainmask = sc->sc_ah->ah_caps.rx_chainmask; |
| } else { |
| sc->sc_tx_chainmask = 1; |
| sc->sc_rx_chainmask = 1; |
| } |
| |
| ARN_DBG((ARN_DBG_ATTACH, "arn: arn_attach(): " |
| "tx_chainmask = %d, rx_chainmask = %d\n", |
| sc->sc_tx_chainmask, sc->sc_rx_chainmask)); |
| } |
| |
| 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 |