text: /*-
   * 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.
   */
  
  #include <sys/cdefs.h>
  __FBSDID("$FreeBSD: soc2013/dpl/head/sys/dev/mwl/if_mwl.c 245050 2012-12-04 09:32:43Z glebius $");
  
  /*
   * Driver for the Marvell 88W8363 Wireless LAN controller.
   */
  
  #include "opt_inet.h"
  #include "opt_mwl.h"
  #include "opt_wlan.h"
  
  #include <sys/param.h>
  #include <sys/systm.h> 
  #include <sys/sysctl.h>
  #include <sys/mbuf.h>   
  #include <sys/malloc.h>
  #include <sys/lock.h>
  #include <sys/mutex.h>
  #include <sys/kernel.h>
  #include <sys/socket.h>
  #include <sys/sockio.h>
  #include <sys/errno.h>
  #include <sys/callout.h>
  #include <sys/bus.h>
  #include <sys/endian.h>
  #include <sys/kthread.h>
  #include <sys/taskqueue.h>
  
  #include <machine/bus.h>
   
  #include <net/if.h>
  #include <net/if_dl.h>
  #include <net/if_media.h>
  #include <net/if_types.h>
  #include <net/if_arp.h>
  #include <net/ethernet.h>
  #include <net/if_llc.h>
  
  #include <net/bpf.h>
  
  #include <net80211/ieee80211_var.h>
  #include <net80211/ieee80211_regdomain.h>
  
  #ifdef INET
  #include <netinet/in.h>
  #include <netinet/if_ether.h>
  #endif /* INET */
  
  #include <dev/mwl/if_mwlvar.h>
  #include <dev/mwl/mwldiag.h>
  
  /* idiomatic shorthands: MS = mask+shift, SM = shift+mask */
  #define	MS(v,x)	(((v) & x) >> x##_S)
  #define	SM(v,x)	(((v) << x##_S) & x)
  
  static struct ieee80211vap *mwl_vap_create(struct ieee80211com *,
  		    const char [IFNAMSIZ], int, enum ieee80211_opmode, int,
  		    const uint8_t [IEEE80211_ADDR_LEN],
  		    const uint8_t [IEEE80211_ADDR_LEN]);
  static void	mwl_vap_delete(struct ieee80211vap *);
  static int	mwl_setupdma(struct mwl_softc *);
  static int	mwl_hal_reset(struct mwl_softc *sc);
  static int	mwl_init_locked(struct mwl_softc *);
  static void	mwl_init(void *);
  static void	mwl_stop_locked(struct ifnet *, int);
  static int	mwl_reset(struct ieee80211vap *, u_long);
  static void	mwl_stop(struct ifnet *, int);
  static void	mwl_start(struct ifnet *);
  static int	mwl_raw_xmit(struct ieee80211_node *, struct mbuf *,
  			const struct ieee80211_bpf_params *);
  static int	mwl_media_change(struct ifnet *);
  static void	mwl_watchdog(void *);
  static int	mwl_ioctl(struct ifnet *, u_long, caddr_t);
  static void	mwl_radar_proc(void *, int);
  static void	mwl_chanswitch_proc(void *, int);
  static void	mwl_bawatchdog_proc(void *, int);
  static int	mwl_key_alloc(struct ieee80211vap *,
  			struct ieee80211_key *,
  			ieee80211_keyix *, ieee80211_keyix *);
  static int	mwl_key_delete(struct ieee80211vap *,
  			const struct ieee80211_key *);
  static int	mwl_key_set(struct ieee80211vap *, const struct ieee80211_key *,
  			const uint8_t mac[IEEE80211_ADDR_LEN]);
  static int	mwl_mode_init(struct mwl_softc *);
  static void	mwl_update_mcast(struct ifnet *);
  static void	mwl_update_promisc(struct ifnet *);
  static void	mwl_updateslot(struct ifnet *);
  static int	mwl_beacon_setup(struct ieee80211vap *);
  static void	mwl_beacon_update(struct ieee80211vap *, int);
  #ifdef MWL_HOST_PS_SUPPORT
  static void	mwl_update_ps(struct ieee80211vap *, int);
  static int	mwl_set_tim(struct ieee80211_node *, int);
  #endif
  static int	mwl_dma_setup(struct mwl_softc *);
  static void	mwl_dma_cleanup(struct mwl_softc *);
  static struct ieee80211_node *mwl_node_alloc(struct ieee80211vap *,
  		    const uint8_t [IEEE80211_ADDR_LEN]);
  static void	mwl_node_cleanup(struct ieee80211_node *);
  static void	mwl_node_drain(struct ieee80211_node *);
  static void	mwl_node_getsignal(const struct ieee80211_node *,
  			int8_t *, int8_t *);
  static void	mwl_node_getmimoinfo(const struct ieee80211_node *,
  			struct ieee80211_mimo_info *);
  static int	mwl_rxbuf_init(struct mwl_softc *, struct mwl_rxbuf *);
  static void	mwl_rx_proc(void *, int);
  static void	mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *, int);
  static int	mwl_tx_setup(struct mwl_softc *, int, int);
  static int	mwl_wme_update(struct ieee80211com *);
  static void	mwl_tx_cleanupq(struct mwl_softc *, struct mwl_txq *);
  static void	mwl_tx_cleanup(struct mwl_softc *);
  static uint16_t	mwl_calcformat(uint8_t rate, const struct ieee80211_node *);
  static int	mwl_tx_start(struct mwl_softc *, struct ieee80211_node *,
  			     struct mwl_txbuf *, struct mbuf *);
  static void	mwl_tx_proc(void *, int);
  static int	mwl_chan_set(struct mwl_softc *, struct ieee80211_channel *);
  static void	mwl_draintxq(struct mwl_softc *);
  static void	mwl_cleartxq(struct mwl_softc *, struct ieee80211vap *);
  static int	mwl_recv_action(struct ieee80211_node *,
  			const struct ieee80211_frame *,
  			const uint8_t *, const uint8_t *);
  static int	mwl_addba_request(struct ieee80211_node *,
  			struct ieee80211_tx_ampdu *, int dialogtoken,
  			int baparamset, int batimeout);
  static int	mwl_addba_response(struct ieee80211_node *,
  			struct ieee80211_tx_ampdu *, int status,
  			int baparamset, int batimeout);
  static void	mwl_addba_stop(struct ieee80211_node *,
  			struct ieee80211_tx_ampdu *);
  static int	mwl_startrecv(struct mwl_softc *);
  static MWL_HAL_APMODE mwl_getapmode(const struct ieee80211vap *,
  			struct ieee80211_channel *);
  static int	mwl_setapmode(struct ieee80211vap *, struct ieee80211_channel*);
  static void	mwl_scan_start(struct ieee80211com *);
  static void	mwl_scan_end(struct ieee80211com *);
  static void	mwl_set_channel(struct ieee80211com *);
  static int	mwl_peerstadb(struct ieee80211_node *,
  			int aid, int staid, MWL_HAL_PEERINFO *pi);
  static int	mwl_localstadb(struct ieee80211vap *);
  static int	mwl_newstate(struct ieee80211vap *, enum ieee80211_state, int);
  static int	allocstaid(struct mwl_softc *sc, int aid);
  static void	delstaid(struct mwl_softc *sc, int staid);
  static void	mwl_newassoc(struct ieee80211_node *, int);
  static void	mwl_agestations(void *);
  static int	mwl_setregdomain(struct ieee80211com *,
  			struct ieee80211_regdomain *, int,
  			struct ieee80211_channel []);
  static void	mwl_getradiocaps(struct ieee80211com *, int, int *,
  			struct ieee80211_channel []);
  static int	mwl_getchannels(struct mwl_softc *);
  
  static void	mwl_sysctlattach(struct mwl_softc *);
  static void	mwl_announce(struct mwl_softc *);
  
  SYSCTL_NODE(_hw, OID_AUTO, mwl, CTLFLAG_RD, 0, "Marvell driver parameters");
  
  static	int mwl_rxdesc = MWL_RXDESC;		/* # rx desc's to allocate */
  SYSCTL_INT(_hw_mwl, OID_AUTO, rxdesc, CTLFLAG_RW, &mwl_rxdesc,
  	    0, "rx descriptors allocated");
  static	int mwl_rxbuf = MWL_RXBUF;		/* # rx buffers to allocate */
  SYSCTL_INT(_hw_mwl, OID_AUTO, rxbuf, CTLFLAG_RW, &mwl_rxbuf,
  	    0, "rx buffers allocated");
  TUNABLE_INT("hw.mwl.rxbuf", &mwl_rxbuf);
  static	int mwl_txbuf = MWL_TXBUF;		/* # tx buffers to allocate */
  SYSCTL_INT(_hw_mwl, OID_AUTO, txbuf, CTLFLAG_RW, &mwl_txbuf,
  	    0, "tx buffers allocated");
  TUNABLE_INT("hw.mwl.txbuf", &mwl_txbuf);
  static	int mwl_txcoalesce = 8;		/* # tx packets to q before poking f/w*/
  SYSCTL_INT(_hw_mwl, OID_AUTO, txcoalesce, CTLFLAG_RW, &mwl_txcoalesce,
  	    0, "tx buffers to send at once");
  TUNABLE_INT("hw.mwl.txcoalesce", &mwl_txcoalesce);
  static	int mwl_rxquota = MWL_RXBUF;		/* # max buffers to process */
  SYSCTL_INT(_hw_mwl, OID_AUTO, rxquota, CTLFLAG_RW, &mwl_rxquota,
  	    0, "max rx buffers to process per interrupt");
  TUNABLE_INT("hw.mwl.rxquota", &mwl_rxquota);
  static	int mwl_rxdmalow = 3;			/* # min buffers for wakeup */
  SYSCTL_INT(_hw_mwl, OID_AUTO, rxdmalow, CTLFLAG_RW, &mwl_rxdmalow,
  	    0, "min free rx buffers before restarting traffic");
  TUNABLE_INT("hw.mwl.rxdmalow", &mwl_rxdmalow);
  
  #ifdef MWL_DEBUG
  static	int mwl_debug = 0;
  SYSCTL_INT(_hw_mwl, OID_AUTO, debug, CTLFLAG_RW, &mwl_debug,
  	    0, "control debugging printfs");
  TUNABLE_INT("hw.mwl.debug", &mwl_debug);
  enum {
  	MWL_DEBUG_XMIT		= 0x00000001,	/* basic xmit operation */
  	MWL_DEBUG_XMIT_DESC	= 0x00000002,	/* xmit descriptors */
  	MWL_DEBUG_RECV		= 0x00000004,	/* basic recv operation */
  	MWL_DEBUG_RECV_DESC	= 0x00000008,	/* recv descriptors */
  	MWL_DEBUG_RESET		= 0x00000010,	/* reset processing */
  	MWL_DEBUG_BEACON 	= 0x00000020,	/* beacon handling */
  	MWL_DEBUG_INTR		= 0x00000040,	/* ISR */
  	MWL_DEBUG_TX_PROC	= 0x00000080,	/* tx ISR proc */
  	MWL_DEBUG_RX_PROC	= 0x00000100,	/* rx ISR proc */
  	MWL_DEBUG_KEYCACHE	= 0x00000200,	/* key cache management */
  	MWL_DEBUG_STATE		= 0x00000400,	/* 802.11 state transitions */
  	MWL_DEBUG_NODE		= 0x00000800,	/* node management */
  	MWL_DEBUG_RECV_ALL	= 0x00001000,	/* trace all frames (beacons) */
  	MWL_DEBUG_TSO		= 0x00002000,	/* TSO processing */
  	MWL_DEBUG_AMPDU		= 0x00004000,	/* BA stream handling */
  	MWL_DEBUG_ANY		= 0xffffffff
  };
  #define	IS_BEACON(wh) \
      ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) == \
  	 (IEEE80211_FC0_TYPE_MGT|IEEE80211_FC0_SUBTYPE_BEACON))
  #define	IFF_DUMPPKTS_RECV(sc, wh) \
      (((sc->sc_debug & MWL_DEBUG_RECV) && \
        ((sc->sc_debug & MWL_DEBUG_RECV_ALL) || !IS_BEACON(wh))) || \
       (sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2))
  #define	IFF_DUMPPKTS_XMIT(sc) \
  	((sc->sc_debug & MWL_DEBUG_XMIT) || \
  	 (sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2))
  #define	DPRINTF(sc, m, fmt, ...) do {				\
  	if (sc->sc_debug & (m))					\
  		printf(fmt, __VA_ARGS__);			\
  } while (0)
  #define	KEYPRINTF(sc, hk, mac) do {				\
  	if (sc->sc_debug & MWL_DEBUG_KEYCACHE)			\
  		mwl_keyprint(sc, __func__, hk, mac);		\
  } while (0)
  static	void mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix);
  static	void mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix);
  #else
  #define	IFF_DUMPPKTS_RECV(sc, wh) \
  	((sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2))
  #define	IFF_DUMPPKTS_XMIT(sc) \
  	((sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2))
  #define	DPRINTF(sc, m, fmt, ...) do {				\
  	(void) sc;						\
  } while (0)
  #define	KEYPRINTF(sc, k, mac) do {				\
  	(void) sc;						\
  } while (0)
  #endif
  
  static MALLOC_DEFINE(M_MWLDEV, "mwldev", "mwl driver dma buffers");
  
  /*
   * Each packet has fixed front matter: a 2-byte length
   * of the payload, followed by a 4-address 802.11 header
   * (regardless of the actual header and always w/o any
   * QoS header).  The payload then follows.
   */
  struct mwltxrec {
  	uint16_t fwlen;
  	struct ieee80211_frame_addr4 wh;
  } __packed;
  
  /*
   * Read/Write shorthands for accesses to BAR 0.  Note
   * that all BAR 1 operations are done in the "hal" and
   * there should be no reference to them here.
   */
  static __inline uint32_t
  RD4(struct mwl_softc *sc, bus_size_t off)
  {
  	return bus_space_read_4(sc->sc_io0t, sc->sc_io0h, off);
  }
  
  static __inline void
  WR4(struct mwl_softc *sc, bus_size_t off, uint32_t val)
  {
  	bus_space_write_4(sc->sc_io0t, sc->sc_io0h, off, val);
  }
  
  int
  mwl_attach(uint16_t devid, struct mwl_softc *sc)
  {
  	struct ifnet *ifp;
  	struct ieee80211com *ic;
  	struct mwl_hal *mh;
  	int error = 0;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: devid 0x%x\n", __func__, devid);
  
  	ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211);
  	if (ifp == NULL) {
  		device_printf(sc->sc_dev, "cannot if_alloc()\n");
  		return ENOSPC;
  	}
  	ic = ifp->if_l2com;
  
  	/*
  	 * Setup the RX free list lock early, so it can be consistently
  	 * removed.
  	 */
  	MWL_RXFREE_INIT(sc);
  
  	/* set these up early for if_printf use */
  	if_initname(ifp, device_get_name(sc->sc_dev),
  		device_get_unit(sc->sc_dev));
  
  	mh = mwl_hal_attach(sc->sc_dev, devid,
  	    sc->sc_io1h, sc->sc_io1t, sc->sc_dmat);
  	if (mh == NULL) {
  		if_printf(ifp, "unable to attach HAL\n");
  		error = EIO;
  		goto bad;
  	}
  	sc->sc_mh = mh;
  	/*
  	 * Load firmware so we can get setup.  We arbitrarily
  	 * pick station firmware; we'll re-load firmware as
  	 * needed so setting up the wrong mode isn't a big deal.
  	 */
  	if (mwl_hal_fwload(mh, NULL) != 0) {
  		if_printf(ifp, "unable to setup builtin firmware\n");
  		error = EIO;
  		goto bad1;
  	}
  	if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) {
  		if_printf(ifp, "unable to fetch h/w specs\n");
  		error = EIO;
  		goto bad1;
  	}
  	error = mwl_getchannels(sc);
  	if (error != 0)
  		goto bad1;
  
  	sc->sc_txantenna = 0;		/* h/w default */
  	sc->sc_rxantenna = 0;		/* h/w default */
  	sc->sc_invalid = 0;		/* ready to go, enable int handling */
  	sc->sc_ageinterval = MWL_AGEINTERVAL;
  
  	/*
  	 * Allocate tx+rx descriptors and populate the lists.
  	 * We immediately push the information to the firmware
  	 * as otherwise it gets upset.
  	 */
  	error = mwl_dma_setup(sc);
  	if (error != 0) {
  		if_printf(ifp, "failed to setup descriptors: %d\n", error);
  		goto bad1;
  	}
  	error = mwl_setupdma(sc);	/* push to firmware */
  	if (error != 0)			/* NB: mwl_setupdma prints msg */
  		goto bad1;
  
  	callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
  	callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
  
  	sc->sc_tq = taskqueue_create("mwl_taskq", M_NOWAIT,
  		taskqueue_thread_enqueue, &sc->sc_tq);
  	taskqueue_start_threads(&sc->sc_tq, 1, PI_NET,
  		"%s taskq", ifp->if_xname);
  
  	TASK_INIT(&sc->sc_rxtask, 0, mwl_rx_proc, sc);
  	TASK_INIT(&sc->sc_radartask, 0, mwl_radar_proc, sc);
  	TASK_INIT(&sc->sc_chanswitchtask, 0, mwl_chanswitch_proc, sc);
  	TASK_INIT(&sc->sc_bawatchdogtask, 0, mwl_bawatchdog_proc, sc);
  
  	/* NB: insure BK queue is the lowest priority h/w queue */
  	if (!mwl_tx_setup(sc, WME_AC_BK, MWL_WME_AC_BK)) {
  		if_printf(ifp, "unable to setup xmit queue for %s traffic!\n",
  			ieee80211_wme_acnames[WME_AC_BK]);
  		error = EIO;
  		goto bad2;
  	}
  	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.
  		 */
  		if (sc->sc_ac2q[WME_AC_VI] != NULL)
  			mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_VI]);
  		if (sc->sc_ac2q[WME_AC_BE] != NULL)
  			mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_BE]);
  		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];
  	}
  	TASK_INIT(&sc->sc_txtask, 0, mwl_tx_proc, sc);
  
  	ifp->if_softc = sc;
  	ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST;
  	ifp->if_start = mwl_start;
  	ifp->if_ioctl = mwl_ioctl;
  	ifp->if_init = mwl_init;
  	IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
  	ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
  	IFQ_SET_READY(&ifp->if_snd);
  
  	ic->ic_ifp = ifp;
  	/* XXX not right but it's not used anywhere important */
  	ic->ic_phytype = IEEE80211_T_OFDM;
  	ic->ic_opmode = IEEE80211_M_STA;
  	ic->ic_caps =
  		  IEEE80211_C_STA		/* station mode supported */
  		| IEEE80211_C_HOSTAP		/* hostap mode */
  		| IEEE80211_C_MONITOR		/* monitor mode */
  #if 0
  		| IEEE80211_C_IBSS		/* ibss, nee adhoc, mode */
  		| IEEE80211_C_AHDEMO		/* adhoc demo mode */
  #endif
  		| IEEE80211_C_MBSS		/* mesh point link mode */
  		| IEEE80211_C_WDS		/* WDS supported */
  		| IEEE80211_C_SHPREAMBLE	/* short preamble supported */
  		| IEEE80211_C_SHSLOT		/* short slot time supported */
  		| IEEE80211_C_WME		/* WME/WMM supported */
  		| IEEE80211_C_BURST		/* xmit bursting supported */
  		| IEEE80211_C_WPA		/* capable of WPA1+WPA2 */
  		| IEEE80211_C_BGSCAN		/* capable of bg scanning */
  		| IEEE80211_C_TXFRAG		/* handle tx frags */
  		| IEEE80211_C_TXPMGT		/* capable of txpow mgt */
  		| IEEE80211_C_DFS		/* DFS supported */
  		;
  
  	ic->ic_htcaps =
  		  IEEE80211_HTCAP_SMPS_ENA	/* SM PS mode enabled */
  		| IEEE80211_HTCAP_CHWIDTH40	/* 40MHz channel width */
  		| IEEE80211_HTCAP_SHORTGI20	/* short GI in 20MHz */
  		| IEEE80211_HTCAP_SHORTGI40	/* short GI in 40MHz */
  		| IEEE80211_HTCAP_RXSTBC_2STREAM/* 1-2 spatial streams */
  #if MWL_AGGR_SIZE == 7935
  		| IEEE80211_HTCAP_MAXAMSDU_7935	/* max A-MSDU length */
  #else
  		| IEEE80211_HTCAP_MAXAMSDU_3839	/* max A-MSDU length */
  #endif
  #if 0
  		| IEEE80211_HTCAP_PSMP		/* PSMP supported */
  		| IEEE80211_HTCAP_40INTOLERANT	/* 40MHz intolerant */
  #endif
  		/* s/w capabilities */
  		| IEEE80211_HTC_HT		/* HT operation */
  		| IEEE80211_HTC_AMPDU		/* tx A-MPDU */
  		| IEEE80211_HTC_AMSDU		/* tx A-MSDU */
  		| IEEE80211_HTC_SMPS		/* SMPS available */
  		;
  
  	/*
  	 * Mark h/w crypto support.
  	 * XXX no way to query h/w support.
  	 */
  	ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP
  			  |  IEEE80211_CRYPTO_AES_CCM
  			  |  IEEE80211_CRYPTO_TKIP
  			  |  IEEE80211_CRYPTO_TKIPMIC
  			  ;
  	/*
  	 * Transmit requires space in the packet for a special
  	 * format transmit record and optional padding between
  	 * this record and the payload.  Ask the net80211 layer
  	 * to arrange this when encapsulating packets so we can
  	 * add it efficiently. 
  	 */
  	ic->ic_headroom = sizeof(struct mwltxrec) -
  		sizeof(struct ieee80211_frame);
  
  	/* call MI attach routine. */
  	ieee80211_ifattach(ic, sc->sc_hwspecs.macAddr);
  	ic->ic_setregdomain = mwl_setregdomain;
  	ic->ic_getradiocaps = mwl_getradiocaps;
  	/* override default methods */
  	ic->ic_raw_xmit = mwl_raw_xmit;
  	ic->ic_newassoc = mwl_newassoc;
  	ic->ic_updateslot = mwl_updateslot;
  	ic->ic_update_mcast = mwl_update_mcast;
  	ic->ic_update_promisc = mwl_update_promisc;
  	ic->ic_wme.wme_update = mwl_wme_update;
  
  	ic->ic_node_alloc = mwl_node_alloc;
  	sc->sc_node_cleanup = ic->ic_node_cleanup;
  	ic->ic_node_cleanup = mwl_node_cleanup;
  	sc->sc_node_drain = ic->ic_node_drain;
  	ic->ic_node_drain = mwl_node_drain;
  	ic->ic_node_getsignal = mwl_node_getsignal;
  	ic->ic_node_getmimoinfo = mwl_node_getmimoinfo;
  
  	ic->ic_scan_start = mwl_scan_start;
  	ic->ic_scan_end = mwl_scan_end;
  	ic->ic_set_channel = mwl_set_channel;
  
  	sc->sc_recv_action = ic->ic_recv_action;
  	ic->ic_recv_action = mwl_recv_action;
  	sc->sc_addba_request = ic->ic_addba_request;
  	ic->ic_addba_request = mwl_addba_request;
  	sc->sc_addba_response = ic->ic_addba_response;
  	ic->ic_addba_response = mwl_addba_response;
  	sc->sc_addba_stop = ic->ic_addba_stop;
  	ic->ic_addba_stop = mwl_addba_stop;
  
  	ic->ic_vap_create = mwl_vap_create;
  	ic->ic_vap_delete = mwl_vap_delete;
  
  	ieee80211_radiotap_attach(ic,
  	    &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th),
  		MWL_TX_RADIOTAP_PRESENT,
  	    &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th),
  		MWL_RX_RADIOTAP_PRESENT);
  	/*
  	 * Setup dynamic sysctl's now that country code and
  	 * regdomain are available from the hal.
  	 */
  	mwl_sysctlattach(sc);
  
  	if (bootverbose)
  		ieee80211_announce(ic);
  	mwl_announce(sc);
  	return 0;
  bad2:
  	mwl_dma_cleanup(sc);
  bad1:
  	mwl_hal_detach(mh);
  bad:
  	MWL_RXFREE_DESTROY(sc);
  	if_free(ifp);
  	sc->sc_invalid = 1;
  	return error;
  }
  
  int
  mwl_detach(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n",
  		__func__, ifp->if_flags);
  
  	mwl_stop(ifp, 1);
  	/*
  	 * NB: the order of these is important:
  	 * o call the 802.11 layer before detaching the hal to
  	 *   insure callbacks into the driver to delete global
  	 *   key cache entries can be handled
  	 * o reclaim the tx queue data structures after calling
  	 *   the 802.11 layer as we'll get called back to reclaim
  	 *   node state and potentially want to use them
  	 * o to cleanup the tx queues the hal is called, so detach
  	 *   it last
  	 * Other than that, it's straightforward...
  	 */
  	ieee80211_ifdetach(ic);
  	callout_drain(&sc->sc_watchdog);
  	mwl_dma_cleanup(sc);
  	MWL_RXFREE_DESTROY(sc);
  	mwl_tx_cleanup(sc);
  	mwl_hal_detach(sc->sc_mh);
  	if_free(ifp);
  
  	return 0;
  }
  
  /*
   * MAC address handling for multiple BSS on the same radio.
   * The first vap uses the MAC address from the EEPROM.  For
   * subsequent vap's we set the U/L bit (bit 1) in the MAC
   * address and use the next six bits as an index.
   */
  static void
  assign_address(struct mwl_softc *sc, uint8_t mac[IEEE80211_ADDR_LEN], int clone)
  {
  	int i;
  
  	if (clone && mwl_hal_ismbsscapable(sc->sc_mh)) {
  		/* NB: we only do this if h/w supports multiple bssid */
  		for (i = 0; i < 32; i++)
  			if ((sc->sc_bssidmask & (1<<i)) == 0)
  				break;
  		if (i != 0)
  			mac[0] |= (i << 2)|0x2;
  	} else
  		i = 0;
  	sc->sc_bssidmask |= 1<<i;
  	if (i == 0)
  		sc->sc_nbssid0++;
  }
  
  static void
  reclaim_address(struct mwl_softc *sc, uint8_t mac[IEEE80211_ADDR_LEN])
  {
  	int i = mac[0] >> 2;
  	if (i != 0 || --sc->sc_nbssid0 == 0)
  		sc->sc_bssidmask &= ~(1<<i);
  }
  
  static struct ieee80211vap *
  mwl_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
      enum ieee80211_opmode opmode, int flags,
      const uint8_t bssid[IEEE80211_ADDR_LEN],
      const uint8_t mac0[IEEE80211_ADDR_LEN])
  {
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  	struct mwl_hal *mh = sc->sc_mh;
  	struct ieee80211vap *vap, *apvap;
  	struct mwl_hal_vap *hvap;
  	struct mwl_vap *mvp;
  	uint8_t mac[IEEE80211_ADDR_LEN];
  
  	IEEE80211_ADDR_COPY(mac, mac0);
  	switch (opmode) {
  	case IEEE80211_M_HOSTAP:
  	case IEEE80211_M_MBSS:
  		if ((flags & IEEE80211_CLONE_MACADDR) == 0)
  			assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID);
  		hvap = mwl_hal_newvap(mh, MWL_HAL_AP, mac);
  		if (hvap == NULL) {
  			if ((flags & IEEE80211_CLONE_MACADDR) == 0)
  				reclaim_address(sc, mac);
  			return NULL;
  		}
  		break;
  	case IEEE80211_M_STA:
  		if ((flags & IEEE80211_CLONE_MACADDR) == 0)
  			assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID);
  		hvap = mwl_hal_newvap(mh, MWL_HAL_STA, mac);
  		if (hvap == NULL) {
  			if ((flags & IEEE80211_CLONE_MACADDR) == 0)
  				reclaim_address(sc, mac);
  			return NULL;
  		}
  		/* no h/w beacon miss support; always use s/w */
  		flags |= IEEE80211_CLONE_NOBEACONS;
  		break;
  	case IEEE80211_M_WDS:
  		hvap = NULL;		/* NB: we use associated AP vap */
  		if (sc->sc_napvaps == 0)
  			return NULL;	/* no existing AP vap */
  		break;
  	case IEEE80211_M_MONITOR:
  		hvap = NULL;
  		break;
  	case IEEE80211_M_IBSS:
  	case IEEE80211_M_AHDEMO:
  	default:
  		return NULL;
  	}
  
  	mvp = (struct mwl_vap *) malloc(sizeof(struct mwl_vap),
  	    M_80211_VAP, M_NOWAIT | M_ZERO);
  	if (mvp == NULL) {
  		if (hvap != NULL) {
  			mwl_hal_delvap(hvap);
  			if ((flags & IEEE80211_CLONE_MACADDR) == 0)
  				reclaim_address(sc, mac);
  		}
  		/* XXX msg */
  		return NULL;
  	}
  	mvp->mv_hvap = hvap;
  	if (opmode == IEEE80211_M_WDS) {
  		/*
  		 * WDS vaps must have an associated AP vap; find one.
  		 * XXX not right.
  		 */
  		TAILQ_FOREACH(apvap, &ic->ic_vaps, iv_next)
  			if (apvap->iv_opmode == IEEE80211_M_HOSTAP) {
  				mvp->mv_ap_hvap = MWL_VAP(apvap)->mv_hvap;
  				break;
  			}
  		KASSERT(mvp->mv_ap_hvap != NULL, ("no ap vap"));
  	}
  	vap = &mvp->mv_vap;
  	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac);
  	if (hvap != NULL)
  		IEEE80211_ADDR_COPY(vap->iv_myaddr, mac);
  	/* override with driver methods */
  	mvp->mv_newstate = vap->iv_newstate;
  	vap->iv_newstate = mwl_newstate;
  	vap->iv_max_keyix = 0;	/* XXX */
  	vap->iv_key_alloc = mwl_key_alloc;
  	vap->iv_key_delete = mwl_key_delete;
  	vap->iv_key_set = mwl_key_set;
  #ifdef MWL_HOST_PS_SUPPORT
  	if (opmode == IEEE80211_M_HOSTAP || opmode == IEEE80211_M_MBSS) {
  		vap->iv_update_ps = mwl_update_ps;
  		mvp->mv_set_tim = vap->iv_set_tim;
  		vap->iv_set_tim = mwl_set_tim;
  	}
  #endif
  	vap->iv_reset = mwl_reset;
  	vap->iv_update_beacon = mwl_beacon_update;
  
  	/* override max aid so sta's cannot assoc when we're out of sta id's */
  	vap->iv_max_aid = MWL_MAXSTAID;
  	/* override default A-MPDU rx parameters */
  	vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
  	vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_4;
  
  	/* complete setup */
  	ieee80211_vap_attach(vap, mwl_media_change, ieee80211_media_status);
  
  	switch (vap->iv_opmode) {
  	case IEEE80211_M_HOSTAP:
  	case IEEE80211_M_MBSS:
  	case IEEE80211_M_STA:
  		/*
  		 * Setup sta db entry for local address.
  		 */
  		mwl_localstadb(vap);
  		if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
  		    vap->iv_opmode == IEEE80211_M_MBSS)
  			sc->sc_napvaps++;
  		else
  			sc->sc_nstavaps++;
  		break;
  	case IEEE80211_M_WDS:
  		sc->sc_nwdsvaps++;
  		break;
  	default:
  		break;
  	}
  	/*
  	 * Setup overall operating mode.
  	 */
  	if (sc->sc_napvaps)
  		ic->ic_opmode = IEEE80211_M_HOSTAP;
  	else if (sc->sc_nstavaps)
  		ic->ic_opmode = IEEE80211_M_STA;
  	else
  		ic->ic_opmode = opmode;
  
  	return vap;
  }
  
  static void
  mwl_vap_delete(struct ieee80211vap *vap)
  {
  	struct mwl_vap *mvp = MWL_VAP(vap);
  	struct ifnet *parent = vap->iv_ic->ic_ifp;
  	struct mwl_softc *sc = parent->if_softc;
  	struct mwl_hal *mh = sc->sc_mh;
  	struct mwl_hal_vap *hvap = mvp->mv_hvap;
  	enum ieee80211_opmode opmode = vap->iv_opmode;
  
  	/* XXX disallow ap vap delete if WDS still present */
  	if (parent->if_drv_flags & IFF_DRV_RUNNING) {
  		/* quiesce h/w while we remove the vap */
  		mwl_hal_intrset(mh, 0);		/* disable interrupts */
  	}
  	ieee80211_vap_detach(vap);
  	switch (opmode) {
  	case IEEE80211_M_HOSTAP:
  	case IEEE80211_M_MBSS:
  	case IEEE80211_M_STA:
  		KASSERT(hvap != NULL, ("no hal vap handle"));
  		(void) mwl_hal_delstation(hvap, vap->iv_myaddr);
  		mwl_hal_delvap(hvap);
  		if (opmode == IEEE80211_M_HOSTAP || opmode == IEEE80211_M_MBSS)
  			sc->sc_napvaps--;
  		else
  			sc->sc_nstavaps--;
  		/* XXX don't do it for IEEE80211_CLONE_MACADDR */
  		reclaim_address(sc, vap->iv_myaddr);
  		break;
  	case IEEE80211_M_WDS:
  		sc->sc_nwdsvaps--;
  		break;
  	default:
  		break;
  	}
  	mwl_cleartxq(sc, vap);
  	free(mvp, M_80211_VAP);
  	if (parent->if_drv_flags & IFF_DRV_RUNNING)
  		mwl_hal_intrset(mh, sc->sc_imask);
  }
  
  void
  mwl_suspend(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n",
  		__func__, ifp->if_flags);
  
  	mwl_stop(ifp, 1);
  }
  
  void
  mwl_resume(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n",
  		__func__, ifp->if_flags);
  
  	if (ifp->if_flags & IFF_UP)
  		mwl_init(sc);
  }
  
  void
  mwl_shutdown(void *arg)
  {
  	struct mwl_softc *sc = arg;
  
  	mwl_stop(sc->sc_ifp, 1);
  }
  
  /*
   * Interrupt handler.  Most of the actual processing is deferred.
   */
  void
  mwl_intr(void *arg)
  {
  	struct mwl_softc *sc = arg;
  	struct mwl_hal *mh = sc->sc_mh;
  	uint32_t status;
  
  	if (sc->sc_invalid) {
  		/*
  		 * The hardware is not ready/present, don't touch anything.
  		 * Note this can happen early on if the IRQ is shared.
  		 */
  		DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid; ignored\n", __func__);
  		return;
  	}
  	/*
  	 * Figure out the reason(s) for the interrupt.
  	 */
  	mwl_hal_getisr(mh, &status);		/* NB: clears ISR too */
  	if (status == 0)			/* must be a shared irq */
  		return;
  
  	DPRINTF(sc, MWL_DEBUG_INTR, "%s: status 0x%x imask 0x%x\n",
  	    __func__, status, sc->sc_imask);
  	if (status & MACREG_A2HRIC_BIT_RX_RDY)
  		taskqueue_enqueue(sc->sc_tq, &sc->sc_rxtask);
  	if (status & MACREG_A2HRIC_BIT_TX_DONE)
  		taskqueue_enqueue(sc->sc_tq, &sc->sc_txtask);
  	if (status & MACREG_A2HRIC_BIT_BA_WATCHDOG)
  		taskqueue_enqueue(sc->sc_tq, &sc->sc_bawatchdogtask);
  	if (status & MACREG_A2HRIC_BIT_OPC_DONE)
  		mwl_hal_cmddone(mh);
  	if (status & MACREG_A2HRIC_BIT_MAC_EVENT) {
  		;
  	}
  	if (status & MACREG_A2HRIC_BIT_ICV_ERROR) {
  		/* TKIP ICV error */
  		sc->sc_stats.mst_rx_badtkipicv++;
  	}
  	if (status & MACREG_A2HRIC_BIT_QUEUE_EMPTY) {
  		/* 11n aggregation queue is empty, re-fill */
  		;
  	}
  	if (status & MACREG_A2HRIC_BIT_QUEUE_FULL) {
  		;
  	}
  	if (status & MACREG_A2HRIC_BIT_RADAR_DETECT) {
  		/* radar detected, process event */
  		taskqueue_enqueue(sc->sc_tq, &sc->sc_radartask);
  	}
  	if (status & MACREG_A2HRIC_BIT_CHAN_SWITCH) {
  		/* DFS channel switch */
  		taskqueue_enqueue(sc->sc_tq, &sc->sc_chanswitchtask);
  	}
  }
  
  static void
  mwl_radar_proc(void *arg, int pending)
  {
  	struct mwl_softc *sc = arg;
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: radar detected, pending %u\n",
  	    __func__, pending);
  
  	sc->sc_stats.mst_radardetect++;
  	/* XXX stop h/w BA streams? */
  
  	IEEE80211_LOCK(ic);
  	ieee80211_dfs_notify_radar(ic, ic->ic_curchan);
  	IEEE80211_UNLOCK(ic);
  }
  
  static void
  mwl_chanswitch_proc(void *arg, int pending)
  {
  	struct mwl_softc *sc = arg;
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: channel switch notice, pending %u\n",
  	    __func__, pending);
  
  	IEEE80211_LOCK(ic);
  	sc->sc_csapending = 0;
  	ieee80211_csa_completeswitch(ic);
  	IEEE80211_UNLOCK(ic);
  }
  
  static void
  mwl_bawatchdog(const MWL_HAL_BASTREAM *sp)
  {
  	struct ieee80211_node *ni = sp->data[0];
  
  	/* send DELBA and drop the stream */
  	ieee80211_ampdu_stop(ni, sp->data[1], IEEE80211_REASON_UNSPECIFIED);
  }
  
  static void
  mwl_bawatchdog_proc(void *arg, int pending)
  {
  	struct mwl_softc *sc = arg;
  	struct mwl_hal *mh = sc->sc_mh;
  	const MWL_HAL_BASTREAM *sp;
  	uint8_t bitmap, n;
  
  	sc->sc_stats.mst_bawatchdog++;
  
  	if (mwl_hal_getwatchdogbitmap(mh, &bitmap) != 0) {
  		DPRINTF(sc, MWL_DEBUG_AMPDU,
  		    "%s: could not get bitmap\n", __func__);
  		sc->sc_stats.mst_bawatchdog_failed++;
  		return;
  	}
  	DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: bitmap 0x%x\n", __func__, bitmap);
  	if (bitmap == 0xff) {
  		n = 0;
  		/* disable all ba streams */
  		for (bitmap = 0; bitmap < 8; bitmap++) {
  			sp = mwl_hal_bastream_lookup(mh, bitmap);
  			if (sp != NULL) {
  				mwl_bawatchdog(sp);
  				n++;
  			}
  		}
  		if (n == 0) {
  			DPRINTF(sc, MWL_DEBUG_AMPDU,
  			    "%s: no BA streams found\n", __func__);
  			sc->sc_stats.mst_bawatchdog_empty++;
  		}
  	} else if (bitmap != 0xaa) {
  		/* disable a single ba stream */
  		sp = mwl_hal_bastream_lookup(mh, bitmap);
  		if (sp != NULL) {
  			mwl_bawatchdog(sp);
  		} else {
  			DPRINTF(sc, MWL_DEBUG_AMPDU,
  			    "%s: no BA stream %d\n", __func__, bitmap);
  			sc->sc_stats.mst_bawatchdog_notfound++;
  		}
  	}
  }
  
  /*
   * Convert net80211 channel to a HAL channel.
   */
  static void
  mwl_mapchan(MWL_HAL_CHANNEL *hc, const struct ieee80211_channel *chan)
  {
  	hc->channel = chan->ic_ieee;
  
  	*(uint32_t *)&hc->channelFlags = 0;
  	if (IEEE80211_IS_CHAN_2GHZ(chan))
  		hc->channelFlags.FreqBand = MWL_FREQ_BAND_2DOT4GHZ;
  	else if (IEEE80211_IS_CHAN_5GHZ(chan))
  		hc->channelFlags.FreqBand = MWL_FREQ_BAND_5GHZ;
  	if (IEEE80211_IS_CHAN_HT40(chan)) {
  		hc->channelFlags.ChnlWidth = MWL_CH_40_MHz_WIDTH;
  		if (IEEE80211_IS_CHAN_HT40U(chan))
  			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 */
  }
  
  /*
   * 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 error, i;
  
  	sc->sc_hwdma.rxDescRead = sc->sc_rxdma.dd_desc_paddr;
  	WR4(sc, sc->sc_hwspecs.rxDescRead, sc->sc_hwdma.rxDescRead);
  	WR4(sc, sc->sc_hwspecs.rxDescWrite, sc->sc_hwdma.rxDescRead);
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES-MWL_NUM_ACK_QUEUES; i++) {
  		struct mwl_txq *txq = &sc->sc_txq[i];
  		sc->sc_hwdma.wcbBase[i] = txq->dma.dd_desc_paddr;
  		WR4(sc, sc->sc_hwspecs.wcbBase[i], sc->sc_hwdma.wcbBase[i]);
  	}
  	sc->sc_hwdma.maxNumTxWcb = mwl_txbuf;
  	sc->sc_hwdma.maxNumWCB = MWL_NUM_TX_QUEUES-MWL_NUM_ACK_QUEUES;
  
  	error = mwl_hal_sethwdma(sc->sc_mh, &sc->sc_hwdma);
  	if (error != 0) {
  		device_printf(sc->sc_dev,
  		    "unable to setup tx/rx dma; hal status %u\n", error);
  		/* XXX */
  	}
  	return error;
  }
  
  /*
   * Inform firmware of tx rate parameters.
   * Called after a channel change.
   */
  static int
  mwl_setcurchanrates(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	const struct ieee80211_rateset *rs;
  	MWL_HAL_TXRATE rates;
  
  	memset(&rates, 0, sizeof(rates));
  	rs = ieee80211_get_suprates(ic, ic->ic_curchan);
  	/* rate used to send management frames */
  	rates.MgtRate = rs->rs_rates[0] & IEEE80211_RATE_VAL;
  	/* rate used to send multicast frames */
  	rates.McastRate = rates.MgtRate;
  
  	return mwl_hal_settxrate_auto(sc->sc_mh, &rates);
  }
  
  /*
   * Inform firmware of tx rate parameters.  Called whenever
   * user-settable params change and after a channel change.
   */
  static int
  mwl_setrates(struct ieee80211vap *vap)
  {
  	struct mwl_vap *mvp = MWL_VAP(vap);
  	struct ieee80211_node *ni = vap->iv_bss;
  	const struct ieee80211_txparam *tp = ni->ni_txparms;
  	MWL_HAL_TXRATE rates;
  
  	KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state));
  
  	/*
  	 * Update the h/w rate map.
  	 * NB: 0x80 for MCS is passed through unchanged
  	 */
  	memset(&rates, 0, sizeof(rates));
  	/* rate used to send management frames */
  	rates.MgtRate = tp->mgmtrate;
  	/* rate used to send multicast frames */
  	rates.McastRate = tp->mcastrate;
  
  	/* while here calculate EAPOL fixed rate cookie */
  	mvp->mv_eapolformat = htole16(mwl_calcformat(rates.MgtRate, ni));
  
  	return mwl_hal_settxrate(mvp->mv_hvap,
  	    tp->ucastrate != IEEE80211_FIXED_RATE_NONE ?
  		RATE_FIXED : RATE_AUTO, &rates);
  }
  
  /*
   * Setup a fixed xmit rate cookie for EAPOL frames.
   */
  static void
  mwl_seteapolformat(struct ieee80211vap *vap)
  {
  	struct mwl_vap *mvp = MWL_VAP(vap);
  	struct ieee80211_node *ni = vap->iv_bss;
  	enum ieee80211_phymode mode;
  	uint8_t rate;
  
  	KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state));
  
  	mode = ieee80211_chan2mode(ni->ni_chan);
  	/*
  	 * Use legacy rates when operating a mixed HT+non-HT bss.
  	 * NB: this may violate POLA for sta and wds vap's.
  	 */
  	if (mode == IEEE80211_MODE_11NA &&
  	    (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0)
  		rate = vap->iv_txparms[IEEE80211_MODE_11A].mgmtrate;
  	else if (mode == IEEE80211_MODE_11NG &&
  	    (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0)
  		rate = vap->iv_txparms[IEEE80211_MODE_11G].mgmtrate;
  	else
  		rate = vap->iv_txparms[mode].mgmtrate;
  
  	mvp->mv_eapolformat = htole16(mwl_calcformat(rate, ni));
  }
  
  /*
   * Map SKU+country code to region code for radar bin'ing.
   */
  static int
  mwl_map2regioncode(const struct ieee80211_regdomain *rd)
  {
  	switch (rd->regdomain) {
  	case SKU_FCC:
  	case SKU_FCC3:
  		return DOMAIN_CODE_FCC;
  	case SKU_CA:
  		return DOMAIN_CODE_IC;
  	case SKU_ETSI:
  	case SKU_ETSI2:
  	case SKU_ETSI3:
  		if (rd->country == CTRY_SPAIN)
  			return DOMAIN_CODE_SPAIN;
  		if (rd->country == CTRY_FRANCE || rd->country == CTRY_FRANCE2)
  			return DOMAIN_CODE_FRANCE;
  		/* XXX force 1.3.1 radar type */
  		return DOMAIN_CODE_ETSI_131;
  	case SKU_JAPAN:
  		return DOMAIN_CODE_MKK;
  	case SKU_ROW:
  		return DOMAIN_CODE_DGT;	/* Taiwan */
  	case SKU_APAC:
  	case SKU_APAC2:
  	case SKU_APAC3:
  		return DOMAIN_CODE_AUS;	/* Australia */
  	}
  	/* XXX KOREA? */
  	return DOMAIN_CODE_FCC;			/* XXX? */
  }
  
  static int
  mwl_hal_reset(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_hal *mh = sc->sc_mh;
  
  	mwl_hal_setantenna(mh, WL_ANTENNATYPE_RX, sc->sc_rxantenna);
  	mwl_hal_setantenna(mh, WL_ANTENNATYPE_TX, sc->sc_txantenna);
  	mwl_hal_setradio(mh, 1, WL_AUTO_PREAMBLE);
  	mwl_hal_setwmm(sc->sc_mh, (ic->ic_flags & IEEE80211_F_WME) != 0);
  	mwl_chan_set(sc, ic->ic_curchan);
  	/* NB: RF/RA performance tuned for indoor mode */
  	mwl_hal_setrateadaptmode(mh, 0);
  	mwl_hal_setoptimizationlevel(mh,
  	    (ic->ic_flags & IEEE80211_F_BURST) != 0);
  
  	mwl_hal_setregioncode(mh, mwl_map2regioncode(&ic->ic_regdomain));
  
  	mwl_hal_setaggampduratemode(mh, 1, 80);		/* XXX */
  	mwl_hal_setcfend(mh, 0);			/* XXX */
  
  	return 1;
  }
  
  static int
  mwl_init_locked(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct mwl_hal *mh = sc->sc_mh;
  	int error = 0;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags 0x%x\n",
  		__func__, ifp->if_flags);
  
  	MWL_LOCK_ASSERT(sc);
  
  	/*
  	 * Stop anything previously setup.  This is safe
  	 * whether this is the first time through or not.
  	 */
  	mwl_stop_locked(ifp, 0);
  
  	/*
  	 * Push vap-independent state to the firmware.
  	 */
  	if (!mwl_hal_reset(sc)) {
  		if_printf(ifp, "unable to reset hardware\n");
  		return EIO;
  	}
  
  	/*
  	 * Setup recv (once); transmit is already good to go.
  	 */
  	error = mwl_startrecv(sc);
  	if (error != 0) {
  		if_printf(ifp, "unable to start recv logic\n");
  		return error;
  	}
  
  	/*
  	 * Enable interrupts.
  	 */
  	sc->sc_imask = MACREG_A2HRIC_BIT_RX_RDY
  		     | MACREG_A2HRIC_BIT_TX_DONE
  		     | MACREG_A2HRIC_BIT_OPC_DONE
  #if 0
  		     | MACREG_A2HRIC_BIT_MAC_EVENT
  #endif
  		     | MACREG_A2HRIC_BIT_ICV_ERROR
  		     | MACREG_A2HRIC_BIT_RADAR_DETECT
  		     | MACREG_A2HRIC_BIT_CHAN_SWITCH
  #if 0
  		     | MACREG_A2HRIC_BIT_QUEUE_EMPTY
  #endif
  		     | MACREG_A2HRIC_BIT_BA_WATCHDOG
  		     | MACREQ_A2HRIC_BIT_TX_ACK
  		     ;
  
  	ifp->if_drv_flags |= IFF_DRV_RUNNING;
  	mwl_hal_intrset(mh, sc->sc_imask);
  	callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc);
  
  	return 0;
  }
  
  static void
  mwl_init(void *arg)
  {
  	struct mwl_softc *sc = arg;
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	int error = 0;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags 0x%x\n",
  		__func__, ifp->if_flags);
  
  	MWL_LOCK(sc);
  	error = mwl_init_locked(sc);
  	MWL_UNLOCK(sc);
  
  	if (error == 0)
  		ieee80211_start_all(ic);	/* start all vap's */
  }
  
  static void
  mwl_stop_locked(struct ifnet *ifp, int disable)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  
  	DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid %u if_flags 0x%x\n",
  		__func__, sc->sc_invalid, ifp->if_flags);
  
  	MWL_LOCK_ASSERT(sc);
  	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
  		/*
  		 * Shutdown the hardware and driver.
  		 */
  		ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
  		callout_stop(&sc->sc_watchdog);
  		sc->sc_tx_timer = 0;
  		mwl_draintxq(sc);
  	}
  }
  
  static void
  mwl_stop(struct ifnet *ifp, int disable)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  
  	MWL_LOCK(sc);
  	mwl_stop_locked(ifp, disable);
  	MWL_UNLOCK(sc);
  }
  
  static int
  mwl_reset_vap(struct ieee80211vap *vap, int state)
  {
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	struct ieee80211com *ic = vap->iv_ic;
  
  	if (state == IEEE80211_S_RUN)
  		mwl_setrates(vap);
  	/* XXX off by 1? */
  	mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold);
  	/* XXX auto? 20/40 split? */
  	mwl_hal_sethtgi(hvap, (vap->iv_flags_ht &
  	    (IEEE80211_FHT_SHORTGI20|IEEE80211_FHT_SHORTGI40)) ? 1 : 0);
  	mwl_hal_setnprot(hvap, ic->ic_htprotmode == IEEE80211_PROT_NONE ?
  	    HTPROTECT_NONE : HTPROTECT_AUTO);
  	/* XXX txpower cap */
  
  	/* re-setup beacons */
  	if (state == IEEE80211_S_RUN &&
  	    (vap->iv_opmode == IEEE80211_M_HOSTAP ||
  	     vap->iv_opmode == IEEE80211_M_MBSS ||
  	     vap->iv_opmode == IEEE80211_M_IBSS)) {
  		mwl_setapmode(vap, vap->iv_bss->ni_chan);
  		mwl_hal_setnprotmode(hvap,
  		    MS(ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE));
  		return mwl_beacon_setup(vap);
  	}
  	return 0;
  }
  
  /*
   * Reset the hardware w/o losing operational state.
   * Used to to reset or reload hardware state for a vap.
   */
  static int
  mwl_reset(struct ieee80211vap *vap, u_long cmd)
  {
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	int error = 0;
  
  	if (hvap != NULL) {			/* WDS, MONITOR, etc. */
  		struct ieee80211com *ic = vap->iv_ic;
  		struct ifnet *ifp = ic->ic_ifp;
  		struct mwl_softc *sc = ifp->if_softc;
  		struct mwl_hal *mh = sc->sc_mh;
  
  		/* XXX handle DWDS sta vap change */
  		/* XXX do we need to disable interrupts? */
  		mwl_hal_intrset(mh, 0);		/* disable interrupts */
  		error = mwl_reset_vap(vap, vap->iv_state);
  		mwl_hal_intrset(mh, sc->sc_imask);
  	}
  	return error;
  }
  
  /*
   * Allocate a tx buffer for sending a frame.  The
   * packet is assumed to have the WME AC stored so
   * we can use it to select the appropriate h/w queue.
   */
  static struct mwl_txbuf *
  mwl_gettxbuf(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	struct mwl_txbuf *bf;
  
  	/*
  	 * Grab a TX buffer and associated resources.
  	 */
  	MWL_TXQ_LOCK(txq);
  	bf = STAILQ_FIRST(&txq->free);
  	if (bf != NULL) {
  		STAILQ_REMOVE_HEAD(&txq->free, bf_list);
  		txq->nfree--;
  	}
  	MWL_TXQ_UNLOCK(txq);
  	if (bf == NULL)
  		DPRINTF(sc, MWL_DEBUG_XMIT,
  		    "%s: out of xmit buffers on q %d\n", __func__, txq->qnum);
  	return bf;
  }
  
  /*
   * Return a tx buffer to the queue it came from.  Note there
   * are two cases because we must preserve the order of buffers
   * as it reflects the fixed order of descriptors in memory
   * (the firmware pre-fetches descriptors so we cannot reorder).
   */
  static void
  mwl_puttxbuf_head(struct mwl_txq *txq, struct mwl_txbuf *bf)
  {
  	bf->bf_m = NULL;
  	bf->bf_node = NULL;
  	MWL_TXQ_LOCK(txq);
  	STAILQ_INSERT_HEAD(&txq->free, bf, bf_list);
  	txq->nfree++;
  	MWL_TXQ_UNLOCK(txq);
  }
  
  static void
  mwl_puttxbuf_tail(struct mwl_txq *txq, struct mwl_txbuf *bf)
  {
  	bf->bf_m = NULL;
  	bf->bf_node = NULL;
  	MWL_TXQ_LOCK(txq);
  	STAILQ_INSERT_TAIL(&txq->free, bf, bf_list);
  	txq->nfree++;
  	MWL_TXQ_UNLOCK(txq);
  }
  
  static void
  mwl_start(struct ifnet *ifp)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  	struct ieee80211_node *ni;
  	struct mwl_txbuf *bf;
  	struct mbuf *m;
  	struct mwl_txq *txq = NULL;	/* XXX silence gcc */
  	int nqueued;
  
  	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->sc_invalid)
  		return;
  	nqueued = 0;
  	for (;;) {
  		bf = NULL;
  		IFQ_DEQUEUE(&ifp->if_snd, m);
  		if (m == NULL)
  			break;
  		/*
  		 * Grab the node for the destination.
  		 */
  		ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
  		KASSERT(ni != NULL, ("no node"));
  		m->m_pkthdr.rcvif = NULL;	/* committed, clear ref */
  		/*
  		 * Grab a TX buffer and associated resources.
  		 * We honor the classification by the 802.11 layer.
  		 */
  		txq = sc->sc_ac2q[M_WME_GETAC(m)];
  		bf = mwl_gettxbuf(sc, txq);
  		if (bf == NULL) {
  			m_freem(m);
  			ieee80211_free_node(ni);
  #ifdef MWL_TX_NODROP
  			sc->sc_stats.mst_tx_qstop++;
  			/* XXX blocks other traffic */
  			ifp->if_drv_flags |= IFF_DRV_OACTIVE;
  			break;
  #else
  			DPRINTF(sc, MWL_DEBUG_XMIT,
  			    "%s: tail drop on q %d\n", __func__, txq->qnum);
  			sc->sc_stats.mst_tx_qdrop++;
  			continue;
  #endif /* MWL_TX_NODROP */
  		}
  
  		/*
  		 * Pass the frame to the h/w for transmission.
  		 */
  		if (mwl_tx_start(sc, ni, bf, m)) {
  			ifp->if_oerrors++;
  			mwl_puttxbuf_head(txq, bf);
  			ieee80211_free_node(ni);
  			continue;
  		}
  		nqueued++;
  		if (nqueued >= mwl_txcoalesce) {
  			/*
  			 * Poke the firmware to process queued frames;
  			 * see below about (lack of) locking.
  			 */
  			nqueued = 0;
  			mwl_hal_txstart(sc->sc_mh, 0/*XXX*/);
  		}
  	}
  	if (nqueued) {
  		/*
  		 * NB: We don't need to lock against tx done because
  		 * this just prods the firmware to check the transmit
  		 * descriptors.  The firmware will also start fetching
  		 * descriptors by itself if it notices new ones are
  		 * present when it goes to deliver a tx done interrupt
  		 * to the host. So if we race with tx done processing
  		 * it's ok.  Delivering the kick here rather than in
  		 * mwl_tx_start is an optimization to avoid poking the
  		 * firmware for each packet.
  		 *
  		 * NB: the queue id isn't used so 0 is ok.
  		 */
  		mwl_hal_txstart(sc->sc_mh, 0/*XXX*/);
  	}
  }
  
  static int
  mwl_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
  	const struct ieee80211_bpf_params *params)
  {
  	struct ieee80211com *ic = ni->ni_ic;
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  	struct mwl_txbuf *bf;
  	struct mwl_txq *txq;
  
  	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->sc_invalid) {
  		ieee80211_free_node(ni);
  		m_freem(m);
  		return ENETDOWN;
  	}
  	/*
  	 * Grab a TX buffer and associated resources.
  	 * Note that we depend on the classification
  	 * by the 802.11 layer to get to the right h/w
  	 * queue.  Management frames must ALWAYS go on
  	 * queue 1 but we cannot just force that here
  	 * because we may receive non-mgt frames.
  	 */
  	txq = sc->sc_ac2q[M_WME_GETAC(m)];
  	bf = mwl_gettxbuf(sc, txq);
  	if (bf == NULL) {
  		sc->sc_stats.mst_tx_qstop++;
  		/* XXX blocks other traffic */
  		ifp->if_drv_flags |= IFF_DRV_OACTIVE;
  		ieee80211_free_node(ni);
  		m_freem(m);
  		return ENOBUFS;
  	}
  	/*
  	 * Pass the frame to the h/w for transmission.
  	 */
  	if (mwl_tx_start(sc, ni, bf, m)) {
  		ifp->if_oerrors++;
  		mwl_puttxbuf_head(txq, bf);
  
  		ieee80211_free_node(ni);
  		return EIO;		/* XXX */
  	}
  	/*
  	 * NB: We don't need to lock against tx done because
  	 * this just prods the firmware to check the transmit
  	 * descriptors.  The firmware will also start fetching
  	 * descriptors by itself if it notices new ones are
  	 * present when it goes to deliver a tx done interrupt
  	 * to the host. So if we race with tx done processing
  	 * it's ok.  Delivering the kick here rather than in
  	 * mwl_tx_start is an optimization to avoid poking the
  	 * firmware for each packet.
  	 *
  	 * NB: the queue id isn't used so 0 is ok.
  	 */
  	mwl_hal_txstart(sc->sc_mh, 0/*XXX*/);
  	return 0;
  }
  
  static int
  mwl_media_change(struct ifnet *ifp)
  {
  	struct ieee80211vap *vap = ifp->if_softc;
  	int error;
  
  	error = ieee80211_media_change(ifp);
  	/* NB: only the fixed rate can change and that doesn't need a reset */
  	if (error == ENETRESET) {
  		mwl_setrates(vap);
  		error = 0;
  	}
  	return error;
  }
  
  #ifdef MWL_DEBUG
  static void
  mwl_keyprint(struct mwl_softc *sc, const char *tag,
  	const MWL_HAL_KEYVAL *hk, const uint8_t mac[IEEE80211_ADDR_LEN])
  {
  	static const char *ciphers[] = {
  		"WEP",
  		"TKIP",
  		"AES-CCM",
  	};
  	int i, n;
  
  	printf("%s: [%u] %-7s", tag, hk->keyIndex, ciphers[hk->keyTypeId]);
  	for (i = 0, n = hk->keyLen; i < n; i++)
  		printf(" %02x", hk->key.aes[i]);
  	printf(" mac %s", ether_sprintf(mac));
  	if (hk->keyTypeId == KEY_TYPE_ID_TKIP) {
  		printf(" %s", "rxmic");
  		for (i = 0; i < sizeof(hk->key.tkip.rxMic); i++)
  			printf(" %02x", hk->key.tkip.rxMic[i]);
  		printf(" txmic");
  		for (i = 0; i < sizeof(hk->key.tkip.txMic); i++)
  			printf(" %02x", hk->key.tkip.txMic[i]);
  	}
  	printf(" flags 0x%x\n", hk->keyFlags);
  }
  #endif
  
  /*
   * 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 ieee80211vap *vap, struct ieee80211_key *k,
  	ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
  {
  	struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc;
  
  	if (k->wk_keyix != IEEE80211_KEYIX_NONE ||
  	    (k->wk_flags & IEEE80211_KEY_GROUP)) {
  		if (!(&vap->iv_nw_keys[0] <= k &&
  		      k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) {
  			/* should not happen */
  			DPRINTF(sc, MWL_DEBUG_KEYCACHE,
  				"%s: bogus group key\n", __func__);
  			return 0;
  		}
  		/* give the caller what they requested */
  		*keyix = *rxkeyix = k - vap->iv_nw_keys;
  	} else {
  		/*
  		 * Firmware handles key allocation.
  		 */
  		*keyix = *rxkeyix = 0;
  	}
  	return 1;
  }
  
  /*
   * Delete a key entry allocated by mwl_key_alloc.
   */
  static int
  mwl_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
  {
  	struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc;
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	MWL_HAL_KEYVAL hk;
  	const uint8_t bcastaddr[IEEE80211_ADDR_LEN] =
  	    { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
  
  	if (hvap == NULL) {
  		if (vap->iv_opmode != IEEE80211_M_WDS) {
  			/* XXX monitor mode? */
  			DPRINTF(sc, MWL_DEBUG_KEYCACHE,
  			    "%s: no hvap for opmode %d\n", __func__,
  			    vap->iv_opmode);
  			return 0;
  		}
  		hvap = MWL_VAP(vap)->mv_ap_hvap;
  	}
  
  	DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: delete key %u\n",
  	    __func__, k->wk_keyix);
  
  	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 */
  		DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n",
  		    __func__, k->wk_cipher->ic_cipher);
  		return 0;
  	}
  	return (mwl_hal_keyreset(hvap, &hk, bcastaddr) == 0);	/*XXX*/
  }
  
  static __inline 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 the key cache contents for the specified key.  Key cache
   * slot(s) must already have been allocated by mwl_key_alloc.
   */
  static int
  mwl_key_set(struct ieee80211vap *vap, 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 = vap->iv_ic->ic_ifp->if_softc;
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	const struct ieee80211_cipher *cip = k->wk_cipher;
  	const uint8_t *macaddr;
  	MWL_HAL_KEYVAL hk;
  
  	KASSERT((k->wk_flags & IEEE80211_KEY_SWCRYPT) == 0,
  		("s/w crypto set?"));
  
  	if (hvap == NULL) {
  		if (vap->iv_opmode != IEEE80211_M_WDS) {
  			/* XXX monitor mode? */
  			DPRINTF(sc, MWL_DEBUG_KEYCACHE,
  			    "%s: no hvap for opmode %d\n", __func__,
  			    vap->iv_opmode);
  			return 0;
  		}
  		hvap = MWL_VAP(vap)->mv_ap_hvap;
  	}
  	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 == vap->iv_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 */
  		DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n",
  		    __func__, 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.
  	 */
  	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.
  	 */
  	if (vap->iv_opmode == IEEE80211_M_STA) {
  		/*
  		 * 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 = vap->iv_bss->ni_bssid;
  		if ((k->wk_flags & IEEE80211_KEY_GROUP) == 0) {
  			/* XXX plumb to local sta db too for static key wep */
  			mwl_hal_keyset(hvap, &hk, vap->iv_myaddr);
  		}
  	} else if (vap->iv_opmode == IEEE80211_M_WDS &&
  	    vap->iv_state != IEEE80211_S_RUN) {
  		/*
  		 * Prior to RUN state a WDS vap will not it's BSS node
  		 * setup so we will plumb the key to the wrong mac
  		 * address (it'll be our local address).  Workaround
  		 * this for the moment by grabbing the correct address.
  		 */
  		macaddr = vap->iv_des_bssid;
  	} else if ((k->wk_flags & GRPXMIT) == GRPXMIT)
  		macaddr = vap->iv_myaddr;
  	else
  		macaddr = mac;
  	KEYPRINTF(sc, &hk, macaddr);
  	return (mwl_hal_keyset(hvap, &hk, macaddr) == 0);
  #undef IEEE80211_IS_STATICKEY
  #undef GRPXMIT
  }
  
  /* unaligned little endian access */
  #define LE_READ_2(p)				\
  	((uint16_t)				\
  	 ((((const uint8_t *)(p))[0]      ) |	\
  	  (((const uint8_t *)(p))[1] <<  8)))
  #define LE_READ_4(p)				\
  	((uint32_t)				\
  	 ((((const uint8_t *)(p))[0]      ) |	\
  	  (((const uint8_t *)(p))[1] <<  8) |	\
  	  (((const uint8_t *)(p))[2] << 16) |	\
  	  (((const uint8_t *)(p))[3] << 24)))
  
  /*
   * Set the multicast filter contents into the hardware.
   * XXX f/w has no support; just defer to the os.
   */
  static void
  mwl_setmcastfilter(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  #if 0
  	struct ether_multi *enm;
  	struct ether_multistep estep;
  	uint8_t macs[IEEE80211_ADDR_LEN*MWL_HAL_MCAST_MAX];/* XXX stack use */
  	uint8_t *mp;
  	int nmc;
  
  	mp = macs;
  	nmc = 0;
  	ETHER_FIRST_MULTI(estep, &sc->sc_ec, enm);
  	while (enm != NULL) {
  		/* XXX Punt on ranges. */
  		if (nmc == MWL_HAL_MCAST_MAX ||
  		    !IEEE80211_ADDR_EQ(enm->enm_addrlo, enm->enm_addrhi)) {
  			ifp->if_flags |= IFF_ALLMULTI;
  			return;
  		}
  		IEEE80211_ADDR_COPY(mp, enm->enm_addrlo);
  		mp += IEEE80211_ADDR_LEN, nmc++;
  		ETHER_NEXT_MULTI(estep, enm);
  	}
  	ifp->if_flags &= ~IFF_ALLMULTI;
  	mwl_hal_setmcast(sc->sc_mh, nmc, macs);
  #else
  	/* XXX no mcast filter support; we get everything */
  	ifp->if_flags |= IFF_ALLMULTI;
  #endif
  }
  
  static int
  mwl_mode_init(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_hal *mh = sc->sc_mh;
  
  	/*
  	 * NB: Ignore promisc in hostap mode; it's set by the
  	 * bridge.  This is wrong but we have no way to
  	 * identify internal requests (from the bridge)
  	 * versus external requests such as for tcpdump.
  	 */
  	mwl_hal_setpromisc(mh, (ifp->if_flags & IFF_PROMISC) &&
  	    ic->ic_opmode != IEEE80211_M_HOSTAP);
  	mwl_setmcastfilter(sc);
  
  	return 0;
  }
  
  /*
   * Callback from the 802.11 layer after a multicast state change.
   */
  static void
  mwl_update_mcast(struct ifnet *ifp)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  
  	mwl_setmcastfilter(sc);
  }
  
  /*
   * Callback from the 802.11 layer after a promiscuous mode change.
   * Note this interface does not check the operating mode as this
   * is an internal callback and we are expected to honor the current
   * state (e.g. this is used for setting the interface in promiscuous
   * mode when operating in hostap mode to do ACS).
   */
  static void
  mwl_update_promisc(struct ifnet *ifp)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  
  	mwl_hal_setpromisc(sc->sc_mh, (ifp->if_flags & IFF_PROMISC) != 0);
  }
  
  /*
   * Callback from the 802.11 layer to update the slot time
   * based on the current setting.  We use it to notify the
   * firmware of ERP changes and the f/w takes care of things
   * like slot time and preamble.
   */
  static void
  mwl_updateslot(struct ifnet *ifp)
  {
  	struct mwl_softc *sc = ifp->if_softc;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_hal *mh = sc->sc_mh;
  	int prot;
  
  	/* NB: can be called early; suppress needless cmds */
  	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
  		return;
  
  	/*
  	 * Calculate the ERP flags.  The firwmare will use
  	 * this to carry out the appropriate measures.
  	 */
  	prot = 0;
  	if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) {
  		if ((ic->ic_flags & IEEE80211_F_SHSLOT) == 0)
  			prot |= IEEE80211_ERP_NON_ERP_PRESENT;
  		if (ic->ic_flags & IEEE80211_F_USEPROT)
  			prot |= IEEE80211_ERP_USE_PROTECTION;
  		if (ic->ic_flags & IEEE80211_F_USEBARKER)
  			prot |= IEEE80211_ERP_LONG_PREAMBLE;
  	}
  
  	DPRINTF(sc, MWL_DEBUG_RESET,
  	    "%s: chan %u MHz/flags 0x%x %s slot, (prot 0x%x ic_flags 0x%x)\n",
  	    __func__, ic->ic_curchan->ic_freq, ic->ic_curchan->ic_flags,
  	    ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", prot,
  	    ic->ic_flags);
  
  	mwl_hal_setgprot(mh, prot);
  }
  
  /*
   * Setup the beacon frame.
   */
  static int
  mwl_beacon_setup(struct ieee80211vap *vap)
  {
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	struct ieee80211_node *ni = vap->iv_bss;
  	struct ieee80211_beacon_offsets bo;
  	struct mbuf *m;
  
  	m = ieee80211_beacon_alloc(ni, &bo);
  	if (m == NULL)
  		return ENOBUFS;
  	mwl_hal_setbeacon(hvap, mtod(m, const void *), m->m_len);
  	m_free(m);
  
  	return 0;
  }
  
  /*
   * Update the beacon frame in response to a change.
   */
  static void
  mwl_beacon_update(struct ieee80211vap *vap, int item)
  {
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	struct ieee80211com *ic = vap->iv_ic;
  
  	KASSERT(hvap != NULL, ("no beacon"));
  	switch (item) {
  	case IEEE80211_BEACON_ERP:
  		mwl_updateslot(ic->ic_ifp);
  		break;
  	case IEEE80211_BEACON_HTINFO:
  		mwl_hal_setnprotmode(hvap,
  		    MS(ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE));
  		break;
  	case IEEE80211_BEACON_CAPS:
  	case IEEE80211_BEACON_WME:
  	case IEEE80211_BEACON_APPIE:
  	case IEEE80211_BEACON_CSA:
  		break;
  	case IEEE80211_BEACON_TIM:
  		/* NB: firmware always forms TIM */
  		return;
  	}
  	/* XXX retain beacon frame and update */
  	mwl_beacon_setup(vap);
  }
  
  static void
  mwl_load_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
  {
  	bus_addr_t *paddr = (bus_addr_t*) arg;
  	KASSERT(error == 0, ("error %u on bus_dma callback", error));
  	*paddr = segs->ds_addr;
  }
  
  #ifdef MWL_HOST_PS_SUPPORT
  /*
   * Handle power save station occupancy changes.
   */
  static void
  mwl_update_ps(struct ieee80211vap *vap, int nsta)
  {
  	struct mwl_vap *mvp = MWL_VAP(vap);
  
  	if (nsta == 0 || mvp->mv_last_ps_sta == 0)
  		mwl_hal_setpowersave_bss(mvp->mv_hvap, nsta);
  	mvp->mv_last_ps_sta = nsta;
  }
  
  /*
   * Handle associated station power save state changes.
   */
  static int
  mwl_set_tim(struct ieee80211_node *ni, int set)
  {
  	struct ieee80211vap *vap = ni->ni_vap;
  	struct mwl_vap *mvp = MWL_VAP(vap);
  
  	if (mvp->mv_set_tim(ni, set)) {		/* NB: state change */
  		mwl_hal_setpowersave_sta(mvp->mv_hvap,
  		    IEEE80211_AID(ni->ni_associd), set);
  		return 1;
  	} else
  		return 0;
  }
  #endif /* MWL_HOST_PS_SUPPORT */
  
  static int
  mwl_desc_setup(struct mwl_softc *sc, const char *name,
  	struct mwl_descdma *dd,
  	int nbuf, size_t bufsize, int ndesc, size_t descsize)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	uint8_t *ds;
  	int error;
  
  	DPRINTF(sc, MWL_DEBUG_RESET,
  	    "%s: %s DMA: %u bufs (%ju) %u desc/buf (%ju)\n",
  	    __func__, name, nbuf, (uintmax_t) bufsize,
  	    ndesc, (uintmax_t) descsize);
  
  	dd->dd_name = name;
  	dd->dd_desc_len = nbuf * ndesc * descsize;
  
  	/*
  	 * Setup DMA descriptor area.
  	 */
  	error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev),	/* parent */
  		       PAGE_SIZE, 0,		/* alignment, bounds */
  		       BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
  		       BUS_SPACE_MAXADDR,	/* highaddr */
  		       NULL, NULL,		/* filter, filterarg */
  		       dd->dd_desc_len,		/* maxsize */
  		       1,			/* nsegments */
  		       dd->dd_desc_len,		/* maxsegsize */
  		       BUS_DMA_ALLOCNOW,	/* flags */
  		       NULL,			/* lockfunc */
  		       NULL,			/* lockarg */
  		       &dd->dd_dmat);
  	if (error != 0) {
  		if_printf(ifp, "cannot allocate %s DMA tag\n", dd->dd_name);
  		return error;
  	}
  
  	/* allocate descriptors */
  	error = bus_dmamap_create(dd->dd_dmat, BUS_DMA_NOWAIT, &dd->dd_dmamap);
  	if (error != 0) {
  		if_printf(ifp, "unable to create dmamap for %s descriptors, "
  			"error %u\n", dd->dd_name, error);
  		goto fail0;
  	}
  
  	error = bus_dmamem_alloc(dd->dd_dmat, (void**) &dd->dd_desc,
  				 BUS_DMA_NOWAIT | BUS_DMA_COHERENT, 
  				 &dd->dd_dmamap);
  	if (error != 0) {
  		if_printf(ifp, "unable to alloc memory for %u %s descriptors, "
  			"error %u\n", nbuf * ndesc, dd->dd_name, error);
  		goto fail1;
  	}
  
  	error = bus_dmamap_load(dd->dd_dmat, dd->dd_dmamap,
  				dd->dd_desc, dd->dd_desc_len,
  				mwl_load_cb, &dd->dd_desc_paddr,
  				BUS_DMA_NOWAIT);
  	if (error != 0) {
  		if_printf(ifp, "unable to map %s descriptors, error %u\n",
  			dd->dd_name, error);
  		goto fail2;
  	}
  
  	ds = dd->dd_desc;
  	memset(ds, 0, dd->dd_desc_len);
  	DPRINTF(sc, MWL_DEBUG_RESET, "%s: %s DMA map: %p (%lu) -> %p (%lu)\n",
  	    __func__, dd->dd_name, ds, (u_long) dd->dd_desc_len,
  	    (caddr_t) dd->dd_desc_paddr, /*XXX*/ (u_long) dd->dd_desc_len);
  
  	return 0;
  fail2:
  	bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap);
  fail1:
  	bus_dmamap_destroy(dd->dd_dmat, dd->dd_dmamap);
  fail0:
  	bus_dma_tag_destroy(dd->dd_dmat);
  	memset(dd, 0, sizeof(*dd));
  	return error;
  #undef DS2PHYS
  }
  
  static void
  mwl_desc_cleanup(struct mwl_softc *sc, struct mwl_descdma *dd)
  {
  	bus_dmamap_unload(dd->dd_dmat, dd->dd_dmamap);
  	bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap);
  	bus_dmamap_destroy(dd->dd_dmat, dd->dd_dmamap);
  	bus_dma_tag_destroy(dd->dd_dmat);
  
  	memset(dd, 0, sizeof(*dd));
  }
  
  /* 
   * Construct a tx q's free list.  The order of entries on
   * the list must reflect the physical layout of tx descriptors
   * because the firmware pre-fetches descriptors.
   *
   * XXX might be better to use indices into the buffer array.
   */
  static void
  mwl_txq_reset(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	struct mwl_txbuf *bf;
  	int i;
  
  	bf = txq->dma.dd_bufptr;
  	STAILQ_INIT(&txq->free);
  	for (i = 0; i < mwl_txbuf; i++, bf++)
  		STAILQ_INSERT_TAIL(&txq->free, bf, bf_list);
  	txq->nfree = i;
  }
  
  #define	DS2PHYS(_dd, _ds) \
  	((_dd)->dd_desc_paddr + ((caddr_t)(_ds) - (caddr_t)(_dd)->dd_desc))
  
  static int
  mwl_txdma_setup(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	int error, bsize, i;
  	struct mwl_txbuf *bf;
  	struct mwl_txdesc *ds;
  
  	error = mwl_desc_setup(sc, "tx", &txq->dma,
  			mwl_txbuf, sizeof(struct mwl_txbuf),
  			MWL_TXDESC, sizeof(struct mwl_txdesc));
  	if (error != 0)
  		return error;
  
  	/* allocate and setup tx buffers */
  	bsize = mwl_txbuf * sizeof(struct mwl_txbuf);
  	bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO);
  	if (bf == NULL) {
  		if_printf(ifp, "malloc of %u tx buffers failed\n",
  			mwl_txbuf);
  		return ENOMEM;
  	}
  	txq->dma.dd_bufptr = bf;
  
  	ds = txq->dma.dd_desc;
  	for (i = 0; i < mwl_txbuf; i++, bf++, ds += MWL_TXDESC) {
  		bf->bf_desc = ds;
  		bf->bf_daddr = DS2PHYS(&txq->dma, ds);
  		error = bus_dmamap_create(sc->sc_dmat, BUS_DMA_NOWAIT,
  				&bf->bf_dmamap);
  		if (error != 0) {
  			if_printf(ifp, "unable to create dmamap for tx "
  				"buffer %u, error %u\n", i, error);
  			return error;
  		}
  	}
  	mwl_txq_reset(sc, txq);
  	return 0;
  }
  
  static void
  mwl_txdma_cleanup(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	struct mwl_txbuf *bf;
  	int i;
  
  	bf = txq->dma.dd_bufptr;
  	for (i = 0; i < mwl_txbuf; i++, bf++) {
  		KASSERT(bf->bf_m == NULL, ("mbuf on free list"));
  		KASSERT(bf->bf_node == NULL, ("node on free list"));
  		if (bf->bf_dmamap != NULL)
  			bus_dmamap_destroy(sc->sc_dmat, bf->bf_dmamap);
  	}
  	STAILQ_INIT(&txq->free);
  	txq->nfree = 0;
  	if (txq->dma.dd_bufptr != NULL) {
  		free(txq->dma.dd_bufptr, M_MWLDEV);
  		txq->dma.dd_bufptr = NULL;
  	}
  	if (txq->dma.dd_desc_len != 0)
  		mwl_desc_cleanup(sc, &txq->dma);
  }
  
  static int
  mwl_rxdma_setup(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	int error, jumbosize, bsize, i;
  	struct mwl_rxbuf *bf;
  	struct mwl_jumbo *rbuf;
  	struct mwl_rxdesc *ds;
  	caddr_t data;
  
  	error = mwl_desc_setup(sc, "rx", &sc->sc_rxdma,
  			mwl_rxdesc, sizeof(struct mwl_rxbuf),
  			1, sizeof(struct mwl_rxdesc));
  	if (error != 0)
  		return error;
  
  	/*
  	 * Receive is done to a private pool of jumbo buffers.
  	 * This allows us to attach to mbuf's and avoid re-mapping
  	 * memory on each rx we post.  We allocate a large chunk
  	 * of memory and manage it in the driver.  The mbuf free
  	 * callback method is used to reclaim frames after sending
  	 * them up the stack.  By default we allocate 2x the number of
  	 * rx descriptors configured so we have some slop to hold
  	 * us while frames are processed.
  	 */
  	if (mwl_rxbuf < 2*mwl_rxdesc) {
  		if_printf(ifp,
  		    "too few rx dma buffers (%d); increasing to %d\n",
  		    mwl_rxbuf, 2*mwl_rxdesc);
  		mwl_rxbuf = 2*mwl_rxdesc;
  	}
  	jumbosize = roundup(MWL_AGGR_SIZE, PAGE_SIZE);
  	sc->sc_rxmemsize = mwl_rxbuf*jumbosize;
  
  	error = bus_dma_tag_create(sc->sc_dmat,	/* parent */
  		       PAGE_SIZE, 0,		/* alignment, bounds */
  		       BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
  		       BUS_SPACE_MAXADDR,	/* highaddr */
  		       NULL, NULL,		/* filter, filterarg */
  		       sc->sc_rxmemsize,	/* maxsize */
  		       1,			/* nsegments */
  		       sc->sc_rxmemsize,	/* maxsegsize */
  		       BUS_DMA_ALLOCNOW,	/* flags */
  		       NULL,			/* lockfunc */
  		       NULL,			/* lockarg */
  		       &sc->sc_rxdmat);
  	error = bus_dmamap_create(sc->sc_rxdmat, BUS_DMA_NOWAIT, &sc->sc_rxmap);
  	if (error != 0) {
  		if_printf(ifp, "could not create rx DMA map\n");
  		return error;
  	}
  
  	error = bus_dmamem_alloc(sc->sc_rxdmat, (void**) &sc->sc_rxmem,
  				 BUS_DMA_NOWAIT | BUS_DMA_COHERENT, 
  				 &sc->sc_rxmap);
  	if (error != 0) {
  		if_printf(ifp, "could not alloc %ju bytes of rx DMA memory\n",
  		    (uintmax_t) sc->sc_rxmemsize);
  		return error;
  	}
  
  	error = bus_dmamap_load(sc->sc_rxdmat, sc->sc_rxmap,
  				sc->sc_rxmem, sc->sc_rxmemsize,
  				mwl_load_cb, &sc->sc_rxmem_paddr,
  				BUS_DMA_NOWAIT);
  	if (error != 0) {
  		if_printf(ifp, "could not load rx DMA map\n");
  		return error;
  	}
  
  	/*
  	 * Allocate rx buffers and set them up.
  	 */
  	bsize = mwl_rxdesc * sizeof(struct mwl_rxbuf);
  	bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO);
  	if (bf == NULL) {
  		if_printf(ifp, "malloc of %u rx buffers failed\n", bsize);
  		return error;
  	}
  	sc->sc_rxdma.dd_bufptr = bf;
  
  	STAILQ_INIT(&sc->sc_rxbuf);
  	ds = sc->sc_rxdma.dd_desc;
  	for (i = 0; i < mwl_rxdesc; i++, bf++, ds++) {
  		bf->bf_desc = ds;
  		bf->bf_daddr = DS2PHYS(&sc->sc_rxdma, ds);
  		/* pre-assign dma buffer */
  		bf->bf_data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize);
  		/* NB: tail is intentional to preserve descriptor order */
  		STAILQ_INSERT_TAIL(&sc->sc_rxbuf, bf, bf_list);
  	}
  
  	/*
  	 * Place remainder of dma memory buffers on the free list.
  	 */
  	SLIST_INIT(&sc->sc_rxfree);
  	for (; i < mwl_rxbuf; i++) {
  		data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize);
  		rbuf = MWL_JUMBO_DATA2BUF(data);
  		SLIST_INSERT_HEAD(&sc->sc_rxfree, rbuf, next);
  		sc->sc_nrxfree++;
  	}
  	return 0;
  }
  #undef DS2PHYS
  
  static void
  mwl_rxdma_cleanup(struct mwl_softc *sc)
  {
  	if (sc->sc_rxmap != NULL)
  		bus_dmamap_unload(sc->sc_rxdmat, sc->sc_rxmap);
  	if (sc->sc_rxmem != NULL) {
  		bus_dmamem_free(sc->sc_rxdmat, sc->sc_rxmem, sc->sc_rxmap);
  		sc->sc_rxmem = NULL;
  	}
  	if (sc->sc_rxmap != NULL) {
  		bus_dmamap_destroy(sc->sc_rxdmat, sc->sc_rxmap);
  		sc->sc_rxmap = NULL;
  	}
  	if (sc->sc_rxdma.dd_bufptr != NULL) {
  		free(sc->sc_rxdma.dd_bufptr, M_MWLDEV);
  		sc->sc_rxdma.dd_bufptr = NULL;
  	}
  	if (sc->sc_rxdma.dd_desc_len != 0)
  		mwl_desc_cleanup(sc, &sc->sc_rxdma);
  }
  
  static int
  mwl_dma_setup(struct mwl_softc *sc)
  {
  	int error, i;
  
  	error = mwl_rxdma_setup(sc);
  	if (error != 0) {
  		mwl_rxdma_cleanup(sc);
  		return error;
  	}
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++) {
  		error = mwl_txdma_setup(sc, &sc->sc_txq[i]);
  		if (error != 0) {
  			mwl_dma_cleanup(sc);
  			return error;
  		}
  	}
  	return 0;
  }
  
  static void
  mwl_dma_cleanup(struct mwl_softc *sc)
  {
  	int i;
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++)
  		mwl_txdma_cleanup(sc, &sc->sc_txq[i]);
  	mwl_rxdma_cleanup(sc);
  }
  
  static struct ieee80211_node *
  mwl_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
  {
  	struct ieee80211com *ic = vap->iv_ic;
  	struct mwl_softc *sc = ic->ic_ifp->if_softc;
  	const size_t space = sizeof(struct mwl_node);
  	struct mwl_node *mn;
  
  	mn = malloc(space, M_80211_NODE, M_NOWAIT|M_ZERO);
  	if (mn == NULL) {
  		/* XXX stat+msg */
  		return NULL;
  	}
  	DPRINTF(sc, MWL_DEBUG_NODE, "%s: mn %p\n", __func__, mn);
  	return &mn->mn_node;
  }
  
  static void
  mwl_node_cleanup(struct ieee80211_node *ni)
  {
  	struct ieee80211com *ic = ni->ni_ic;
          struct mwl_softc *sc = ic->ic_ifp->if_softc;
  	struct mwl_node *mn = MWL_NODE(ni);
  
  	DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p ic %p staid %d\n",
  	    __func__, ni, ni->ni_ic, mn->mn_staid);
  
  	if (mn->mn_staid != 0) {
  		struct ieee80211vap *vap = ni->ni_vap;
  
  		if (mn->mn_hvap != NULL) {
  			if (vap->iv_opmode == IEEE80211_M_STA)
  				mwl_hal_delstation(mn->mn_hvap, vap->iv_myaddr);
  			else
  				mwl_hal_delstation(mn->mn_hvap, ni->ni_macaddr);
  		}
  		/*
  		 * NB: legacy WDS peer sta db entry is installed using
  		 * the associate ap's hvap; use it again to delete it.
  		 * XXX can vap be NULL?
  		 */
  		else if (vap->iv_opmode == IEEE80211_M_WDS &&
  		    MWL_VAP(vap)->mv_ap_hvap != NULL)
  			mwl_hal_delstation(MWL_VAP(vap)->mv_ap_hvap,
  			    ni->ni_macaddr);
  		delstaid(sc, mn->mn_staid);
  		mn->mn_staid = 0;
  	}
  	sc->sc_node_cleanup(ni);
  }
  
  /*
   * Reclaim rx dma buffers from packets sitting on the ampdu
   * reorder queue for a station.  We replace buffers with a
   * system cluster (if available).
   */
  static void
  mwl_ampdu_rxdma_reclaim(struct ieee80211_rx_ampdu *rap)
  {
  #if 0
  	int i, n, off;
  	struct mbuf *m;
  	void *cl;
  
  	n = rap->rxa_qframes;
  	for (i = 0; i < rap->rxa_wnd && n > 0; i++) {
  		m = rap->rxa_m[i];
  		if (m == NULL)
  			continue;
  		n--;
  		/* our dma buffers have a well-known free routine */
  		if ((m->m_flags & M_EXT) == 0 ||
  		    m->m_ext.ext_free != mwl_ext_free)
  			continue;
  		/*
  		 * Try to allocate a cluster and move the data.
  		 */
  		off = m->m_data - m->m_ext.ext_buf;
  		if (off + m->m_pkthdr.len > MCLBYTES) {
  			/* XXX no AMSDU for now */
  			continue;
  		}
  		cl = pool_cache_get_paddr(&mclpool_cache, 0,
  		    &m->m_ext.ext_paddr);
  		if (cl != NULL) {
  			/*
  			 * Copy the existing data to the cluster, remove
  			 * the rx dma buffer, and attach the cluster in
  			 * its place.  Note we preserve the offset to the
  			 * data so frames being bridged can still prepend
  			 * their headers without adding another mbuf.
  			 */
  			memcpy((caddr_t) cl + off, m->m_data, m->m_pkthdr.len);
  			MEXTREMOVE(m);
  			MEXTADD(m, cl, MCLBYTES, 0, NULL, &mclpool_cache);
  			/* setup mbuf like _MCLGET does */
  			m->m_flags |= M_CLUSTER | M_EXT_RW;
  			_MOWNERREF(m, M_EXT | M_CLUSTER);
  			/* NB: m_data is clobbered by MEXTADDR, adjust */
  			m->m_data += off;
  		}
  	}
  #endif
  }
  
  /*
   * Callback to reclaim resources.  We first let the
   * net80211 layer do it's thing, then if we are still
   * blocked by a lack of rx dma buffers we walk the ampdu
   * reorder q's to reclaim buffers by copying to a system
   * cluster.
   */
  static void
  mwl_node_drain(struct ieee80211_node *ni)
  {
  	struct ieee80211com *ic = ni->ni_ic;
          struct mwl_softc *sc = ic->ic_ifp->if_softc;
  	struct mwl_node *mn = MWL_NODE(ni);
  
  	DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p vap %p staid %d\n",
  	    __func__, ni, ni->ni_vap, mn->mn_staid);
  
  	/* NB: call up first to age out ampdu q's */
  	sc->sc_node_drain(ni);
  
  	/* XXX better to not check low water mark? */
  	if (sc->sc_rxblocked && mn->mn_staid != 0 &&
  	    (ni->ni_flags & IEEE80211_NODE_HT)) {
  		uint8_t tid;
  		/*
  		 * Walk the reorder q and reclaim rx dma buffers by copying
  		 * the packet contents into clusters.
  		 */
  		for (tid = 0; tid < WME_NUM_TID; tid++) {
  			struct ieee80211_rx_ampdu *rap;
  
  			rap = &ni->ni_rx_ampdu[tid];
  			if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0)
  				continue;
  			if (rap->rxa_qframes)
  				mwl_ampdu_rxdma_reclaim(rap);
  		}
  	}
  }
  
  static void
  mwl_node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise)
  {
  	*rssi = ni->ni_ic->ic_node_getrssi(ni);
  #ifdef MWL_ANT_INFO_SUPPORT
  #if 0
  	/* XXX need to smooth data */
  	*noise = -MWL_NODE_CONST(ni)->mn_ai.nf;
  #else
  	*noise = -95;		/* XXX */
  #endif
  #else
  	*noise = -95;		/* XXX */
  #endif
  }
  
  /*
   * Convert Hardware per-antenna rssi info to common format:
   * Let a1, a2, a3 represent the amplitudes per chain
   * Let amax represent max[a1, a2, a3]
   * Rssi1_dBm = RSSI_dBm + 20*log10(a1/amax)
   * Rssi1_dBm = RSSI_dBm + 20*log10(a1) - 20*log10(amax)
   * We store a table that is 4*20*log10(idx) - the extra 4 is to store or
   * maintain some extra precision.
   *
   * Values are stored in .5 db format capped at 127.
   */
  static void
  mwl_node_getmimoinfo(const struct ieee80211_node *ni,
  	struct ieee80211_mimo_info *mi)
  {
  #define	CVT(_dst, _src) do {						\
  	(_dst) = rssi + ((logdbtbl[_src] - logdbtbl[rssi_max]) >> 2);	\
  	(_dst) = (_dst) > 64 ? 127 : ((_dst) << 1);			\
  } while (0)
  	static const int8_t logdbtbl[32] = {
  	       0,   0,  24,  38,  48,  56,  62,  68, 
  	      72,  76,  80,  83,  86,  89,  92,  94, 
  	      96,  98, 100, 102, 104, 106, 107, 109, 
  	     110, 112, 113, 115, 116, 117, 118, 119
  	};
  	const struct mwl_node *mn = MWL_NODE_CONST(ni);
  	uint8_t rssi = mn->mn_ai.rsvd1/2;		/* XXX */
  	uint32_t rssi_max;
  
  	rssi_max = mn->mn_ai.rssi_a;
  	if (mn->mn_ai.rssi_b > rssi_max)
  		rssi_max = mn->mn_ai.rssi_b;
  	if (mn->mn_ai.rssi_c > rssi_max)
  		rssi_max = mn->mn_ai.rssi_c;
  
  	CVT(mi->rssi[0], mn->mn_ai.rssi_a);
  	CVT(mi->rssi[1], mn->mn_ai.rssi_b);
  	CVT(mi->rssi[2], mn->mn_ai.rssi_c);
  
  	mi->noise[0] = mn->mn_ai.nf_a;
  	mi->noise[1] = mn->mn_ai.nf_b;
  	mi->noise[2] = mn->mn_ai.nf_c;
  #undef CVT
  }
  
  static __inline void *
  mwl_getrxdma(struct mwl_softc *sc)
  {
  	struct mwl_jumbo *buf;
  	void *data;
  
  	/*
  	 * Allocate from jumbo pool.
  	 */
  	MWL_RXFREE_LOCK(sc);
  	buf = SLIST_FIRST(&sc->sc_rxfree);
  	if (buf == NULL) {
  		DPRINTF(sc, MWL_DEBUG_ANY,
  		    "%s: out of rx dma buffers\n", __func__);
  		sc->sc_stats.mst_rx_nodmabuf++;
  		data = NULL;
  	} else {
  		SLIST_REMOVE_HEAD(&sc->sc_rxfree, next);
  		sc->sc_nrxfree--;
  		data = MWL_JUMBO_BUF2DATA(buf);
  	}
  	MWL_RXFREE_UNLOCK(sc);
  	return data;
  }
  
  static __inline void
  mwl_putrxdma(struct mwl_softc *sc, void *data)
  {
  	struct mwl_jumbo *buf;
  
  	/* XXX bounds check data */
  	MWL_RXFREE_LOCK(sc);
  	buf = MWL_JUMBO_DATA2BUF(data);
  	SLIST_INSERT_HEAD(&sc->sc_rxfree, buf, next);
  	sc->sc_nrxfree++;
  	MWL_RXFREE_UNLOCK(sc);
  }
  
  static int
  mwl_rxbuf_init(struct mwl_softc *sc, struct mwl_rxbuf *bf)
  {
  	struct mwl_rxdesc *ds;
  
  	ds = bf->bf_desc;
  	if (bf->bf_data == NULL) {
  		bf->bf_data = mwl_getrxdma(sc);
  		if (bf->bf_data == NULL) {
  			/* mark descriptor to be skipped */
  			ds->RxControl = EAGLE_RXD_CTRL_OS_OWN;
  			/* NB: don't need PREREAD */
  			MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREWRITE);
  			sc->sc_stats.mst_rxbuf_failed++;
  			return ENOMEM;
  		}
  	}
  	/*
  	 * NB: DMA buffer contents is known to be unmodified
  	 *     so there's no need to flush the data cache.
  	 */
  
  	/*
  	 * Setup descriptor.
  	 */
  	ds->QosCtrl = 0;
  	ds->RSSI = 0;
  	ds->Status = EAGLE_RXD_STATUS_IDLE;
  	ds->Channel = 0;
  	ds->PktLen = htole16(MWL_AGGR_SIZE);
  	ds->SQ2 = 0;
  	ds->pPhysBuffData = htole32(MWL_JUMBO_DMA_ADDR(sc, bf->bf_data));
  	/* NB: don't touch pPhysNext, set once */
  	ds->RxControl = EAGLE_RXD_CTRL_DRIVER_OWN;
  	MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
  
  	return 0;
  }
  
  static void
  mwl_ext_free(void *data, void *arg)
  {
  	struct mwl_softc *sc = arg;
  
  	/* XXX bounds check data */
  	mwl_putrxdma(sc, data);
  	/*
  	 * If we were previously blocked by a lack of rx dma buffers
  	 * check if we now have enough to restart rx interrupt handling.
  	 * NB: we know we are called at splvm which is above splnet.
  	 */
  	if (sc->sc_rxblocked && sc->sc_nrxfree > mwl_rxdmalow) {
  		sc->sc_rxblocked = 0;
  		mwl_hal_intrset(sc->sc_mh, sc->sc_imask);
  	}
  }
  
  struct mwl_frame_bar {
  	u_int8_t	i_fc[2];
  	u_int8_t	i_dur[2];
  	u_int8_t	i_ra[IEEE80211_ADDR_LEN];
  	u_int8_t	i_ta[IEEE80211_ADDR_LEN];
  	/* ctl, seq, FCS */
  } __packed;
  
  /*
   * Like ieee80211_anyhdrsize, but handles BAR frames
   * specially so the logic below to piece the 802.11
   * header together works.
   */
  static __inline int
  mwl_anyhdrsize(const void *data)
  {
  	const struct ieee80211_frame *wh = data;
  
  	if ((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) {
  		switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) {
  		case IEEE80211_FC0_SUBTYPE_CTS:
  		case IEEE80211_FC0_SUBTYPE_ACK:
  			return sizeof(struct ieee80211_frame_ack);
  		case IEEE80211_FC0_SUBTYPE_BAR:
  			return sizeof(struct mwl_frame_bar);
  		}
  		return sizeof(struct ieee80211_frame_min);
  	} else
  		return ieee80211_hdrsize(data);
  }
  
  static void
  mwl_handlemicerror(struct ieee80211com *ic, const uint8_t *data)
  {
  	const struct ieee80211_frame *wh;
  	struct ieee80211_node *ni;
  
  	wh = (const struct ieee80211_frame *)(data + sizeof(uint16_t));
  	ni = ieee80211_find_rxnode(ic, (const struct ieee80211_frame_min *) wh);
  	if (ni != NULL) {
  		ieee80211_notify_michael_failure(ni->ni_vap, wh, 0);
  		ieee80211_free_node(ni);
  	}
  }
  
  /*
   * Convert hardware signal strength to rssi.  The value
   * provided by the device has the noise floor added in;
   * we need to compensate for this but we don't have that
   * so we use a fixed value.
   *
   * The offset of 8 is good for both 2.4 and 5GHz.  The LNA
   * offset is already set as part of the initial gain.  This
   * will give at least +/- 3dB for 2.4GHz and +/- 5dB for 5GHz.
   */
  static __inline int
  cvtrssi(uint8_t ssi)
  {
  	int rssi = (int) ssi + 8;
  	/* XXX hack guess until we have a real noise floor */
  	rssi = 2*(87 - rssi);	/* NB: .5 dBm units */
  	return (rssi < 0 ? 0 : rssi > 127 ? 127 : rssi);
  }
  
  static void
  mwl_rx_proc(void *arg, int npending)
  {
  #define	IEEE80211_DIR_DSTODS(wh) \
  	((((const struct ieee80211_frame *)wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS)
  	struct mwl_softc *sc = arg;
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_rxbuf *bf;
  	struct mwl_rxdesc *ds;
  	struct mbuf *m;
  	struct ieee80211_qosframe *wh;
  	struct ieee80211_qosframe_addr4 *wh4;
  	struct ieee80211_node *ni;
  	struct mwl_node *mn;
  	int off, len, hdrlen, pktlen, rssi, ntodo;
  	uint8_t *data, status;
  	void *newdata;
  	int16_t nf;
  
  	DPRINTF(sc, MWL_DEBUG_RX_PROC, "%s: pending %u rdptr 0x%x wrptr 0x%x\n",
  	    __func__, npending, RD4(sc, sc->sc_hwspecs.rxDescRead),
  	    RD4(sc, sc->sc_hwspecs.rxDescWrite));
  	nf = -96;			/* XXX */
  	bf = sc->sc_rxnext;
  	for (ntodo = mwl_rxquota; ntodo > 0; ntodo--) {
  		if (bf == NULL)
  			bf = STAILQ_FIRST(&sc->sc_rxbuf);
  		ds = bf->bf_desc;
  		data = bf->bf_data;
  		if (data == NULL) {
  			/*
  			 * If data allocation failed previously there
  			 * will be no buffer; try again to re-populate it.
  			 * Note the firmware will not advance to the next
  			 * descriptor with a dma buffer so we must mimic
  			 * this or we'll get out of sync.
  			 */ 
  			DPRINTF(sc, MWL_DEBUG_ANY,
  			    "%s: rx buf w/o dma memory\n", __func__);
  			(void) mwl_rxbuf_init(sc, bf);
  			sc->sc_stats.mst_rx_dmabufmissing++;
  			break;
  		}
  		MWL_RXDESC_SYNC(sc, ds,
  		    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
  		if (ds->RxControl != EAGLE_RXD_CTRL_DMA_OWN)
  			break;
  #ifdef MWL_DEBUG
  		if (sc->sc_debug & MWL_DEBUG_RECV_DESC)
  			mwl_printrxbuf(bf, 0);
  #endif
  		status = ds->Status;
  		if (status & EAGLE_RXD_STATUS_DECRYPT_ERR_MASK) {
  			ifp->if_ierrors++;
  			sc->sc_stats.mst_rx_crypto++;
  			/*
  			 * NB: Check EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR
  			 *     for backwards compatibility.
  			 */
  			if (status != EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR &&
  			    (status & EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR)) {
  				/*
  				 * MIC error, notify upper layers.
  				 */
  				bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap,
  				    BUS_DMASYNC_POSTREAD);
  				mwl_handlemicerror(ic, data);
  				sc->sc_stats.mst_rx_tkipmic++;
  			}
  			/* XXX too painful to tap packets */
  			goto rx_next;
  		}
  		/*
  		 * Sync the data buffer.
  		 */
  		len = le16toh(ds->PktLen);
  		bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, BUS_DMASYNC_POSTREAD);
  		/*
  		 * The 802.11 header is provided all or in part at the front;
  		 * use it to calculate the true size of the header that we'll
  		 * construct below.  We use this to figure out where to copy
  		 * payload prior to constructing the header.
  		 */
  		hdrlen = mwl_anyhdrsize(data + sizeof(uint16_t));
  		off = sizeof(uint16_t) + sizeof(struct ieee80211_frame_addr4);
  
  		/* calculate rssi early so we can re-use for each aggregate */
  		rssi = cvtrssi(ds->RSSI);
  
  		pktlen = hdrlen + (len - off);
  		/*
  		 * NB: we know our frame is at least as large as
  		 * IEEE80211_MIN_LEN because there is a 4-address
  		 * frame at the front.  Hence there's no need to
  		 * vet the packet length.  If the frame in fact
  		 * is too small it should be discarded at the
  		 * net80211 layer.
  		 */
  
  		/*
  		 * Attach dma buffer to an mbuf.  We tried
  		 * doing this based on the packet size (i.e.
  		 * copying small packets) but it turns out to
  		 * be a net loss.  The tradeoff might be system
  		 * dependent (cache architecture is important).
  		 */
  		MGETHDR(m, M_NOWAIT, MT_DATA);
  		if (m == NULL) {
  			DPRINTF(sc, MWL_DEBUG_ANY,
  			    "%s: no rx mbuf\n", __func__);
  			sc->sc_stats.mst_rx_nombuf++;
  			goto rx_next;
  		}
  		/*
  		 * Acquire the replacement dma buffer before
  		 * processing the frame.  If we're out of dma
  		 * buffers we disable rx interrupts and wait
  		 * for the free pool to reach mlw_rxdmalow buffers
  		 * before starting to do work again.  If the firmware
  		 * runs out of descriptors then it will toss frames
  		 * which is better than our doing it as that can
  		 * starve our processing.  It is also important that
  		 * we always process rx'd frames in case they are
  		 * A-MPDU as otherwise the host's view of the BA
  		 * window may get out of sync with the firmware.
  		 */
  		newdata = mwl_getrxdma(sc);
  		if (newdata == NULL) {
  			/* NB: stat+msg in mwl_getrxdma */
  			m_free(m);
  			/* disable RX interrupt and mark state */
  			mwl_hal_intrset(sc->sc_mh,
  			    sc->sc_imask &~ MACREG_A2HRIC_BIT_RX_RDY);
  			sc->sc_rxblocked = 1;
  			ieee80211_drain(ic);
  			/* XXX check rxblocked and immediately start again? */
  			goto rx_stop;
  		}
  		bf->bf_data = newdata;
  		/*
  		 * Attach the dma buffer to the mbuf;
  		 * mwl_rxbuf_init will re-setup the rx
  		 * descriptor using the replacement dma
  		 * buffer we just installed above.
  		 */
  		MEXTADD(m, data, MWL_AGGR_SIZE, mwl_ext_free,
  		    data, sc, 0, EXT_NET_DRV);
  		m->m_data += off - hdrlen;
  		m->m_pkthdr.len = m->m_len = pktlen;
  		m->m_pkthdr.rcvif = ifp;
  		/* NB: dma buffer assumed read-only */
  
  		/*
  		 * Piece 802.11 header together.
  		 */
  		wh = mtod(m, struct ieee80211_qosframe *);
  		/* NB: don't need to do this sometimes but ... */
  		/* XXX special case so we can memcpy after m_devget? */
  		ovbcopy(data + sizeof(uint16_t), wh, hdrlen);
  		if (IEEE80211_QOS_HAS_SEQ(wh)) {
  			if (IEEE80211_DIR_DSTODS(wh)) {
  				wh4 = mtod(m,
  				    struct ieee80211_qosframe_addr4*);
  				*(uint16_t *)wh4->i_qos = ds->QosCtrl;
  			} else {
  				*(uint16_t *)wh->i_qos = ds->QosCtrl;
  			}
  		}
  		/*
  		 * The f/w strips WEP header but doesn't clear
  		 * the WEP bit; mark the packet with M_WEP so
  		 * net80211 will treat the data as decrypted.
  		 * While here also clear the PWR_MGT bit since
  		 * power save is handled by the firmware and
  		 * passing this up will potentially cause the
  		 * upper layer to put a station in power save
  		 * (except when configured with MWL_HOST_PS_SUPPORT).
  		 */
  		if (wh->i_fc[1] & IEEE80211_FC1_WEP)
  			m->m_flags |= M_WEP;
  #ifdef MWL_HOST_PS_SUPPORT
  		wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
  #else
  		wh->i_fc[1] &= ~(IEEE80211_FC1_WEP | IEEE80211_FC1_PWR_MGT);
  #endif
  
  		if (ieee80211_radiotap_active(ic)) {
  			struct mwl_rx_radiotap_header *tap = &sc->sc_rx_th;
  
  			tap->wr_flags = 0;
  			tap->wr_rate = ds->Rate;
  			tap->wr_antsignal = rssi + nf;
  			tap->wr_antnoise = nf;
  		}
  		if (IFF_DUMPPKTS_RECV(sc, wh)) {
  			ieee80211_dump_pkt(ic, mtod(m, caddr_t),
  			    len, ds->Rate, rssi);
  		}
  		ifp->if_ipackets++;
  
  		/* dispatch */
  		ni = ieee80211_find_rxnode(ic,
  		    (const struct ieee80211_frame_min *) wh);
  		if (ni != NULL) {
  			mn = MWL_NODE(ni);
  #ifdef MWL_ANT_INFO_SUPPORT
  			mn->mn_ai.rssi_a = ds->ai.rssi_a;
  			mn->mn_ai.rssi_b = ds->ai.rssi_b;
  			mn->mn_ai.rssi_c = ds->ai.rssi_c;
  			mn->mn_ai.rsvd1 = rssi;
  #endif
  			/* tag AMPDU aggregates for reorder processing */
  			if (ni->ni_flags & IEEE80211_NODE_HT)
  				m->m_flags |= M_AMPDU;
  			(void) ieee80211_input(ni, m, rssi, nf);
  			ieee80211_free_node(ni);
  		} else
  			(void) ieee80211_input_all(ic, m, rssi, nf);
  rx_next:
  		/* NB: ignore ENOMEM so we process more descriptors */
  		(void) mwl_rxbuf_init(sc, bf);
  		bf = STAILQ_NEXT(bf, bf_list);
  	}
  rx_stop:
  	sc->sc_rxnext = bf;
  
  	if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 &&
  	    !IFQ_IS_EMPTY(&ifp->if_snd)) {
  		/* NB: kick fw; the tx thread may have been preempted */
  		mwl_hal_txstart(sc->sc_mh, 0);
  		mwl_start(ifp);
  	}
  #undef IEEE80211_DIR_DSTODS
  }
  
  static void
  mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *txq, int qnum)
  {
  	struct mwl_txbuf *bf, *bn;
  	struct mwl_txdesc *ds;
  
  	MWL_TXQ_LOCK_INIT(sc, txq);
  	txq->qnum = qnum;
  	txq->txpri = 0;	/* XXX */
  #if 0
  	/* NB: q setup by mwl_txdma_setup XXX */
  	STAILQ_INIT(&txq->free);
  #endif
  	STAILQ_FOREACH(bf, &txq->free, bf_list) {
  		bf->bf_txq = txq;
  
  		ds = bf->bf_desc;
  		bn = STAILQ_NEXT(bf, bf_list);
  		if (bn == NULL)
  			bn = STAILQ_FIRST(&txq->free);
  		ds->pPhysNext = htole32(bn->bf_daddr);
  	}
  	STAILQ_INIT(&txq->active);
  }
  
  /*
   * 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_txq *txq;
  
  	if (ac >= N(sc->sc_ac2q)) {
  		device_printf(sc->sc_dev, "AC %u out of range, max %zu!\n",
  			ac, N(sc->sc_ac2q));
  		return 0;
  	}
  	if (mvtype >= MWL_NUM_TX_QUEUES) {
  		device_printf(sc->sc_dev, "mvtype %u out of range, max %u!\n",
  			mvtype, MWL_NUM_TX_QUEUES);
  		return 0;
  	}
  	txq = &sc->sc_txq[mvtype];
  	mwl_txq_init(sc, txq, mvtype);
  	sc->sc_ac2q[ac] = txq;
  	return 1;
  #undef N
  }
  
  /*
   * Update WME parameters for a transmit queue.
   */
  static int
  mwl_txq_update(struct mwl_softc *sc, int ac)
  {
  #define	MWL_EXPONENT_TO_VALUE(v)	((1<<v)-1)
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_txq *txq = sc->sc_ac2q[ac];
  	struct wmeParams *wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[ac];
  	struct mwl_hal *mh = sc->sc_mh;
  	int aifs, cwmin, cwmax, txoplim;
  
  	aifs = wmep->wmep_aifsn;
  	/* XXX in sta mode need to pass log values for cwmin/max */
  	cwmin = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmin);
  	cwmax = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmax);
  	txoplim = wmep->wmep_txopLimit;		/* NB: units of 32us */
  
  	if (mwl_hal_setedcaparams(mh, txq->qnum, cwmin, cwmax, aifs, txoplim)) {
  		device_printf(sc->sc_dev, "unable to update hardware queue "
  			"parameters for %s traffic!\n",
  			ieee80211_wme_acnames[ac]);
  		return 0;
  	}
  	return 1;
  #undef MWL_EXPONENT_TO_VALUE
  }
  
  /*
   * Callback from the 802.11 layer to update WME parameters.
   */
  static int
  mwl_wme_update(struct ieee80211com *ic)
  {
  	struct mwl_softc *sc = ic->ic_ifp->if_softc;
  
  	return !mwl_txq_update(sc, WME_AC_BE) ||
  	    !mwl_txq_update(sc, WME_AC_BK) ||
  	    !mwl_txq_update(sc, WME_AC_VI) ||
  	    !mwl_txq_update(sc, WME_AC_VO) ? EIO : 0;
  }
  
  /*
   * Reclaim resources for a setup queue.
   */
  static void
  mwl_tx_cleanupq(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	/* XXX hal work? */
  	MWL_TXQ_LOCK_DESTROY(txq);
  }
  
  /*
   * Reclaim all tx queue resources.
   */
  static void
  mwl_tx_cleanup(struct mwl_softc *sc)
  {
  	int i;
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++)
  		mwl_tx_cleanupq(sc, &sc->sc_txq[i]);
  }
  
  static int
  mwl_tx_dmasetup(struct mwl_softc *sc, struct mwl_txbuf *bf, struct mbuf *m0)
  {
  	struct mbuf *m;
  	int error;
  
  	/*
  	 * Load the DMA map so any coalescing is done.  This
  	 * also calculates the number of descriptors we need.
  	 */
  	error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0,
  				     bf->bf_segs, &bf->bf_nseg,
  				     BUS_DMA_NOWAIT);
  	if (error == EFBIG) {
  		/* XXX packet requires too many descriptors */
  		bf->bf_nseg = MWL_TXDESC+1;
  	} else if (error != 0) {
  		sc->sc_stats.mst_tx_busdma++;
  		m_freem(m0);
  		return error;
  	}
  	/*
  	 * Discard null packets and check for packets that
  	 * require too many TX descriptors.  We try to convert
  	 * the latter to a cluster.
  	 */
  	if (error == EFBIG) {		/* too many desc's, linearize */
  		sc->sc_stats.mst_tx_linear++;
  #if MWL_TXDESC > 1
  		m = m_collapse(m0, M_NOWAIT, MWL_TXDESC);
  #else
  		m = m_defrag(m0, M_NOWAIT);
  #endif
  		if (m == NULL) {
  			m_freem(m0);
  			sc->sc_stats.mst_tx_nombuf++;
  			return ENOMEM;
  		}
  		m0 = m;
  		error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0,
  					     bf->bf_segs, &bf->bf_nseg,
  					     BUS_DMA_NOWAIT);
  		if (error != 0) {
  			sc->sc_stats.mst_tx_busdma++;
  			m_freem(m0);
  			return error;
  		}
  		KASSERT(bf->bf_nseg <= MWL_TXDESC,
  		    ("too many segments after defrag; nseg %u", bf->bf_nseg));
  	} else if (bf->bf_nseg == 0) {		/* null packet, discard */
  		sc->sc_stats.mst_tx_nodata++;
  		m_freem(m0);
  		return EIO;
  	}
  	DPRINTF(sc, MWL_DEBUG_XMIT, "%s: m %p len %u\n",
  		__func__, m0, m0->m_pkthdr.len);
  	bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, BUS_DMASYNC_PREWRITE);
  	bf->bf_m = m0;
  
  	return 0;
  }
  
  static __inline int
  mwl_cvtlegacyrate(int rate)
  {
  	switch (rate) {
  	case 2:	 return 0;
  	case 4:	 return 1;
  	case 11: return 2;
  	case 22: return 3;
  	case 44: return 4;
  	case 12: return 5;
  	case 18: return 6;
  	case 24: return 7;
  	case 36: return 8;
  	case 48: return 9;
  	case 72: return 10;
  	case 96: return 11;
  	case 108:return 12;
  	}
  	return 0;
  }
  
  /*
   * Calculate fixed tx rate information per client state;
   * this value is suitable for writing to the Format field
   * of a tx descriptor.
   */
  static uint16_t
  mwl_calcformat(uint8_t rate, const struct ieee80211_node *ni)
  {
  	uint16_t fmt;
  
  	fmt = SM(3, EAGLE_TXD_ANTENNA)
  	    | (IEEE80211_IS_CHAN_HT40D(ni->ni_chan) ?
  		EAGLE_TXD_EXTCHAN_LO : EAGLE_TXD_EXTCHAN_HI);
  	if (rate & IEEE80211_RATE_MCS) {	/* HT MCS */
  		fmt |= EAGLE_TXD_FORMAT_HT
  		    /* NB: 0x80 implicitly stripped from ucastrate */
  		    | SM(rate, EAGLE_TXD_RATE);
  		/* XXX short/long GI may be wrong; re-check */
  		if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) {
  			fmt |= EAGLE_TXD_CHW_40
  			    | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40 ?
  			        EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG);
  		} else {
  			fmt |= EAGLE_TXD_CHW_20
  			    | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20 ?
  			        EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG);
  		}
  	} else {			/* legacy rate */
  		fmt |= EAGLE_TXD_FORMAT_LEGACY
  		    | SM(mwl_cvtlegacyrate(rate), EAGLE_TXD_RATE)
  		    | EAGLE_TXD_CHW_20
  		    /* XXX iv_flags & IEEE80211_F_SHPREAMBLE? */
  		    | (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE ?
  			EAGLE_TXD_PREAMBLE_SHORT : EAGLE_TXD_PREAMBLE_LONG);
  	}
  	return fmt;
  }
  
  static int
  mwl_tx_start(struct mwl_softc *sc, struct ieee80211_node *ni, struct mwl_txbuf *bf,
      struct mbuf *m0)
  {
  #define	IEEE80211_DIR_DSTODS(wh) \
  	((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS)
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct ieee80211vap *vap = ni->ni_vap;
  	int error, iswep, ismcast;
  	int hdrlen, copyhdrlen, pktlen;
  	struct mwl_txdesc *ds;
  	struct mwl_txq *txq;
  	struct ieee80211_frame *wh;
  	struct mwltxrec *tr;
  	struct mwl_node *mn;
  	uint16_t qos;
  #if MWL_TXDESC > 1
  	int i;
  #endif
  
  	wh = mtod(m0, struct ieee80211_frame *);
  	iswep = wh->i_fc[1] & IEEE80211_FC1_WEP;
  	ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
  	hdrlen = ieee80211_anyhdrsize(wh);
  	copyhdrlen = hdrlen;
  	pktlen = m0->m_pkthdr.len;
  	if (IEEE80211_QOS_HAS_SEQ(wh)) {
  		if (IEEE80211_DIR_DSTODS(wh)) {
  			qos = *(uint16_t *)
  			    (((struct ieee80211_qosframe_addr4 *) wh)->i_qos);
  			copyhdrlen -= sizeof(qos);
  		} else
  			qos = *(uint16_t *)
  			    (((struct ieee80211_qosframe *) wh)->i_qos);
  	} else
  		qos = 0;
  
  	if (iswep) {
  		const struct ieee80211_cipher *cip;
  		struct ieee80211_key *k;
  
  		/*
  		 * Construct the 802.11 header+trailer for an encrypted
  		 * frame. The only reason this can fail is because of an
  		 * unknown or unsupported cipher/key type.
  		 *
  		 * NB: we do this even though the firmware will ignore
  		 *     what we've done for WEP and TKIP as we need the
  		 *     ExtIV filled in for CCMP and this also adjusts
  		 *     the headers which simplifies our work below.
  		 */
  		k = ieee80211_crypto_encap(ni, m0);
  		if (k == NULL) {
  			/*
  			 * This can happen when the key is yanked after the
  			 * frame was queued.  Just discard the frame; the
  			 * 802.11 layer counts failures and provides
  			 * debugging/diagnostics.
  			 */
  			m_freem(m0);
  			return EIO;
  		}
  		/*
  		 * Adjust the packet length for the crypto additions
  		 * done during encap and any other bits that the f/w
  		 * will add later on.
  		 */
  		cip = k->wk_cipher;
  		pktlen += cip->ic_header + cip->ic_miclen + cip->ic_trailer;
  
  		/* packet header may have moved, reset our local pointer */
  		wh = mtod(m0, struct ieee80211_frame *);
  	}
  
  	if (ieee80211_radiotap_active_vap(vap)) {
  		sc->sc_tx_th.wt_flags = 0;	/* XXX */
  		if (iswep)
  			sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP;
  #if 0
  		sc->sc_tx_th.wt_rate = ds->DataRate;
  #endif
  		sc->sc_tx_th.wt_txpower = ni->ni_txpower;
  		sc->sc_tx_th.wt_antenna = sc->sc_txantenna;
  
  		ieee80211_radiotap_tx(vap, m0);
  	}
  	/*
  	 * Copy up/down the 802.11 header; the firmware requires
  	 * we present a 2-byte payload length followed by a
  	 * 4-address header (w/o QoS), followed (optionally) by
  	 * any WEP/ExtIV header (but only filled in for CCMP).
  	 * We are assured the mbuf has sufficient headroom to
  	 * prepend in-place by the setup of ic_headroom in
  	 * mwl_attach.
  	 */
  	if (hdrlen < sizeof(struct mwltxrec)) {
  		const int space = sizeof(struct mwltxrec) - hdrlen;
  		if (M_LEADINGSPACE(m0) < space) {
  			/* NB: should never happen */
  			device_printf(sc->sc_dev,
  			    "not enough headroom, need %d found %zd, "
  			    "m_flags 0x%x m_len %d\n",
  			    space, M_LEADINGSPACE(m0), m0->m_flags, m0->m_len);
  			ieee80211_dump_pkt(ic,
  			    mtod(m0, const uint8_t *), m0->m_len, 0, -1);
  			m_freem(m0);
  			sc->sc_stats.mst_tx_noheadroom++;
  			return EIO;
  		}
  		M_PREPEND(m0, space, M_NOWAIT);
  	}
  	tr = mtod(m0, struct mwltxrec *);
  	if (wh != (struct ieee80211_frame *) &tr->wh)
  		ovbcopy(wh, &tr->wh, hdrlen);
  	/*
  	 * Note: the "firmware length" is actually the length
  	 * of the fully formed "802.11 payload".  That is, it's
  	 * everything except for the 802.11 header.  In particular
  	 * this includes all crypto material including the MIC!
  	 */
  	tr->fwlen = htole16(pktlen - hdrlen);
  
  	/*
  	 * Load the DMA map so any coalescing is done.  This
  	 * also calculates the number of descriptors we need.
  	 */
  	error = mwl_tx_dmasetup(sc, bf, m0);
  	if (error != 0) {
  		/* NB: stat collected in mwl_tx_dmasetup */
  		DPRINTF(sc, MWL_DEBUG_XMIT,
  		    "%s: unable to setup dma\n", __func__);
  		return error;
  	}
  	bf->bf_node = ni;			/* NB: held reference */
  	m0 = bf->bf_m;				/* NB: may have changed */
  	tr = mtod(m0, struct mwltxrec *);
  	wh = (struct ieee80211_frame *)&tr->wh;
  
  	/*
  	 * Formulate tx descriptor.
  	 */
  	ds = bf->bf_desc;
  	txq = bf->bf_txq;
  
  	ds->QosCtrl = qos;			/* NB: already little-endian */
  #if MWL_TXDESC == 1
  	/*
  	 * NB: multiframes should be zero because the descriptors
  	 *     are initialized to zero.  This should handle the case
  	 *     where the driver is built with MWL_TXDESC=1 but we are
  	 *     using firmware with multi-segment support.
  	 */
  	ds->PktPtr = htole32(bf->bf_segs[0].ds_addr);
  	ds->PktLen = htole16(bf->bf_segs[0].ds_len);
  #else
  	ds->multiframes = htole32(bf->bf_nseg);
  	ds->PktLen = htole16(m0->m_pkthdr.len);
  	for (i = 0; i < bf->bf_nseg; i++) {
  		ds->PktPtrArray[i] = htole32(bf->bf_segs[i].ds_addr);
  		ds->PktLenArray[i] = htole16(bf->bf_segs[i].ds_len);
  	}
  #endif
  	/* NB: pPhysNext, DataRate, and SapPktInfo setup once, don't touch */
  	ds->Format = 0;
  	ds->pad = 0;
  	ds->ack_wcb_addr = 0;
  
  	mn = MWL_NODE(ni);
  	/*
  	 * Select transmit rate.
  	 */
  	switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) {
  	case IEEE80211_FC0_TYPE_MGT:
  		sc->sc_stats.mst_tx_mgmt++;
  		/* fall thru... */
  	case IEEE80211_FC0_TYPE_CTL:
  		/* NB: assign to BE q to avoid bursting */
  		ds->TxPriority = MWL_WME_AC_BE;
  		break;
  	case IEEE80211_FC0_TYPE_DATA:
  		if (!ismcast) {
  			const struct ieee80211_txparam *tp = ni->ni_txparms;
  			/*
  			 * EAPOL frames get forced to a fixed rate and w/o
  			 * aggregation; otherwise check for any fixed rate
  			 * for the client (may depend on association state).
  			 */
  			if (m0->m_flags & M_EAPOL) {
  				const struct mwl_vap *mvp = MWL_VAP_CONST(vap);
  				ds->Format = mvp->mv_eapolformat;
  				ds->pad = htole16(
  				    EAGLE_TXD_FIXED_RATE | EAGLE_TXD_DONT_AGGR);
  			} else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) {
  				/* XXX pre-calculate per node */
  				ds->Format = htole16(
  				    mwl_calcformat(tp->ucastrate, ni));
  				ds->pad = htole16(EAGLE_TXD_FIXED_RATE);
  			}
  			/* NB: EAPOL frames will never have qos set */
  			if (qos == 0)
  				ds->TxPriority = txq->qnum;
  #if MWL_MAXBA > 3
  			else if (mwl_bastream_match(&mn->mn_ba[3], qos))
  				ds->TxPriority = mn->mn_ba[3].txq;
  #endif
  #if MWL_MAXBA > 2
  			else if (mwl_bastream_match(&mn->mn_ba[2], qos))
  				ds->TxPriority = mn->mn_ba[2].txq;
  #endif
  #if MWL_MAXBA > 1
  			else if (mwl_bastream_match(&mn->mn_ba[1], qos))
  				ds->TxPriority = mn->mn_ba[1].txq;
  #endif
  #if MWL_MAXBA > 0
  			else if (mwl_bastream_match(&mn->mn_ba[0], qos))
  				ds->TxPriority = mn->mn_ba[0].txq;
  #endif
  			else
  				ds->TxPriority = txq->qnum;
  		} else
  			ds->TxPriority = txq->qnum;
  		break;
  	default:
  		if_printf(ifp, "bogus frame type 0x%x (%s)\n",
  			wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__);
  		sc->sc_stats.mst_tx_badframetype++;
  		m_freem(m0);
  		return EIO;
  	}
  
  	if (IFF_DUMPPKTS_XMIT(sc))
  		ieee80211_dump_pkt(ic,
  		    mtod(m0, const uint8_t *)+sizeof(uint16_t),
  		    m0->m_len - sizeof(uint16_t), ds->DataRate, -1);
  
  	MWL_TXQ_LOCK(txq);
  	ds->Status = htole32(EAGLE_TXD_STATUS_FW_OWNED);
  	STAILQ_INSERT_TAIL(&txq->active, bf, bf_list);
  	MWL_TXDESC_SYNC(txq, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
  
  	ifp->if_opackets++;
  	sc->sc_tx_timer = 5;
  	MWL_TXQ_UNLOCK(txq);
  
  	return 0;
  #undef	IEEE80211_DIR_DSTODS
  }
  
  static __inline int
  mwl_cvtlegacyrix(int rix)
  {
  #define	N(x)	(sizeof(x)/sizeof(x[0]))
  	static const int ieeerates[] =
  	    { 2, 4, 11, 22, 44, 12, 18, 24, 36, 48, 72, 96, 108 };
  	return (rix < N(ieeerates) ? ieeerates[rix] : 0);
  #undef N
  }
  
  /*
   * Process completed xmit descriptors from the specified queue.
   */
  static int
  mwl_tx_processq(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  #define	EAGLE_TXD_STATUS_MCAST \
  	(EAGLE_TXD_STATUS_MULTICAST_TX | EAGLE_TXD_STATUS_BROADCAST_TX)
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct mwl_txbuf *bf;
  	struct mwl_txdesc *ds;
  	struct ieee80211_node *ni;
  	struct mwl_node *an;
  	int nreaped;
  	uint32_t status;
  
  	DPRINTF(sc, MWL_DEBUG_TX_PROC, "%s: tx queue %u\n", __func__, txq->qnum);
  	for (nreaped = 0;; nreaped++) {
  		MWL_TXQ_LOCK(txq);
  		bf = STAILQ_FIRST(&txq->active);
  		if (bf == NULL) {
  			MWL_TXQ_UNLOCK(txq);
  			break;
  		}
  		ds = bf->bf_desc;
  		MWL_TXDESC_SYNC(txq, ds,
  		    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
  		if (ds->Status & htole32(EAGLE_TXD_STATUS_FW_OWNED)) {
  			MWL_TXQ_UNLOCK(txq);
  			break;
  		}
  		STAILQ_REMOVE_HEAD(&txq->active, bf_list);
  		MWL_TXQ_UNLOCK(txq);
  
  #ifdef MWL_DEBUG
  		if (sc->sc_debug & MWL_DEBUG_XMIT_DESC)
  			mwl_printtxbuf(bf, txq->qnum, nreaped);
  #endif
  		ni = bf->bf_node;
  		if (ni != NULL) {
  			an = MWL_NODE(ni);
  			status = le32toh(ds->Status);
  			if (status & EAGLE_TXD_STATUS_OK) {
  				uint16_t Format = le16toh(ds->Format);
  				uint8_t txant = MS(Format, EAGLE_TXD_ANTENNA);
  
  				sc->sc_stats.mst_ant_tx[txant]++;
  				if (status & EAGLE_TXD_STATUS_OK_RETRY)
  					sc->sc_stats.mst_tx_retries++;
  				if (status & EAGLE_TXD_STATUS_OK_MORE_RETRY)
  					sc->sc_stats.mst_tx_mretries++;
  				if (txq->qnum >= MWL_WME_AC_VO)
  					ic->ic_wme.wme_hipri_traffic++;
  				ni->ni_txrate = MS(Format, EAGLE_TXD_RATE);
  				if ((Format & EAGLE_TXD_FORMAT_HT) == 0) {
  					ni->ni_txrate = mwl_cvtlegacyrix(
  					    ni->ni_txrate);
  				} else
  					ni->ni_txrate |= IEEE80211_RATE_MCS;
  				sc->sc_stats.mst_tx_rate = ni->ni_txrate;
  			} else {
  				if (status & EAGLE_TXD_STATUS_FAILED_LINK_ERROR)
  					sc->sc_stats.mst_tx_linkerror++;
  				if (status & EAGLE_TXD_STATUS_FAILED_XRETRY)
  					sc->sc_stats.mst_tx_xretries++;
  				if (status & EAGLE_TXD_STATUS_FAILED_AGING)
  					sc->sc_stats.mst_tx_aging++;
  				if (bf->bf_m->m_flags & M_FF)
  					sc->sc_stats.mst_ff_txerr++;
  			}
  			/*
  			 * Do any tx complete callback.  Note this must
  			 * be done before releasing the node reference.
  			 * XXX no way to figure out if frame was ACK'd
  			 */
  			if (bf->bf_m->m_flags & M_TXCB) {
  				/* XXX strip fw len in case header inspected */
  				m_adj(bf->bf_m, sizeof(uint16_t));
  				ieee80211_process_callback(ni, bf->bf_m,
  					(status & EAGLE_TXD_STATUS_OK) == 0);
  			}
  			/*
  			 * Reclaim reference to node.
  			 *
  			 * NB: the node may be reclaimed here if, for example
  			 *     this is a DEAUTH message that was sent and the
  			 *     node was timed out due to inactivity.
  			 */
  			ieee80211_free_node(ni);
  		}
  		ds->Status = htole32(EAGLE_TXD_STATUS_IDLE);
  
  		bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap,
  		    BUS_DMASYNC_POSTWRITE);
  		bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap);
  		m_freem(bf->bf_m);
  
  		mwl_puttxbuf_tail(txq, bf);
  	}
  	return nreaped;
  #undef EAGLE_TXD_STATUS_MCAST
  }
  
  /*
   * Deferred processing of transmit interrupt; special-cased
   * for four hardware queues, 0-3.
   */
  static void
  mwl_tx_proc(void *arg, int npending)
  {
  	struct mwl_softc *sc = arg;
  	struct ifnet *ifp = sc->sc_ifp;
  	int nreaped;
  
  	/*
  	 * Process each active queue.
  	 */
  	nreaped = 0;
  	if (!STAILQ_EMPTY(&sc->sc_txq[0].active))
  		nreaped += mwl_tx_processq(sc, &sc->sc_txq[0]);
  	if (!STAILQ_EMPTY(&sc->sc_txq[1].active))
  		nreaped += mwl_tx_processq(sc, &sc->sc_txq[1]);
  	if (!STAILQ_EMPTY(&sc->sc_txq[2].active))
  		nreaped += mwl_tx_processq(sc, &sc->sc_txq[2]);
  	if (!STAILQ_EMPTY(&sc->sc_txq[3].active))
  		nreaped += mwl_tx_processq(sc, &sc->sc_txq[3]);
  
  	if (nreaped != 0) {
  		ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
  		sc->sc_tx_timer = 0;
  		if (!IFQ_IS_EMPTY(&ifp->if_snd)) {
  			/* NB: kick fw; the tx thread may have been preempted */
  			mwl_hal_txstart(sc->sc_mh, 0);
  			mwl_start(ifp);
  		}
  	}
  }
  
  static void
  mwl_tx_draintxq(struct mwl_softc *sc, struct mwl_txq *txq)
  {
  	struct ieee80211_node *ni;
  	struct mwl_txbuf *bf;
  	u_int ix;
  
  	/*
  	 * NB: this assumes output has been stopped and
  	 *     we do not need to block mwl_tx_tasklet
  	 */
  	for (ix = 0;; ix++) {
  		MWL_TXQ_LOCK(txq);
  		bf = STAILQ_FIRST(&txq->active);
  		if (bf == NULL) {
  			MWL_TXQ_UNLOCK(txq);
  			break;
  		}
  		STAILQ_REMOVE_HEAD(&txq->active, bf_list);
  		MWL_TXQ_UNLOCK(txq);
  #ifdef MWL_DEBUG
  		if (sc->sc_debug & MWL_DEBUG_RESET) {
  			struct ifnet *ifp = sc->sc_ifp;
  			struct ieee80211com *ic = ifp->if_l2com;
  			const struct mwltxrec *tr =
  			    mtod(bf->bf_m, const struct mwltxrec *);
  			mwl_printtxbuf(bf, txq->qnum, ix);
  			ieee80211_dump_pkt(ic, (const uint8_t *)&tr->wh,
  				bf->bf_m->m_len - sizeof(tr->fwlen), 0, -1);
  		}
  #endif /* MWL_DEBUG */
  		bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap);
  		ni = bf->bf_node;
  		if (ni != NULL) {
  			/*
  			 * Reclaim node reference.
  			 */
  			ieee80211_free_node(ni);
  		}
  		m_freem(bf->bf_m);
  
  		mwl_puttxbuf_tail(txq, bf);
  	}
  }
  
  /*
   * Drain the transmit queues and reclaim resources.
   */
  static void
  mwl_draintxq(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	int i;
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++)
  		mwl_tx_draintxq(sc, &sc->sc_txq[i]);
  	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
  	sc->sc_tx_timer = 0;
  }
  
  #ifdef MWL_DIAGAPI
  /*
   * Reset the transmit queues to a pristine state after a fw download.
   */
  static void
  mwl_resettxq(struct mwl_softc *sc)
  {
  	int i;
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++)
  		mwl_txq_reset(sc, &sc->sc_txq[i]);
  }
  #endif /* MWL_DIAGAPI */
  
  /*
   * Clear the transmit queues of any frames submitted for the
   * specified vap.  This is done when the vap is deleted so we
   * don't potentially reference the vap after it is gone.
   * Note we cannot remove the frames; we only reclaim the node
   * reference.
   */
  static void
  mwl_cleartxq(struct mwl_softc *sc, struct ieee80211vap *vap)
  {
  	struct mwl_txq *txq;
  	struct mwl_txbuf *bf;
  	int i;
  
  	for (i = 0; i < MWL_NUM_TX_QUEUES; i++) {
  		txq = &sc->sc_txq[i];
  		MWL_TXQ_LOCK(txq);
  		STAILQ_FOREACH(bf, &txq->active, bf_list) {
  			struct ieee80211_node *ni = bf->bf_node;
  			if (ni != NULL && ni->ni_vap == vap) {
  				bf->bf_node = NULL;
  				ieee80211_free_node(ni);
  			}
  		}
  		MWL_TXQ_UNLOCK(txq);
  	}
  }
  
  static int
  mwl_recv_action(struct ieee80211_node *ni, const struct ieee80211_frame *wh,
  	const uint8_t *frm, const uint8_t *efrm)
  {
  	struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc;
  	const struct ieee80211_action *ia;
  
  	ia = (const struct ieee80211_action *) frm;
  	if (ia->ia_category == IEEE80211_ACTION_CAT_HT &&
  	    ia->ia_action == IEEE80211_ACTION_HT_MIMOPWRSAVE) {
  		const struct ieee80211_action_ht_mimopowersave *mps =
  		    (const struct ieee80211_action_ht_mimopowersave *) ia;
  
  		mwl_hal_setmimops(sc->sc_mh, ni->ni_macaddr,
  		    mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_ENA,
  		    MS(mps->am_control, IEEE80211_A_HT_MIMOPWRSAVE_MODE));
  		return 0;
  	} else
  		return sc->sc_recv_action(ni, wh, frm, efrm);
  }
  
  static int
  mwl_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
  	int dialogtoken, int baparamset, int batimeout)
  {
  	struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc;
  	struct ieee80211vap *vap = ni->ni_vap;
  	struct mwl_node *mn = MWL_NODE(ni);
  	struct mwl_bastate *bas;
  
  	bas = tap->txa_private;
  	if (bas == NULL) {
  		const MWL_HAL_BASTREAM *sp;
  		/*
  		 * Check for a free BA stream slot.
  		 */
  #if MWL_MAXBA > 3
  		if (mn->mn_ba[3].bastream == NULL)
  			bas = &mn->mn_ba[3];
  		else
  #endif
  #if MWL_MAXBA > 2
  		if (mn->mn_ba[2].bastream == NULL)
  			bas = &mn->mn_ba[2];
  		else
  #endif
  #if MWL_MAXBA > 1
  		if (mn->mn_ba[1].bastream == NULL)
  			bas = &mn->mn_ba[1];
  		else
  #endif
  #if MWL_MAXBA > 0
  		if (mn->mn_ba[0].bastream == NULL)
  			bas = &mn->mn_ba[0];
  		else 
  #endif
  		{
  			/* sta already has max BA streams */
  			/* XXX assign BA stream to highest priority tid */
  			DPRINTF(sc, MWL_DEBUG_AMPDU,
  			    "%s: already has max bastreams\n", __func__);
  			sc->sc_stats.mst_ampdu_reject++;
  			return 0;
  		}
  		/* NB: no held reference to ni */
  		sp = mwl_hal_bastream_alloc(MWL_VAP(vap)->mv_hvap,
  		    (baparamset & IEEE80211_BAPS_POLICY_IMMEDIATE) != 0,
  		    ni->ni_macaddr, tap->txa_tid, ni->ni_htparam,
  		    ni, tap);
  		if (sp == NULL) {
  			/*
  			 * No available stream, return 0 so no
  			 * a-mpdu aggregation will be done.
  			 */
  			DPRINTF(sc, MWL_DEBUG_AMPDU,
  			    "%s: no bastream available\n", __func__);
  			sc->sc_stats.mst_ampdu_nostream++;
  			return 0;
  		}
  		DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: alloc bastream %p\n",
  		    __func__, sp);
  		/* NB: qos is left zero so we won't match in mwl_tx_start */
  		bas->bastream = sp;
  		tap->txa_private = bas;
  	}
  	/* fetch current seq# from the firmware; if available */
  	if (mwl_hal_bastream_get_seqno(sc->sc_mh, bas->bastream,
  	    vap->iv_opmode == IEEE80211_M_STA ? vap->iv_myaddr : ni->ni_macaddr,
  	    &tap->txa_start) != 0)
  		tap->txa_start = 0;
  	return sc->sc_addba_request(ni, tap, dialogtoken, baparamset, batimeout);
  }
  
  static int
  mwl_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
  	int code, int baparamset, int batimeout)
  {
  	struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc;
  	struct mwl_bastate *bas;
  
  	bas = tap->txa_private;
  	if (bas == NULL) {
  		/* XXX should not happen */
  		DPRINTF(sc, MWL_DEBUG_AMPDU,
  		    "%s: no BA stream allocated, TID %d\n",
  		    __func__, tap->txa_tid);
  		sc->sc_stats.mst_addba_nostream++;
  		return 0;
  	}
  	if (code == IEEE80211_STATUS_SUCCESS) {
  		struct ieee80211vap *vap = ni->ni_vap;
  		int bufsiz, error;
  
  		/*
  		 * Tell the firmware to setup the BA stream;
  		 * we know resources are available because we
  		 * pre-allocated one before forming the request.
  		 */
  		bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ);
  		if (bufsiz == 0)
  			bufsiz = IEEE80211_AGGR_BAWMAX;
  		error = mwl_hal_bastream_create(MWL_VAP(vap)->mv_hvap,
  		    bas->bastream, bufsiz, bufsiz, tap->txa_start);
  		if (error != 0) {
  			/*
  			 * Setup failed, return immediately so no a-mpdu
  			 * aggregation will be done.
  			 */
  			mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream);
  			mwl_bastream_free(bas);
  			tap->txa_private = NULL;
  
  			DPRINTF(sc, MWL_DEBUG_AMPDU,
  			    "%s: create failed, error %d, bufsiz %d TID %d "
  			    "htparam 0x%x\n", __func__, error, bufsiz,
  			    tap->txa_tid, ni->ni_htparam);
  			sc->sc_stats.mst_bacreate_failed++;
  			return 0;
  		}
  		/* NB: cache txq to avoid ptr indirect */
  		mwl_bastream_setup(bas, tap->txa_tid, bas->bastream->txq);
  		DPRINTF(sc, MWL_DEBUG_AMPDU,
  		    "%s: bastream %p assigned to txq %d TID %d bufsiz %d "
  		    "htparam 0x%x\n", __func__, bas->bastream,
  		    bas->txq, tap->txa_tid, bufsiz, ni->ni_htparam);
  	} else {
  		/*
  		 * Other side NAK'd us; return the resources.
  		 */
  		DPRINTF(sc, MWL_DEBUG_AMPDU,
  		    "%s: request failed with code %d, destroy bastream %p\n",
  		    __func__, code, bas->bastream);
  		mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream);
  		mwl_bastream_free(bas);
  		tap->txa_private = NULL;
  	}
  	/* NB: firmware sends BAR so we don't need to */
  	return sc->sc_addba_response(ni, tap, code, baparamset, batimeout);
  }
  
  static void
  mwl_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
  {
  	struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc;
  	struct mwl_bastate *bas;
  
  	bas = tap->txa_private;
  	if (bas != NULL) {
  		DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: destroy bastream %p\n",
  		    __func__, bas->bastream);
  		mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream);
  		mwl_bastream_free(bas);
  		tap->txa_private = NULL;
  	}
  	sc->sc_addba_stop(ni, tap);
  }
  
  /*
   * Setup the rx data structures.  This should only be
   * done once or we may get out of sync with the firmware.
   */
  static int
  mwl_startrecv(struct mwl_softc *sc)
  {
  	if (!sc->sc_recvsetup) {
  		struct mwl_rxbuf *bf, *prev;
  		struct mwl_rxdesc *ds;
  
  		prev = NULL;
  		STAILQ_FOREACH(bf, &sc->sc_rxbuf, bf_list) {
  			int error = mwl_rxbuf_init(sc, bf);
  			if (error != 0) {
  				DPRINTF(sc, MWL_DEBUG_RECV,
  					"%s: mwl_rxbuf_init failed %d\n",
  					__func__, error);
  				return error;
  			}
  			if (prev != NULL) {
  				ds = prev->bf_desc;
  				ds->pPhysNext = htole32(bf->bf_daddr);
  			}
  			prev = bf;
  		}
  		if (prev != NULL) {
  			ds = prev->bf_desc;
  			ds->pPhysNext =
  			    htole32(STAILQ_FIRST(&sc->sc_rxbuf)->bf_daddr);
  		}
  		sc->sc_recvsetup = 1;
  	}
  	mwl_mode_init(sc);		/* set filters, etc. */
  	return 0;
  }
  
  static MWL_HAL_APMODE
  mwl_getapmode(const struct ieee80211vap *vap, struct ieee80211_channel *chan)
  {
  	MWL_HAL_APMODE mode;
  
  	if (IEEE80211_IS_CHAN_HT(chan)) {
  		if (vap->iv_flags_ht & IEEE80211_FHT_PUREN)
  			mode = AP_MODE_N_ONLY;
  		else if (IEEE80211_IS_CHAN_5GHZ(chan))
  			mode = AP_MODE_AandN;
  		else if (vap->iv_flags & IEEE80211_F_PUREG)
  			mode = AP_MODE_GandN;
  		else
  			mode = AP_MODE_BandGandN;
  	} else if (IEEE80211_IS_CHAN_ANYG(chan)) {
  		if (vap->iv_flags & IEEE80211_F_PUREG)
  			mode = AP_MODE_G_ONLY;
  		else
  			mode = AP_MODE_MIXED;
  	} else if (IEEE80211_IS_CHAN_B(chan))
  		mode = AP_MODE_B_ONLY;
  	else if (IEEE80211_IS_CHAN_A(chan))
  		mode = AP_MODE_A_ONLY;
  	else
  		mode = AP_MODE_MIXED;		/* XXX should not happen? */
  	return mode;
  }
  
  static int
  mwl_setapmode(struct ieee80211vap *vap, struct ieee80211_channel *chan)
  {
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	return mwl_hal_setapmode(hvap, mwl_getapmode(vap, chan));
  }
  
  /*
   * Set/change channels.
   */
  static int
  mwl_chan_set(struct mwl_softc *sc, struct ieee80211_channel *chan)
  {
  	struct mwl_hal *mh = sc->sc_mh;
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  	MWL_HAL_CHANNEL hchan;
  	int maxtxpow;
  
  	DPRINTF(sc, MWL_DEBUG_RESET, "%s: chan %u MHz/flags 0x%x\n",
  	    __func__, 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(mh, 0);		/* disable interrupts */
  #if 0
  	mwl_draintxq(sc);		/* clear pending tx frames */
  #endif
  	mwl_hal_setchannel(mh, &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 > ic->ic_txpowlimit)
  		maxtxpow = ic->ic_txpowlimit;
  	mwl_hal_settxpower(mh, &hchan, maxtxpow / 2);
  	/* NB: potentially change mcast/mgt rates */
  	mwl_setcurchanrates(sc);
  
  	/*
  	 * Update internal state.
  	 */
  	sc->sc_tx_th.wt_chan_freq = htole16(chan->ic_freq);
  	sc->sc_rx_th.wr_chan_freq = htole16(chan->ic_freq);
  	if (IEEE80211_IS_CHAN_A(chan)) {
  		sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_A);
  		sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_A);
  	} else if (IEEE80211_IS_CHAN_ANYG(chan)) {
  		sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_G);
  		sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_G);
  	} else {
  		sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_B);
  		sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_B);
  	}
  	sc->sc_curchan = hchan;
  	mwl_hal_intrset(mh, sc->sc_imask);
  
  	return 0;
  }
  
  static void
  mwl_scan_start(struct ieee80211com *ic)
  {
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  
  	DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__);
  }
  
  static void
  mwl_scan_end(struct ieee80211com *ic)
  {
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  
  	DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__);
  }
  
  static void
  mwl_set_channel(struct ieee80211com *ic)
  {
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  
  	(void) mwl_chan_set(sc, ic->ic_curchan);
  }
  
  /* 
   * Handle a channel switch request.  We inform the firmware
   * and mark the global state to suppress various actions.
   * NB: we issue only one request to the fw; we may be called
   * multiple times if there are multiple vap's.
   */
  static void
  mwl_startcsa(struct ieee80211vap *vap)
  {
  	struct ieee80211com *ic = vap->iv_ic;
  	struct mwl_softc *sc = ic->ic_ifp->if_softc;
  	MWL_HAL_CHANNEL hchan;
  
  	if (sc->sc_csapending)
  		return;
  
  	mwl_mapchan(&hchan, ic->ic_csa_newchan);
  	/* 1 =>'s quiet channel */
  	mwl_hal_setchannelswitchie(sc->sc_mh, &hchan, 1, ic->ic_csa_count);
  	sc->sc_csapending = 1;
  }
  
  /*
   * 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 ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
  {
  	if ((vap->iv_flags & (IEEE80211_F_PRIVACY|IEEE80211_F_WPA)) ==
  		IEEE80211_F_PRIVACY &&
  	    vap->iv_def_txkey != IEEE80211_KEYIX_NONE &&
  	    vap->iv_nw_keys[vap->iv_def_txkey].wk_keyix != IEEE80211_KEYIX_NONE)
  		(void) mwl_key_set(vap, &vap->iv_nw_keys[vap->iv_def_txkey], mac);
  }
  
  static int
  mwl_peerstadb(struct ieee80211_node *ni, int aid, int staid, MWL_HAL_PEERINFO *pi)
  {
  #define	WME(ie) ((const struct ieee80211_wme_info *) ie)
  	struct ieee80211vap *vap = ni->ni_vap;
  	struct mwl_hal_vap *hvap;
  	int error;
  
  	if (vap->iv_opmode == IEEE80211_M_WDS) {
  		/*
  		 * WDS vap's do not have a f/w vap; instead they piggyback
  		 * on an AP vap and we must install the sta db entry and
  		 * crypto state using that AP's handle (the WDS vap has none).
  		 */
  		hvap = MWL_VAP(vap)->mv_ap_hvap;
  	} else
  		hvap = MWL_VAP(vap)->mv_hvap;
  	error = mwl_hal_newstation(hvap, ni->ni_macaddr,
  	    aid, staid, pi,
  	    ni->ni_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT),
  	    ni->ni_ies.wme_ie != NULL ? WME(ni->ni_ies.wme_ie)->wme_info : 0);
  	if (error == 0) {
  		/*
  		 * Setup security for this station.  For sta mode this is
  		 * needed even though do the same thing on transition to
  		 * AUTH state because the call to mwl_hal_newstation
  		 * clobbers the crypto state we setup.
  		 */
  		mwl_setanywepkey(vap, ni->ni_macaddr);
  	}
  	return error;
  #undef WME
  }
  
  static void
  mwl_setglobalkeys(struct ieee80211vap *vap)
  {
  	struct ieee80211_key *wk;
  
  	wk = &vap->iv_nw_keys[0];
  	for (; wk < &vap->iv_nw_keys[IEEE80211_WEP_NKID]; wk++)
  		if (wk->wk_keyix != IEEE80211_KEYIX_NONE)
  			(void) mwl_key_set(vap, wk, vap->iv_myaddr);
  }
  
  /*
   * Convert a legacy rate set to a firmware bitmask.
   */
  static uint32_t
  get_rate_bitmap(const struct ieee80211_rateset *rs)
  {
  	uint32_t rates;
  	int i;
  
  	rates = 0;
  	for (i = 0; i < rs->rs_nrates; i++)
  		switch (rs->rs_rates[i] & IEEE80211_RATE_VAL) {
  		case 2:	  rates |= 0x001; break;
  		case 4:	  rates |= 0x002; break;
  		case 11:  rates |= 0x004; break;
  		case 22:  rates |= 0x008; break;
  		case 44:  rates |= 0x010; break;
  		case 12:  rates |= 0x020; break;
  		case 18:  rates |= 0x040; break;
  		case 24:  rates |= 0x080; break;
  		case 36:  rates |= 0x100; break;
  		case 48:  rates |= 0x200; break;
  		case 72:  rates |= 0x400; break;
  		case 96:  rates |= 0x800; break;
  		case 108: rates |= 0x1000; break;
  		}
  	return rates;
  }
  
  /*
   * Construct an HT firmware bitmask from an HT rate set.
   */
  static uint32_t
  get_htrate_bitmap(const struct ieee80211_htrateset *rs)
  {
  	uint32_t rates;
  	int i;
  
  	rates = 0;
  	for (i = 0; i < rs->rs_nrates; i++) {
  		if (rs->rs_rates[i] < 16)
  			rates |= 1<<rs->rs_rates[i];
  	}
  	return rates;
  }
  
  /*
   * Craft station database entry for station.
   * NB: use host byte order here, the hal handles byte swapping.
   */
  static MWL_HAL_PEERINFO *
  mkpeerinfo(MWL_HAL_PEERINFO *pi, const struct ieee80211_node *ni)
  {
  	const struct ieee80211vap *vap = ni->ni_vap;
  
  	memset(pi, 0, sizeof(*pi));
  	pi->LegacyRateBitMap = get_rate_bitmap(&ni->ni_rates);
  	pi->CapInfo = ni->ni_capinfo;
  	if (ni->ni_flags & IEEE80211_NODE_HT) {
  		/* HT capabilities, etc */
  		pi->HTCapabilitiesInfo = ni->ni_htcap;
  		/* XXX pi.HTCapabilitiesInfo */
  	        pi->MacHTParamInfo = ni->ni_htparam;	
  		pi->HTRateBitMap = get_htrate_bitmap(&ni->ni_htrates);
  		pi->AddHtInfo.ControlChan = ni->ni_htctlchan;
  		pi->AddHtInfo.AddChan = ni->ni_ht2ndchan;
  		pi->AddHtInfo.OpMode = ni->ni_htopmode;
  		pi->AddHtInfo.stbc = ni->ni_htstbc;
  
  		/* constrain according to local configuration */
  		if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) == 0)
  			pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI40;
  		if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) == 0)
  			pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI20;
  		if (ni->ni_chw != 40)
  			pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_CHWIDTH40;
  	}
  	return pi;
  }
  
  /*
   * Re-create the local sta db entry for a vap to ensure
   * up to date WME state is pushed to the firmware.  Because
   * this resets crypto state this must be followed by a
   * reload of any keys in the global key table.
   */
  static int
  mwl_localstadb(struct ieee80211vap *vap)
  {
  #define	WME(ie) ((const struct ieee80211_wme_info *) ie)
  	struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap;
  	struct ieee80211_node *bss;
  	MWL_HAL_PEERINFO pi;
  	int error;
  
  	switch (vap->iv_opmode) {
  	case IEEE80211_M_STA:
  		bss = vap->iv_bss;
  		error = mwl_hal_newstation(hvap, vap->iv_myaddr, 0, 0,
  		    vap->iv_state == IEEE80211_S_RUN ?
  			mkpeerinfo(&pi, bss) : NULL,
  		    (bss->ni_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT)),
  		    bss->ni_ies.wme_ie != NULL ?
  			WME(bss->ni_ies.wme_ie)->wme_info : 0);
  		if (error == 0)
  			mwl_setglobalkeys(vap);
  		break;
  	case IEEE80211_M_HOSTAP:
  	case IEEE80211_M_MBSS:
  		error = mwl_hal_newstation(hvap, vap->iv_myaddr,
  		    0, 0, NULL, vap->iv_flags & IEEE80211_F_WME, 0);
  		if (error == 0)
  			mwl_setglobalkeys(vap);
  		break;
  	default:
  		error = 0;
  		break;
  	}
  	return error;
  #undef WME
  }
  
  static int
  mwl_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
  {
  	struct mwl_vap *mvp = MWL_VAP(vap);
  	struct mwl_hal_vap *hvap = mvp->mv_hvap;
  	struct ieee80211com *ic = vap->iv_ic;
  	struct ieee80211_node *ni = NULL;
  	struct ifnet *ifp = ic->ic_ifp;
  	struct mwl_softc *sc = ifp->if_softc;
  	struct mwl_hal *mh = sc->sc_mh;
  	enum ieee80211_state ostate = vap->iv_state;
  	int error;
  
  	DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: %s -> %s\n",
  	    vap->iv_ifp->if_xname, __func__,
  	    ieee80211_state_name[ostate], ieee80211_state_name[nstate]);
  
  	callout_stop(&sc->sc_timer);
  	/*
  	 * Clear current radar detection state.
  	 */
  	if (ostate == IEEE80211_S_CAC) {
  		/* stop quiet mode radar detection */
  		mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_STOP);
  	} else if (sc->sc_radarena) {
  		/* stop in-service radar detection */
  		mwl_hal_setradardetection(mh, DR_DFS_DISABLE);
  		sc->sc_radarena = 0;
  	}
  	/*
  	 * Carry out per-state actions before doing net80211 work.
  	 */
  	if (nstate == IEEE80211_S_INIT) {
  		/* NB: only ap+sta vap's have a fw entity */
  		if (hvap != NULL)
  			mwl_hal_stop(hvap);
  	} else if (nstate == IEEE80211_S_SCAN) {
  		mwl_hal_start(hvap);
  		/* NB: this disables beacon frames */
  		mwl_hal_setinframode(hvap);
  	} else if (nstate == IEEE80211_S_AUTH) {
  		/*
  		 * Must create a sta db entry in case a WEP key needs to
  		 * be plumbed.  This entry will be overwritten if we
  		 * associate; otherwise it will be reclaimed on node free.
  		 */
  		ni = vap->iv_bss;
  		MWL_NODE(ni)->mn_hvap = hvap;
  		(void) mwl_peerstadb(ni, 0, 0, NULL);
  	} else if (nstate == IEEE80211_S_CSA) {
  		/* XXX move to below? */
  		if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
  		    vap->iv_opmode == IEEE80211_M_MBSS)
  			mwl_startcsa(vap);
  	} else if (nstate == IEEE80211_S_CAC) {
  		/* XXX move to below? */
  		/* stop ap xmit and enable quiet mode radar detection */
  		mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_START);
  	}
  
  	/*
  	 * Invoke the parent method to do net80211 work.
  	 */
  	error = mvp->mv_newstate(vap, nstate, arg);
  
  	/*
  	 * Carry out work that must be done after net80211 runs;
  	 * this work requires up to date state (e.g. iv_bss).
  	 */
  	if (error == 0 && nstate == IEEE80211_S_RUN) {
  		/* NB: collect bss node again, it may have changed */
  		ni = vap->iv_bss;
  
  		DPRINTF(sc, MWL_DEBUG_STATE,
  		    "%s: %s(RUN): iv_flags 0x%08x bintvl %d bssid %s "
  		    "capinfo 0x%04x chan %d\n",
  		    vap->iv_ifp->if_xname, __func__, vap->iv_flags,
  		    ni->ni_intval, ether_sprintf(ni->ni_bssid), ni->ni_capinfo,
  		    ieee80211_chan2ieee(ic, ic->ic_curchan));
  
  		/*
  		 * Recreate local sta db entry to update WME/HT state.
  		 */
  		mwl_localstadb(vap);
  		switch (vap->iv_opmode) {
  		case IEEE80211_M_HOSTAP:
  		case IEEE80211_M_MBSS:
  			if (ostate == IEEE80211_S_CAC) {
  				/* enable in-service radar detection */
  				mwl_hal_setradardetection(mh,
  				    DR_IN_SERVICE_MONITOR_START);
  				sc->sc_radarena = 1;
  			}
  			/*
  			 * Allocate and setup the beacon frame
  			 * (and related state).
  			 */
  			error = mwl_reset_vap(vap, IEEE80211_S_RUN);
  			if (error != 0) {
  				DPRINTF(sc, MWL_DEBUG_STATE,
  				    "%s: beacon setup failed, error %d\n",
  				    __func__, error);
  				goto bad;
  			}
  			/* NB: must be after setting up beacon */
  			mwl_hal_start(hvap);
  			break;
  		case IEEE80211_M_STA:
  			DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: aid 0x%x\n",
  			    vap->iv_ifp->if_xname, __func__, ni->ni_associd);
  			/*
  			 * Set state now that we're associated.
  			 */
  			mwl_hal_setassocid(hvap, ni->ni_bssid, ni->ni_associd);
  			mwl_setrates(vap);
  			mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold);
  			if ((vap->iv_flags & IEEE80211_F_DWDS) &&
  			    sc->sc_ndwdsvaps++ == 0)
  				mwl_hal_setdwds(mh, 1);
  			break;
  		case IEEE80211_M_WDS:
  			DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: bssid %s\n",
  			    vap->iv_ifp->if_xname, __func__,
  			    ether_sprintf(ni->ni_bssid));
  			mwl_seteapolformat(vap);
  			break;
  		default:
  			break;
  		}
  		/*
  		 * Set CS mode according to operating channel;
  		 * this mostly an optimization for 5GHz.
  		 *
  		 * NB: must follow mwl_hal_start which resets csmode
  		 */
  		if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan))
  			mwl_hal_setcsmode(mh, CSMODE_AGGRESSIVE);
  		else
  			mwl_hal_setcsmode(mh, CSMODE_AUTO_ENA);
  		/*
  		 * Start timer to prod firmware.
  		 */
  		if (sc->sc_ageinterval != 0)
  			callout_reset(&sc->sc_timer, sc->sc_ageinterval*hz,
  			    mwl_agestations, sc);
  	} else if (nstate == IEEE80211_S_SLEEP) {
  		/* XXX set chip in power save */
  	} else if ((vap->iv_flags & IEEE80211_F_DWDS) &&
  	    --sc->sc_ndwdsvaps == 0)
  		mwl_hal_setdwds(mh, 0);
  bad:
  	return error;
  }
  
  /*
   * Manage station id's; these are separate from AID's
   * as AID's may have values out of the range of possible
   * station id's acceptable to the firmware.
   */
  static int
  allocstaid(struct mwl_softc *sc, int aid)
  {
  	int staid;
  
  	if (!(0 < aid && aid < MWL_MAXSTAID) || isset(sc->sc_staid, aid)) {
  		/* NB: don't use 0 */
  		for (staid = 1; staid < MWL_MAXSTAID; staid++)
  			if (isclr(sc->sc_staid, staid))
  				break;
  	} else
  		staid = aid;
  	setbit(sc->sc_staid, staid);
  	return staid;
  }
  
  static void
  delstaid(struct mwl_softc *sc, int staid)
  {
  	clrbit(sc->sc_staid, staid);
  }
  
  /*
   * Setup driver-specific state for a newly associated node.
   * Note that we're called also on a re-associate, the isnew
   * param tells us if this is the first time or not.
   */
  static void
  mwl_newassoc(struct ieee80211_node *ni, int isnew)
  {
  	struct ieee80211vap *vap = ni->ni_vap;
          struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc;
  	struct mwl_node *mn = MWL_NODE(ni);
  	MWL_HAL_PEERINFO pi;
  	uint16_t aid;
  	int error;
  
  	aid = IEEE80211_AID(ni->ni_associd);
  	if (isnew) {
  		mn->mn_staid = allocstaid(sc, aid);
  		mn->mn_hvap = MWL_VAP(vap)->mv_hvap;
  	} else {
  		mn = MWL_NODE(ni);
  		/* XXX reset BA stream? */
  	}
  	DPRINTF(sc, MWL_DEBUG_NODE, "%s: mac %s isnew %d aid %d staid %d\n",
  	    __func__, ether_sprintf(ni->ni_macaddr), isnew, aid, mn->mn_staid);
  	error = mwl_peerstadb(ni, aid, mn->mn_staid, mkpeerinfo(&pi, ni));
  	if (error != 0) {
  		DPRINTF(sc, MWL_DEBUG_NODE,
  		    "%s: error %d creating sta db entry\n",
  		    __func__, error);
  		/* XXX how to deal with error? */
  	}
  }
  
  /*
   * Periodically poke the firmware to age out station state
   * (power save queues, pending tx aggregates).
   */
  static void
  mwl_agestations(void *arg)
  {
  	struct mwl_softc *sc = arg;
  
  	mwl_hal_setkeepalive(sc->sc_mh);
  	if (sc->sc_ageinterval != 0)		/* NB: catch dynamic changes */
  		callout_schedule(&sc->sc_timer, sc->sc_ageinterval*hz);
  }
  
  static const struct mwl_hal_channel *
  findhalchannel(const MWL_HAL_CHANNELINFO *ci, int ieee)
  {
  	int i;
  
  	for (i = 0; i < ci->nchannels; i++) {
  		const struct mwl_hal_channel *hc = &ci->channels[i];
  		if (hc->ieee == ieee)
  			return hc;
  	}
  	return NULL;
  }
  
  static int
  mwl_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *rd,
  	int nchan, struct ieee80211_channel chans[])
  {
  	struct mwl_softc *sc = ic->ic_ifp->if_softc;
  	struct mwl_hal *mh = sc->sc_mh;
  	const MWL_HAL_CHANNELINFO *ci;
  	int i;
  
  	for (i = 0; i < nchan; i++) {
  		struct ieee80211_channel *c = &chans[i];
  		const struct mwl_hal_channel *hc;
  
  		if (IEEE80211_IS_CHAN_2GHZ(c)) {
  			mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_2DOT4GHZ,
  			    IEEE80211_IS_CHAN_HT40(c) ?
  				MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci);
  		} else if (IEEE80211_IS_CHAN_5GHZ(c)) {
  			mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_5GHZ,
  			    IEEE80211_IS_CHAN_HT40(c) ?
  				MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci);
  		} else {
  			if_printf(ic->ic_ifp,
  			    "%s: channel %u freq %u/0x%x not 2.4/5GHz\n",
  			    __func__, c->ic_ieee, c->ic_freq, c->ic_flags);
  			return EINVAL;
  		}
  		/* 
  		 * Verify channel has cal data and cap tx power.
  		 */
  		hc = findhalchannel(ci, c->ic_ieee);
  		if (hc != NULL) {
  			if (c->ic_maxpower > 2*hc->maxTxPow)
  				c->ic_maxpower = 2*hc->maxTxPow;
  			goto next;
  		}
  		if (IEEE80211_IS_CHAN_HT40(c)) {
  			/*
  			 * Look for the extension channel since the
  			 * hal table only has the primary channel.
  			 */
  			hc = findhalchannel(ci, c->ic_extieee);
  			if (hc != NULL) {
  				if (c->ic_maxpower > 2*hc->maxTxPow)
  					c->ic_maxpower = 2*hc->maxTxPow;
  				goto next;
  			}
  		}
  		if_printf(ic->ic_ifp,
  		    "%s: no cal data for channel %u ext %u freq %u/0x%x\n",
  		    __func__, c->ic_ieee, c->ic_extieee,
  		    c->ic_freq, c->ic_flags);
  		return EINVAL;
  	next:
  		;
  	}
  	return 0;
  }
  
  #define	IEEE80211_CHAN_HTG	(IEEE80211_CHAN_HT|IEEE80211_CHAN_G)
  #define	IEEE80211_CHAN_HTA	(IEEE80211_CHAN_HT|IEEE80211_CHAN_A)
  
  static void
  addchan(struct ieee80211_channel *c, int freq, int flags, int ieee, int txpow)
  {
  	c->ic_freq = freq;
  	c->ic_flags = flags;
  	c->ic_ieee = ieee;
  	c->ic_minpower = 0;
  	c->ic_maxpower = 2*txpow;
  	c->ic_maxregpower = txpow;
  }
  
  static const struct ieee80211_channel *
  findchannel(const struct ieee80211_channel chans[], int nchans,
  	int freq, int flags)
  {
  	const struct ieee80211_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 ieee80211_channel chans[], int maxchans, int *nchans,
  	const MWL_HAL_CHANNELINFO *ci, int flags)
  {
  	struct ieee80211_channel *c;
  	const struct ieee80211_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 ieee80211_channel chans[], int maxchans, int *nchans,
  	const MWL_HAL_CHANNELINFO *ci, int flags)
  {
  	struct ieee80211_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 void
  getchannels(struct mwl_softc *sc, int maxchans, int *nchans,
  	struct ieee80211_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->sc_mh,
  	    MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0)
  		addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG);
  	if (mwl_hal_getchannelinfo(sc->sc_mh,
  	    MWL_FREQ_BAND_5GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0)
  		addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA);
  	if (mwl_hal_getchannelinfo(sc->sc_mh,
  	    MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0)
  		addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG);
  	if (mwl_hal_getchannelinfo(sc->sc_mh,
  	    MWL_FREQ_BAND_5GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0)
  		addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA);
  }
  
  static void
  mwl_getradiocaps(struct ieee80211com *ic,
  	int maxchans, int *nchans, struct ieee80211_channel chans[])
  {
  	struct mwl_softc *sc = ic->ic_ifp->if_softc;
  
  	getchannels(sc, maxchans, nchans, chans);
  }
  
  static int
  mwl_getchannels(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  	struct ieee80211com *ic = ifp->if_l2com;
  
  	/*
  	 * 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.
  	 */
  	memset(ic->ic_channels, 0, sizeof(ic->ic_channels));
  	ic->ic_nchans = 0;
  	getchannels(sc, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels);
  
  	ic->ic_regdomain.regdomain = SKU_DEBUG;
  	ic->ic_regdomain.country = CTRY_DEFAULT;
  	ic->ic_regdomain.location = 'I';
  	ic->ic_regdomain.isocc[0] = ' ';	/* XXX? */
  	ic->ic_regdomain.isocc[1] = ' ';
  	return (ic->ic_nchans == 0 ? EIO : 0);
  }
  #undef IEEE80211_CHAN_HTA
  #undef IEEE80211_CHAN_HTG
  
  #ifdef MWL_DEBUG
  static void
  mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix)
  {
  	const struct mwl_rxdesc *ds = bf->bf_desc;
  	uint32_t status = le32toh(ds->Status);
  
  	printf("R[%2u] (DS.V:%p DS.P:%p) NEXT:%08x DATA:%08x RC:%02x%s\n"
  	       "      STAT:%02x LEN:%04x RSSI:%02x CHAN:%02x RATE:%02x QOS:%04x HT:%04x\n",
  	    ix, ds, (const struct mwl_desc *)bf->bf_daddr,
  	    le32toh(ds->pPhysNext), le32toh(ds->pPhysBuffData),
  	    ds->RxControl, 
  	    ds->RxControl != EAGLE_RXD_CTRL_DRIVER_OWN ?
  	        "" : (status & EAGLE_RXD_STATUS_OK) ? " *" : " !",
  	    ds->Status, le16toh(ds->PktLen), ds->RSSI, ds->Channel,
  	    ds->Rate, le16toh(ds->QosCtrl), le16toh(ds->HtSig2));
  }
  
  static void
  mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix)
  {
  	const struct mwl_txdesc *ds = bf->bf_desc;
  	uint32_t status = le32toh(ds->Status);
  
  	printf("Q%u[%3u]", qnum, ix);
  	printf(" (DS.V:%p DS.P:%p)\n",
  	    ds, (const struct mwl_txdesc *)bf->bf_daddr);
  	printf("    NEXT:%08x DATA:%08x LEN:%04x STAT:%08x%s\n",
  	    le32toh(ds->pPhysNext),
  	    le32toh(ds->PktPtr), le16toh(ds->PktLen), status,
  	    status & EAGLE_TXD_STATUS_USED ?
  		"" : (status & 3) != 0 ? " *" : " !");
  	printf("    RATE:%02x PRI:%x QOS:%04x SAP:%08x FORMAT:%04x\n",
  	    ds->DataRate, ds->TxPriority, le16toh(ds->QosCtrl),
  	    le32toh(ds->SapPktInfo), le16toh(ds->Format));
  #if MWL_TXDESC > 1
  	printf("    MULTIFRAMES:%u LEN:%04x %04x %04x %04x %04x %04x\n"
  	    , le32toh(ds->multiframes)
  	    , le16toh(ds->PktLenArray[0]), le16toh(ds->PktLenArray[1])
  	    , le16toh(ds->PktLenArray[2]), le16toh(ds->PktLenArray[3])
  	    , le16toh(ds->PktLenArray[4]), le16toh(ds->PktLenArray[5])
  	);
  	printf("    DATA:%08x %08x %08x %08x %08x %08x\n"
  	    , le32toh(ds->PktPtrArray[0]), le32toh(ds->PktPtrArray[1])
  	    , le32toh(ds->PktPtrArray[2]), le32toh(ds->PktPtrArray[3])
  	    , le32toh(ds->PktPtrArray[4]), le32toh(ds->PktPtrArray[5])
  	);
  #endif
  #if 0
  { const uint8_t *cp = (const uint8_t *) ds;
    int i;
    for (i = 0; i < sizeof(struct mwl_txdesc); i++) {
  	printf("%02x ", cp[i]);
  	if (((i+1) % 16) == 0)
  		printf("\n");
    }
    printf("\n");
  }
  #endif
  }
  #endif /* MWL_DEBUG */
  
  #if 0
  static void
  mwl_txq_dump(struct mwl_txq *txq)
  {
  	struct mwl_txbuf *bf;
  	int i = 0;
  
  	MWL_TXQ_LOCK(txq);
  	STAILQ_FOREACH(bf, &txq->active, bf_list) {
  		struct mwl_txdesc *ds = bf->bf_desc;
  		MWL_TXDESC_SYNC(txq, ds,
  		    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
  #ifdef MWL_DEBUG
  		mwl_printtxbuf(bf, txq->qnum, i);
  #endif
  		i++;
  	}
  	MWL_TXQ_UNLOCK(txq);
  }
  #endif
  
  static void
  mwl_watchdog(void *arg)
  {
  	struct mwl_softc *sc;
  	struct ifnet *ifp;
  
  	sc = arg;
  	callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc);
  	if (sc->sc_tx_timer == 0 || --sc->sc_tx_timer > 0)
  		return;
  
  	ifp = sc->sc_ifp;
  	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && !sc->sc_invalid) {
  		if (mwl_hal_setkeepalive(sc->sc_mh))
  			if_printf(ifp, "transmit timeout (firmware hung?)\n");
  		else
  			if_printf(ifp, "transmit timeout\n");
  #if 0
  		mwl_reset(ifp);
  mwl_txq_dump(&sc->sc_txq[0]);/*XXX*/
  #endif
  		ifp->if_oerrors++;
  		sc->sc_stats.mst_watchdog++;
  	}
  }
  
  #ifdef MWL_DIAGAPI
  /*
   * Diagnostic interface to the HAL.  This is used by various
   * tools to do things like retrieve register contents for
   * debugging.  The mechanism is intentionally opaque so that
   * it can change frequently w/o concern for compatiblity.
   */
  static int
  mwl_ioctl_diag(struct mwl_softc *sc, struct mwl_diag *md)
  {
  	struct mwl_hal *mh = sc->sc_mh;
  	u_int id = md->md_id & MWL_DIAG_ID;
  	void *indata = NULL;
  	void *outdata = NULL;
  	u_int32_t insize = md->md_in_size;
  	u_int32_t outsize = md->md_out_size;
  	int error = 0;
  
  	if (md->md_id & MWL_DIAG_IN) {
  		/*
  		 * Copy in data.
  		 */
  		indata = malloc(insize, M_TEMP, M_NOWAIT);
  		if (indata == NULL) {
  			error = ENOMEM;
  			goto bad;
  		}
  		error = copyin(md->md_in_data, indata, insize);
  		if (error)
  			goto bad;
  	}
  	if (md->md_id & MWL_DIAG_DYN) {
  		/*
  		 * Allocate a buffer for the results (otherwise the HAL
  		 * returns a pointer to a buffer where we can read the
  		 * results).  Note that we depend on the HAL leaving this
  		 * pointer for us to use below in reclaiming the buffer;
  		 * may want to be more defensive.
  		 */
  		outdata = malloc(outsize, M_TEMP, M_NOWAIT);
  		if (outdata == NULL) {
  			error = ENOMEM;
  			goto bad;
  		}
  	}
  	if (mwl_hal_getdiagstate(mh, id, indata, insize, &outdata, &outsize)) {
  		if (outsize < md->md_out_size)
  			md->md_out_size = outsize;
  		if (outdata != NULL)
  			error = copyout(outdata, md->md_out_data,
  					md->md_out_size);
  	} else {
  		error = EINVAL;
  	}
  bad:
  	if ((md->md_id & MWL_DIAG_IN) && indata != NULL)
  		free(indata, M_TEMP);
  	if ((md->md_id & MWL_DIAG_DYN) && outdata != NULL)
  		free(outdata, M_TEMP);
  	return error;
  }
  
  static int
  mwl_ioctl_reset(struct mwl_softc *sc, struct mwl_diag *md)
  {
  	struct mwl_hal *mh = sc->sc_mh;
  	int error;
  
  	MWL_LOCK_ASSERT(sc);
  
  	if (md->md_id == 0 && mwl_hal_fwload(mh, NULL) != 0) {
  		device_printf(sc->sc_dev, "unable to load firmware\n");
  		return EIO;
  	}
  	if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) {
  		device_printf(sc->sc_dev, "unable to fetch h/w specs\n");
  		return EIO;
  	}
  	error = mwl_setupdma(sc);
  	if (error != 0) {
  		/* NB: mwl_setupdma prints a msg */
  		return error;
  	}
  	/*
  	 * Reset tx/rx data structures; after reload we must
  	 * re-start the driver's notion of the next xmit/recv.
  	 */
  	mwl_draintxq(sc);		/* clear pending frames */
  	mwl_resettxq(sc);		/* rebuild tx q lists */
  	sc->sc_rxnext = NULL;		/* force rx to start at the list head */
  	return 0;
  }
  #endif /* MWL_DIAGAPI */
  
  static int
  mwl_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
  {
  #define	IS_RUNNING(ifp) \
  	((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))
  	struct mwl_softc *sc = ifp->if_softc;
  	struct ieee80211com *ic = ifp->if_l2com;
  	struct ifreq *ifr = (struct ifreq *)data;
  	int error = 0, startall;
  
  	switch (cmd) {
  	case SIOCSIFFLAGS:
  		MWL_LOCK(sc);
  		startall = 0;
  		if (IS_RUNNING(ifp)) {
  			/*
  			 * To avoid rescanning another access point,
  			 * do not call mwl_init() here.  Instead,
  			 * only reflect promisc mode settings.
  			 */
  			mwl_mode_init(sc);
  		} else if (ifp->if_flags & IFF_UP) {
  			/*
  			 * Beware of being called during attach/detach
  			 * to reset promiscuous mode.  In that case we
  			 * will still be marked UP but not RUNNING.
  			 * However trying to re-init the interface
  			 * is the wrong thing to do as we've already
  			 * torn down much of our state.  There's
  			 * probably a better way to deal with this.
  			 */
  			if (!sc->sc_invalid) {
  				mwl_init_locked(sc);	/* XXX lose error */
  				startall = 1;
  			}
  		} else
  			mwl_stop_locked(ifp, 1);
  		MWL_UNLOCK(sc);
  		if (startall)
  			ieee80211_start_all(ic);
  		break;
  	case SIOCGMVSTATS:
  		mwl_hal_gethwstats(sc->sc_mh, &sc->sc_stats.hw_stats);
  		/* NB: embed these numbers to get a consistent view */
  		sc->sc_stats.mst_tx_packets = ifp->if_opackets;
  		sc->sc_stats.mst_rx_packets = ifp->if_ipackets;
  		/*
  		 * NB: Drop the softc lock in case of a page fault;
  		 * we'll accept any potential inconsisentcy in the
  		 * statistics.  The alternative is to copy the data
  		 * to a local structure.
  		 */
  		return copyout(&sc->sc_stats,
  				ifr->ifr_data, sizeof (sc->sc_stats));
  #ifdef MWL_DIAGAPI
  	case SIOCGMVDIAG:
  		/* XXX check privs */
  		return mwl_ioctl_diag(sc, (struct mwl_diag *) ifr);
  	case SIOCGMVRESET:
  		/* XXX check privs */
  		MWL_LOCK(sc);
  		error = mwl_ioctl_reset(sc,(struct mwl_diag *) ifr); 
  		MWL_UNLOCK(sc);
  		break;
  #endif /* MWL_DIAGAPI */
  	case SIOCGIFMEDIA:
  		error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd);
  		break;
  	case SIOCGIFADDR:
  		error = ether_ioctl(ifp, cmd, data);
  		break;
  	default:
  		error = EINVAL;
  		break;
  	}
  	return error;
  #undef IS_RUNNING
  }
  
  #ifdef	MWL_DEBUG
  static int
  mwl_sysctl_debug(SYSCTL_HANDLER_ARGS)
  {
  	struct mwl_softc *sc = arg1;
  	int debug, error;
  
  	debug = sc->sc_debug | (mwl_hal_getdebug(sc->sc_mh) << 24);
  	error = sysctl_handle_int(oidp, &debug, 0, req);
  	if (error || !req->newptr)
  		return error;
  	mwl_hal_setdebug(sc->sc_mh, debug >> 24);
  	sc->sc_debug = debug & 0x00ffffff;
  	return 0;
  }
  #endif /* MWL_DEBUG */
  
  static void
  mwl_sysctlattach(struct mwl_softc *sc)
  {
  #ifdef	MWL_DEBUG
  	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
  	struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
  
  	sc->sc_debug = mwl_debug;
  	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
  		"debug", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
  		mwl_sysctl_debug, "I", "control debugging printfs");
  #endif
  }
  
  /*
   * Announce various information on device/driver attach.
   */
  static void
  mwl_announce(struct mwl_softc *sc)
  {
  	struct ifnet *ifp = sc->sc_ifp;
  
  	if_printf(ifp, "Rev A%d hardware, v%d.%d.%d.%d firmware (regioncode %d)\n",
  		sc->sc_hwspecs.hwVersion,
  		(sc->sc_hwspecs.fwReleaseNumber>>24) & 0xff,
  		(sc->sc_hwspecs.fwReleaseNumber>>16) & 0xff,
  		(sc->sc_hwspecs.fwReleaseNumber>>8) & 0xff,
  		(sc->sc_hwspecs.fwReleaseNumber>>0) & 0xff,
  		sc->sc_hwspecs.regionCode);
  	sc->sc_fwrelease = sc->sc_hwspecs.fwReleaseNumber;
  
  	if (bootverbose) {
  		int i;
  		for (i = 0; i <= WME_AC_VO; i++) {
  			struct mwl_txq *txq = sc->sc_ac2q[i];
  			if_printf(ifp, "Use hw queue %u for %s traffic\n",
  				txq->qnum, ieee80211_wme_acnames[i]);
  		}
  	}
  	if (bootverbose || mwl_rxdesc != MWL_RXDESC)
  		if_printf(ifp, "using %u rx descriptors\n", mwl_rxdesc);
  	if (bootverbose || mwl_rxbuf != MWL_RXBUF)
  		if_printf(ifp, "using %u rx buffers\n", mwl_rxbuf);
  	if (bootverbose || mwl_txbuf != MWL_TXBUF)
  		if_printf(ifp, "using %u tx buffers\n", mwl_txbuf);
  	if (bootverbose && mwl_hal_ismbsscapable(sc->sc_mh))
  		if_printf(ifp, "multi-bss support\n");
  #ifdef MWL_TX_NODROP
  	if (bootverbose)
  		if_printf(ifp, "no tx drop\n");
  #endif
  }
  