blob: 98d08923262b3040b6a93c8061c77f5630530e2d [file] [log] [blame]
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2007-2009 Sam Leffler, Errno Consulting
* Copyright (c) 2007-2008 Marvell Semiconductor, Inc.
* 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.
*
* 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.
*/
/*
* Driver for the Marvell 88W8363 Wireless LAN controller.
*/
#include <sys/stat.h>
#include <sys/dlpi.h>
#include <inet/common.h>
#include <inet/mi.h>
#include <sys/stream.h>
#include <sys/errno.h>
#include <sys/stropts.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/pci.h>
#include <sys/mac_provider.h>
#include <sys/mac_wifi.h>
#include <sys/net80211.h>
#include <inet/wifi_ioctl.h>
#include "mwl_var.h"
static int mwl_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd);
static int mwl_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd);
static int mwl_quiesce(dev_info_t *devinfo);
DDI_DEFINE_STREAM_OPS(mwl_dev_ops, nulldev, nulldev, mwl_attach, mwl_detach,
nodev, NULL, D_MP, NULL, mwl_quiesce);
static struct modldrv mwl_modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"Marvell 88W8363 WiFi driver v1.1", /* short description */
&mwl_dev_ops /* driver specific ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&mwl_modldrv, NULL
};
static void *mwl_soft_state_p = NULL;
static int mwl_m_stat(void *, uint_t, uint64_t *);
static int mwl_m_start(void *);
static void mwl_m_stop(void *);
static int mwl_m_promisc(void *, boolean_t);
static int mwl_m_multicst(void *, boolean_t, const uint8_t *);
static int mwl_m_unicst(void *, const uint8_t *);
static mblk_t *mwl_m_tx(void *, mblk_t *);
static void mwl_m_ioctl(void *, queue_t *, mblk_t *);
static int mwl_m_setprop(void *arg, const char *pr_name,
mac_prop_id_t wldp_pr_num,
uint_t wldp_length, const void *wldp_buf);
static int mwl_m_getprop(void *arg, const char *pr_name,
mac_prop_id_t wldp_pr_num, uint_t wldp_length,
void *wldp_buf);
static void mwl_m_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
static mac_callbacks_t mwl_m_callbacks = {
MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
mwl_m_stat,
mwl_m_start,
mwl_m_stop,
mwl_m_promisc,
mwl_m_multicst,
mwl_m_unicst,
mwl_m_tx,
NULL,
mwl_m_ioctl,
NULL,
NULL,
NULL,
mwl_m_setprop,
mwl_m_getprop,
mwl_m_propinfo
};
#define MWL_DBG_ATTACH (1 << 0)
#define MWL_DBG_DMA (1 << 1)
#define MWL_DBG_FW (1 << 2)
#define MWL_DBG_HW (1 << 3)
#define MWL_DBG_INTR (1 << 4)
#define MWL_DBG_RX (1 << 5)
#define MWL_DBG_TX (1 << 6)
#define MWL_DBG_CMD (1 << 7)
#define MWL_DBG_CRYPTO (1 << 8)
#define MWL_DBG_SR (1 << 9)
#define MWL_DBG_MSG (1 << 10)
uint32_t mwl_dbg_flags = 0x0;
#ifdef DEBUG
#define MWL_DBG \
mwl_debug
#else
#define MWL_DBG
#endif
/*
* PIO access attributes for registers
*/
static ddi_device_acc_attr_t mwl_reg_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC
};
static ddi_device_acc_attr_t mwl_cmdbuf_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC
};
/*
* DMA access attributes for descriptors and bufs: NOT to be byte swapped.
*/
static ddi_device_acc_attr_t mwl_desc_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC
};
static ddi_device_acc_attr_t mwl_buf_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC,
DDI_DEFAULT_ACC
};
/*
* Describes the chip's DMA engine
*/
static ddi_dma_attr_t mwl_dma_attr = {
DMA_ATTR_V0, /* dma_attr version */
0x0000000000000000ull, /* dma_attr_addr_lo */
0xFFFFFFFF, /* dma_attr_addr_hi */
0x00000000FFFFFFFFull, /* dma_attr_count_max */
0x0000000000000001ull, /* dma_attr_align */
0x00000FFF, /* dma_attr_burstsizes */
0x00000001, /* dma_attr_minxfer */
0x000000000000FFFFull, /* dma_attr_maxxfer */
0xFFFFFFFFFFFFFFFFull, /* dma_attr_seg */
1, /* dma_attr_sgllen */
0x00000001, /* dma_attr_granular */
0 /* dma_attr_flags */
};
/*
* Supported rates for 802.11a/b/g modes (in 500Kbps unit).
*/
static const struct ieee80211_rateset mwl_rateset_11b =
{ 4, { 2, 4, 11, 22 } };
static const struct ieee80211_rateset mwl_rateset_11g =
{ 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } };
static int mwl_alloc_dma_mem(dev_info_t *, ddi_dma_attr_t *, size_t,
ddi_device_acc_attr_t *, uint_t, uint_t,
struct dma_area *);
static void mwl_free_dma_mem(struct dma_area *);
static int mwl_alloc_cmdbuf(struct mwl_softc *);
static void mwl_free_cmdbuf(struct mwl_softc *);
static int mwl_alloc_rx_ring(struct mwl_softc *, int);
static void mwl_free_rx_ring(struct mwl_softc *);
static int mwl_alloc_tx_ring(struct mwl_softc *, struct mwl_tx_ring *,
int);
static void mwl_free_tx_ring(struct mwl_softc *, struct mwl_tx_ring *);
static int mwl_setupdma(struct mwl_softc *);
static void mwl_txq_init(struct mwl_softc *, struct mwl_tx_ring *, int);
static int mwl_tx_setup(struct mwl_softc *, int, int);
static int mwl_setup_txq(struct mwl_softc *);
static int mwl_fwload(struct mwl_softc *, void *);
static int mwl_loadsym(ddi_modhandle_t, char *, char **, size_t *);
static void mwlFwReset(struct mwl_softc *);
static void mwlPokeSdramController(struct mwl_softc *, int);
static void mwlTriggerPciCmd(struct mwl_softc *);
static int mwlWaitFor(struct mwl_softc *, uint32_t);
static int mwlSendBlock(struct mwl_softc *, int, const void *, size_t);
static int mwlSendBlock2(struct mwl_softc *, const void *, size_t);
static void mwlSendCmd(struct mwl_softc *);
static int mwlExecuteCmd(struct mwl_softc *, unsigned short);
static int mwlWaitForCmdComplete(struct mwl_softc *, uint16_t);
static void dumpresult(struct mwl_softc *, int);
static int mwlResetHalState(struct mwl_softc *);
static int mwlGetPwrCalTable(struct mwl_softc *);
static int mwlGetCalTable(struct mwl_softc *, uint8_t, uint8_t);
static int mwlGetPwrCalTable(struct mwl_softc *);
static void dumpcaldata(const char *, const uint8_t *, int);
static void get2Ghz(MWL_HAL_CHANNELINFO *, const uint8_t *, int);
static void get5Ghz(MWL_HAL_CHANNELINFO *, const uint8_t *, int);
static void setmaxtxpow(struct mwl_hal_channel *, int, int);
static uint16_t ieee2mhz(int);
static const char *
mwlcmdname(int);
static int mwl_gethwspecs(struct mwl_softc *);
static int mwl_getchannels(struct mwl_softc *);
static void getchannels(struct mwl_softc *, int, int *,
struct mwl_channel *);
static void addchannels(struct mwl_channel *, int, int *,
const MWL_HAL_CHANNELINFO *, int);
static void addht40channels(struct mwl_channel *, int, int *,
const MWL_HAL_CHANNELINFO *, int);
static const struct mwl_channel *
findchannel(const struct mwl_channel *, int,
int, int);
static void addchan(struct mwl_channel *, int, int, int, int);
static int mwl_chan_set(struct mwl_softc *, struct mwl_channel *);
static void mwl_mapchan(MWL_HAL_CHANNEL *, const struct mwl_channel *);
static int mwl_setcurchanrates(struct mwl_softc *);
const struct ieee80211_rateset *
mwl_get_suprates(struct ieee80211com *,
const struct mwl_channel *);
static uint32_t cvtChannelFlags(const MWL_HAL_CHANNEL *);
static const struct mwl_hal_channel *
findhalchannel(const struct mwl_softc *,
const MWL_HAL_CHANNEL *);
enum ieee80211_phymode
mwl_chan2mode(const struct mwl_channel *);
static int mwl_map2regioncode(const struct mwl_regdomain *);
static int mwl_startrecv(struct mwl_softc *);
static int mwl_mode_init(struct mwl_softc *);
static void mwl_hal_intrset(struct mwl_softc *, uint32_t);
static void mwl_hal_getisr(struct mwl_softc *, uint32_t *);
static int mwl_hal_sethwdma(struct mwl_softc *,
const struct mwl_hal_txrxdma *);
static int mwl_hal_getchannelinfo(struct mwl_softc *, int, int,
const MWL_HAL_CHANNELINFO **);
static int mwl_hal_setmac_locked(struct mwl_softc *, const uint8_t *);
static int mwl_hal_keyreset(struct mwl_softc *, const MWL_HAL_KEYVAL *,
const uint8_t mac[IEEE80211_ADDR_LEN]);
static int mwl_hal_keyset(struct mwl_softc *, const MWL_HAL_KEYVAL *,
const uint8_t mac[IEEE80211_ADDR_LEN]);
static int mwl_hal_newstation(struct mwl_softc *, const uint8_t *,
uint16_t, uint16_t, const MWL_HAL_PEERINFO *, int, int);
static int mwl_hal_setantenna(struct mwl_softc *, MWL_HAL_ANTENNA, int);
static int mwl_hal_setradio(struct mwl_softc *, int, MWL_HAL_PREAMBLE);
static int mwl_hal_setwmm(struct mwl_softc *, int);
static int mwl_hal_setchannel(struct mwl_softc *, const MWL_HAL_CHANNEL *);
static int mwl_hal_settxpower(struct mwl_softc *, const MWL_HAL_CHANNEL *,
uint8_t);
static int mwl_hal_settxrate(struct mwl_softc *, MWL_HAL_TXRATE_HANDLING,
const MWL_HAL_TXRATE *);
static int mwl_hal_settxrate_auto(struct mwl_softc *,
const MWL_HAL_TXRATE *);
static int mwl_hal_setrateadaptmode(struct mwl_softc *, uint16_t);
static int mwl_hal_setoptimizationlevel(struct mwl_softc *, int);
static int mwl_hal_setregioncode(struct mwl_softc *, int);
static int mwl_hal_setassocid(struct mwl_softc *, const uint8_t *,
uint16_t);
static int mwl_setrates(struct ieee80211com *);
static int mwl_hal_setrtsthreshold(struct mwl_softc *, int);
static int mwl_hal_setcsmode(struct mwl_softc *, MWL_HAL_CSMODE);
static int mwl_hal_setpromisc(struct mwl_softc *, int);
static int mwl_hal_start(struct mwl_softc *);
static int mwl_hal_setinframode(struct mwl_softc *);
static int mwl_hal_stop(struct mwl_softc *);
static struct ieee80211_node *
mwl_node_alloc(struct ieee80211com *);
static void mwl_node_free(struct ieee80211_node *);
static int mwl_key_alloc(struct ieee80211com *,
const struct ieee80211_key *,
ieee80211_keyix *, ieee80211_keyix *);
static int mwl_key_delete(struct ieee80211com *,
const struct ieee80211_key *);
static int mwl_key_set(struct ieee80211com *, const struct ieee80211_key *,
const uint8_t mac[IEEE80211_ADDR_LEN]);
static void mwl_setanywepkey(struct ieee80211com *, const uint8_t *);
static void mwl_setglobalkeys(struct ieee80211com *c);
static int addgroupflags(MWL_HAL_KEYVAL *, const struct ieee80211_key *);
static void mwl_hal_txstart(struct mwl_softc *, int);
static int mwl_send(ieee80211com_t *, mblk_t *, uint8_t);
static void mwl_next_scan(void *);
static MWL_HAL_PEERINFO *
mkpeerinfo(MWL_HAL_PEERINFO *, const struct ieee80211_node *);
static uint32_t get_rate_bitmap(const struct ieee80211_rateset *);
static int mwl_newstate(struct ieee80211com *, enum ieee80211_state, int);
static int cvtrssi(uint8_t);
static uint_t mwl_intr(caddr_t, caddr_t);
static uint_t mwl_softintr(caddr_t, caddr_t);
static void mwl_tx_intr(struct mwl_softc *);
static void mwl_rx_intr(struct mwl_softc *);
static int mwl_init(struct mwl_softc *);
static void mwl_stop(struct mwl_softc *);
static int mwl_resume(struct mwl_softc *);
#ifdef DEBUG
static void
mwl_debug(uint32_t dbg_flags, const int8_t *fmt, ...)
{
va_list args;
if (dbg_flags & mwl_dbg_flags) {
va_start(args, fmt);
vcmn_err(CE_CONT, fmt, args);
va_end(args);
}
}
#endif
/*
* Allocate an DMA memory and a DMA handle for accessing it
*/
static int
mwl_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, struct dma_area *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) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_dma_mem(): "
"failed to alloc handle\n");
goto fail1;
}
/*
* 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) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_dma_mem(): "
"failed to alloc mem\n");
goto fail2;
}
/*
* 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) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_dma_mem(): "
"failed to bind handle\n");
goto fail3;
}
if (dma_p->ncookies != 1) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_dma_mem(): "
"failed to alloc cookies\n");
goto fail4;
}
dma_p->nslots = ~0U;
dma_p->size = ~0U;
dma_p->token = ~0U;
dma_p->offset = 0;
return (DDI_SUCCESS);
fail4:
(void) ddi_dma_unbind_handle(dma_p->dma_hdl);
fail3:
ddi_dma_mem_free(&dma_p->acc_hdl);
fail2:
ddi_dma_free_handle(&dma_p->dma_hdl);
fail1:
return (err);
}
static void
mwl_free_dma_mem(struct dma_area *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;
}
}
static int
mwl_alloc_cmdbuf(struct mwl_softc *sc)
{
int err;
size_t size;
size = MWL_CMDBUF_SIZE;
err = mwl_alloc_dma_mem(sc->sc_dev, &mwl_dma_attr, size,
&mwl_cmdbuf_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
&sc->sc_cmd_dma);
if (err != DDI_SUCCESS) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_cmdbuf(): "
"failed to alloc dma mem\n");
return (DDI_FAILURE);
}
sc->sc_cmd_mem = (uint16_t *)sc->sc_cmd_dma.mem_va;
sc->sc_cmd_dmaaddr = sc->sc_cmd_dma.cookie.dmac_address;
return (DDI_SUCCESS);
}
static void
mwl_free_cmdbuf(struct mwl_softc *sc)
{
if (sc->sc_cmd_mem != NULL)
mwl_free_dma_mem(&sc->sc_cmd_dma);
}
static int
mwl_alloc_rx_ring(struct mwl_softc *sc, int count)
{
struct mwl_rx_ring *ring;
struct mwl_rxdesc *ds;
struct mwl_rxbuf *bf;
int i, err, datadlen;
ring = &sc->sc_rxring;
ring->count = count;
ring->cur = ring->next = 0;
err = mwl_alloc_dma_mem(sc->sc_dev, &mwl_dma_attr,
count * sizeof (struct mwl_rxdesc),
&mwl_desc_accattr,
DDI_DMA_CONSISTENT, DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
&ring->rxdesc_dma);
if (err) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_rxring(): "
"alloc tx ring failed, size %d\n",
(uint32_t)(count * sizeof (struct mwl_rxdesc)));
return (DDI_FAILURE);
}
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_rx_ring(): "
"dma len = %d\n", (uint32_t)(ring->rxdesc_dma.alength));
ring->desc = (struct mwl_rxdesc *)ring->rxdesc_dma.mem_va;
ring->physaddr = ring->rxdesc_dma.cookie.dmac_address;
bzero(ring->desc, count * sizeof (struct mwl_rxdesc));
datadlen = count * sizeof (struct mwl_rxbuf);
ring->buf = (struct mwl_rxbuf *)kmem_zalloc(datadlen, KM_SLEEP);
if (ring->buf == NULL) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_rxring(): "
"could not alloc rx ring data buffer\n");
return (DDI_FAILURE);
}
bzero(ring->buf, count * sizeof (struct mwl_rxbuf));
/*
* Pre-allocate Rx buffers and populate Rx ring.
*/
for (i = 0; i < count; i++) {
ds = &ring->desc[i];
bf = &ring->buf[i];
/* alloc DMA memory */
(void) mwl_alloc_dma_mem(sc->sc_dev, &mwl_dma_attr,
sc->sc_dmabuf_size,
&mwl_buf_accattr,
DDI_DMA_STREAMING,
DDI_DMA_READ | DDI_DMA_STREAMING,
&bf->rxbuf_dma);
bf->bf_mem = (uint8_t *)(bf->rxbuf_dma.mem_va);
bf->bf_baddr = bf->rxbuf_dma.cookie.dmac_address;
bf->bf_desc = ds;
bf->bf_daddr = ring->physaddr + _PTRDIFF(ds, ring->desc);
}
(void) ddi_dma_sync(ring->rxdesc_dma.dma_hdl,
0,
ring->rxdesc_dma.alength,
DDI_DMA_SYNC_FORDEV);
return (0);
}
static void
mwl_free_rx_ring(struct mwl_softc *sc)
{
struct mwl_rx_ring *ring;
struct mwl_rxbuf *bf;
int i;
ring = &sc->sc_rxring;
if (ring->desc != NULL) {
mwl_free_dma_mem(&ring->rxdesc_dma);
}
if (ring->buf != NULL) {
for (i = 0; i < ring->count; i++) {
bf = &ring->buf[i];
mwl_free_dma_mem(&bf->rxbuf_dma);
}
kmem_free(ring->buf,
(ring->count * sizeof (struct mwl_rxbuf)));
}
}
static int
mwl_alloc_tx_ring(struct mwl_softc *sc, struct mwl_tx_ring *ring,
int count)
{
struct mwl_txdesc *ds;
struct mwl_txbuf *bf;
int i, err, datadlen;
ring->count = count;
ring->queued = 0;
ring->cur = ring->next = ring->stat = 0;
err = mwl_alloc_dma_mem(sc->sc_dev, &mwl_dma_attr,
count * sizeof (struct mwl_txdesc), &mwl_desc_accattr,
DDI_DMA_CONSISTENT, DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
&ring->txdesc_dma);
if (err) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_tx_ring(): "
"alloc tx ring failed, size %d\n",
(uint32_t)(count * sizeof (struct mwl_txdesc)));
return (DDI_FAILURE);
}
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_tx_ring(): "
"dma len = %d\n", (uint32_t)(ring->txdesc_dma.alength));
ring->desc = (struct mwl_txdesc *)ring->txdesc_dma.mem_va;
ring->physaddr = ring->txdesc_dma.cookie.dmac_address;
bzero(ring->desc, count * sizeof (struct mwl_txdesc));
datadlen = count * sizeof (struct mwl_txbuf);
ring->buf = kmem_zalloc(datadlen, KM_SLEEP);
if (ring->buf == NULL) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_alloc_tx_ring(): "
"could not alloc tx ring data buffer\n");
return (DDI_FAILURE);
}
bzero(ring->buf, count * sizeof (struct mwl_txbuf));
for (i = 0; i < count; i++) {
ds = &ring->desc[i];
bf = &ring->buf[i];
/* alloc DMA memory */
(void) mwl_alloc_dma_mem(sc->sc_dev, &mwl_dma_attr,
sc->sc_dmabuf_size,
&mwl_buf_accattr,
DDI_DMA_STREAMING,
DDI_DMA_WRITE | DDI_DMA_STREAMING,
&bf->txbuf_dma);
bf->bf_baddr = bf->txbuf_dma.cookie.dmac_address;
bf->bf_mem = (uint8_t *)(bf->txbuf_dma.mem_va);
bf->bf_daddr = ring->physaddr + _PTRDIFF(ds, ring->desc);
bf->bf_desc = ds;
}
(void) ddi_dma_sync(ring->txdesc_dma.dma_hdl,
0,
ring->txdesc_dma.alength,
DDI_DMA_SYNC_FORDEV);
return (0);
}
/* ARGSUSED */
static void
mwl_free_tx_ring(struct mwl_softc *sc, struct mwl_tx_ring *ring)
{
struct mwl_txbuf *bf;
int i;
if (ring->desc != NULL) {
mwl_free_dma_mem(&ring->txdesc_dma);
}
if (ring->buf != NULL) {
for (i = 0; i < ring->count; i++) {
bf = &ring->buf[i];
mwl_free_dma_mem(&bf->txbuf_dma);
}
kmem_free(ring->buf,
(ring->count * sizeof (struct mwl_txbuf)));
}
}
/*
* Inform the f/w about location of the tx/rx dma data structures
* and related state. This cmd must be done immediately after a
* mwl_hal_gethwspecs call or the f/w will lockup.
*/
static int
mwl_hal_sethwdma(struct mwl_softc *sc, const struct mwl_hal_txrxdma *dma)
{
HostCmd_DS_SET_HW_SPEC *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_DS_SET_HW_SPEC, HostCmd_CMD_SET_HW_SPEC);
pCmd->WcbBase[0] = LE_32(dma->wcbBase[0]);
pCmd->WcbBase[1] = LE_32(dma->wcbBase[1]);
pCmd->WcbBase[2] = LE_32(dma->wcbBase[2]);
pCmd->WcbBase[3] = LE_32(dma->wcbBase[3]);
pCmd->TxWcbNumPerQueue = LE_32(dma->maxNumTxWcb);
pCmd->NumTxQueues = LE_32(dma->maxNumWCB);
pCmd->TotalRxWcb = LE_32(1); /* XXX */
pCmd->RxPdWrPtr = LE_32(dma->rxDescRead);
/*
* pCmd->Flags = LE_32(SET_HW_SPEC_HOSTFORM_BEACON
* #ifdef MWL_HOST_PS_SUPPORT
* | SET_HW_SPEC_HOST_POWERSAVE
* #endif
* | SET_HW_SPEC_HOSTFORM_PROBERESP);
*/
pCmd->Flags = 0;
/* disable multi-bss operation for A1-A4 parts */
if (sc->sc_revs.mh_macRev < 5)
pCmd->Flags |= LE_32(SET_HW_SPEC_DISABLEMBSS);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_HW_SPEC);
if (retval == 0) {
if (pCmd->Flags & LE_32(SET_HW_SPEC_DISABLEMBSS))
sc->sc_hw_flags &= ~MHF_MBSS;
else
sc->sc_hw_flags |= MHF_MBSS;
}
return (retval);
}
/*
* Inform firmware of our tx/rx dma setup. The BAR 0
* writes below are for compatibility with older firmware.
* For current firmware we send this information with a
* cmd block via mwl_hal_sethwdma.
*/
static int
mwl_setupdma(struct mwl_softc *sc)
{
int i, err;
sc->sc_hwdma.rxDescRead = sc->sc_rxring.physaddr;
mwl_mem_write4(sc, sc->sc_hwspecs.rxDescRead, sc->sc_hwdma.rxDescRead);
mwl_mem_write4(sc, sc->sc_hwspecs.rxDescWrite, sc->sc_hwdma.rxDescRead);
for (i = 0; i < MWL_NUM_TX_QUEUES - MWL_NUM_ACK_QUEUES; i++) {
struct mwl_tx_ring *txring = &sc->sc_txring[i];
sc->sc_hwdma.wcbBase[i] = txring->physaddr;
mwl_mem_write4(sc, sc->sc_hwspecs.wcbBase[i],
sc->sc_hwdma.wcbBase[i]);
}
sc->sc_hwdma.maxNumTxWcb = MWL_TX_RING_COUNT;
sc->sc_hwdma.maxNumWCB = MWL_NUM_TX_QUEUES - MWL_NUM_ACK_QUEUES;
err = mwl_hal_sethwdma(sc, &sc->sc_hwdma);
if (err != 0) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_setupdma(): "
"unable to setup tx/rx dma; hal status %u\n", err);
/* XXX */
}
return (err);
}
/* ARGSUSED */
static void
mwl_txq_init(struct mwl_softc *sc, struct mwl_tx_ring *txring, int qnum)
{
struct mwl_txbuf *bf;
struct mwl_txdesc *ds;
int i;
txring->qnum = qnum;
txring->txpri = 0; /* XXX */
bf = txring->buf;
ds = txring->desc;
for (i = 0; i < MWL_TX_RING_COUNT - 1; i++) {
bf++;
ds->pPhysNext = bf->bf_daddr;
ds++;
}
bf = txring->buf;
ds->pPhysNext = LE_32(bf->bf_daddr);
}
/*
* Setup a hardware data transmit queue for the specified
* access control. We record the mapping from ac's
* to h/w queues for use by mwl_tx_start.
*/
static int
mwl_tx_setup(struct mwl_softc *sc, int ac, int mvtype)
{
#define N(a) (sizeof (a)/sizeof (a[0]))
struct mwl_tx_ring *txring;
if (ac >= N(sc->sc_ac2q)) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_tx_setup(): "
"AC %u out of range, max %u!\n",
ac, (uint_t)N(sc->sc_ac2q));
return (0);
}
if (mvtype >= MWL_NUM_TX_QUEUES) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_tx_setup(): "
"mvtype %u out of range, max %u!\n",
mvtype, MWL_NUM_TX_QUEUES);
return (0);
}
txring = &sc->sc_txring[mvtype];
mwl_txq_init(sc, txring, mvtype);
sc->sc_ac2q[ac] = txring;
return (1);
#undef N
}
static int
mwl_setup_txq(struct mwl_softc *sc)
{
int err = 0;
/* NB: insure BK queue is the lowest priority h/w queue */
if (!mwl_tx_setup(sc, WME_AC_BK, MWL_WME_AC_BK)) {
MWL_DBG(MWL_DBG_DMA, "mwl: mwl_setup_txq(): "
"unable to setup xmit queue for %s traffic!\n",
mwl_wme_acnames[WME_AC_BK]);
err = EIO;
return (err);
}
if (!mwl_tx_setup(sc, WME_AC_BE, MWL_WME_AC_BE) ||
!mwl_tx_setup(sc, WME_AC_VI, MWL_WME_AC_VI) ||
!mwl_tx_setup(sc, WME_AC_VO, MWL_WME_AC_VO)) {
/*
* Not enough hardware tx queues to properly do WME;
* just punt and assign them all to the same h/w queue.
* We could do a better job of this if, for example,
* we allocate queues when we switch from station to
* AP mode.
*/
sc->sc_ac2q[WME_AC_BE] = sc->sc_ac2q[WME_AC_BK];
sc->sc_ac2q[WME_AC_VI] = sc->sc_ac2q[WME_AC_BK];
sc->sc_ac2q[WME_AC_VO] = sc->sc_ac2q[WME_AC_BK];
}
return (err);
}
/*
* find mwl firmware module's "_start" "_end" symbols
* and get its size.
*/
static int
mwl_loadsym(ddi_modhandle_t modp, char *sym, char **start, size_t *len)
{
char start_sym[64];
char end_sym[64];
char *p, *end;
int rv;
size_t n;
(void) snprintf(start_sym, sizeof (start_sym), "%s_start", sym);
(void) snprintf(end_sym, sizeof (end_sym), "%s_end", sym);
p = (char *)ddi_modsym(modp, start_sym, &rv);
if (p == NULL || rv != 0) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_loadsym(): "
"mod %s: symbol %s not found\n", sym, start_sym);
return (-1);
}
end = (char *)ddi_modsym(modp, end_sym, &rv);
if (end == NULL || rv != 0) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_loadsym(): "
"mod %s: symbol %s not found\n", sym, end_sym);
return (-1);
}
n = _PTRDIFF(end, p);
*start = p;
*len = n;
return (0);
}
static void
mwlFwReset(struct mwl_softc *sc)
{
if (mwl_ctl_read4(sc, MACREG_REG_INT_CODE) == 0xffffffff) {
MWL_DBG(MWL_DBG_FW, "mwl: mwlFWReset(): "
"device not present!\n");
return;
}
mwl_ctl_write4(sc, MACREG_REG_H2A_INTERRUPT_EVENTS, ISR_RESET);
sc->sc_hw_flags &= ~MHF_FWHANG;
}
static void
mwlPokeSdramController(struct mwl_softc *sc, int SDRAMSIZE_Addr)
{
/* Set up sdram controller for superflyv2 */
mwl_ctl_write4(sc, 0x00006014, 0x33);
mwl_ctl_write4(sc, 0x00006018, 0xa3a2632);
mwl_ctl_write4(sc, 0x00006010, SDRAMSIZE_Addr);
}
static void
mwlTriggerPciCmd(struct mwl_softc *sc)
{
(void) ddi_dma_sync(sc->sc_cmd_dma.dma_hdl,
0,
sc->sc_cmd_dma.alength,
DDI_DMA_SYNC_FORDEV);
mwl_ctl_write4(sc, MACREG_REG_GEN_PTR, sc->sc_cmd_dmaaddr);
(void) mwl_ctl_read4(sc, MACREG_REG_INT_CODE);
mwl_ctl_write4(sc, MACREG_REG_INT_CODE, 0x00);
(void) mwl_ctl_read4(sc, MACREG_REG_INT_CODE);
mwl_ctl_write4(sc, MACREG_REG_H2A_INTERRUPT_EVENTS,
MACREG_H2ARIC_BIT_DOOR_BELL);
(void) mwl_ctl_read4(sc, MACREG_REG_INT_CODE);
}
static int
mwlWaitFor(struct mwl_softc *sc, uint32_t val)
{
int i;
for (i = 0; i < FW_MAX_NUM_CHECKS; i++) {
DELAY(FW_CHECK_USECS);
if (mwl_ctl_read4(sc, MACREG_REG_INT_CODE) == val)
return (1);
}
return (0);
}
/*
* Firmware block xmit when talking to the boot-rom.
*/
static int
mwlSendBlock(struct mwl_softc *sc, int bsize, const void *data, size_t dsize)
{
sc->sc_cmd_mem[0] = LE_16(HostCmd_CMD_CODE_DNLD);
sc->sc_cmd_mem[1] = LE_16(bsize);
(void) memcpy(&sc->sc_cmd_mem[4], data, dsize);
mwlTriggerPciCmd(sc);
/* XXX 2000 vs 200 */
if (mwlWaitFor(sc, MACREG_INT_CODE_CMD_FINISHED)) {
mwl_ctl_write4(sc, MACREG_REG_INT_CODE, 0);
return (1);
}
MWL_DBG(MWL_DBG_FW, "mwl: mwlSendBlock(): "
"timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n",
mwl_ctl_read4(sc, MACREG_REG_INT_CODE));
return (0);
}
/*
* Firmware block xmit when talking to the 1st-stage loader.
*/
static int
mwlSendBlock2(struct mwl_softc *sc, const void *data, size_t dsize)
{
(void) memcpy(&sc->sc_cmd_mem[0], data, dsize);
mwlTriggerPciCmd(sc);
if (mwlWaitFor(sc, MACREG_INT_CODE_CMD_FINISHED)) {
mwl_ctl_write4(sc, MACREG_REG_INT_CODE, 0);
return (1);
}
MWL_DBG(MWL_DBG_FW, "mwl: mwlSendBlock2(): "
"timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n",
mwl_ctl_read4(sc, MACREG_REG_INT_CODE));
return (0);
}
/* ARGSUSED */
static int
mwl_fwload(struct mwl_softc *sc, void *fwargs)
{
char *fwname = "mwlfw";
char *fwbootname = "mwlboot";
char *fwbinname = "mw88W8363fw";
char *fwboot_index, *fw_index;
uint8_t *fw, *fwboot;
ddi_modhandle_t modfw;
/* XXX get from firmware header */
uint32_t FwReadySignature = HostCmd_SOFTAP_FWRDY_SIGNATURE;
uint32_t OpMode = HostCmd_SOFTAP_MODE;
const uint8_t *fp, *ep;
size_t fw_size, fwboot_size;
uint32_t blocksize, nbytes;
int i, rv, err, ntries;
rv = err = 0;
fw = fwboot = NULL;
fw_index = fwboot_index = NULL;
modfw = ddi_modopen(fwname, KRTLD_MODE_FIRST, &rv);
if (modfw == NULL) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"module %s not found\n", fwname);
err = -1;
goto bad2;
}
err = mwl_loadsym(modfw, fwbootname, &fwboot_index, &fwboot_size);
if (err != 0) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"could not get boot firmware\n");
err = -1;
goto bad2;
}
err = mwl_loadsym(modfw, fwbinname, &fw_index, &fw_size);
if (err != 0) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"could not get firmware\n");
err = -1;
goto bad2;
}
fwboot = (uint8_t *)kmem_alloc(fwboot_size, KM_SLEEP);
if (fwboot == NULL) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_loadfirmware(): "
"failed to alloc boot firmware memory\n");
err = -1;
goto bad2;
}
(void) memcpy(fwboot, fwboot_index, fwboot_size);
fw = (uint8_t *)kmem_alloc(fw_size, KM_SLEEP);
if (fw == NULL) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_loadfirmware(): "
"failed to alloc firmware memory\n");
err = -1;
goto bad2;
}
(void) memcpy(fw, fw_index, fw_size);
if (modfw != NULL)
(void) ddi_modclose(modfw);
if (fw_size < 4) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"could not load firmware image %s\n",
fwname);
err = ENXIO;
goto bad2;
}
if (fw[0] == 0x01 && fw[1] == 0x00 &&
fw[2] == 0x00 && fw[3] == 0x00) {
/*
* 2-stage load, get the boot firmware.
*/
if (fwboot == NULL) {
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"could not load firmware image %s\n",
fwbootname);
err = ENXIO;
goto bad2;
}
} else
fwboot = NULL;
mwlFwReset(sc);
mwl_ctl_write4(sc, MACREG_REG_A2H_INTERRUPT_CLEAR_SEL,
MACREG_A2HRIC_BIT_MASK);
mwl_ctl_write4(sc, MACREG_REG_A2H_INTERRUPT_CAUSE, 0x00);
mwl_ctl_write4(sc, MACREG_REG_A2H_INTERRUPT_MASK, 0x00);
mwl_ctl_write4(sc, MACREG_REG_A2H_INTERRUPT_STATUS_MASK,
MACREG_A2HRIC_BIT_MASK);
if (sc->sc_SDRAMSIZE_Addr != 0) {
/* Set up sdram controller for superflyv2 */
mwlPokeSdramController(sc, sc->sc_SDRAMSIZE_Addr);
}
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"load %s firmware image (%u bytes)\n",
fwname, (unsigned int)fw_size);
if (fwboot != NULL) {
/*
* Do 2-stage load. The 1st stage loader is setup
* with the bootrom loader then we load the real
* image using a different handshake. With this
* mechanism the firmware is segmented into chunks
* that have a CRC. If a chunk is incorrect we'll
* be told to retransmit.
*/
/* XXX assumes hlpimage fits in a block */
/* NB: zero size block indicates download is finished */
if (!mwlSendBlock(sc, fwboot_size, fwboot, fwboot_size) ||
!mwlSendBlock(sc, 0, NULL, 0)) {
err = ETIMEDOUT;
goto bad;
}
DELAY(200 * FW_CHECK_USECS);
if (sc->sc_SDRAMSIZE_Addr != 0) {
/* Set up sdram controller for superflyv2 */
mwlPokeSdramController(sc, sc->sc_SDRAMSIZE_Addr);
}
nbytes = ntries = 0; /* NB: silence compiler */
for (fp = fw, ep = fp + fw_size; fp < ep; ) {
mwl_ctl_write4(sc, MACREG_REG_INT_CODE, 0);
blocksize = mwl_ctl_read4(sc, MACREG_REG_SCRATCH);
if (blocksize == 0) /* download complete */
break;
if (blocksize > 0x00000c00) {
err = EINVAL;
goto bad;
}
if ((blocksize & 0x1) == 0) {
/* block successfully downloaded, advance */
fp += nbytes;
ntries = 0;
} else {
if (++ntries > 2) {
/*
* Guard against f/w telling us to
* retry infinitely.
*/
err = ELOOP;
goto bad;
}
/* clear NAK bit/flag */
blocksize &= ~0x1;
}
if (blocksize > _PTRDIFF(ep, fp)) {
/* XXX this should not happen, what to do? */
blocksize = _PTRDIFF(ep, fp);
}
nbytes = blocksize;
if (!mwlSendBlock2(sc, fp, nbytes)) {
err = ETIMEDOUT;
goto bad;
}
}
} else {
for (fp = fw, ep = fp + fw_size; fp < ep; ) {
nbytes = _PTRDIFF(ep, fp);
if (nbytes > FW_DOWNLOAD_BLOCK_SIZE)
nbytes = FW_DOWNLOAD_BLOCK_SIZE;
if (!mwlSendBlock(sc, FW_DOWNLOAD_BLOCK_SIZE, fp,
nbytes)) {
err = EIO;
goto bad;
}
fp += nbytes;
}
}
/*
* Wait for firmware to startup; we monitor the
* INT_CODE register waiting for a signature to
* written back indicating it's ready to go.
*/
sc->sc_cmd_mem[1] = 0;
/*
* XXX WAR for mfg fw download
*/
if (OpMode != HostCmd_STA_MODE)
mwlTriggerPciCmd(sc);
for (i = 0; i < FW_MAX_NUM_CHECKS; i++) {
mwl_ctl_write4(sc, MACREG_REG_GEN_PTR, OpMode);
DELAY(FW_CHECK_USECS);
if (mwl_ctl_read4(sc, MACREG_REG_INT_CODE) ==
FwReadySignature) {
mwl_ctl_write4(sc, MACREG_REG_INT_CODE, 0x00);
return (mwlResetHalState(sc));
}
}
MWL_DBG(MWL_DBG_FW, "mwl: mwl_fwload(): "
"firmware download timeout\n");
return (ETIMEDOUT);
bad:
mwlFwReset(sc);
bad2:
if (fw != NULL)
kmem_free(fw, fw_size);
if (fwboot != NULL)
kmem_free(fwboot, fwboot_size);
fwboot = fw = NULL;
fwboot_index = fw_index = NULL;
if (modfw != NULL)
(void) ddi_modclose(modfw);
return (err);
}
/*
* Low level firmware cmd block handshake support.
*/
static void
mwlSendCmd(struct mwl_softc *sc)
{
(void) ddi_dma_sync(sc->sc_cmd_dma.dma_hdl,
0,
sc->sc_cmd_dma.alength,
DDI_DMA_SYNC_FORDEV);
mwl_ctl_write4(sc, MACREG_REG_GEN_PTR, sc->sc_cmd_dmaaddr);
(void) mwl_ctl_read4(sc, MACREG_REG_INT_CODE);
mwl_ctl_write4(sc, MACREG_REG_H2A_INTERRUPT_EVENTS,
MACREG_H2ARIC_BIT_DOOR_BELL);
}
static int
mwlExecuteCmd(struct mwl_softc *sc, unsigned short cmd)
{
if (mwl_ctl_read4(sc, MACREG_REG_INT_CODE) == 0xffffffff) {
MWL_DBG(MWL_DBG_CMD, "mwl: mwlExecuteCmd(): "
"device not present!\n");
return (EIO);
}
mwlSendCmd(sc);
if (!mwlWaitForCmdComplete(sc, 0x8000 | cmd)) {
MWL_DBG(MWL_DBG_CMD, "mwl: mwlExecuteCmd(): "
"timeout waiting for f/w cmd %s\n", mwlcmdname(cmd));
return (ETIMEDOUT);
}
(void) ddi_dma_sync(sc->sc_cmd_dma.dma_hdl,
0,
sc->sc_cmd_dma.alength,
DDI_DMA_SYNC_FORDEV);
MWL_DBG(MWL_DBG_CMD, "mwl: mwlExecuteCmd(): "
"send cmd %s\n", mwlcmdname(cmd));
if (mwl_dbg_flags & MWL_DBG_CMD)
dumpresult(sc, 1);
return (0);
}
static int
mwlWaitForCmdComplete(struct mwl_softc *sc, uint16_t cmdCode)
{
#define MAX_WAIT_FW_COMPLETE_ITERATIONS 10000
int i;
for (i = 0; i < MAX_WAIT_FW_COMPLETE_ITERATIONS; i++) {
if (sc->sc_cmd_mem[0] == LE_16(cmdCode))
return (1);
DELAY(1 * 1000);
}
return (0);
#undef MAX_WAIT_FW_COMPLETE_ITERATIONS
}
static const char *
mwlcmdname(int cmd)
{
static char buf[12];
#define CMD(x) case HostCmd_CMD_##x: return #x
switch (cmd) {
CMD(CODE_DNLD);
CMD(GET_HW_SPEC);
CMD(SET_HW_SPEC);
CMD(MAC_MULTICAST_ADR);
CMD(802_11_GET_STAT);
CMD(MAC_REG_ACCESS);
CMD(BBP_REG_ACCESS);
CMD(RF_REG_ACCESS);
CMD(802_11_RADIO_CONTROL);
CMD(802_11_RF_TX_POWER);
CMD(802_11_RF_ANTENNA);
CMD(SET_BEACON);
CMD(SET_RF_CHANNEL);
CMD(SET_AID);
CMD(SET_INFRA_MODE);
CMD(SET_G_PROTECT_FLAG);
CMD(802_11_RTS_THSD);
CMD(802_11_SET_SLOT);
CMD(SET_EDCA_PARAMS);
CMD(802_11H_DETECT_RADAR);
CMD(SET_WMM_MODE);
CMD(HT_GUARD_INTERVAL);
CMD(SET_FIXED_RATE);
CMD(SET_LINKADAPT_CS_MODE);
CMD(SET_MAC_ADDR);
CMD(SET_RATE_ADAPT_MODE);
CMD(BSS_START);
CMD(SET_NEW_STN);
CMD(SET_KEEP_ALIVE);
CMD(SET_APMODE);
CMD(SET_SWITCH_CHANNEL);
CMD(UPDATE_ENCRYPTION);
CMD(BASTREAM);
CMD(SET_RIFS);
CMD(SET_N_PROTECT_FLAG);
CMD(SET_N_PROTECT_OPMODE);
CMD(SET_OPTIMIZATION_LEVEL);
CMD(GET_CALTABLE);
CMD(SET_MIMOPSHT);
CMD(GET_BEACON);
CMD(SET_REGION_CODE);
CMD(SET_POWERSAVESTATION);
CMD(SET_TIM);
CMD(GET_TIM);
CMD(GET_SEQNO);
CMD(DWDS_ENABLE);
CMD(AMPDU_RETRY_RATEDROP_MODE);
CMD(CFEND_ENABLE);
}
(void) snprintf(buf, sizeof (buf), "0x%x", cmd);
return (buf);
#undef CMD
}
static void
dumpresult(struct mwl_softc *sc, int showresult)
{
const FWCmdHdr *h = (const FWCmdHdr *)sc->sc_cmd_mem;
int len;
len = LE_16(h->Length);
#ifdef MWL_MBSS_SUPPORT
MWL_DBG(MWL_DBG_CMD, "mwl: mwl_dumpresult(): "
"Cmd %s Length %d SeqNum %d MacId %d",
mwlcmdname(LE_16(h->Cmd) & ~0x8000), len, h->SeqNum, h->MacId);
#else
MWL_DBG(MWL_DBG_CMD, "mwl: mwl_dumpresult(): "
"Cmd %s Length %d SeqNum %d",
mwlcmdname(LE_16(h->Cmd) & ~0x8000), len, LE_16(h->SeqNum));
#endif
if (showresult) {
const char *results[] =
{ "OK", "ERROR", "NOT_SUPPORT", "PENDING", "BUSY",
"PARTIAL_DATA" };
int result = LE_16(h->Result);
if (result <= HostCmd_RESULT_PARTIAL_DATA)
MWL_DBG(MWL_DBG_CMD, "mwl: dumpresult(): "
"Result %s", results[result]);
else
MWL_DBG(MWL_DBG_CMD, "mwl: dumpresult(): "
"Result %d", result);
}
}
static int
mwlGetCalTable(struct mwl_softc *sc, uint8_t annex, uint8_t index)
{
HostCmd_FW_GET_CALTABLE *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_FW_GET_CALTABLE, HostCmd_CMD_GET_CALTABLE);
pCmd->annex = annex;
pCmd->index = index;
(void) memset(pCmd->calTbl, 0, sizeof (pCmd->calTbl));
retval = mwlExecuteCmd(sc, HostCmd_CMD_GET_CALTABLE);
if (retval == 0 &&
pCmd->calTbl[0] != annex && annex != 0 && annex != 255)
retval = EIO;
return (retval);
}
/*
* Construct channel info for 2.4GHz channels from cal data.
*/
static void
get2Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len)
{
int i, j;
j = 0;
for (i = 0; i < len; i += 4) {
struct mwl_hal_channel *hc = &ci->channels[j];
hc->ieee = 1+j;
hc->freq = ieee2mhz(1+j);
(void) memcpy(hc->targetPowers, &table[i], 4);
setmaxtxpow(hc, 0, 4);
j++;
}
ci->nchannels = j;
ci->freqLow = ieee2mhz(1);
ci->freqHigh = ieee2mhz(j);
}
/*
* Construct channel info for 5GHz channels from cal data.
*/
static void
get5Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len)
{
int i, j, f, l, h;
l = 32000;
h = 0;
j = 0;
for (i = 0; i < len; i += 4) {
struct mwl_hal_channel *hc;
if (table[i] == 0)
continue;
f = 5000 + 5*table[i];
if (f < l)
l = f;
if (f > h)
h = f;
hc = &ci->channels[j];
hc->freq = (uint16_t)f;
hc->ieee = table[i];
(void) memcpy(hc->targetPowers, &table[i], 4);
setmaxtxpow(hc, 1, 4); /* NB: col 1 is the freq, skip */
j++;
}
ci->nchannels = j;
ci->freqLow = (uint16_t)((l == 32000) ? 0 : l);
ci->freqHigh = (uint16_t)h;
}
/*
* Calculate the max tx power from the channel's cal data.
*/
static void
setmaxtxpow(struct mwl_hal_channel *hc, int i, int maxix)
{
hc->maxTxPow = hc->targetPowers[i];
for (i++; i < maxix; i++)
if (hc->targetPowers[i] > hc->maxTxPow)
hc->maxTxPow = hc->targetPowers[i];
}
static uint16_t
ieee2mhz(int chan)
{
if (chan == 14)
return (2484);
if (chan < 14)
return (2407 + chan * 5);
return (2512 + (chan - 15) * 20);
}
static void
dumpcaldata(const char *name, const uint8_t *table, int n)
{
int i;
MWL_DBG(MWL_DBG_HW, "\n%s:\n", name);
for (i = 0; i < n; i += 4)
MWL_DBG(MWL_DBG_HW, "[%2d] %3d %3d %3d %3d\n",
i/4, table[i+0], table[i+1], table[i+2], table[i+3]);
}
static int
mwlGetPwrCalTable(struct mwl_softc *sc)
{
const uint8_t *data;
MWL_HAL_CHANNELINFO *ci;
int len;
/* NB: we hold the lock so it's ok to use cmdbuf */
data = ((const HostCmd_FW_GET_CALTABLE *) sc->sc_cmd_mem)->calTbl;
if (mwlGetCalTable(sc, 33, 0) == 0) {
len = (data[2] | (data[3] << 8)) - 12;
if (len > PWTAGETRATETABLE20M)
len = PWTAGETRATETABLE20M;
dumpcaldata("2.4G 20M", &data[12], len);
get2Ghz(&sc->sc_20M, &data[12], len);
}
if (mwlGetCalTable(sc, 34, 0) == 0) {
len = (data[2] | (data[3] << 8)) - 12;
if (len > PWTAGETRATETABLE40M)
len = PWTAGETRATETABLE40M;
dumpcaldata("2.4G 40M", &data[12], len);
ci = &sc->sc_40M;
get2Ghz(ci, &data[12], len);
}
if (mwlGetCalTable(sc, 35, 0) == 0) {
len = (data[2] | (data[3] << 8)) - 20;
if (len > PWTAGETRATETABLE20M_5G)
len = PWTAGETRATETABLE20M_5G;
dumpcaldata("5G 20M", &data[20], len);
get5Ghz(&sc->sc_20M_5G, &data[20], len);
}
if (mwlGetCalTable(sc, 36, 0) == 0) {
len = (data[2] | (data[3] << 8)) - 20;
if (len > PWTAGETRATETABLE40M_5G)
len = PWTAGETRATETABLE40M_5G;
dumpcaldata("5G 40M", &data[20], len);
ci = &sc->sc_40M_5G;
get5Ghz(ci, &data[20], len);
}
sc->sc_hw_flags |= MHF_CALDATA;
return (0);
}
/*
* Reset internal state after a firmware download.
*/
static int
mwlResetHalState(struct mwl_softc *sc)
{
int err = 0;
/*
* Fetch cal data for later use.
* XXX may want to fetch other stuff too.
*/
/* XXX check return */
if ((sc->sc_hw_flags & MHF_CALDATA) == 0)
err = mwlGetPwrCalTable(sc);
return (err);
}
#define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT|IEEE80211_CHAN_G)
#define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT|IEEE80211_CHAN_A)
static void
addchan(struct mwl_channel *c, int freq, int flags, int ieee, int txpow)
{
c->ic_freq = (uint16_t)freq;
c->ic_flags = flags;
c->ic_ieee = (uint8_t)ieee;
c->ic_minpower = 0;
c->ic_maxpower = 2*txpow;
c->ic_maxregpower = (uint8_t)txpow;
}
static const struct mwl_channel *
findchannel(const struct mwl_channel chans[], int nchans,
int freq, int flags)
{
const struct mwl_channel *c;
int i;
for (i = 0; i < nchans; i++) {
c = &chans[i];
if (c->ic_freq == freq && c->ic_flags == flags)
return (c);
}
return (NULL);
}
static void
addht40channels(struct mwl_channel chans[], int maxchans, int *nchans,
const MWL_HAL_CHANNELINFO *ci, int flags)
{
struct mwl_channel *c;
const struct mwl_channel *extc;
const struct mwl_hal_channel *hc;
int i;
c = &chans[*nchans];
flags &= ~IEEE80211_CHAN_HT;
for (i = 0; i < ci->nchannels; i++) {
/*
* Each entry defines an HT40 channel pair; find the
* extension channel above and the insert the pair.
*/
hc = &ci->channels[i];
extc = findchannel(chans, *nchans, hc->freq+20,
flags | IEEE80211_CHAN_HT20);
if (extc != NULL) {
if (*nchans >= maxchans)
break;
addchan(c, hc->freq, flags | IEEE80211_CHAN_HT40U,
hc->ieee, hc->maxTxPow);
c->ic_extieee = extc->ic_ieee;
c++, (*nchans)++;
if (*nchans >= maxchans)
break;
addchan(c, extc->ic_freq, flags | IEEE80211_CHAN_HT40D,
extc->ic_ieee, hc->maxTxPow);
c->ic_extieee = hc->ieee;
c++, (*nchans)++;
}
}
}
static void
addchannels(struct mwl_channel chans[], int maxchans, int *nchans,
const MWL_HAL_CHANNELINFO *ci, int flags)
{
struct mwl_channel *c;
int i;
c = &chans[*nchans];
for (i = 0; i < ci->nchannels; i++) {
const struct mwl_hal_channel *hc;
hc = &ci->channels[i];
if (*nchans >= maxchans)
break;
addchan(c, hc->freq, flags, hc->ieee, hc->maxTxPow);
c++, (*nchans)++;
if (flags == IEEE80211_CHAN_G || flags == IEEE80211_CHAN_HTG) {
/* g channel have a separate b-only entry */
if (*nchans >= maxchans)
break;
c[0] = c[-1];
c[-1].ic_flags = IEEE80211_CHAN_B;
c++, (*nchans)++;
}
if (flags == IEEE80211_CHAN_HTG) {
/* HT g channel have a separate g-only entry */
if (*nchans >= maxchans)
break;
c[-1].ic_flags = IEEE80211_CHAN_G;
c[0] = c[-1];
c[0].ic_flags &= ~IEEE80211_CHAN_HT;
c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */
c++, (*nchans)++;
}
if (flags == IEEE80211_CHAN_HTA) {
/* HT a channel have a separate a-only entry */
if (*nchans >= maxchans)
break;
c[-1].ic_flags = IEEE80211_CHAN_A;
c[0] = c[-1];
c[0].ic_flags &= ~IEEE80211_CHAN_HT;
c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */
c++, (*nchans)++;
}
}
}
static int
mwl_hal_getchannelinfo(struct mwl_softc *sc, int band, int chw,
const MWL_HAL_CHANNELINFO **ci)
{
switch (band) {
case MWL_FREQ_BAND_2DOT4GHZ:
*ci = (chw == MWL_CH_20_MHz_WIDTH) ? &sc->sc_20M : &sc->sc_40M;
break;
case MWL_FREQ_BAND_5GHZ:
*ci = (chw == MWL_CH_20_MHz_WIDTH) ?
&sc->sc_20M_5G : &sc->sc_40M_5G;
break;
default:
return (EINVAL);
}
return (((*ci)->freqLow == (*ci)->freqHigh) ? EINVAL : 0);
}
static void
getchannels(struct mwl_softc *sc, int maxchans, int *nchans,
struct mwl_channel chans[])
{
const MWL_HAL_CHANNELINFO *ci;
/*
* Use the channel info from the hal to craft the
* channel list. Note that we pass back an unsorted
* list; the caller is required to sort it for us
* (if desired).
*/
*nchans = 0;
if (mwl_hal_getchannelinfo(sc,
MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0)
addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG);
if (mwl_hal_getchannelinfo(sc,
MWL_FREQ_BAND_5GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0)
addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA);
if (mwl_hal_getchannelinfo(sc,
MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0)
addht40channels(chans, maxchans, nchans, ci,
IEEE80211_CHAN_HTG);
if (mwl_hal_getchannelinfo(sc,
MWL_FREQ_BAND_5GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0)
addht40channels(chans, maxchans, nchans, ci,
IEEE80211_CHAN_HTA);
}
static int
mwl_getchannels(struct mwl_softc *sc)
{
/*
* Use the channel info from the hal to craft the
* channel list for net80211. Note that we pass up
* an unsorted list; net80211 will sort it for us.
*/
(void) memset(sc->sc_channels, 0, sizeof (sc->sc_channels));
sc->sc_nchans = 0;
getchannels(sc, IEEE80211_CHAN_MAX, &sc->sc_nchans, sc->sc_channels);
sc->sc_regdomain.regdomain = SKU_DEBUG;
sc->sc_regdomain.country = CTRY_DEFAULT;
sc->sc_regdomain.location = 'I';
sc->sc_regdomain.isocc[0] = ' '; /* XXX? */
sc->sc_regdomain.isocc[1] = ' ';
return (sc->sc_nchans == 0 ? EIO : 0);
}
#undef IEEE80211_CHAN_HTA
#undef IEEE80211_CHAN_HTG
/*
* Return "hw specs". Note this must be the first
* cmd MUST be done after a firmware download or the
* f/w will lockup.
* XXX move into the hal so driver doesn't need to be responsible
*/
static int
mwl_gethwspecs(struct mwl_softc *sc)
{
struct mwl_hal_hwspec *hw;
HostCmd_DS_GET_HW_SPEC *pCmd;
int retval;
hw = &sc->sc_hwspecs;
_CMD_SETUP(pCmd, HostCmd_DS_GET_HW_SPEC, HostCmd_CMD_GET_HW_SPEC);
(void) memset(&pCmd->PermanentAddr[0], 0xff, IEEE80211_ADDR_LEN);
pCmd->ulFwAwakeCookie = LE_32((unsigned int)sc->sc_cmd_dmaaddr + 2048);
retval = mwlExecuteCmd(sc, HostCmd_CMD_GET_HW_SPEC);
if (retval == 0) {
IEEE80211_ADDR_COPY(hw->macAddr, pCmd->PermanentAddr);
hw->wcbBase[0] = LE_32(pCmd->WcbBase0) & 0x0000ffff;
hw->wcbBase[1] = LE_32(pCmd->WcbBase1[0]) & 0x0000ffff;
hw->wcbBase[2] = LE_32(pCmd->WcbBase1[1]) & 0x0000ffff;
hw->wcbBase[3] = LE_32(pCmd->WcbBase1[2]) & 0x0000ffff;
hw->rxDescRead = LE_32(pCmd->RxPdRdPtr)& 0x0000ffff;
hw->rxDescWrite = LE_32(pCmd->RxPdWrPtr)& 0x0000ffff;
hw->regionCode = LE_16(pCmd->RegionCode) & 0x00ff;
hw->fwReleaseNumber = LE_32(pCmd->FWReleaseNumber);
hw->maxNumWCB = LE_16(pCmd->NumOfWCB);
hw->maxNumMCAddr = LE_16(pCmd->NumOfMCastAddr);
hw->numAntennas = LE_16(pCmd->NumberOfAntenna);
hw->hwVersion = pCmd->Version;
hw->hostInterface = pCmd->HostIf;
sc->sc_revs.mh_macRev = hw->hwVersion; /* XXX */
sc->sc_revs.mh_phyRev = hw->hostInterface; /* XXX */
}
return (retval);
}
static int
mwl_hal_setmac_locked(struct mwl_softc *sc,
const uint8_t addr[IEEE80211_ADDR_LEN])
{
HostCmd_DS_SET_MAC *pCmd;
_VCMD_SETUP(pCmd, HostCmd_DS_SET_MAC, HostCmd_CMD_SET_MAC_ADDR);
IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr);
#ifdef MWL_MBSS_SUPPORT
/* NB: already byte swapped */
pCmd->MacType = WL_MAC_TYPE_PRIMARY_CLIENT;
#endif
return (mwlExecuteCmd(sc, HostCmd_CMD_SET_MAC_ADDR));
}
static void
cvtPeerInfo(PeerInfo_t *to, const MWL_HAL_PEERINFO *from)
{
to->LegacyRateBitMap = LE_32(from->LegacyRateBitMap);
to->HTRateBitMap = LE_32(from->HTRateBitMap);
to->CapInfo = LE_16(from->CapInfo);
to->HTCapabilitiesInfo = LE_16(from->HTCapabilitiesInfo);
to->MacHTParamInfo = from->MacHTParamInfo;
to->AddHtInfo.ControlChan = from->AddHtInfo.ControlChan;
to->AddHtInfo.AddChan = from->AddHtInfo.AddChan;
to->AddHtInfo.OpMode = LE_16(from->AddHtInfo.OpMode);
to->AddHtInfo.stbc = LE_16(from->AddHtInfo.stbc);
}
/* XXX station id must be in [0..63] */
static int
mwl_hal_newstation(struct mwl_softc *sc,
const uint8_t addr[IEEE80211_ADDR_LEN], uint16_t aid, uint16_t sid,
const MWL_HAL_PEERINFO *peer, int isQosSta, int wmeInfo)
{
HostCmd_FW_SET_NEW_STN *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_FW_SET_NEW_STN, HostCmd_CMD_SET_NEW_STN);
pCmd->AID = LE_16(aid);
pCmd->StnId = LE_16(sid);
pCmd->Action = LE_16(0); /* SET */
if (peer != NULL) {
/* NB: must fix up byte order */
cvtPeerInfo(&pCmd->PeerInfo, peer);
}
IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr);
pCmd->Qosinfo = (uint8_t)wmeInfo;
pCmd->isQosSta = (isQosSta != 0);
MWL_DBG(MWL_DBG_HW, "mwl: mwl_hal_newstation(): "
"LegacyRateBitMap %x, CapInfo %x\n",
pCmd->PeerInfo.LegacyRateBitMap, pCmd->PeerInfo.CapInfo);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_NEW_STN);
return (retval);
}
/*
* Configure antenna use.
* Takes effect immediately.
* XXX tx antenna setting ignored
* XXX rx antenna setting should always be 3 (for now)
*/
static int
mwl_hal_setantenna(struct mwl_softc *sc, MWL_HAL_ANTENNA dirSet, int ant)
{
HostCmd_DS_802_11_RF_ANTENNA *pCmd;
int retval;
if (!(dirSet == WL_ANTENNATYPE_RX || dirSet == WL_ANTENNATYPE_TX))
return (EINVAL);
_CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_ANTENNA,
HostCmd_CMD_802_11_RF_ANTENNA);
pCmd->Action = LE_16(dirSet);
if (ant == 0) /* default to all/both antennae */
ant = 3;
pCmd->AntennaMode = LE_16(ant);
retval = mwlExecuteCmd(sc, HostCmd_CMD_802_11_RF_ANTENNA);
return (retval);
}
/*
* Configure radio.
* Takes effect immediately.
* XXX preamble installed after set fixed rate cmd
*/
static int
mwl_hal_setradio(struct mwl_softc *sc, int onoff, MWL_HAL_PREAMBLE preamble)
{
HostCmd_DS_802_11_RADIO_CONTROL *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_DS_802_11_RADIO_CONTROL,
HostCmd_CMD_802_11_RADIO_CONTROL);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET);
if (onoff == 0)
pCmd->Control = 0;
else
pCmd->Control = LE_16(preamble);
pCmd->RadioOn = LE_16(onoff);
retval = mwlExecuteCmd(sc, HostCmd_CMD_802_11_RADIO_CONTROL);
return (retval);
}
static int
mwl_hal_setwmm(struct mwl_softc *sc, int onoff)
{
HostCmd_FW_SetWMMMode *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_FW_SetWMMMode,
HostCmd_CMD_SET_WMM_MODE);
pCmd->Action = LE_16(onoff);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_WMM_MODE);
return (retval);
}
/*
* Convert public channel flags definition to a
* value suitable for feeding to the firmware.
* Note this includes byte swapping.
*/
static uint32_t
cvtChannelFlags(const MWL_HAL_CHANNEL *chan)
{
uint32_t w;
/*
* NB: f/w only understands FREQ_BAND_5GHZ, supplying the more
* precise band info causes it to lockup (sometimes).
*/
w = (chan->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) ?
FREQ_BAND_2DOT4GHZ : FREQ_BAND_5GHZ;
switch (chan->channelFlags.ChnlWidth) {
case MWL_CH_10_MHz_WIDTH:
w |= CH_10_MHz_WIDTH;
break;
case MWL_CH_20_MHz_WIDTH:
w |= CH_20_MHz_WIDTH;
break;
case MWL_CH_40_MHz_WIDTH:
default:
w |= CH_40_MHz_WIDTH;
break;
}
switch (chan->channelFlags.ExtChnlOffset) {
case MWL_EXT_CH_NONE:
w |= EXT_CH_NONE;
break;
case MWL_EXT_CH_ABOVE_CTRL_CH:
w |= EXT_CH_ABOVE_CTRL_CH;
break;
case MWL_EXT_CH_BELOW_CTRL_CH:
w |= EXT_CH_BELOW_CTRL_CH;
break;
}
return (LE_32(w));
}
static int
mwl_hal_setchannel(struct mwl_softc *sc, const MWL_HAL_CHANNEL *chan)
{
HostCmd_FW_SET_RF_CHANNEL *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_FW_SET_RF_CHANNEL, HostCmd_CMD_SET_RF_CHANNEL);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET);
pCmd->CurrentChannel = chan->channel;
pCmd->ChannelFlags = cvtChannelFlags(chan); /* NB: byte-swapped */
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_RF_CHANNEL);
return (retval);
}
static int
mwl_hal_settxpower(struct mwl_softc *sc,
const MWL_HAL_CHANNEL *c, uint8_t maxtxpow)
{
HostCmd_DS_802_11_RF_TX_POWER *pCmd;
const struct mwl_hal_channel *hc;
int i = 0, retval;
hc = findhalchannel(sc, c);
if (hc == NULL) {
/* XXX temp while testing */
MWL_DBG(MWL_DBG_HW, "mwl: mwl_hal_settxpower(): "
"no cal data for channel %u band %u width %u ext %u\n",
c->channel, c->channelFlags.FreqBand,
c->channelFlags.ChnlWidth, c->channelFlags.ExtChnlOffset);
return (EINVAL);
}
_CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_TX_POWER,
HostCmd_CMD_802_11_RF_TX_POWER);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET_LIST);
/* NB: 5Ghz cal data have the channel # in [0]; don't truncate */
if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ)
pCmd->PowerLevelList[i++] = LE_16(hc->targetPowers[0]);
for (; i < 4; i++) {
uint16_t pow = hc->targetPowers[i];
if (pow > maxtxpow)
pow = maxtxpow;
pCmd->PowerLevelList[i] = LE_16(pow);
}
retval = mwlExecuteCmd(sc, HostCmd_CMD_802_11_RF_TX_POWER);
return (retval);
}
#define RATEVAL(r) ((r) &~ RATE_MCS)
#define RATETYPE(r) (((r) & RATE_MCS) ? HT_RATE_TYPE : LEGACY_RATE_TYPE)
static int
mwl_hal_settxrate(struct mwl_softc *sc, MWL_HAL_TXRATE_HANDLING handling,
const MWL_HAL_TXRATE *rate)
{
HostCmd_FW_USE_FIXED_RATE *pCmd;
FIXED_RATE_ENTRY *fp;
int retval, i, n;
_VCMD_SETUP(pCmd, HostCmd_FW_USE_FIXED_RATE,
HostCmd_CMD_SET_FIXED_RATE);
pCmd->MulticastRate = RATEVAL(rate->McastRate);
pCmd->MultiRateTxType = RATETYPE(rate->McastRate);
/* NB: no rate type field */
pCmd->ManagementRate = RATEVAL(rate->MgtRate);
(void) memset(pCmd->FixedRateTable, 0, sizeof (pCmd->FixedRateTable));
if (handling == RATE_FIXED) {
pCmd->Action = LE_32(HostCmd_ACT_GEN_SET);
pCmd->AllowRateDrop = LE_32(FIXED_RATE_WITHOUT_AUTORATE_DROP);
fp = pCmd->FixedRateTable;
fp->FixedRate =
LE_32(RATEVAL(rate->RateSeries[0].Rate));
fp->FixRateTypeFlags.FixRateType =
LE_32(RATETYPE(rate->RateSeries[0].Rate));
pCmd->EntryCount = LE_32(1);
} else if (handling == RATE_FIXED_DROP) {
pCmd->Action = LE_32(HostCmd_ACT_GEN_SET);
pCmd->AllowRateDrop = LE_32(FIXED_RATE_WITH_AUTO_RATE_DROP);
n = 0;
fp = pCmd->FixedRateTable;
for (i = 0; i < 4; i++) {
if (rate->RateSeries[0].TryCount == 0)
break;
fp->FixRateTypeFlags.FixRateType =
LE_32(RATETYPE(rate->RateSeries[i].Rate));
fp->FixedRate =
LE_32(RATEVAL(rate->RateSeries[i].Rate));
fp->FixRateTypeFlags.RetryCountValid =
LE_32(RETRY_COUNT_VALID);
fp->RetryCount =
LE_32(rate->RateSeries[i].TryCount-1);
n++;
}
pCmd->EntryCount = LE_32(n);
} else
pCmd->Action = LE_32(HostCmd_ACT_NOT_USE_FIXED_RATE);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_FIXED_RATE);
return (retval);
}
static int
mwl_hal_settxrate_auto(struct mwl_softc *sc, const MWL_HAL_TXRATE *rate)
{
HostCmd_FW_USE_FIXED_RATE *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_FW_USE_FIXED_RATE,
HostCmd_CMD_SET_FIXED_RATE);
pCmd->MulticastRate = RATEVAL(rate->McastRate);
pCmd->MultiRateTxType = RATETYPE(rate->McastRate);
/* NB: no rate type field */
pCmd->ManagementRate = RATEVAL(rate->MgtRate);
(void) memset(pCmd->FixedRateTable, 0, sizeof (pCmd->FixedRateTable));
pCmd->Action = LE_32(HostCmd_ACT_NOT_USE_FIXED_RATE);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_FIXED_RATE);
return (retval);
}
#undef RATEVAL
#undef RATETYPE
/* XXX 0 = indoor, 1 = outdoor */
static int
mwl_hal_setrateadaptmode(struct mwl_softc *sc, uint16_t mode)
{
HostCmd_DS_SET_RATE_ADAPT_MODE *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_DS_SET_RATE_ADAPT_MODE,
HostCmd_CMD_SET_RATE_ADAPT_MODE);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET);
pCmd->RateAdaptMode = LE_16(mode);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_RATE_ADAPT_MODE);
return (retval);
}
static int
mwl_hal_setoptimizationlevel(struct mwl_softc *sc, int level)
{
HostCmd_FW_SET_OPTIMIZATION_LEVEL *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_FW_SET_OPTIMIZATION_LEVEL,
HostCmd_CMD_SET_OPTIMIZATION_LEVEL);
pCmd->OptLevel = (uint8_t)level;
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_OPTIMIZATION_LEVEL);
return (retval);
}
/*
* Set the region code that selects the radar bin'ing agorithm.
*/
static int
mwl_hal_setregioncode(struct mwl_softc *sc, int regionCode)
{
HostCmd_SET_REGIONCODE_INFO *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_SET_REGIONCODE_INFO,
HostCmd_CMD_SET_REGION_CODE);
/* XXX map pseudo-codes to fw codes */
switch (regionCode) {
case DOMAIN_CODE_ETSI_131:
pCmd->regionCode = LE_16(DOMAIN_CODE_ETSI);
break;
default:
pCmd->regionCode = LE_16(regionCode);
break;
}
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_REGION_CODE);
return (retval);
}
static int
mwl_hal_setassocid(struct mwl_softc *sc,
const uint8_t bssId[IEEE80211_ADDR_LEN], uint16_t assocId)
{
HostCmd_FW_SET_AID *pCmd = (HostCmd_FW_SET_AID *) &sc->sc_cmd_mem[0];
int retval;
_VCMD_SETUP(pCmd, HostCmd_FW_SET_AID, HostCmd_CMD_SET_AID);
pCmd->AssocID = LE_16(assocId);
IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], bssId);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_AID);
return (retval);
}
/*
* Inform firmware of tx rate parameters. Called whenever
* user-settable params change and after a channel change.
*/
static int
mwl_setrates(struct ieee80211com *ic)
{
struct mwl_softc *sc = (struct mwl_softc *)ic;
MWL_HAL_TXRATE rates;
const struct ieee80211_rateset *rs;
rs = &ic->ic_bss->in_rates;
/*
* Update the h/w rate map.
* NB: 0x80 for MCS is passed through unchanged
*/
(void) memset(&rates, 0, sizeof (rates));
/* rate used to send management frames */
rates.MgtRate = rs->ir_rates[0] & IEEE80211_RATE_VAL;
/* rate used to send multicast frames */
rates.McastRate = rates.MgtRate;
return (mwl_hal_settxrate(sc, RATE_AUTO, &rates));
}
/*
* Set packet size threshold for implicit use of RTS.
* Takes effect immediately.
* XXX packet length > threshold =>'s RTS
*/
static int
mwl_hal_setrtsthreshold(struct mwl_softc *sc, int threshold)
{
HostCmd_DS_802_11_RTS_THSD *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_DS_802_11_RTS_THSD,
HostCmd_CMD_802_11_RTS_THSD);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET);
pCmd->Threshold = LE_16(threshold);
retval = mwlExecuteCmd(sc, HostCmd_CMD_802_11_RTS_THSD);
return (retval);
}
static int
mwl_hal_setcsmode(struct mwl_softc *sc, MWL_HAL_CSMODE csmode)
{
HostCmd_DS_SET_LINKADAPT_CS_MODE *pCmd;
int retval;
_CMD_SETUP(pCmd, HostCmd_DS_SET_LINKADAPT_CS_MODE,
HostCmd_CMD_SET_LINKADAPT_CS_MODE);
pCmd->Action = LE_16(HostCmd_ACT_GEN_SET);
pCmd->CSMode = LE_16(csmode);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_LINKADAPT_CS_MODE);
return (retval);
}
static int
mwl_hal_setpromisc(struct mwl_softc *sc, int ena)
{
uint32_t v;
v = mwl_ctl_read4(sc, MACREG_REG_PROMISCUOUS);
mwl_ctl_write4(sc, MACREG_REG_PROMISCUOUS, ena ? v | 1 : v & ~1);
return (0);
}
static int
mwl_hal_start(struct mwl_softc *sc)
{
HostCmd_DS_BSS_START *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_DS_BSS_START, HostCmd_CMD_BSS_START);
pCmd->Enable = LE_32(HostCmd_ACT_GEN_ON);
retval = mwlExecuteCmd(sc, HostCmd_CMD_BSS_START);
return (retval);
}
/*
* Enable sta-mode operation (disables beacon frame xmit).
*/
static int
mwl_hal_setinframode(struct mwl_softc *sc)
{
HostCmd_FW_SET_INFRA_MODE *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_FW_SET_INFRA_MODE,
HostCmd_CMD_SET_INFRA_MODE);
retval = mwlExecuteCmd(sc, HostCmd_CMD_SET_INFRA_MODE);
return (retval);
}
static int
mwl_hal_stop(struct mwl_softc *sc)
{
HostCmd_DS_BSS_START *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_DS_BSS_START,
HostCmd_CMD_BSS_START);
pCmd->Enable = LE_32(HostCmd_ACT_GEN_OFF);
retval = mwlExecuteCmd(sc, HostCmd_CMD_BSS_START);
return (retval);
}
static int
mwl_hal_keyset(struct mwl_softc *sc, const MWL_HAL_KEYVAL *kv,
const uint8_t mac[IEEE80211_ADDR_LEN])
{
HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY,
HostCmd_CMD_UPDATE_ENCRYPTION);
if (kv->keyFlags & (KEY_FLAG_TXGROUPKEY|KEY_FLAG_RXGROUPKEY))
pCmd->ActionType = LE_32(EncrActionTypeSetGroupKey);
else
pCmd->ActionType = LE_32(EncrActionTypeSetKey);
pCmd->KeyParam.Length = LE_16(sizeof (pCmd->KeyParam));
pCmd->KeyParam.KeyTypeId = LE_16(kv->keyTypeId);
pCmd->KeyParam.KeyInfo = LE_32(kv->keyFlags);
pCmd->KeyParam.KeyIndex = LE_32(kv->keyIndex);
/* NB: includes TKIP MIC keys */
(void) memcpy(&pCmd->KeyParam.Key, &kv->key, kv->keyLen);
switch (kv->keyTypeId) {
case KEY_TYPE_ID_WEP:
pCmd->KeyParam.KeyLen = LE_16(kv->keyLen);
break;
case KEY_TYPE_ID_TKIP:
pCmd->KeyParam.KeyLen = LE_16(sizeof (TKIP_TYPE_KEY));
pCmd->KeyParam.Key.TkipKey.TkipRsc.low =
LE_16(kv->key.tkip.rsc.low);
pCmd->KeyParam.Key.TkipKey.TkipRsc.high =
LE_32(kv->key.tkip.rsc.high);
pCmd->KeyParam.Key.TkipKey.TkipTsc.low =
LE_16(kv->key.tkip.tsc.low);
pCmd->KeyParam.Key.TkipKey.TkipTsc.high =
LE_32(kv->key.tkip.tsc.high);
break;
case KEY_TYPE_ID_AES:
pCmd->KeyParam.KeyLen = LE_16(sizeof (AES_TYPE_KEY));
break;
}
#ifdef MWL_MBSS_SUPPORT
IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac);
#else
IEEE80211_ADDR_COPY(pCmd->Macaddr, mac);
#endif
retval = mwlExecuteCmd(sc, HostCmd_CMD_UPDATE_ENCRYPTION);
return (retval);
}
static int
mwl_hal_keyreset(struct mwl_softc *sc, const MWL_HAL_KEYVAL *kv,
const uint8_t mac[IEEE80211_ADDR_LEN])
{
HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd;
int retval;
_VCMD_SETUP(pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY,
HostCmd_CMD_UPDATE_ENCRYPTION);
pCmd->ActionType = LE_16(EncrActionTypeRemoveKey);
pCmd->KeyParam.Length = LE_16(sizeof (pCmd->KeyParam));
pCmd->KeyParam.KeyTypeId = LE_16(kv->keyTypeId);
pCmd->KeyParam.KeyInfo = LE_32(kv->keyFlags);
pCmd->KeyParam.KeyIndex = LE_32(kv->keyIndex);
#ifdef MWL_MBSS_SUPPORT
IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac);
#else
IEEE80211_ADDR_COPY(pCmd->Macaddr, mac);
#endif
retval = mwlExecuteCmd(sc, HostCmd_CMD_UPDATE_ENCRYPTION);
return (retval);
}
/* ARGSUSED */
static struct ieee80211_node *
mwl_node_alloc(struct ieee80211com *ic)
{
struct mwl_node *mn;
mn = kmem_zalloc(sizeof (struct mwl_node), KM_SLEEP);
if (mn == NULL) {
/* XXX stat+msg */
MWL_DBG(MWL_DBG_MSG, "mwl: mwl_node_alloc(): "
"alloc node failed\n");
return (NULL);
}
return (&mn->mn_node);
}
static void
mwl_node_free(struct ieee80211_node *ni)
{
struct ieee80211com *ic = ni->in_ic;
struct mwl_node *mn = MWL_NODE(ni);
if (mn->mn_staid != 0) {
// mwl_hal_delstation(mn->mn_hvap, vap->iv_myaddr);
// delstaid(sc, mn->mn_staid);
mn->mn_staid = 0;
}
ic->ic_node_cleanup(ni);
kmem_free(ni, sizeof (struct mwl_node));
}
/*
* Allocate a key cache slot for a unicast key. The
* firmware handles key allocation and every station is
* guaranteed key space so we are always successful.
*/
static int
mwl_key_alloc(struct ieee80211com *ic, const struct ieee80211_key *k,
ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
{
if (k->wk_keyix != IEEE80211_KEYIX_NONE ||
(k->wk_flags & IEEE80211_KEY_GROUP)) {
if (!(&ic->ic_nw_keys[0] <= k &&
k < &ic->ic_nw_keys[IEEE80211_WEP_NKID])) {
/* should not happen */
MWL_DBG(MWL_DBG_CRYPTO, "mwl: mwl_key_alloc(): "
"bogus group key\n");
return (0);
}
/* give the caller what they requested */
*keyix = *rxkeyix = k - ic->ic_nw_keys;
MWL_DBG(MWL_DBG_CRYPTO, "mwl: mwl_key_alloc(): "
"alloc GROUP key keyix %x, rxkeyix %x\n",
*keyix, *rxkeyix);
} else {
/*
* Firmware handles key allocation.
*/
*keyix = *rxkeyix = 0;
MWL_DBG(MWL_DBG_CRYPTO, "mwl: mwl_key_alloc(): "
"reset key index in key allocation\n");
}
return (1);
}
/*
* Delete a key entry allocated by mwl_key_alloc.
*/
static int
mwl_key_delete(struct ieee80211com *ic, const struct ieee80211_key *k)
{
struct mwl_softc *sc = (struct mwl_softc *)ic;
MWL_HAL_KEYVAL hk;
const uint8_t bcastaddr[IEEE80211_ADDR_LEN] =
{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
(void) memset(&hk, 0, sizeof (hk));
hk.keyIndex = k->wk_keyix;
switch (k->wk_cipher->ic_cipher) {
case IEEE80211_CIPHER_WEP:
hk.keyTypeId = KEY_TYPE_ID_WEP;
break;
case IEEE80211_CIPHER_TKIP:
hk.keyTypeId = KEY_TYPE_ID_TKIP;
break;
case IEEE80211_CIPHER_AES_CCM:
hk.keyTypeId = KEY_TYPE_ID_AES;
break;
default:
/* XXX should not happen */
MWL_DBG(MWL_DBG_CRYPTO, "mwl: mwl_key_delete(): "
"unknown cipher %d\n", k->wk_cipher->ic_cipher);
return (0);
}
return (mwl_hal_keyreset(sc, &hk, bcastaddr) == 0);
}
/*
* Set the key cache contents for the specified key. Key cache
* slot(s) must already have been allocated by mwl_key_alloc.
*/
/* ARGSUSED */
static int
mwl_key_set(struct ieee80211com *ic, const struct ieee80211_key *k,
const uint8_t mac[IEEE80211_ADDR_LEN])
{
#define GRPXMIT (IEEE80211_KEY_XMIT | IEEE80211_KEY_GROUP)
/* NB: static wep keys are marked GROUP+tx/rx; GTK will be tx or rx */
#define IEEE80211_IS_STATICKEY(k) \
(((k)->wk_flags & (GRPXMIT|IEEE80211_KEY_RECV)) == \
(GRPXMIT|IEEE80211_KEY_RECV))
struct mwl_softc *sc = (struct mwl_softc *)ic;
const struct ieee80211_cipher *cip = k->wk_cipher;
const uint8_t *macaddr;
MWL_HAL_KEYVAL hk;
(void) memset(&hk, 0, sizeof (hk));
hk.keyIndex = k->wk_keyix;
switch (cip->ic_cipher) {
case IEEE80211_CIPHER_WEP:
hk.keyTypeId = KEY_TYPE_ID_WEP;
hk.keyLen = k->wk_keylen;
if (k->wk_keyix == ic->ic_def_txkey)
hk.keyFlags = KEY_FLAG_WEP_TXKEY;
if (!IEEE80211_IS_STATICKEY(k)) {
/* NB: WEP is never used for the PTK */
(void) addgroupflags(&hk, k);
}
break;
case IEEE80211_CIPHER_TKIP:
hk.keyTypeId = KEY_TYPE_ID_TKIP;
hk.key.tkip.tsc.high = (uint32_t)(k->wk_keytsc >> 16);
hk.key.tkip.tsc.low = (uint16_t)k->wk_keytsc;
hk.keyFlags = KEY_FLAG_TSC_VALID | KEY_FLAG_MICKEY_VALID;
hk.keyLen = k->wk_keylen + IEEE80211_MICBUF_SIZE;
if (!addgroupflags(&hk, k))
hk.keyFlags |= KEY_FLAG_PAIRWISE;
break;
case IEEE80211_CIPHER_AES_CCM:
hk.keyTypeId = KEY_TYPE_ID_AES;
hk.keyLen = k->wk_keylen;
if (!addgroupflags(&hk, k))
hk.keyFlags |= KEY_FLAG_PAIRWISE;
break;
default:
/* XXX should not happen */
MWL_DBG(MWL_DBG_CRYPTO, "mwl: mwl_key_set(): "
"unknown cipher %d\n",
k->wk_cipher->ic_cipher);
return (0);
}
/*
* NB: tkip mic keys get copied here too; the layout
* just happens to match that in ieee80211_key.
*/
(void) memcpy(hk.key.aes, k->wk_key, hk.keyLen);
/*
* Locate address of sta db entry for writing key;
* the convention unfortunately is somewhat different
* than how net80211, hostapd, and wpa_supplicant think.
*/
/*
* NB: keys plumbed before the sta reaches AUTH state
* will be discarded or written to the wrong sta db
* entry because iv_bss is meaningless. This is ok
* (right now) because we handle deferred plumbing of
* WEP keys when the sta reaches AUTH state.
*/
macaddr = ic->ic_bss->in_bssid;
if (k->wk_flags & IEEE80211_KEY_XMIT) {
/* XXX plumb to local sta db too for static key wep */
(void) mwl_hal_keyset(sc, &hk, ic->ic_macaddr);
}
return (mwl_hal_keyset(sc, &hk, macaddr) == 0);
#undef IEEE80211_IS_STATICKEY
#undef GRPXMIT
}
/*
* Plumb any static WEP key for the station. This is
* necessary as we must propagate the key from the
* global key table of the vap to each sta db entry.
*/
static void
mwl_setanywepkey(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
{
if ((ic->ic_flags & (IEEE80211_F_PRIVACY|IEEE80211_F_WPA)) ==
IEEE80211_F_PRIVACY &&
ic->ic_def_txkey != IEEE80211_KEYIX_NONE &&
ic->ic_nw_keys[ic->ic_def_txkey].wk_keyix != IEEE80211_KEYIX_NONE)
(void) mwl_key_set(ic, &ic->ic_nw_keys[ic->ic_def_txkey], mac);
}
static void
mwl_setglobalkeys(struct ieee80211com *ic)
{
struct ieee80211_key *wk;
wk = &ic->ic_nw_keys[0];
for (; wk < &ic->ic_nw_keys[IEEE80211_WEP_NKID]; wk++)
if (wk->wk_keyix != IEEE80211_KEYIX_NONE)
(void) mwl_key_set(ic, wk, ic->ic_macaddr);
}
static int
addgroupflags(MWL_HAL_KEYVAL *hk, const struct ieee80211_key *k)
{
if (k->wk_flags & IEEE80211_KEY_GROUP) {
if (k->wk_flags & IEEE80211_KEY_XMIT)
hk->keyFlags |= KEY_FLAG_TXGROUPKEY;
if (k->wk_flags & IEEE80211_KEY_RECV)
hk->keyFlags |= KEY_FLAG_RXGROUPKEY;
return (1);
} else
return (0);
}
/*
* Set/change channels.
*/
static int
mwl_chan_set(struct mwl_softc *sc, struct mwl_channel *chan)
{
MWL_HAL_CHANNEL hchan;
int maxtxpow;
MWL_DBG(MWL_DBG_HW, "mwl: mwl_chan_set(): "
"chan %u MHz/flags 0x%x\n",
chan->ic_freq, chan->ic_flags);
/*
* Convert to a HAL channel description with
* the flags constrained to reflect the current
* operating mode.
*/
mwl_mapchan(&hchan, chan);
mwl_hal_intrset(sc, 0); /* disable interrupts */
(void) mwl_hal_setchannel(sc, &hchan);
/*
* Tx power is cap'd by the regulatory setting and
* possibly a user-set limit. We pass the min of
* these to the hal to apply them to the cal data
* for this channel.
* XXX min bound?
*/
maxtxpow = 2 * chan->ic_maxregpower;
if (maxtxpow > 100)
maxtxpow = 100;
(void) mwl_hal_settxpower(sc, &hchan, maxtxpow / 2);
/* NB: potentially change mcast/mgt rates */
(void) mwl_setcurchanrates(sc);
sc->sc_curchan = hchan;
mwl_hal_intrset(sc, sc->sc_imask);
return (0);
}
/*
* Convert net80211 channel to a HAL channel.
*/
static void
mwl_mapchan(MWL_HAL_CHANNEL *hc, const struct mwl_channel *chan)
{
hc->channel = chan->ic_ieee;
*(uint32_t *)&hc->channelFlags = 0;
if (((chan)->ic_flags & IEEE80211_CHAN_2GHZ) != 0)
hc->channelFlags.FreqBand = MWL_FREQ_BAND_2DOT4GHZ;
else if (((chan)->ic_flags & IEEE80211_CHAN_5GHZ) != 0)
hc->channelFlags.FreqBand = MWL_FREQ_BAND_5GHZ;
if (((chan)->ic_flags & IEEE80211_CHAN_HT40) != 0) {
hc->channelFlags.ChnlWidth = MWL_CH_40_MHz_WIDTH;
if (((chan)->ic_flags & IEEE80211_CHAN_HT40U) != 0)
hc->channelFlags.ExtChnlOffset =
MWL_EXT_CH_ABOVE_CTRL_CH;
else
hc->channelFlags.ExtChnlOffset =
MWL_EXT_CH_BELOW_CTRL_CH;
} else
hc->channelFlags.ChnlWidth = MWL_CH_20_MHz_WIDTH;
/* XXX 10MHz channels */
}
/*
* Return the phy mode for with the specified channel.
*/
enum ieee80211_phymode
mwl_chan2mode(const struct mwl_channel *chan)
{
if (IEEE80211_IS_CHAN_HTA(chan))
return (IEEE80211_MODE_11NA);
else if (IEEE80211_IS_CHAN_HTG(chan))
return (IEEE80211_MODE_11NG);
else if (IEEE80211_IS_CHAN_108G(chan))
return (IEEE80211_MODE_TURBO_G);
else if (IEEE80211_IS_CHAN_ST(chan))
return (IEEE80211_MODE_STURBO_A);
else if (IEEE80211_IS_CHAN_TURBO(chan))
return (IEEE80211_MODE_TURBO_A);
else if (IEEE80211_IS_CHAN_HALF(chan))
return (IEEE80211_MODE_HALF);
else if (IEEE80211_IS_CHAN_QUARTER(chan))
return (IEEE80211_MODE_QUARTER);
else if (IEEE80211_IS_CHAN_A(chan))
return (IEEE80211_MODE_11A);
else if (IEEE80211_IS_CHAN_ANYG(chan))
return (IEEE80211_MODE_11G);
else if (IEEE80211_IS_CHAN_B(chan))
return (IEEE80211_MODE_11B);
else if (IEEE80211_IS_CHAN_FHSS(chan))
return (IEEE80211_MODE_FH);
/* NB: should not get here */
MWL_DBG(MWL_DBG_HW, "mwl: mwl_chan2mode(): "
"cannot map channel to mode; freq %u flags 0x%x\n",
chan->ic_freq, chan->ic_flags);
return (IEEE80211_MODE_11B);
}
/* XXX inline or eliminate? */
const struct ieee80211_rateset *
mwl_get_suprates(struct ieee80211com *ic, const struct mwl_channel *c)
{
/* XXX does this work for 11ng basic rates? */
return (&ic->ic_sup_rates[mwl_chan2mode(c)]);
}
/*
* Inform firmware of tx rate parameters.
* Called after a channel change.
*/
static int
mwl_setcurchanrates(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
const struct ieee80211_rateset *rs;
MWL_HAL_TXRATE rates;
(void) memset(&rates, 0, sizeof (rates));
rs = mwl_get_suprates(ic, sc->sc_cur_chan);
/* rate used to send management frames */
rates.MgtRate = rs->ir_rates[0] & IEEE80211_RATE_VAL;
/* rate used to send multicast frames */
rates.McastRate = rates.MgtRate;
return (mwl_hal_settxrate_auto(sc, &rates));
}
static const struct mwl_hal_channel *
findhalchannel(const struct mwl_softc *sc, const MWL_HAL_CHANNEL *c)
{
const struct mwl_hal_channel *hc;
const MWL_HAL_CHANNELINFO *ci;
int chan = c->channel, i;
if (c->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) {
i = chan - 1;
if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) {
ci = &sc->sc_40M;
if (c->channelFlags.ExtChnlOffset ==
MWL_EXT_CH_BELOW_CTRL_CH)
i -= 4;
} else
ci = &sc->sc_20M;
/* 2.4G channel table is directly indexed */
hc = ((unsigned)i < ci->nchannels) ? &ci->channels[i] : NULL;
} else if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ) {
if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) {
ci = &sc->sc_40M_5G;
if (c->channelFlags.ExtChnlOffset ==
MWL_EXT_CH_BELOW_CTRL_CH)
chan -= 4;
} else
ci = &sc->sc_20M_5G;
/* 5GHz channel table is sparse and must be searched */
for (i = 0; i < ci->nchannels; i++)
if (ci->channels[i].ieee == chan)
break;
hc = (i < ci->nchannels) ? &ci->channels[i] : NULL;
} else
hc = NULL;
return (hc);
}
/*
* Map SKU+country code to region code for radar bin'ing.
*/
static int
mwl_map2regioncode(const struct mwl_regdomain *rd)
{
switch (rd->regdomain) {
case SKU_FCC:
case SKU_FCC3:
return (DOMAIN_CODE_FCC);