diff options
| author | Sam Leffler <sam@FreeBSD.org> | 2009-06-01 18:07:01 +0000 |
|---|---|---|
| committer | Sam Leffler <sam@FreeBSD.org> | 2009-06-01 18:07:01 +0000 |
| commit | cf4c5a533126ca1ddb1f070af73f8f53b9e77fd4 (patch) | |
| tree | 4aa57b01fcce3ef5523174cf4075c853234998b3 /sys/dev/mwl | |
| parent | a43cee7009182f029996c7c7c33c812385e8fe41 (diff) | |
Notes
Diffstat (limited to 'sys/dev/mwl')
| -rw-r--r-- | sys/dev/mwl/if_mwl.c | 4990 | ||||
| -rw-r--r-- | sys/dev/mwl/if_mwl_pci.c | 316 | ||||
| -rw-r--r-- | sys/dev/mwl/if_mwlioctl.h | 136 | ||||
| -rw-r--r-- | sys/dev/mwl/if_mwlvar.h | 354 | ||||
| -rw-r--r-- | sys/dev/mwl/mwldiag.h | 108 | ||||
| -rw-r--r-- | sys/dev/mwl/mwlhal.c | 2703 | ||||
| -rw-r--r-- | sys/dev/mwl/mwlhal.h | 666 | ||||
| -rw-r--r-- | sys/dev/mwl/mwlreg.h | 1352 |
8 files changed, 10625 insertions, 0 deletions
diff --git a/sys/dev/mwl/if_mwl.c b/sys/dev/mwl/if_mwl.c new file mode 100644 index 0000000000000..ae13c9d9168ca --- /dev/null +++ b/sys/dev/mwl/if_mwl.c @@ -0,0 +1,4990 @@ +/*- + * 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$"); + +/* + * Driver for the Marvell 88W8363 Wireless LAN controller. + */ + +#include "opt_inet.h" +#include "opt_mwl.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 name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[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(struct ifnet *); +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 void mwl_recv_action(struct ieee80211_node *, + 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 + +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, "can not if_alloc()\n"); + return ENOSPC; + } + ic = ifp->if_l2com; + + /* 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); + + 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_watchdog = mwl_watchdog; + ifp->if_ioctl = mwl_ioctl; + ifp->if_init = mwl_init; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + 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_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: + 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); + mwl_dma_cleanup(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, int 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: + 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) { + 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_STA: + /* + * Setup sta db entry for local address. + */ + mwl_localstadb(vap); + if (vap->iv_opmode == IEEE80211_M_HOSTAP) + 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_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) + 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++; + + 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; 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; + + 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, 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_ext & IEEE80211_FEXT_PUREN) == 0) + rate = vap->iv_txparms[IEEE80211_MODE_11A].mgmtrate; + else if (mode == IEEE80211_MODE_11NG && + (vap->iv_flags_ext & IEEE80211_FEXT_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); + mwl_hal_setrateadaptmode(mh, ic->ic_regdomain.location == 'O'); + mwl_hal_setoptimizationlevel(mh, + (ic->ic_flags & IEEE80211_F_BURST) != 0); + + mwl_hal_setregioncode(mh, mwl_map2regioncode(&ic->ic_regdomain)); + + 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 + ; + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + mwl_hal_intrset(mh, sc->sc_imask); + + 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; + ifp->if_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_ext & + (IEEE80211_FEXT_SHORTGI20|IEEE80211_FEXT_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_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 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; + } 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++; + } + MWL_RXFREE_INIT(sc); + 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); + MWL_RXFREE_DESTROY(sc); +} + +static int +mwl_dma_setup(struct mwl_softc *sc) +{ + int error, i; + + error = mwl_rxdma_setup(sc); + if (error != 0) + 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_DONTWAIT, 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 0 + if ((ds->Rate & 0x80) && (ds->HtSig2 & 0x8)) +#else + if (ni->ni_flags & IEEE80211_NODE_HT) +#endif + 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_DONTWAIT, MWL_TXDESC); +#else + m = m_defrag(m0, M_DONTWAIT); +#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 & 0x80) { /* 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; + + 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 != NULL && /* XXX temp dwds WAR */ + 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++; + ifp->if_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; + ifp->if_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; + ifp->if_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 void +mwl_recv_action(struct ieee80211_node *ni, 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)); + } else + sc->sc_recv_action(ni, 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 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(sc->sc_mh, + 1/* XXX immediate*/, ni->ni_macaddr, tap->txa_ac, + 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, + &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, AC %d\n", + __func__, tap->txa_ac); + sc->sc_stats.mst_addba_nostream++; + return 0; + } + if (code == IEEE80211_STATUS_SUCCESS) { + 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(sc->sc_mh, bas->bastream, + bufsiz, bufsiz-1, 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 AC %d " + "htparam 0x%x\n", __func__, error, bufsiz, + tap->txa_ac, ni->ni_htparam); + sc->sc_stats.mst_bacreate_failed++; + return 0; + } + /* NB: cache txq to avoid ptr indirect */ + mwl_bastream_setup(bas, tap->txa_ac, bas->bastream->txq); + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: bastream %p assigned to txq %d AC %d bufsiz %d " + "htparam 0x%x\n", __func__, bas->bastream, + bas->txq, tap->txa_ac, 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_ext & IEEE80211_FEXT_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); +} + +/* + * 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; + int error; + + switch (vap->iv_opmode) { + case IEEE80211_M_STA: + bss = vap->iv_bss; + error = mwl_hal_newstation(hvap, vap->iv_myaddr, + 0, 0, NULL, bss->ni_flags & IEEE80211_NODE_QOS, + 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: + 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) + 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 state. + */ + mwl_localstadb(vap); + switch (vap->iv_opmode) { + case IEEE80211_M_HOSTAP: + 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); + 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 */ + } +bad: + return error; +} + +/* + * 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; +} + +/* + * 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); + /* + * Craft station database entry for station. + * NB: use host byte order here, the hal handles byte swapping. + */ + 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_ext & IEEE80211_FEXT_SHORTGI40) == 0) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI40; + if ((vap->iv_flags_ext & IEEE80211_FEXT_SHORTGI20) == 0) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI20; + if (ni->ni_chw != 40) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_CHWIDTH40; + } + error = mwl_peerstadb(ni, aid, mn->mn_staid, &pi); + 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_reset(&sc->sc_timer, sc->sc_ageinterval*hz, + mwl_agestations, sc); +} + +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(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + + 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 +} diff --git a/sys/dev/mwl/if_mwl_pci.c b/sys/dev/mwl/if_mwl_pci.c new file mode 100644 index 0000000000000..2fb09227d0bde --- /dev/null +++ b/sys/dev/mwl/if_mwl_pci.c @@ -0,0 +1,316 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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> +#ifdef __FreeBSD__ +__FBSDID("$FreeBSD$"); +#endif + +/* + * PCI front-end for the Marvell Wireless LAN controller driver. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/errno.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/bus.h> +#include <sys/rman.h> + +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_arp.h> + +#include <net80211/ieee80211_var.h> + +#include <dev/mwl/if_mwlvar.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +/* + * PCI glue. + */ + +struct mwl_pci_softc { + struct mwl_softc sc_sc; + struct resource *sc_sr0; /* BAR0 memory resource */ + struct resource *sc_sr1; /* BAR1 memory resource */ + struct resource *sc_irq; /* irq resource */ + void *sc_ih; /* interrupt handler */ +}; + +#define BS_BAR0 0x10 +#define BS_BAR1 0x14 + +struct mwl_pci_ident { + uint16_t vendor; + uint16_t device; + const char *name; +}; + +static const struct mwl_pci_ident mwl_pci_ids[] = { + { 0x11ab, 0x2a02, "Marvell 88W8363" }, + { 0x11ab, 0x2a03, "Marvell 88W8363" }, + { 0x11ab, 0x2a0a, "Marvell 88W8363" }, + { 0x11ab, 0x2a0b, "Marvell 88W8363" }, + { 0x11ab, 0x2a0c, "Marvell 88W8363" }, + { 0x11ab, 0x2a21, "Marvell 88W8363" }, + { 0x11ab, 0x2a24, "Marvell 88W8363" }, + + { 0, 0, NULL } +}; + +const static struct mwl_pci_ident * +mwl_pci_lookup(int vendor, int device) +{ + const struct mwl_pci_ident *ident; + + for (ident = mwl_pci_ids; ident->name != NULL; ident++) + if (vendor == ident->vendor && device == ident->device) + return ident; + return NULL; +} + +static int +mwl_pci_probe(device_t dev) +{ + const struct mwl_pci_ident *ident; + + ident = mwl_pci_lookup(pci_get_vendor(dev), pci_get_device(dev)); + if (ident != NULL) { + device_set_desc(dev, ident->name); + return BUS_PROBE_DEFAULT; + } + return ENXIO; +} + +static u_int32_t +mwl_pci_setup(device_t dev) +{ + u_int32_t cmd; + + /* + * Enable memory mapping and bus mastering. + */ + cmd = pci_read_config(dev, PCIR_COMMAND, 4); + cmd |= PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN; + pci_write_config(dev, PCIR_COMMAND, cmd, 4); + cmd = pci_read_config(dev, PCIR_COMMAND, 4); + if ((cmd & PCIM_CMD_MEMEN) == 0) { + device_printf(dev, "failed to enable memory mapping\n"); + return 0; + } + if ((cmd & PCIM_CMD_BUSMASTEREN) == 0) { + device_printf(dev, "failed to enable bus mastering\n"); + return 0; + } + return 1; +} + +static int +mwl_pci_attach(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + struct mwl_softc *sc = &psc->sc_sc; + int rid, error = ENXIO; + + sc->sc_dev = dev; + + /* + * Enable memory mapping and bus mastering. + */ + if (!mwl_pci_setup(dev)) + return 0; + /* + * Setup memory-mapping of PCI registers. + */ + rid = BS_BAR0; + psc->sc_sr0 = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (psc->sc_sr0 == NULL) { + device_printf(dev, "cannot map BAR0 register space\n"); + goto bad; + } + rid = BS_BAR1; + psc->sc_sr1 = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (psc->sc_sr1 == NULL) { + device_printf(dev, "cannot map BAR1 register space\n"); + goto bad1; + } + sc->sc_invalid = 1; + + /* + * Arrange interrupt line. + */ + rid = 0; + psc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE|RF_ACTIVE); + if (psc->sc_irq == NULL) { + device_printf(dev, "could not map interrupt\n"); + goto bad2; + } + if (bus_setup_intr(dev, psc->sc_irq, + INTR_TYPE_NET | INTR_MPSAFE, + NULL, mwl_intr, sc, &psc->sc_ih)) { + device_printf(dev, "could not establish interrupt\n"); + goto bad3; + } + + /* + * Setup DMA descriptor area. + */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ + 1, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXADDR, /* maxsize */ + MWL_TXDESC, /* nsegments */ + BUS_SPACE_MAXADDR, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &sc->sc_dmat)) { + device_printf(dev, "cannot allocate DMA tag\n"); + goto bad4; + } + + /* + * Finish off the attach. + */ + MWL_LOCK_INIT(sc); + sc->sc_io0t = rman_get_bustag(psc->sc_sr0); + sc->sc_io0h = rman_get_bushandle(psc->sc_sr0); + sc->sc_io1t = rman_get_bustag(psc->sc_sr1); + sc->sc_io1h = rman_get_bushandle(psc->sc_sr1); + if (mwl_attach(pci_get_device(dev), sc) == 0) + return (0); + + MWL_LOCK_DESTROY(sc); + bus_dma_tag_destroy(sc->sc_dmat); +bad4: + bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih); +bad3: + bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq); +bad2: + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR1, psc->sc_sr1); +bad1: + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR0, psc->sc_sr0); +bad: + return (error); +} + +static int +mwl_pci_detach(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + struct mwl_softc *sc = &psc->sc_sc; + + /* check if device was removed */ + sc->sc_invalid = !bus_child_present(dev); + + mwl_detach(sc); + + bus_generic_detach(dev); + bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq); + + bus_dma_tag_destroy(sc->sc_dmat); + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR1, psc->sc_sr1); + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR0, psc->sc_sr0); + + MWL_LOCK_DESTROY(sc); + + return (0); +} + +static int +mwl_pci_shutdown(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + mwl_shutdown(&psc->sc_sc); + return (0); +} + +static int +mwl_pci_suspend(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + mwl_suspend(&psc->sc_sc); + + return (0); +} + +static int +mwl_pci_resume(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + if (!mwl_pci_setup(dev)) + return ENXIO; + + mwl_resume(&psc->sc_sc); + + return (0); +} + +static device_method_t mwl_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mwl_pci_probe), + DEVMETHOD(device_attach, mwl_pci_attach), + DEVMETHOD(device_detach, mwl_pci_detach), + DEVMETHOD(device_shutdown, mwl_pci_shutdown), + DEVMETHOD(device_suspend, mwl_pci_suspend), + DEVMETHOD(device_resume, mwl_pci_resume), + + { 0,0 } +}; +static driver_t mwl_pci_driver = { + "mwl", + mwl_pci_methods, + sizeof (struct mwl_pci_softc) +}; +static devclass_t mwl_devclass; +DRIVER_MODULE(mwl, pci, mwl_pci_driver, mwl_devclass, 0, 0); +MODULE_VERSION(mwl, 1); +MODULE_DEPEND(mwl, wlan, 1, 1, 1); /* 802.11 media layer */ +MODULE_DEPEND(mwl, mwlfw_fw, 1, 1, 1); /* firmware */ diff --git a/sys/dev/mwl/if_mwlioctl.h b/sys/dev/mwl/if_mwlioctl.h new file mode 100644 index 0000000000000..e0760194c42a1 --- /dev/null +++ b/sys/dev/mwl/if_mwlioctl.h @@ -0,0 +1,136 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +/* + * Ioctl-related defintions for the Marvel Wireless LAN controller driver. + */ +#ifndef _DEV_MWL_MVIOCTL_H +#define _DEV_MWL_MVIOCTL_H + +struct mwl_stats { + struct mwl_hal_hwstats hw_stats; /* XXX tied to h/w defs */ + uint32_t mst_failure; /* generic hardware failure */ + uint32_t mst_rx_badtkipicv; + uint32_t mst_tx_discard; + uint32_t mst_tx_qstop; + uint32_t mst_tx_encap; + uint32_t mst_tx_mgmt; + uint32_t mst_rx_nombuf; + uint32_t mst_rx_busdma; + uint32_t mst_rx_tooshort; + uint32_t mst_tx_busdma; + uint32_t mst_tx_linear; + uint32_t mst_tx_nombuf; + uint32_t mst_tx_nodata; + uint32_t mst_tx_shortpre; + uint32_t mst_tx_retries; + uint32_t mst_tx_mretries; + uint32_t mst_tx_linkerror; + uint32_t mst_tx_xretries; + uint32_t mst_tx_aging; + uint32_t mst_tx_qdrop; + uint32_t mst_ff_txerr; + uint32_t mst_watchdog; + uint32_t mst_tx_packets; + uint32_t mst_rx_packets; + int8_t mst_rx_rssi; + int8_t mst_rx_noise; + uint8_t mst_tx_rate; + uint32_t mst_ant_tx[4]; + uint32_t mst_ant_rx[4]; + uint32_t mst_tx_tso; + uint32_t mst_tso_badeth; + uint32_t mst_tso_nohdr; + uint32_t mst_tso_badsplit; + uint32_t mst_rx_crypto; + uint32_t mst_rx_tkipmic; + uint32_t mst_rx_nodmabuf; + uint32_t mst_tx_noheadroom; + uint32_t mst_tx_badframetype; + uint32_t mst_ampdu_nostream; + uint32_t mst_ampdu_reject; + uint32_t mst_addba_nostream; + uint32_t mst_bacreate_failed; + uint32_t mst_bawatchdog; + uint32_t mst_radardetect; + uint32_t mst_rx_dmabufmissing; + uint32_t mst_bawatchdog_notfound; + uint32_t mst_bawatchdog_empty; + uint32_t mst_bawatchdog_failed; + uint32_t mst_rxbuf_failed; + uint32_t mst_pad[31]; +}; + +#define SIOCGMVSTATS _IOWR('i', 137, struct ifreq) + +/* + * Radio capture format. + */ +#define MWL_RX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + 0) + +struct mwl_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + u_int8_t wr_flags; + u_int8_t wr_rate; + u_int16_t wr_chan_freq; + u_int16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; + u_int8_t wr_antenna; +}; + +#define MWL_TX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + 0) + +struct mwl_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + u_int8_t wt_flags; + u_int8_t wt_rate; + u_int16_t wt_chan_freq; + u_int16_t wt_chan_flags; + u_int8_t wt_txpower; + u_int8_t wt_antenna; +}; + +#endif /* _DEV_MWL_MVIOCTL_H */ diff --git a/sys/dev/mwl/if_mwlvar.h b/sys/dev/mwl/if_mwlvar.h new file mode 100644 index 0000000000000..6730ef6d51bc7 --- /dev/null +++ b/sys/dev/mwl/if_mwlvar.h @@ -0,0 +1,354 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +/* + * Definitions for the Marvell 88W8363 Wireless LAN controller. + */ +#ifndef _DEV_MWL_MVVAR_H +#define _DEV_MWL_MVVAR_H + +#include <sys/endian.h> +#include <net80211/ieee80211_radiotap.h> +#include <dev/mwl/mwlhal.h> +#include <dev/mwl/mwlreg.h> +#include <dev/mwl/if_mwlioctl.h> + +#ifndef MWL_TXBUF +#define MWL_TXBUF 256 /* number of TX descriptors/buffers */ +#endif +#ifndef MWL_RXDESC +#define MWL_RXDESC 256 /* number of RX descriptors */ +#endif +#ifndef MWL_RXBUF +#define MWL_RXBUF ((5*MWL_RXDESC)/2)/* number of RX dma buffers */ +#endif +#ifndef MWL_MAXBA +#define MWL_MAXBA 2 /* max BA streams/sta */ +#endif + +#ifdef MWL_SGDMA_SUPPORT +#define MWL_TXDESC 6 /* max tx descriptors/segments */ +#else +#define MWL_TXDESC 1 /* max tx descriptors/segments */ +#endif +#ifndef MWL_AGGR_SIZE +#define MWL_AGGR_SIZE 3839 /* max tx agregation size */ +#endif +#define MWL_AGEINTERVAL 1 /* poke f/w every sec to age q's */ +#define MWL_MAXSTAID 64 /* max of 64 stations */ + +/* + * DMA state for tx/rx descriptors. + */ + +/* + * Software backed version of tx/rx descriptors. We keep + * the software state out of the h/w descriptor structure + * so that may be allocated in uncached memory w/o paying + * performance hit. + */ +struct mwl_txbuf { + STAILQ_ENTRY(mwl_txbuf) bf_list; + void *bf_desc; /* h/w descriptor */ + bus_addr_t bf_daddr; /* physical addr of desc */ + bus_dmamap_t bf_dmamap; /* DMA map for descriptors */ + int bf_nseg; + bus_dma_segment_t bf_segs[MWL_TXDESC]; + struct mbuf *bf_m; + struct ieee80211_node *bf_node; + struct mwl_txq *bf_txq; /* backpointer to tx q/ring */ +}; +typedef STAILQ_HEAD(, mwl_txbuf) mwl_txbufhead; + +/* + * Common "base class" for tx/rx descriptor resources + * allocated using the bus dma api. + */ +struct mwl_descdma { + const char* dd_name; + void *dd_desc; /* descriptors */ + bus_addr_t dd_desc_paddr; /* physical addr of dd_desc */ + bus_size_t dd_desc_len; /* size of dd_desc */ + bus_dma_segment_t dd_dseg; + int dd_dnseg; /* number of segments */ + bus_dma_tag_t dd_dmat; /* bus DMA tag */ + bus_dmamap_t dd_dmamap; /* DMA map for descriptors */ + void *dd_bufptr; /* associated buffers */ +}; + +/* + * TX/RX ring definitions. There are 4 tx rings, one + * per AC, and 1 rx ring. Note carefully that transmit + * descriptors are treated as a contiguous chunk and the + * firmware pre-fetches descriptors. This means that we + * must preserve order when moving descriptors between + * the active+free lists; otherwise we may stall transmit. + */ +struct mwl_txq { + struct mwl_descdma dma; /* bus dma resources */ + struct mtx lock; /* tx q lock */ + char name[12]; /* e.g. "mwl0_txq4" */ + int qnum; /* f/w q number */ + int txpri; /* f/w tx priority */ + int nfree; /* # buffers on free list */ + mwl_txbufhead free; /* queue of free buffers */ + mwl_txbufhead active; /* queue of active buffers */ +}; + +#define MWL_TXQ_LOCK_INIT(_sc, _tq) do { \ + snprintf((_tq)->name, sizeof((_tq)->name), "%s_txq%u", \ + device_get_nameunit((_sc)->sc_dev), (_tq)->qnum); \ + mtx_init(&(_tq)->lock, (_tq)->name, NULL, MTX_DEF); \ +} while (0) +#define MWL_TXQ_LOCK_DESTROY(_tq) mtx_destroy(&(_tq)->lock) +#define MWL_TXQ_LOCK(_tq) mtx_lock(&(_tq)->lock) +#define MWL_TXQ_UNLOCK(_tq) mtx_unlock(&(_tq)->lock) +#define MWL_TXQ_LOCK_ASSERT(_tq) mtx_assert(&(_tq)->lock, MA_OWNED) + +#define MWL_TXDESC_SYNC(txq, ds, how) do { \ + bus_dmamap_sync((txq)->dma.dd_dmat, (txq)->dma.dd_dmamap, how); \ +} while(0) + +/* + * RX dma buffers that are not in use are kept on a list. + */ +struct mwl_jumbo { + SLIST_ENTRY(mwl_jumbo) next; +}; +typedef SLIST_HEAD(, mwl_jumbo) mwl_jumbohead; + +#define MWL_JUMBO_DATA2BUF(_data) ((struct mwl_jumbo *)(_data)) +#define MWL_JUMBO_BUF2DATA(_buf) ((uint8_t *)(_buf)) +#define MWL_JUMBO_OFFSET(_sc, _data) \ + (((const uint8_t *)(_data)) - (const uint8_t *)((_sc)->sc_rxmem)) +#define MWL_JUMBO_DMA_ADDR(_sc, _data) \ + ((_sc)->sc_rxmem_paddr + MWL_JUMBO_OFFSET(_sc, _data)) + +struct mwl_rxbuf { + STAILQ_ENTRY(mwl_rxbuf) bf_list; + void *bf_desc; /* h/w descriptor */ + bus_addr_t bf_daddr; /* physical addr of desc */ + uint8_t *bf_data; /* rx data area */ +}; +typedef STAILQ_HEAD(, mwl_rxbuf) mwl_rxbufhead; + +#define MWL_RXDESC_SYNC(sc, ds, how) do { \ + bus_dmamap_sync((sc)->sc_rxdma.dd_dmat, (sc)->sc_rxdma.dd_dmamap, how);\ +} while (0) + +/* + * BA stream state. One of these is setup for each stream + * allocated/created for use. We pre-allocate the h/w stream + * before sending ADDBA request then complete the setup when + * get ADDBA response (success). The completed state is setup + * to optimize the fast path in mwl_txstart--we precalculate + * the QoS control bits in the outbound frame and use those + * to identify which BA stream to use (assigning the h/w q to + * the TxPriority field of the descriptor). + * + * NB: Each station may have at most MWL_MAXBA streams at one time. + */ +struct mwl_bastate { + uint16_t qos; /* QoS ctl for BA stream */ + uint8_t txq; /* h/w q for BA stream */ + const MWL_HAL_BASTREAM *bastream; /* A-MPDU BA stream */ +}; + +static __inline__ void +mwl_bastream_setup(struct mwl_bastate *bas, int ac, int txq) +{ + bas->txq = txq; + bas->qos = htole16(WME_AC_TO_TID(ac) | IEEE80211_QOS_ACKPOLICY_BA); +} + +static __inline__ void +mwl_bastream_free(struct mwl_bastate *bas) +{ + bas->qos = 0; + bas->bastream = NULL; + /* NB: don't need to clear txq */ +} + +/* + * Check the QoS control bits from an outbound frame against the + * value calculated when a BA stream is setup (above). We need + * to match the TID and also the ACK policy so we only match AMPDU + * frames. The bits from the frame are assumed in network byte + * order, hence the potential byte swap. + */ +static __inline__ int +mwl_bastream_match(const struct mwl_bastate *bas, uint16_t qos) +{ + return (qos & htole16(IEEE80211_QOS_TID|IEEE80211_QOS_ACKPOLICY)) == + bas->qos; +} + +/* driver-specific node state */ +struct mwl_node { + struct ieee80211_node mn_node; /* base class */ + struct mwl_ant_info mn_ai; /* antenna info */ + uint32_t mn_avgrssi; /* average rssi over all rx frames */ + uint16_t mn_staid; /* firmware station id */ + struct mwl_bastate mn_ba[MWL_MAXBA]; + struct mwl_hal_vap *mn_hvap; /* hal vap handle */ +}; +#define MWL_NODE(ni) ((struct mwl_node *)(ni)) +#define MWL_NODE_CONST(ni) ((const struct mwl_node *)(ni)) + +/* + * Driver-specific vap state. + */ +struct mwl_vap { + struct ieee80211vap mv_vap; /* base class */ + struct mwl_hal_vap *mv_hvap; /* hal vap handle */ + struct mwl_hal_vap *mv_ap_hvap; /* ap hal vap handle for wds */ + uint16_t mv_last_ps_sta; /* last count of ps sta's */ + uint16_t mv_eapolformat; /* fixed tx rate for EAPOL */ + int (*mv_newstate)(struct ieee80211vap *, + enum ieee80211_state, int); + int (*mv_set_tim)(struct ieee80211_node *, int); +}; +#define MWL_VAP(vap) ((struct mwl_vap *)(vap)) +#define MWL_VAP_CONST(vap) ((const struct mwl_vap *)(vap)) + +struct mwl_softc { + struct ifnet *sc_ifp; /* interface common */ + struct mwl_stats sc_stats; /* interface statistics */ + int sc_debug; + device_t sc_dev; + bus_dma_tag_t sc_dmat; /* bus DMA tag */ + bus_space_handle_t sc_io0h; /* BAR 0 */ + bus_space_tag_t sc_io0t; + bus_space_handle_t sc_io1h; /* BAR 1 */ + bus_space_tag_t sc_io1t; + struct mtx sc_mtx; /* master lock (recursive) */ + struct taskqueue *sc_tq; /* private task queue */ + unsigned int sc_invalid : 1, /* disable hardware accesses */ + sc_recvsetup:1, /* recv setup */ + sc_csapending:1,/* 11h channel switch pending */ + sc_radarena : 1,/* radar detection enabled */ + sc_rxblocked: 1;/* rx waiting for dma buffers */ + + struct mwl_hal *sc_mh; /* h/w access layer */ + struct mwl_hal_vap *sc_hvap; /* hal vap handle */ + struct mwl_hal_hwspec sc_hwspecs; /* h/w capabilities */ + uint32_t sc_fwrelease; /* release # of loaded f/w */ + struct mwl_hal_txrxdma sc_hwdma; /* h/w dma setup */ + uint32_t sc_imask; /* interrupt mask copy */ + enum ieee80211_phymode sc_curmode; + u_int16_t sc_curaid; /* current association id */ + u_int8_t sc_curbssid[IEEE80211_ADDR_LEN]; + MWL_HAL_CHANNEL sc_curchan; + MWL_HAL_TXRATE_HANDLING sc_txratehandling; + u_int16_t sc_rxantenna; /* rx antenna */ + u_int16_t sc_txantenna; /* tx antenna */ + uint8_t sc_napvaps; /* # ap mode vaps */ + uint8_t sc_nwdsvaps; /* # wds mode vaps */ + uint8_t sc_nstavaps; /* # sta mode vaps */ + uint8_t sc_nbssid0; /* # vap's using base mac */ + uint32_t sc_bssidmask; /* bssid mask */ + + void (*sc_recv_mgmt)(struct ieee80211com *, + struct mbuf *, + struct ieee80211_node *, + int, int, int, u_int32_t); + int (*sc_newstate)(struct ieee80211com *, + enum ieee80211_state, int); + void (*sc_node_cleanup)(struct ieee80211_node *); + void (*sc_node_drain)(struct ieee80211_node *); + void (*sc_recv_action)(struct ieee80211_node *, + const uint8_t *, const uint8_t *); + int (*sc_addba_request)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, + int dialogtoken, int baparamset, + int batimeout); + int (*sc_addba_response)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, + int status, int baparamset, + int batimeout); + void (*sc_addba_stop)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *); + + struct mwl_descdma sc_rxdma; /* rx bus dma resources */ + mwl_rxbufhead sc_rxbuf; /* rx buffers */ + struct mwl_rxbuf *sc_rxnext; /* next rx buffer to process */ + struct task sc_rxtask; /* rx int processing */ + void *sc_rxmem; /* rx dma buffer pool */ + bus_dma_tag_t sc_rxdmat; /* rx bus DMA tag */ + bus_size_t sc_rxmemsize; /* rx dma buffer pool size */ + bus_dmamap_t sc_rxmap; /* map for rx dma buffers */ + bus_addr_t sc_rxmem_paddr; /* physical addr of sc_rxmem */ + mwl_jumbohead sc_rxfree; /* list of free dma buffers */ + int sc_nrxfree; /* # buffers on rx free list */ + struct mtx sc_rxlock; /* lock on sc_rxfree */ + + struct mwl_txq sc_txq[MWL_NUM_TX_QUEUES]; + struct mwl_txq *sc_ac2q[5]; /* WME AC -> h/w q map */ + struct mbuf *sc_aggrq; /* aggregation q */ + struct task sc_txtask; /* tx int processing */ + struct task sc_bawatchdogtask;/* BA watchdog processing */ + + struct task sc_radartask; /* radar detect processing */ + struct task sc_chanswitchtask;/* chan switch processing */ + + uint8_t sc_staid[MWL_MAXSTAID/NBBY]; + int sc_ageinterval; + struct callout sc_timer; /* periodic work */ + + struct mwl_tx_radiotap_header sc_tx_th; + struct mwl_rx_radiotap_header sc_rx_th; +}; + +#define MWL_LOCK_INIT(_sc) \ + mtx_init(&(_sc)->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ + NULL, MTX_DEF | MTX_RECURSE) +#define MWL_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) +#define MWL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define MWL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define MWL_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) + +#define MWL_RXFREE_INIT(_sc) \ + mtx_init(&(_sc)->sc_rxlock, device_get_nameunit((_sc)->sc_dev), \ + NULL, MTX_DEF) +#define MWL_RXFREE_DESTROY(_sc) mtx_destroy(&(_sc)->sc_rxlock) +#define MWL_RXFREE_LOCK(_sc) mtx_lock(&(_sc)->sc_rxlock) +#define MWL_RXFREE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_rxlock) +#define MWL_RXFREE_ASSERT(_sc) mtx_assert(&(_sc)->sc_rxlock, MA_OWNED) + +int mwl_attach(u_int16_t, struct mwl_softc *); +int mwl_detach(struct mwl_softc *); +void mwl_resume(struct mwl_softc *); +void mwl_suspend(struct mwl_softc *); +void mwl_shutdown(void *); +void mwl_intr(void *); + +#endif /* _DEV_MWL_MVVAR_H */ diff --git a/sys/dev/mwl/mwldiag.h b/sys/dev/mwl/mwldiag.h new file mode 100644 index 0000000000000..65c8eb5941ddb --- /dev/null +++ b/sys/dev/mwl/mwldiag.h @@ -0,0 +1,108 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +#ifndef _MWL_DIAG_H_ +#define _MWL_DIAG_H_ +/* + * Diagnostic interface. This is an open-ended interface that + * is opaque to applications. Diagnostic programs use this to + * retrieve internal data structures, etc. There is no guarantee + * that calling conventions for calls other than MWL_DIAG_REVS + * are stable between HAL releases; a diagnostic application must + * use the HAL revision information to deal with ABI/API differences. + * + * NB: do not renumber these, certain codes are publicly used. + */ +enum { + MWL_DIAG_CMD_REVS = 0, /* MAC/PHY/Radio revs */ + MWL_DIAG_CMD_REGS = 1, /* Registers */ + MWL_DIAG_CMD_HOSTCMD = 2, /* issue arbitrary cmd */ + MWL_DIAG_CMD_FWLOAD = 3, /* load firmware */ +}; + +/* + * Device revision information. + */ +typedef struct { + uint16_t mh_devid; /* PCI device ID */ + uint16_t mh_subvendorid; /* PCI subvendor ID */ + uint16_t mh_macRev; /* MAC revision */ + uint16_t mh_phyRev; /* PHY revision */ +} MWL_DIAG_REVS; + +typedef struct { + uint16_t start; /* first register */ + uint16_t end; /* ending register or zero */ +} MWL_DIAG_REGRANGE; + +/* + * Registers are mapped into virtual banks; the hal converts + * r/w operations through the diag api to host cmds as required. + * + * NB: register offsets are 16-bits and we need to avoid real + * register mappings in BAR1. + */ +#define MWL_DIAG_BASE_MAC 0xa000 +#define MWL_DIAG_ISMAC(r) \ + (MWL_DIAG_BASE_MAC <= (r) && (r) < (MWL_DIAG_BASE_MAC+0x1000)) +#define MWL_DIAG_BASE_BB 0xe000 +#define MWL_DIAG_ISBB(r) \ + (MWL_DIAG_BASE_BB <= (r) && (r) < (MWL_DIAG_BASE_BB+0x1000)) +#define MWL_DIAG_BASE_RF 0xf000 +#define MWL_DIAG_ISRF(r) \ + (MWL_DIAG_BASE_RF <= (r) && (r) < (MWL_DIAG_BASE_RF+0x1000)) + +/* + * Firmware download + */ +typedef struct { + uint32_t opmode; /* operating mode */ + uint32_t signature; /* f/w ready signature */ + char name[1]; /* variable length pathname */ +} MWL_DIAG_FWLOAD; + +struct mwl_diag { + char md_name[IFNAMSIZ]; /* if name, e.g. "mv0" */ + uint16_t md_id; +#define MWL_DIAG_DYN 0x8000 /* allocate buffer in caller */ +#define MWL_DIAG_IN 0x4000 /* copy in parameters */ +#define MWL_DIAG_OUT 0x0000 /* copy out results (always) */ +#define MWL_DIAG_ID 0x0fff + uint16_t md_in_size; /* pack to fit, yech */ + void * md_in_data; + void * md_out_data; + u_int md_out_size; + +}; +#define SIOCGMVDIAG _IOWR('i', 138, struct mwl_diag) +#define SIOCGMVRESET _IOW('i', 139, struct mwl_diag) +#endif /* _MWL_DIAG_H_ */ diff --git a/sys/dev/mwl/mwlhal.c b/sys/dev/mwl/mwlhal.c new file mode 100644 index 0000000000000..0ffa0b1bc1978 --- /dev/null +++ b/sys/dev/mwl/mwlhal.c @@ -0,0 +1,2703 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sysctl.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/bus.h> +#include <sys/endian.h> + +#include <sys/linker.h> +#include <sys/firmware.h> + +#include <machine/bus.h> + +#include <dev/mwl/mwlhal.h> +#include <dev/mwl/mwlreg.h> + +#include <sys/socket.h> +#include <sys/sockio.h> +#include <net/if.h> +#include <dev/mwl/mwldiag.h> + +#define MWLHAL_DEBUG /* debug msgs */ + +typedef enum { + WL_ANTENNAMODE_RX = 0xffff, + WL_ANTENNAMODE_TX = 2, +} wlantennamode_e; + +typedef enum { + WL_TX_POWERLEVEL_LOW = 5, + WL_TX_POWERLEVEL_MEDIUM = 10, + WL_TX_POWERLEVEL_HIGH = 15, +} wltxpowerlevel_e; + +#define MWL_CMDBUF_SIZE 0x4000 /* size of f/w command buffer */ +#define MWL_BASTREAMS_MAX 7 /* max BA streams (NB: fw >3.3.5.9) */ +#define MWL_BAQID_MAX 8 /* max BA Q id's (NB: fw >3.3.5.9) */ +#define MWL_MBSS_AP_MAX 8 /* max ap vap's */ +#define MWL_MBSS_STA_MAX 24 /* max station/client vap's */ +#define MWL_MBSS_MAX (MWL_MBSS_AP_MAX+MWL_MBSS_STA_MAX) + +/* + * BA stream -> queue ID mapping + * + * The first 2 streams map to h/w; the remaining streams are + * implemented in firmware. + */ +static const int ba2qid[MWL_BASTREAMS_MAX] = { + 5, 6 /* h/w supported */ +#if MWL_BASTREAMS_MAX == 7 + , 7, 0, 1, 2, 3 /* f/w supported */ +#endif +}; +static int qid2ba[MWL_BAQID_MAX]; + +#define IEEE80211_ADDR_LEN 6 /* XXX */ +#define IEEE80211_ADDR_COPY(_dst, _src) \ + memcpy(_dst, _src, IEEE80211_ADDR_LEN) +#define IEEE80211_ADDR_EQ(_dst, _src) \ + (memcmp(_dst, _src, IEEE80211_ADDR_LEN) == 0) + +#define _CMD_SETUP(pCmd, type, cmd) do { \ + pCmd = (type *)&mh->mh_cmdbuf[0]; \ + memset(pCmd, 0, sizeof(type)); \ + pCmd->CmdHdr.Cmd = htole16(cmd); \ + pCmd->CmdHdr.Length = htole16(sizeof(type)); \ +} while (0) + +#define _VCMD_SETUP(vap, pCmd, type, cmd) do { \ + _CMD_SETUP(pCmd, type, cmd); \ + pCmd->CmdHdr.MacId = vap->macid; \ +} while (0) + +#define PWTAGETRATETABLE20M 14*4 +#define PWTAGETRATETABLE40M 9*4 +#define PWTAGETRATETABLE20M_5G 35*4 +#define PWTAGETRATETABLE40M_5G 16*4 + +struct mwl_hal_bastream { + MWL_HAL_BASTREAM public; /* public state */ + uint8_t stream; /* stream # */ + uint8_t setup; /* f/w cmd sent */ + uint8_t ba_type; + uint8_t tid; + uint8_t paraminfo; + uint8_t macaddr[IEEE80211_ADDR_LEN]; +}; + +struct mwl_hal_priv; + +struct mwl_hal_vap { + struct mwl_hal_priv *mh; /* back pointer */ + uint16_t bss_type; /* f/w type */ + uint8_t vap_type; /* MWL_HAL_BSSTYPE */ + uint8_t macid; /* for passing to f/w */ + uint8_t flags; +#define MVF_RUNNING 0x01 /* BSS_START issued */ +#define MVF_STATION 0x02 /* sta db entry created */ + uint8_t mac[IEEE80211_ADDR_LEN];/* mac address */ +}; +#define MWLVAP(_vap) ((_vap)->mh) + +/* + * Per-device state. We allocate a single cmd buffer for + * submitting operations to the firmware. Access to this + * buffer (and the f/w) are single-threaded. At present + * we spin waiting for cmds to complete which is bad. Not + * sure if it's possible to submit multiple requests or + * control when we get cmd done interrupts. There's no + * documentation and no example code to indicate what can + * or cannot be done so all we can do right now is follow the + * linux driver logic. This falls apart when the f/w fails; + * the system comes to a crawl as we spin waiting for operations + * to finish. + */ +struct mwl_hal_priv { + struct mwl_hal public; /* public area */ + device_t mh_dev; + char mh_mtxname[12]; + struct mtx mh_mtx; + bus_dma_tag_t mh_dmat; /* bus DMA tag for cmd buffer */ + bus_dma_segment_t mh_seg; /* segment for cmd buffer */ + bus_dmamap_t mh_dmamap; /* DMA map for cmd buffer */ + uint16_t *mh_cmdbuf; /* f/w cmd buffer */ + bus_addr_t mh_cmdaddr; /* physaddr of cmd buffer */ + int mh_flags; +#define MHF_CALDATA 0x0001 /* cal data retrieved */ +#define MHF_FWHANG 0x0002 /* fw appears hung */ +#define MHF_MBSS 0x0004 /* mbss enabled */ + struct mwl_hal_vap mh_vaps[MWL_MBSS_MAX+1]; + int mh_bastreams; /* bit mask of available BA streams */ + int mh_regioncode; /* XXX last region code sent to fw */ + struct mwl_hal_bastream mh_streams[MWL_BASTREAMS_MAX]; + int mh_debug; + MWL_HAL_CHANNELINFO mh_20M; + MWL_HAL_CHANNELINFO mh_40M; + MWL_HAL_CHANNELINFO mh_20M_5G; + MWL_HAL_CHANNELINFO mh_40M_5G; + int mh_SDRAMSIZE_Addr; + uint32_t mh_RTSSuccesses;/* cumulative stats for read-on-clear */ + uint32_t mh_RTSFailures; + uint32_t mh_RxDuplicateFrames; + uint32_t mh_FCSErrorCount; + MWL_DIAG_REVS mh_revs; +}; +#define MWLPRIV(_mh) ((struct mwl_hal_priv *)(_mh)) + +static int mwl_hal_setmac_locked(struct mwl_hal_vap *, + const uint8_t addr[IEEE80211_ADDR_LEN]); +static int mwlExecuteCmd(struct mwl_hal_priv *, unsigned short cmd); +static int mwlGetPwrCalTable(struct mwl_hal_priv *); +#ifdef MWLHAL_DEBUG +static const char *mwlcmdname(int cmd); +static void dumpresult(struct mwl_hal_priv *, int showresult); +#endif /* MWLHAL_DEBUG */ + +SYSCTL_DECL(_hw_mwl); +SYSCTL_NODE(_hw_mwl, OID_AUTO, hal, CTLFLAG_RD, 0, "Marvell HAL parameters"); + +static __inline void +MWL_HAL_LOCK(struct mwl_hal_priv *mh) +{ + mtx_lock(&mh->mh_mtx); +} + +static __inline void +MWL_HAL_LOCK_ASSERT(struct mwl_hal_priv *mh) +{ + mtx_assert(&mh->mh_mtx, MA_OWNED); +} + +static __inline void +MWL_HAL_UNLOCK(struct mwl_hal_priv *mh) +{ + mtx_unlock(&mh->mh_mtx); +} + +static __inline uint32_t +RD4(struct mwl_hal_priv *mh, bus_size_t off) +{ + return bus_space_read_4(mh->public.mh_iot, mh->public.mh_ioh, off); +} + +static __inline void +WR4(struct mwl_hal_priv *mh, bus_size_t off, uint32_t val) +{ + bus_space_write_4(mh->public.mh_iot, mh->public.mh_ioh, off, val); +} + +static void +mwl_hal_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; +} + +/* + * Setup for communication with the device. We allocate + * a command buffer and map it for bus dma use. The pci + * device id is used to identify whether the device has + * SRAM on it (in which case f/w download must include a + * memory controller reset). All bus i/o operations happen + * in BAR 1; the driver passes in the tag and handle we need. + */ +struct mwl_hal * +mwl_hal_attach(device_t dev, uint16_t devid, + bus_space_handle_t ioh, bus_space_tag_t iot, bus_dma_tag_t tag) +{ + struct mwl_hal_priv *mh; + struct mwl_hal_vap *hvap; + int error, i; + + mh = malloc(sizeof(struct mwl_hal_priv), M_DEVBUF, M_NOWAIT | M_ZERO); + if (mh == NULL) + return NULL; + mh->mh_dev = dev; + mh->public.mh_ioh = ioh; + mh->public.mh_iot = iot; + for (i = 0; i < MWL_BASTREAMS_MAX; i++) { + mh->mh_streams[i].public.txq = ba2qid[i]; + mh->mh_streams[i].stream = i; + /* construct back-mapping while we're at it */ + if (mh->mh_streams[i].public.txq < MWL_BAQID_MAX) + qid2ba[mh->mh_streams[i].public.txq] = i; + else + device_printf(dev, "unexpected BA tx qid %d for " + "stream %d\n", mh->mh_streams[i].public.txq, i); + } + /* setup constant portion of vap state */ + /* XXX should get max ap/client vap's from f/w */ + i = 0; + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_AP; + hvap->bss_type = htole16(WL_MAC_TYPE_PRIMARY_AP); + hvap->macid = 0; + for (i++; i < MWL_MBSS_AP_MAX; i++) { + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_AP; + hvap->bss_type = htole16(WL_MAC_TYPE_SECONDARY_AP); + hvap->macid = i; + } + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_STA; + hvap->bss_type = htole16(WL_MAC_TYPE_PRIMARY_CLIENT); + hvap->macid = i; + for (i++; i < MWL_MBSS_STA_MAX; i++) { + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_STA; + hvap->bss_type = htole16(WL_MAC_TYPE_SECONDARY_CLIENT); + hvap->macid = i; + } + mh->mh_revs.mh_devid = devid; + snprintf(mh->mh_mtxname, sizeof(mh->mh_mtxname), + "%s_hal", device_get_nameunit(dev)); + mtx_init(&mh->mh_mtx, mh->mh_mtxname, NULL, MTX_DEF); + + /* + * Allocate the command buffer and map into the address + * space of the h/w. We request "coherent" memory which + * will be uncached on some architectures. + */ + error = bus_dma_tag_create(tag, /* parent */ + PAGE_SIZE, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MWL_CMDBUF_SIZE, /* maxsize */ + 1, /* nsegments */ + MWL_CMDBUF_SIZE, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &mh->mh_dmat); + if (error != 0) { + device_printf(dev, "unable to allocate memory for cmd buffer, " + "error %u\n", error); + goto fail0; + } + + /* allocate descriptors */ + error = bus_dmamap_create(mh->mh_dmat, BUS_DMA_NOWAIT, &mh->mh_dmamap); + if (error != 0) { + device_printf(dev, "unable to create dmamap for cmd buffers, " + "error %u\n", error); + goto fail0; + } + + error = bus_dmamem_alloc(mh->mh_dmat, (void**) &mh->mh_cmdbuf, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, + &mh->mh_dmamap); + if (error != 0) { + device_printf(dev, "unable to allocate memory for cmd buffer, " + "error %u\n", error); + goto fail1; + } + + error = bus_dmamap_load(mh->mh_dmat, mh->mh_dmamap, + mh->mh_cmdbuf, MWL_CMDBUF_SIZE, + mwl_hal_load_cb, &mh->mh_cmdaddr, + BUS_DMA_NOWAIT); + if (error != 0) { + device_printf(dev, "unable to load cmd buffer, error %u\n", + error); + goto fail2; + } + + /* + * Some cards have SDRAM. When loading firmware we need + * to reset the SDRAM controller prior to doing this. + * When the SDRAMSIZE is non-zero we do that work in + * mwl_hal_fwload. + */ + switch (devid) { + case 0x2a02: /* CB82 */ + case 0x2a03: /* CB85 */ + case 0x2a08: /* MC85_B1 */ + case 0x2a0b: /* CB85AP */ + case 0x2a24: + mh->mh_SDRAMSIZE_Addr = 0x40fe70b7; /* 8M SDRAM */ + break; + case 0x2a04: /* MC85 */ + mh->mh_SDRAMSIZE_Addr = 0x40fc70b7; /* 16M SDRAM */ + break; + default: + break; + } + return &mh->public; +fail2: + bus_dmamem_free(mh->mh_dmat, mh->mh_cmdbuf, mh->mh_dmamap); +fail1: + bus_dmamap_destroy(mh->mh_dmat, mh->mh_dmamap); +fail0: + bus_dma_tag_destroy(mh->mh_dmat); + mtx_destroy(&mh->mh_mtx); + free(mh, M_DEVBUF); + return NULL; +} + +void +mwl_hal_detach(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + bus_dmamem_free(mh->mh_dmat, mh->mh_cmdbuf, mh->mh_dmamap); + bus_dmamap_destroy(mh->mh_dmat, mh->mh_dmamap); + bus_dma_tag_destroy(mh->mh_dmat); + mtx_destroy(&mh->mh_mtx); + free(mh, M_DEVBUF); +} + +/* + * Reset internal state after a firmware download. + */ +static int +mwlResetHalState(struct mwl_hal_priv *mh) +{ + int i; + + /* XXX get from f/w */ + mh->mh_bastreams = (1<<MWL_BASTREAMS_MAX)-1; + for (i = 0; i < MWL_MBSS_MAX; i++) + mh->mh_vaps[i].mh = NULL; + /* + * Clear cumulative stats. + */ + mh->mh_RTSSuccesses = 0; + mh->mh_RTSFailures = 0; + mh->mh_RxDuplicateFrames = 0; + mh->mh_FCSErrorCount = 0; + /* + * Fetch cal data for later use. + * XXX may want to fetch other stuff too. + */ + /* XXX check return */ + if ((mh->mh_flags & MHF_CALDATA) == 0) + mwlGetPwrCalTable(mh); + return 0; +} + +struct mwl_hal_vap * +mwl_hal_newvap(struct mwl_hal *mh0, MWL_HAL_BSSTYPE type, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_vap *vap; + int i; + + MWL_HAL_LOCK(mh); + /* NB: could optimize but not worth it w/ max 32 bss */ + for (i = 0; i < MWL_MBSS_MAX; i++) { + vap = &mh->mh_vaps[i]; + if (vap->vap_type == type && vap->mh == NULL) { + vap->mh = mh; + mwl_hal_setmac_locked(vap, mac); + break; + } + } + MWL_HAL_UNLOCK(mh); + return (i < MWL_MBSS_MAX) ? vap : NULL; +} + +void +mwl_hal_delvap(struct mwl_hal_vap *vap) +{ + /* NB: locking not needed for single write */ + vap->mh = NULL; +} + +/* + * Manipulate the debug mask. Note debug + * msgs are only provided when this code is + * compiled with MWLHAL_DEBUG defined. + */ + +void +mwl_hal_setdebug(struct mwl_hal *mh, int debug) +{ + MWLPRIV(mh)->mh_debug = debug; +} + +int +mwl_hal_getdebug(struct mwl_hal *mh) +{ + return MWLPRIV(mh)->mh_debug; +} + +void +mwl_hal_setbastreams(struct mwl_hal *mh, int mask) +{ + MWLPRIV(mh)->mh_bastreams = mask & ((1<<MWL_BASTREAMS_MAX)-1); +} + +int +mwl_hal_getbastreams(struct mwl_hal *mh) +{ + return MWLPRIV(mh)->mh_bastreams; +} + +int +mwl_hal_ismbsscapable(struct mwl_hal *mh) +{ + return (MWLPRIV(mh)->mh_flags & MHF_MBSS) != 0; +} + +#if 0 +/* XXX inlined */ +/* + * Return the current ISR setting and clear the cause. + * XXX maybe make inline + */ +void +mwl_hal_getisr(struct mwl_hal *mh0, uint32_t *status) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t cause; + + cause = RD4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE); + if (cause == 0xffffffff) { /* card removed */ +device_printf(mh->mh_dev, "%s: cause 0x%x\n", __func__, cause); + cause = 0; + } else if (cause != 0) { + /* clear cause bits */ + WR4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE, + cause &~ mh->public.mh_imask); + RD4(mh, MACREG_REG_INT_CODE); /* XXX flush write? */ + } + *status = cause; +} +#endif + +/* + * Set the interrupt mask. + */ +void +mwl_hal_intrset(struct mwl_hal *mh0, uint32_t mask) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, 0); + RD4(mh, MACREG_REG_INT_CODE); + + mh->public.mh_imask = mask; + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, mask); + RD4(mh, MACREG_REG_INT_CODE); +} + +#if 0 +/* XXX inlined */ +/* + * Kick the firmware to tell it there are new tx descriptors + * for processing. The driver says what h/w q has work in + * case the f/w ever gets smarter. + */ +void +mwl_hal_txstart(struct mwl_hal *mh0, int qnum) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t dummy; + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_PPA_READY); + dummy = RD4(mh, MACREG_REG_INT_CODE); +} +#endif + +/* + * Callback from the driver on a cmd done interrupt. + * Nothing to do right now as we spin waiting for + * cmd completion. + */ +void +mwl_hal_cmddone(struct mwl_hal *mh0) +{ +#if 0 + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + if (mh->mh_debug & MWL_HAL_DEBUG_CMDDONE) { + device_printf(mh->mh_dev, "cmd done interrupt:\n"); + dumpresult(mh, 1); + } +#endif +} + +/* + * Return "hw specs". Note this must be the first + * cmd MUST be done after a firmware download or the + * f/w will lockup. + * XXX move into the hal so driver doesn't need to be responsible + */ +int +mwl_hal_gethwspecs(struct mwl_hal *mh0, struct mwl_hal_hwspec *hw) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_GET_HW_SPEC *pCmd; + int retval, minrev; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_GET_HW_SPEC, HostCmd_CMD_GET_HW_SPEC); + memset(&pCmd->PermanentAddr[0], 0xff, IEEE80211_ADDR_LEN); + pCmd->ulFwAwakeCookie = htole32((unsigned int)mh->mh_cmdaddr+2048); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_HW_SPEC); + if (retval == 0) { + IEEE80211_ADDR_COPY(hw->macAddr, pCmd->PermanentAddr); + hw->wcbBase[0] = le32toh(pCmd->WcbBase0) & 0x0000ffff; + hw->wcbBase[1] = le32toh(pCmd->WcbBase1) & 0x0000ffff; + hw->wcbBase[2] = le32toh(pCmd->WcbBase2) & 0x0000ffff; + hw->wcbBase[3] = le32toh(pCmd->WcbBase3) & 0x0000ffff; + hw->rxDescRead = le32toh(pCmd->RxPdRdPtr)& 0x0000ffff; + hw->rxDescWrite = le32toh(pCmd->RxPdWrPtr)& 0x0000ffff; + hw->regionCode = le16toh(pCmd->RegionCode) & 0x00ff; + hw->fwReleaseNumber = le32toh(pCmd->FWReleaseNumber); + hw->maxNumWCB = le16toh(pCmd->NumOfWCB); + hw->maxNumMCAddr = le16toh(pCmd->NumOfMCastAddr); + hw->numAntennas = le16toh(pCmd->NumberOfAntenna); + hw->hwVersion = pCmd->Version; + hw->hostInterface = pCmd->HostIf; + + mh->mh_revs.mh_macRev = hw->hwVersion; /* XXX */ + mh->mh_revs.mh_phyRev = hw->hostInterface; /* XXX */ + + minrev = ((hw->fwReleaseNumber) >> 16) & 0xff; + if (minrev >= 4) { + /* starting with 3.4.x.x s/w BA streams supported */ + mh->mh_bastreams &= (1<<MWL_BASTREAMS_MAX)-1; + } else + mh->mh_bastreams &= (1<<2)-1; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Inform the f/w about location of the tx/rx dma data structures + * and related state. This cmd must be done immediately after a + * mwl_hal_gethwspecs call or the f/w will lockup. + */ +int +mwl_hal_sethwdma(struct mwl_hal *mh0, const struct mwl_hal_txrxdma *dma) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_HW_SPEC *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_HW_SPEC, HostCmd_CMD_SET_HW_SPEC); + pCmd->WcbBase[0] = htole32(dma->wcbBase[0]); + pCmd->WcbBase[1] = htole32(dma->wcbBase[1]); + pCmd->WcbBase[2] = htole32(dma->wcbBase[2]); + pCmd->WcbBase[3] = htole32(dma->wcbBase[3]); + pCmd->TxWcbNumPerQueue = htole32(dma->maxNumTxWcb); + pCmd->NumTxQueues = htole32(dma->maxNumWCB); + pCmd->TotalRxWcb = htole32(1); /* XXX */ + pCmd->RxPdWrPtr = htole32(dma->rxDescRead); + pCmd->Flags = htole32(SET_HW_SPEC_HOSTFORM_BEACON +#ifdef MWL_HOST_PS_SUPPORT + | SET_HW_SPEC_HOST_POWERSAVE +#endif + | SET_HW_SPEC_HOSTFORM_PROBERESP); + /* disable multi-bss operation for A1-A4 parts */ + if (mh->mh_revs.mh_macRev < 5) + pCmd->Flags |= htole32(SET_HW_SPEC_DISABLEMBSS); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_HW_SPEC); + if (retval == 0) { + if (pCmd->Flags & htole32(SET_HW_SPEC_DISABLEMBSS)) + mh->mh_flags &= ~MHF_MBSS; + else + mh->mh_flags |= MHF_MBSS; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Retrieve statistics from the f/w. + * XXX should be in memory shared w/ driver + */ +int +mwl_hal_gethwstats(struct mwl_hal *mh0, struct mwl_hal_hwstats *stats) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_GET_STAT *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_GET_STAT, + HostCmd_CMD_802_11_GET_STAT); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_GET_STAT); + if (retval == 0) { + const uint32_t *sp = (const uint32_t *)&pCmd->TxRetrySuccesses; + uint32_t *dp = (uint32_t *)&stats->TxRetrySuccesses; + int i; + + for (i = 0; i < sizeof(*stats)/sizeof(uint32_t); i++) + dp[i] = le32toh(sp[i]); + /* + * Update stats not returned by f/w but available + * through public registers. Note these registers + * are "clear on read" so we maintain cumulative data. + * XXX register defines + */ + mh->mh_RTSSuccesses += RD4(mh, 0xa834); + mh->mh_RTSFailures += RD4(mh, 0xa830); + mh->mh_RxDuplicateFrames += RD4(mh, 0xa84c); + mh->mh_FCSErrorCount += RD4(mh, 0xa840); + } + MWL_HAL_UNLOCK(mh); + + stats->RTSSuccesses = mh->mh_RTSSuccesses; + stats->RTSFailures = mh->mh_RTSFailures; + stats->RxDuplicateFrames = mh->mh_RxDuplicateFrames; + stats->FCSErrorCount = mh->mh_FCSErrorCount; + return retval; +} + +/* + * Set HT guard interval handling. + * Takes effect immediately. + */ +int +mwl_hal_sethtgi(struct mwl_hal_vap *vap, int GIType) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_HT_GUARD_INTERVAL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_HT_GUARD_INTERVAL, + HostCmd_CMD_HT_GUARD_INTERVAL); + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + + if (GIType == 0) { + pCmd->GIType = htole32(GI_TYPE_LONG); + } else if (GIType == 1) { + pCmd->GIType = htole32(GI_TYPE_LONG | GI_TYPE_SHORT); + } else { + pCmd->GIType = htole32(GI_TYPE_LONG); + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_HT_GUARD_INTERVAL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure radio. + * Takes effect immediately. + * XXX preamble installed after set fixed rate cmd + */ +int +mwl_hal_setradio(struct mwl_hal *mh0, int onoff, MWL_HAL_PREAMBLE preamble) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RADIO_CONTROL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RADIO_CONTROL, + HostCmd_CMD_802_11_RADIO_CONTROL); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + if (onoff == 0) + pCmd->Control = 0; + else + pCmd->Control = htole16(preamble); + pCmd->RadioOn = htole16(onoff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RADIO_CONTROL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure antenna use. + * Takes effect immediately. + * XXX tx antenna setting ignored + * XXX rx antenna setting should always be 3 (for now) + */ +int +mwl_hal_setantenna(struct mwl_hal *mh0, MWL_HAL_ANTENNA dirSet, int ant) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_ANTENNA *pCmd; + int retval; + + if (!(dirSet == WL_ANTENNATYPE_RX || dirSet == WL_ANTENNATYPE_TX)) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_ANTENNA, + HostCmd_CMD_802_11_RF_ANTENNA); + pCmd->Action = htole16(dirSet); + if (ant == 0) /* default to all/both antennae */ + ant = 3; + pCmd->AntennaMode = htole16(ant); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_ANTENNA); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Set packet size threshold for implicit use of RTS. + * Takes effect immediately. + * XXX packet length > threshold =>'s RTS + */ +int +mwl_hal_setrtsthreshold(struct mwl_hal_vap *vap, int threshold) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_802_11_RTS_THSD *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_802_11_RTS_THSD, + HostCmd_CMD_802_11_RTS_THSD); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->Threshold = htole16(threshold); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RTS_THSD); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Enable sta-mode operation (disables beacon frame xmit). + */ +int +mwl_hal_setinframode(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_INFRA_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_INFRA_MODE, + HostCmd_CMD_SET_INFRA_MODE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_INFRA_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure radar detection in support of 802.11h. + */ +int +mwl_hal_setradardetection(struct mwl_hal *mh0, MWL_HAL_RADAR action) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_802_11h_Detect_Radar *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_802_11h_Detect_Radar, + HostCmd_CMD_802_11H_DETECT_RADAR); + pCmd->CmdHdr.Length = htole16(sizeof(HostCmd_802_11h_Detect_Radar)); + pCmd->Action = htole16(action); + if (mh->mh_regioncode == DOMAIN_CODE_ETSI_131) + pCmd->RadarTypeCode = htole16(131); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11H_DETECT_RADAR); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Convert public channel flags definition to a + * value suitable for feeding to the firmware. + * Note this includes byte swapping. + */ +static uint32_t +cvtChannelFlags(const MWL_HAL_CHANNEL *chan) +{ + uint32_t w; + + /* + * NB: f/w only understands FREQ_BAND_5GHZ, supplying the more + * precise band info causes it to lockup (sometimes). + */ + w = (chan->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) ? + FREQ_BAND_2DOT4GHZ : FREQ_BAND_5GHZ; + switch (chan->channelFlags.ChnlWidth) { + case MWL_CH_10_MHz_WIDTH: + w |= CH_10_MHz_WIDTH; + break; + case MWL_CH_20_MHz_WIDTH: + w |= CH_20_MHz_WIDTH; + break; + case MWL_CH_40_MHz_WIDTH: + default: + w |= CH_40_MHz_WIDTH; + break; + } + switch (chan->channelFlags.ExtChnlOffset) { + case MWL_EXT_CH_NONE: + w |= EXT_CH_NONE; + break; + case MWL_EXT_CH_ABOVE_CTRL_CH: + w |= EXT_CH_ABOVE_CTRL_CH; + break; + case MWL_EXT_CH_BELOW_CTRL_CH: + w |= EXT_CH_BELOW_CTRL_CH; + break; + } + return htole32(w); +} + +/* + * Start a channel switch announcement countdown. The IE + * in the beacon frame is allowed to go out and the firmware + * counts down and notifies the host when it's time to switch + * channels. + */ +int +mwl_hal_setchannelswitchie(struct mwl_hal *mh0, + const MWL_HAL_CHANNEL *nextchan, uint32_t mode, uint32_t count) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_SET_SWITCH_CHANNEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_SET_SWITCH_CHANNEL, + HostCmd_CMD_SET_SWITCH_CHANNEL); + pCmd->Next11hChannel = htole32(nextchan->channel); + pCmd->Mode = htole32(mode); + pCmd->InitialCount = htole32(count+1); + pCmd->ChannelFlags = cvtChannelFlags(nextchan); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_SWITCH_CHANNEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Set the region code that selects the radar bin'ing agorithm. + */ +int +mwl_hal_setregioncode(struct mwl_hal *mh0, int regionCode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_SET_REGIONCODE_INFO *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_SET_REGIONCODE_INFO, + HostCmd_CMD_SET_REGION_CODE); + /* XXX map pseudo-codes to fw codes */ + switch (regionCode) { + case DOMAIN_CODE_ETSI_131: + pCmd->regionCode = htole16(DOMAIN_CODE_ETSI); + break; + default: + pCmd->regionCode = htole16(regionCode); + break; + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_REGION_CODE); + if (retval == 0) + mh->mh_regioncode = regionCode; + MWL_HAL_UNLOCK(mh); + return retval; +} + +#define RATEVAL(r) ((r) &~ RATE_MCS) +#define RATETYPE(r) (((r) & RATE_MCS) ? HT_RATE_TYPE : LEGACY_RATE_TYPE) + +int +mwl_hal_settxrate(struct mwl_hal_vap *vap, MWL_HAL_TXRATE_HANDLING handling, + const MWL_HAL_TXRATE *rate) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_USE_FIXED_RATE *pCmd; + FIXED_RATE_ENTRY *fp; + int retval, i, n; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_USE_FIXED_RATE, + HostCmd_CMD_SET_FIXED_RATE); + + pCmd->MulticastRate = RATEVAL(rate->McastRate); + pCmd->MultiRateTxType = RATETYPE(rate->McastRate); + /* NB: no rate type field */ + pCmd->ManagementRate = RATEVAL(rate->MgtRate); + memset(pCmd->FixedRateTable, 0, sizeof(pCmd->FixedRateTable)); + if (handling == RATE_FIXED) { + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + pCmd->AllowRateDrop = htole32(FIXED_RATE_WITHOUT_AUTORATE_DROP); + fp = pCmd->FixedRateTable; + fp->FixedRate = + htole32(RATEVAL(rate->RateSeries[0].Rate)); + fp->FixRateTypeFlags.FixRateType = + htole32(RATETYPE(rate->RateSeries[0].Rate)); + pCmd->EntryCount = htole32(1); + } else if (handling == RATE_FIXED_DROP) { + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + pCmd->AllowRateDrop = htole32(FIXED_RATE_WITH_AUTO_RATE_DROP); + n = 0; + fp = pCmd->FixedRateTable; + for (i = 0; i < 4; i++) { + if (rate->RateSeries[0].TryCount == 0) + break; + fp->FixRateTypeFlags.FixRateType = + htole32(RATETYPE(rate->RateSeries[i].Rate)); + fp->FixedRate = + htole32(RATEVAL(rate->RateSeries[i].Rate)); + fp->FixRateTypeFlags.RetryCountValid = + htole32(RETRY_COUNT_VALID); + fp->RetryCount = + htole32(rate->RateSeries[i].TryCount-1); + n++; + } + pCmd->EntryCount = htole32(n); + } else + pCmd->Action = htole32(HostCmd_ACT_NOT_USE_FIXED_RATE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_FIXED_RATE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_settxrate_auto(struct mwl_hal *mh0, const MWL_HAL_TXRATE *rate) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_USE_FIXED_RATE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_USE_FIXED_RATE, + HostCmd_CMD_SET_FIXED_RATE); + + pCmd->MulticastRate = RATEVAL(rate->McastRate); + pCmd->MultiRateTxType = RATETYPE(rate->McastRate); + /* NB: no rate type field */ + pCmd->ManagementRate = RATEVAL(rate->MgtRate); + memset(pCmd->FixedRateTable, 0, sizeof(pCmd->FixedRateTable)); + pCmd->Action = htole32(HostCmd_ACT_NOT_USE_FIXED_RATE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_FIXED_RATE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +#undef RATEVAL +#undef RATETYPE + +int +mwl_hal_setslottime(struct mwl_hal *mh0, int usecs) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_SLOT *pCmd; + int retval; + + if (usecs != 9 && usecs != 20) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_SLOT, + HostCmd_CMD_802_11_SET_SLOT); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->Slot = (usecs == 9 ? 1 : 0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_SET_SLOT); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_adjusttxpower(struct mwl_hal *mh0, uint32_t level) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_TX_POWER *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_TX_POWER, + HostCmd_CMD_802_11_RF_TX_POWER); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + + if (level < 30) { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_LOW); + } else if (level >= 30 && level < 60) { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_MEDIUM); + } else { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_HIGH); + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_TX_POWER); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static const struct mwl_hal_channel * +findchannel(const struct mwl_hal_priv *mh, const MWL_HAL_CHANNEL *c) +{ + const struct mwl_hal_channel *hc; + const MWL_HAL_CHANNELINFO *ci; + int chan = c->channel, i; + + if (c->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) { + i = chan - 1; + if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) { + ci = &mh->mh_40M; + if (c->channelFlags.ExtChnlOffset == MWL_EXT_CH_BELOW_CTRL_CH) + i -= 4; + } else + ci = &mh->mh_20M; + /* 2.4G channel table is directly indexed */ + hc = ((unsigned)i < ci->nchannels) ? &ci->channels[i] : NULL; + } else if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ) { + if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) { + ci = &mh->mh_40M_5G; + if (c->channelFlags.ExtChnlOffset == MWL_EXT_CH_BELOW_CTRL_CH) + chan -= 4; + } else + ci = &mh->mh_20M_5G; + /* 5GHz channel table is sparse and must be searched */ + for (i = 0; i < ci->nchannels; i++) + if (ci->channels[i].ieee == chan) + break; + hc = (i < ci->nchannels) ? &ci->channels[i] : NULL; + } else + hc = NULL; + return hc; +} + +int +mwl_hal_settxpower(struct mwl_hal *mh0, const MWL_HAL_CHANNEL *c, uint8_t maxtxpow) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_TX_POWER *pCmd; + const struct mwl_hal_channel *hc; + int i, retval; + + hc = findchannel(mh, c); + if (hc == NULL) { + /* XXX temp while testing */ + device_printf(mh->mh_dev, + "%s: no cal data for channel %u band %u width %u ext %u\n", + __func__, c->channel, c->channelFlags.FreqBand, + c->channelFlags.ChnlWidth, c->channelFlags.ExtChnlOffset); + return EINVAL; + } + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_TX_POWER, + HostCmd_CMD_802_11_RF_TX_POWER); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET_LIST); + i = 0; + /* NB: 5Ghz cal data have the channel # in [0]; don't truncate */ + if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ) + pCmd->PowerLevelList[i++] = htole16(hc->targetPowers[0]); + for (; i < 4; i++) { + uint16_t pow = hc->targetPowers[i]; + if (pow > maxtxpow) + pow = maxtxpow; + pCmd->PowerLevelList[i] = htole16(pow); + } + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_TX_POWER); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_getchannelinfo(struct mwl_hal *mh0, int band, int chw, + const MWL_HAL_CHANNELINFO **ci) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + switch (band) { + case MWL_FREQ_BAND_2DOT4GHZ: + *ci = (chw == MWL_CH_20_MHz_WIDTH) ? &mh->mh_20M : &mh->mh_40M; + break; + case MWL_FREQ_BAND_5GHZ: + *ci = (chw == MWL_CH_20_MHz_WIDTH) ? + &mh->mh_20M_5G : &mh->mh_40M_5G; + break; + default: + return EINVAL; + } + return ((*ci)->freqLow == (*ci)->freqHigh) ? EINVAL : 0; +} + +int +mwl_hal_setmcast(struct mwl_hal *mh0, int nmc, const uint8_t macs[]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_MAC_MULTICAST_ADR *pCmd; + int retval; + + if (nmc > MWL_HAL_MCAST_MAX) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_MAC_MULTICAST_ADR, + HostCmd_CMD_MAC_MULTICAST_ADR); + memcpy(pCmd->MACList, macs, nmc*IEEE80211_ADDR_LEN); + pCmd->NumOfAdrs = htole16(nmc); + pCmd->Action = htole16(0xffff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_MAC_MULTICAST_ADR); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_keyset(struct mwl_hal_vap *vap, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY, + HostCmd_CMD_UPDATE_ENCRYPTION); + if (kv->keyFlags & (KEY_FLAG_TXGROUPKEY|KEY_FLAG_RXGROUPKEY)) + pCmd->ActionType = htole32(EncrActionTypeSetGroupKey); + else + pCmd->ActionType = htole32(EncrActionTypeSetKey); + pCmd->KeyParam.Length = htole16(sizeof(pCmd->KeyParam)); + pCmd->KeyParam.KeyTypeId = htole16(kv->keyTypeId); + pCmd->KeyParam.KeyInfo = htole32(kv->keyFlags); + pCmd->KeyParam.KeyIndex = htole32(kv->keyIndex); + /* NB: includes TKIP MIC keys */ + memcpy(&pCmd->KeyParam.Key, &kv->key, kv->keyLen); + switch (kv->keyTypeId) { + case KEY_TYPE_ID_WEP: + pCmd->KeyParam.KeyLen = htole16(kv->keyLen); + break; + case KEY_TYPE_ID_TKIP: + pCmd->KeyParam.KeyLen = htole16(sizeof(TKIP_TYPE_KEY)); + pCmd->KeyParam.Key.TkipKey.TkipRsc.low = + htole16(kv->key.tkip.rsc.low); + pCmd->KeyParam.Key.TkipKey.TkipRsc.high = + htole32(kv->key.tkip.rsc.high); + pCmd->KeyParam.Key.TkipKey.TkipTsc.low = + htole16(kv->key.tkip.tsc.low); + pCmd->KeyParam.Key.TkipKey.TkipTsc.high = + htole32(kv->key.tkip.tsc.high); + break; + case KEY_TYPE_ID_AES: + pCmd->KeyParam.KeyLen = htole16(sizeof(AES_TYPE_KEY)); + break; + } +#ifdef MWL_MBSS_SUPPORT + IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac); +#else + IEEE80211_ADDR_COPY(pCmd->Macaddr, mac); +#endif + retval = mwlExecuteCmd(mh, HostCmd_CMD_UPDATE_ENCRYPTION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_keyreset(struct mwl_hal_vap *vap, const MWL_HAL_KEYVAL *kv, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY, + HostCmd_CMD_UPDATE_ENCRYPTION); + pCmd->ActionType = htole16(EncrActionTypeRemoveKey); + pCmd->KeyParam.Length = htole16(sizeof(pCmd->KeyParam)); + pCmd->KeyParam.KeyTypeId = htole16(kv->keyTypeId); + pCmd->KeyParam.KeyInfo = htole32(kv->keyFlags); + pCmd->KeyParam.KeyIndex = htole32(kv->keyIndex); +#ifdef MWL_MBSS_SUPPORT + IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac); +#else + IEEE80211_ADDR_COPY(pCmd->Macaddr, mac); +#endif + retval = mwlExecuteCmd(mh, HostCmd_CMD_UPDATE_ENCRYPTION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +mwl_hal_setmac_locked(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_SET_MAC *pCmd; + + _VCMD_SETUP(vap, pCmd, HostCmd_DS_SET_MAC, HostCmd_CMD_SET_MAC_ADDR); + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); +#ifdef MWL_MBSS_SUPPORT + pCmd->MacType = vap->bss_type; /* NB: already byte swapped */ + IEEE80211_ADDR_COPY(vap->mac, addr); /* XXX do only if success */ +#endif + return mwlExecuteCmd(mh, HostCmd_CMD_SET_MAC_ADDR); +} + +int +mwl_hal_setmac(struct mwl_hal_vap *vap, const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + int retval; + + MWL_HAL_LOCK(mh); + retval = mwl_hal_setmac_locked(vap, addr); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setbeacon(struct mwl_hal_vap *vap, const void *frame, size_t frameLen) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_SET_BEACON *pCmd; + int retval; + + /* XXX verify frameLen fits */ + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_SET_BEACON, HostCmd_CMD_SET_BEACON); + /* XXX override _VCMD_SETUP */ + pCmd->CmdHdr.Length = htole16(sizeof(HostCmd_DS_SET_BEACON)-1+frameLen); + pCmd->FrmBodyLen = htole16(frameLen); + memcpy(pCmd->FrmBody, frame, frameLen); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_BEACON); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpowersave_bss(struct mwl_hal_vap *vap, uint8_t nsta) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_SET_POWERSAVESTATION *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_SET_POWERSAVESTATION, + HostCmd_CMD_SET_POWERSAVESTATION); + pCmd->NumberOfPowersave = nsta; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_POWERSAVESTATION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpowersave_sta(struct mwl_hal_vap *vap, uint16_t aid, int ena) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_SET_TIM *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_SET_TIM, HostCmd_CMD_SET_TIM); + pCmd->Aid = htole16(aid); + pCmd->Set = htole32(ena); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_TIM); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setassocid(struct mwl_hal_vap *vap, + const uint8_t bssId[IEEE80211_ADDR_LEN], uint16_t assocId) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_AID *pCmd = (HostCmd_FW_SET_AID *) &mh->mh_cmdbuf[0]; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_AID, HostCmd_CMD_SET_AID); + pCmd->AssocID = htole16(assocId); + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], bssId); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_AID); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setchannel(struct mwl_hal *mh0, const MWL_HAL_CHANNEL *chan) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_RF_CHANNEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_RF_CHANNEL, HostCmd_CMD_SET_RF_CHANNEL); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->CurrentChannel = chan->channel; + pCmd->ChannelFlags = cvtChannelFlags(chan); /* NB: byte-swapped */ + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RF_CHANNEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +bastream_check_available(struct mwl_hal_priv *mh, int qid, + const uint8_t Macaddr[IEEE80211_ADDR_LEN], + uint8_t Tid, uint8_t ParamInfo) +{ + HostCmd_FW_BASTREAM *pCmd; + int retval; + + MWL_HAL_LOCK_ASSERT(mh); + + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaCheckCreateStream); + pCmd->BaInfo.CreateParams.BarThrs = htole32(63); + pCmd->BaInfo.CreateParams.WindowSize = htole32(64); + pCmd->BaInfo.CreateParams.IdleThrs = htole32(0x22000); + IEEE80211_ADDR_COPY(&pCmd->BaInfo.CreateParams.PeerMacAddr[0], Macaddr); + pCmd->BaInfo.CreateParams.DialogToken = 10; + pCmd->BaInfo.CreateParams.Tid = Tid; + pCmd->BaInfo.CreateParams.QueueId = qid; + pCmd->BaInfo.CreateParams.ParamInfo = (uint8_t) ParamInfo; +#if 0 + cvtBAFlags(&pCmd->BaInfo.CreateParams.Flags, sp->ba_type, 0); +#else + pCmd->BaInfo.CreateParams.Flags = + htole32(BASTREAM_FLAG_IMMEDIATE_TYPE) + | htole32(BASTREAM_FLAG_DIRECTION_UPSTREAM) + ; +#endif + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + if (retval == 0) { + /* + * NB: BA stream create may fail when the stream is + * h/w backed under some (as yet not understood) conditions. + * Check the result code to catch this. + */ + if (le16toh(pCmd->CmdHdr.Result) != HostCmd_RESULT_OK) + retval = EIO; + } + return retval; +} + +const MWL_HAL_BASTREAM * +mwl_hal_bastream_alloc(struct mwl_hal *mh0, int ba_type, + const uint8_t Macaddr[IEEE80211_ADDR_LEN], + uint8_t Tid, uint8_t ParamInfo, void *a1, void *a2) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp; + int s; + + MWL_HAL_LOCK(mh); + if (mh->mh_bastreams == 0) { + /* no streams available */ + MWL_HAL_UNLOCK(mh); + return NULL; + } + for (s = 0; (mh->mh_bastreams & (1<<s)) == 0; s++) + ; + if (bastream_check_available(mh, s, Macaddr, Tid, ParamInfo)) { + MWL_HAL_UNLOCK(mh); + return NULL; + } + sp = &mh->mh_streams[s]; + mh->mh_bastreams &= ~(1<<s); + sp->public.data[0] = a1; + sp->public.data[1] = a2; + IEEE80211_ADDR_COPY(sp->macaddr, Macaddr); + sp->tid = Tid; + sp->paraminfo = ParamInfo; + sp->setup = 0; + sp->ba_type = ba_type; + MWL_HAL_UNLOCK(mh); + return sp != NULL ? &sp->public : NULL; +} + +const MWL_HAL_BASTREAM * +mwl_hal_bastream_lookup(struct mwl_hal *mh0, int s) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + if (!(0 <= s && s < MWL_BASTREAMS_MAX)) + return NULL; + if (mh->mh_bastreams & (1<<s)) + return NULL; + return &mh->mh_streams[s].public; +} + +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif + +int +mwl_hal_bastream_create(struct mwl_hal *mh0, + const MWL_HAL_BASTREAM *s, int BarThrs, int WindowSize, uint16_t seqno) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_FW_BASTREAM *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaCreateStream); + pCmd->BaInfo.CreateParams.BarThrs = htole32(63);//BarThrs; + pCmd->BaInfo.CreateParams.WindowSize = htole32(64); /*WindowSize;*/ + pCmd->BaInfo.CreateParams.IdleThrs = htole32(0x22000); + IEEE80211_ADDR_COPY(&pCmd->BaInfo.CreateParams.PeerMacAddr[0], + sp->macaddr); +#if 0 + pCmd->BaInfo.CreateParams.DialogToken = DialogToken; +#else + pCmd->BaInfo.CreateParams.DialogToken = 10; +#endif + pCmd->BaInfo.CreateParams.Tid = sp->tid; + pCmd->BaInfo.CreateParams.QueueId = sp->stream; + pCmd->BaInfo.CreateParams.ParamInfo = sp->paraminfo; + /* NB: ResetSeqNo known to be zero */ + pCmd->BaInfo.CreateParams.StartSeqNo = htole16(seqno); +#if 0 + cvtBAFlags(&pCmd->BaInfo.CreateParams.Flags, sp->ba_type, 0); +#else + pCmd->BaInfo.CreateParams.Flags = + htole32(BASTREAM_FLAG_IMMEDIATE_TYPE) + | htole32(BASTREAM_FLAG_DIRECTION_UPSTREAM) + ; +#endif + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + if (retval == 0) { + /* + * NB: BA stream create may fail when the stream is + * h/w backed under some (as yet not understood) conditions. + * Check the result code to catch this. + */ + if (le16toh(pCmd->CmdHdr.Result) != HostCmd_RESULT_OK) + retval = EIO; + else + sp->setup = 1; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_bastream_destroy(struct mwl_hal *mh0, const MWL_HAL_BASTREAM *s) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_FW_BASTREAM *pCmd; + int retval; + + if (sp->stream >= MWL_BASTREAMS_MAX) { + /* XXX */ + return EINVAL; + } + MWL_HAL_LOCK(mh); + if (sp->setup) { + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaDestroyStream); + pCmd->BaInfo.DestroyParams.FwBaContext.Context = + htole32(sp->stream); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + } else + retval = 0; + /* NB: always reclaim stream */ + mh->mh_bastreams |= 1<<sp->stream; + sp->public.data[0] = NULL; + sp->public.data[1] = NULL; + sp->setup = 0; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_bastream_get_seqno(struct mwl_hal *mh0, + const MWL_HAL_BASTREAM *s, uint16_t *pseqno) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_GET_SEQNO *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_GET_SEQNO, HostCmd_CMD_GET_SEQNO); + IEEE80211_ADDR_COPY(pCmd->MacAddr, sp->macaddr); + pCmd->TID = sp->tid; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_SEQNO); + if (retval == 0) + *pseqno = le16toh(pCmd->SeqNo); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_getwatchdogbitmap(struct mwl_hal *mh0, uint8_t bitmap[1]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_GET_WATCHDOG_BITMAP *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_GET_WATCHDOG_BITMAP, + HostCmd_CMD_GET_WATCHDOG_BITMAP); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_WATCHDOG_BITMAP); + if (retval == 0) { + bitmap[0] = pCmd->Watchdogbitmap; + /* fw returns qid, map it to BA stream */ + if (bitmap[0] < MWL_BAQID_MAX) + bitmap[0] = qid2ba[bitmap[0]]; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +static void +cvtPeerInfo(PeerInfo_t *to, const MWL_HAL_PEERINFO *from) +{ + to->LegacyRateBitMap = htole32(from->LegacyRateBitMap); + to->HTRateBitMap = htole32(from->HTRateBitMap); + to->CapInfo = htole16(from->CapInfo); + to->HTCapabilitiesInfo = htole16(from->HTCapabilitiesInfo); + to->MacHTParamInfo = from->MacHTParamInfo; + to->AddHtInfo.ControlChan = from->AddHtInfo.ControlChan; + to->AddHtInfo.AddChan = from->AddHtInfo.AddChan; + to->AddHtInfo.OpMode = htole16(from->AddHtInfo.OpMode); + to->AddHtInfo.stbc = htole16(from->AddHtInfo.stbc); +} + +/* XXX station id must be in [0..63] */ +int +mwl_hal_newstation(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN], uint16_t aid, uint16_t sid, + const MWL_HAL_PEERINFO *peer, int isQosSta, int wmeInfo) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_NEW_STN *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_NEW_STN, HostCmd_CMD_SET_NEW_STN); + pCmd->AID = htole16(aid); + pCmd->StnId = htole16(sid); + pCmd->Action = htole16(0); /* SET */ + if (peer != NULL) { + /* NB: must fix up byte order */ + cvtPeerInfo(&pCmd->PeerInfo, peer); + } + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); + pCmd->Qosinfo = wmeInfo; + pCmd->isQosSta = (isQosSta != 0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_NEW_STN); + if (retval == 0 && IEEE80211_ADDR_EQ(vap->mac, addr)) + vap->flags |= MVF_STATION; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_delstation(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_NEW_STN *pCmd; + int retval, islocal; + + MWL_HAL_LOCK(mh); + islocal = IEEE80211_ADDR_EQ(vap->mac, addr); + if (!islocal || (vap->flags & MVF_STATION)) { + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_NEW_STN, + HostCmd_CMD_SET_NEW_STN); + pCmd->Action = htole16(2); /* REMOVE */ + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_NEW_STN); + if (islocal) + vap->flags &= ~MVF_STATION; + } else + retval = 0; + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Prod the firmware to age packets on station power + * save queues and reap frames on the tx aggregation q's. + */ +int +mwl_hal_setkeepalive(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_KEEP_ALIVE_TICK *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_KEEP_ALIVE_TICK, + HostCmd_CMD_SET_KEEP_ALIVE); + /* + * NB: tick must be 0 to prod the f/w; + * a non-zero value is a noop. + */ + pCmd->tick = 0; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_KEEP_ALIVE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setapmode(struct mwl_hal_vap *vap, MWL_HAL_APMODE ApMode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_APMODE *pCmd; + int retval; + + /* XXX validate ApMode? */ + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_APMODE, HostCmd_CMD_SET_APMODE); + pCmd->ApMode = ApMode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_APMODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_stop(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_BSS_START *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + if (vap->flags & MVF_RUNNING) { + _VCMD_SETUP(vap, pCmd, HostCmd_DS_BSS_START, + HostCmd_CMD_BSS_START); + pCmd->Enable = htole32(HostCmd_ACT_GEN_OFF); + retval = mwlExecuteCmd(mh, HostCmd_CMD_BSS_START); + } else + retval = 0; + /* NB: mark !running regardless */ + vap->flags &= ~MVF_RUNNING; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_start(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_BSS_START *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_BSS_START, HostCmd_CMD_BSS_START); + pCmd->Enable = htole32(HostCmd_ACT_GEN_ON); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BSS_START); + if (retval == 0) + vap->flags |= MVF_RUNNING; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setgprot(struct mwl_hal *mh0, int prot) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_G_PROTECT_FLAG *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_G_PROTECT_FLAG, + HostCmd_CMD_SET_G_PROTECT_FLAG); + pCmd->GProtectFlag = htole32(prot); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_G_PROTECT_FLAG); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setwmm(struct mwl_hal *mh0, int onoff) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SetWMMMode *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SetWMMMode, + HostCmd_CMD_SET_WMM_MODE); + pCmd->Action = htole16(onoff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_WMM_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setedcaparams(struct mwl_hal *mh0, uint8_t qnum, + uint32_t CWmin, uint32_t CWmax, uint8_t AIFSN, uint16_t TXOPLimit) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_EDCA_PARAMS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_EDCA_PARAMS, + HostCmd_CMD_SET_EDCA_PARAMS); + /* + * NB: CWmin and CWmax are always set. + * TxOpLimit is set if bit 0x2 is marked in Action + * AIFSN is set if bit 0x4 is marked in Action + */ + pCmd->Action = htole16(0xffff); /* NB: set everything */ + pCmd->TxOP = htole16(TXOPLimit); + pCmd->CWMax = htole32(CWmax); + pCmd->CWMin = htole32(CWmin); + pCmd->AIFSN = AIFSN; + pCmd->TxQNum = qnum; /* XXX check */ + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_EDCA_PARAMS); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* XXX 0 = indoor, 1 = outdoor */ +int +mwl_hal_setrateadaptmode(struct mwl_hal *mh0, uint16_t mode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_RATE_ADAPT_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_RATE_ADAPT_MODE, + HostCmd_CMD_SET_RATE_ADAPT_MODE); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->RateAdaptMode = htole16(mode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RATE_ADAPT_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setcsmode(struct mwl_hal *mh0, MWL_HAL_CSMODE csmode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_LINKADAPT_CS_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_LINKADAPT_CS_MODE, + HostCmd_CMD_SET_LINKADAPT_CS_MODE); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->CSMode = htole16(csmode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_LINKADAPT_CS_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setnprot(struct mwl_hal_vap *vap, MWL_HAL_HTPROTECT mode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_N_PROTECT_FLAG *pCmd; + int retval; + + /* XXX validate mode */ + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_N_PROTECT_FLAG, + HostCmd_CMD_SET_N_PROTECT_FLAG); + pCmd->NProtectFlag = htole32(mode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_N_PROTECT_FLAG); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setnprotmode(struct mwl_hal_vap *vap, uint8_t mode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_N_PROTECT_OPMODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_N_PROTECT_OPMODE, + HostCmd_CMD_SET_N_PROTECT_OPMODE); + pCmd->NProtectOpMode = mode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_N_PROTECT_OPMODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setoptimizationlevel(struct mwl_hal *mh0, int level) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_OPTIMIZATION_LEVEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_OPTIMIZATION_LEVEL, + HostCmd_CMD_SET_OPTIMIZATION_LEVEL); + pCmd->OptLevel = level; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_OPTIMIZATION_LEVEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setmimops(struct mwl_hal *mh0, const uint8_t addr[IEEE80211_ADDR_LEN], + uint8_t enable, uint8_t mode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_MIMOPSHT *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_MIMOPSHT, HostCmd_CMD_SET_MIMOPSHT); + IEEE80211_ADDR_COPY(pCmd->Addr, addr); + pCmd->Enable = enable; + pCmd->Mode = mode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_MIMOPSHT); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +mwlGetCalTable(struct mwl_hal_priv *mh, uint8_t annex, uint8_t index) +{ + HostCmd_FW_GET_CALTABLE *pCmd; + int retval; + + MWL_HAL_LOCK_ASSERT(mh); + + _CMD_SETUP(pCmd, HostCmd_FW_GET_CALTABLE, HostCmd_CMD_GET_CALTABLE); + pCmd->annex = annex; + pCmd->index = index; + memset(pCmd->calTbl, 0, sizeof(pCmd->calTbl)); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_CALTABLE); + if (retval == 0 && + pCmd->calTbl[0] != annex && annex != 0 && annex != 255) + retval = EIO; + return retval; +} + +/* + * Calculate the max tx power from the channel's cal data. + */ +static void +setmaxtxpow(struct mwl_hal_channel *hc, int i, int maxix) +{ + hc->maxTxPow = hc->targetPowers[i]; + for (i++; i < maxix; i++) + if (hc->targetPowers[i] > hc->maxTxPow) + hc->maxTxPow = hc->targetPowers[i]; +} + +/* + * Construct channel info for 5GHz channels from cal data. + */ +static void +get5Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len) +{ + int i, j, f, l, h; + + l = 32000; + h = 0; + j = 0; + for (i = 0; i < len; i += 4) { + struct mwl_hal_channel *hc; + + if (table[i] == 0) + continue; + f = 5000 + 5*table[i]; + if (f < l) + l = f; + if (f > h) + h = f; + hc = &ci->channels[j]; + hc->freq = f; + hc->ieee = table[i]; + memcpy(hc->targetPowers, &table[i], 4); + setmaxtxpow(hc, 1, 4); /* NB: col 1 is the freq, skip*/ + j++; + } + ci->nchannels = j; + ci->freqLow = (l == 32000) ? 0 : l; + ci->freqHigh = h; +} + +static uint16_t +ieee2mhz(int chan) +{ + if (chan == 14) + return 2484; + if (chan < 14) + return 2407 + chan*5; + return 2512 + (chan-15)*20; +} + +/* + * Construct channel info for 2.4GHz channels from cal data. + */ +static void +get2Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len) +{ + int i, j; + + j = 0; + for (i = 0; i < len; i += 4) { + struct mwl_hal_channel *hc = &ci->channels[j]; + hc->ieee = 1+j; + hc->freq = ieee2mhz(1+j); + memcpy(hc->targetPowers, &table[i], 4); + setmaxtxpow(hc, 0, 4); + j++; + } + ci->nchannels = j; + ci->freqLow = ieee2mhz(1); + ci->freqHigh = ieee2mhz(j); +} + +#undef DUMPCALDATA +#ifdef DUMPCALDATA +static void +dumpcaldata(const char *name, const uint8_t *table, int n) +{ + int i; + printf("\n%s:\n", name); + for (i = 0; i < n; i += 4) + printf("[%2d] %3d %3d %3d %3d\n", i/4, table[i+0], table[i+1], table[i+2], table[i+3]); +} +#endif + +static int +mwlGetPwrCalTable(struct mwl_hal_priv *mh) +{ + const uint8_t *data; + MWL_HAL_CHANNELINFO *ci; + int len; + + MWL_HAL_LOCK(mh); + /* NB: we hold the lock so it's ok to use cmdbuf */ + data = ((const HostCmd_FW_GET_CALTABLE *) mh->mh_cmdbuf)->calTbl; + if (mwlGetCalTable(mh, 33, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 12; + if (len > PWTAGETRATETABLE20M) + len = PWTAGETRATETABLE20M; +#ifdef DUMPCALDATA +dumpcaldata("2.4G 20M", &data[12], len);/*XXX*/ +#endif + get2Ghz(&mh->mh_20M, &data[12], len); + } + if (mwlGetCalTable(mh, 34, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 12; + if (len > PWTAGETRATETABLE40M) + len = PWTAGETRATETABLE40M; +#ifdef DUMPCALDATA +dumpcaldata("2.4G 40M", &data[12], len);/*XXX*/ +#endif + ci = &mh->mh_40M; + get2Ghz(ci, &data[12], len); + } + if (mwlGetCalTable(mh, 35, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 20; + if (len > PWTAGETRATETABLE20M_5G) + len = PWTAGETRATETABLE20M_5G; +#ifdef DUMPCALDATA +dumpcaldata("5G 20M", &data[20], len);/*XXX*/ +#endif + get5Ghz(&mh->mh_20M_5G, &data[20], len); + } + if (mwlGetCalTable(mh, 36, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 20; + if (len > PWTAGETRATETABLE40M_5G) + len = PWTAGETRATETABLE40M_5G; +#ifdef DUMPCALDATA +dumpcaldata("5G 40M", &data[20], len);/*XXX*/ +#endif + ci = &mh->mh_40M_5G; + get5Ghz(ci, &data[20], len); + } + mh->mh_flags |= MHF_CALDATA; + MWL_HAL_UNLOCK(mh); + return 0; +} + +int +mwl_hal_getregioncode(struct mwl_hal *mh0, uint8_t *countryCode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + int retval; + + MWL_HAL_LOCK(mh); + retval = mwlGetCalTable(mh, 0, 0); + if (retval == 0) { + const HostCmd_FW_GET_CALTABLE *pCmd = + (const HostCmd_FW_GET_CALTABLE *) mh->mh_cmdbuf; + *countryCode = pCmd->calTbl[16]; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpromisc(struct mwl_hal *mh0, int ena) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t v; + + MWL_HAL_LOCK(mh); + v = RD4(mh, MACREG_REG_PROMISCUOUS); + WR4(mh, MACREG_REG_PROMISCUOUS, ena ? v | 1 : v &~ 1); + MWL_HAL_UNLOCK(mh); + return 0; +} + +int +mwl_hal_getpromisc(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t v; + + MWL_HAL_LOCK(mh); + v = RD4(mh, MACREG_REG_PROMISCUOUS); + MWL_HAL_UNLOCK(mh); + return (v & 1) != 0; +} + +int +mwl_hal_GetBeacon(struct mwl_hal *mh0, uint8_t *pBcn, uint16_t *pLen) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_GET_BEACON *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_GET_BEACON, HostCmd_CMD_GET_BEACON); + pCmd->Bcnlen = htole16(0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_BEACON); + if (retval == 0) { + /* XXX bounds check */ + memcpy(pBcn, &pCmd->Bcn, pCmd->Bcnlen); + *pLen = pCmd->Bcnlen; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_SetRifs(struct mwl_hal *mh0, uint8_t QNum) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_RIFS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_RIFS, HostCmd_CMD_SET_RIFS); + pCmd->QNum = QNum; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RIFS); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Diagnostic api's for set/get registers. + */ + +static int +getRFReg(struct mwl_hal_priv *mh, int flag, uint32_t reg, uint32_t *val) +{ + HostCmd_DS_RF_REG_ACCESS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_RF_REG_ACCESS, HostCmd_CMD_RF_REG_ACCESS); + pCmd->Offset = htole16(reg); + pCmd->Action = htole16(flag); + pCmd->Value = htole32(*val); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_RF_REG_ACCESS); + if (retval == 0) + *val = pCmd->Value; + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +getBBReg(struct mwl_hal_priv *mh, int flag, uint32_t reg, uint32_t *val) +{ + HostCmd_DS_BBP_REG_ACCESS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_BBP_REG_ACCESS, HostCmd_CMD_BBP_REG_ACCESS); + pCmd->Offset = htole16(reg); + pCmd->Action = htole16(flag); + pCmd->Value = htole32(*val); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BBP_REG_ACCESS); + if (retval == 0) + *val = pCmd->Value; + MWL_HAL_UNLOCK(mh); + return retval; +} + +static u_int +mwl_hal_getregdump(struct mwl_hal_priv *mh, const MWL_DIAG_REGRANGE *regs, + void *dstbuf, int space) +{ + uint32_t *dp = dstbuf; + int i; + + for (i = 0; space >= 2*sizeof(uint32_t); i++) { + u_int r = regs[i].start; + u_int e = regs[i].end; + *dp++ = (r<<16) | e; + space -= sizeof(uint32_t); + do { + if (MWL_DIAG_ISMAC(r)) + *dp = RD4(mh, r); + else if (MWL_DIAG_ISBB(r)) + getBBReg(mh, HostCmd_ACT_GEN_READ, + r - MWL_DIAG_BASE_BB, dp); + else if (MWL_DIAG_ISRF(r)) + getRFReg(mh, HostCmd_ACT_GEN_READ, + r - MWL_DIAG_BASE_RF, dp); + else if (r < 0x1000 || r == MACREG_REG_FW_PRESENT) + *dp = RD4(mh, r); + else + *dp = 0xffffffff; + dp++; + r += sizeof(uint32_t); + space -= sizeof(uint32_t); + } while (r <= e && space >= sizeof(uint32_t)); + } + return (char *) dp - (char *) dstbuf; +} + +int +mwl_hal_getdiagstate(struct mwl_hal *mh0, int request, + const void *args, uint32_t argsize, + void **result, uint32_t *resultsize) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + switch (request) { + case MWL_DIAG_CMD_REVS: + *result = &mh->mh_revs; + *resultsize = sizeof(mh->mh_revs); + return 1; + case MWL_DIAG_CMD_REGS: + *resultsize = mwl_hal_getregdump(mh, args, *result, *resultsize); + return 1; + case MWL_DIAG_CMD_HOSTCMD: { + FWCmdHdr *pCmd = (FWCmdHdr *) &mh->mh_cmdbuf[0]; + int retval; + + MWL_HAL_LOCK(mh); + memcpy(pCmd, args, argsize); + retval = mwlExecuteCmd(mh, le16toh(pCmd->Cmd)); + *result = (*resultsize != 0) ? pCmd : NULL; + MWL_HAL_UNLOCK(mh); + return (retval == 0); + } + case MWL_DIAG_CMD_FWLOAD: + if (mwl_hal_fwload(mh0, __DECONST(void *, args))) { + device_printf(mh->mh_dev, "problem loading fw image\n"); + return 0; + } + return 1; + } + return 0; +} + +/* + * Low level firmware cmd block handshake support. + */ + +static void +mwlSendCmd(struct mwl_hal_priv *mh) +{ + uint32_t dummy; + + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + WR4(mh, MACREG_REG_GEN_PTR, mh->mh_cmdaddr); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_DOOR_BELL); +} + +static int +mwlWaitForCmdComplete(struct mwl_hal_priv *mh, uint16_t cmdCode) +{ +#define MAX_WAIT_FW_COMPLETE_ITERATIONS 10000 + int i; + + for (i = 0; i < MAX_WAIT_FW_COMPLETE_ITERATIONS; i++) { + if (mh->mh_cmdbuf[0] == le16toh(cmdCode)) + return 1; + DELAY(1*1000); + } + return 0; +#undef MAX_WAIT_FW_COMPLETE_ITERATIONS +} + +static int +mwlExecuteCmd(struct mwl_hal_priv *mh, unsigned short cmd) +{ + + MWL_HAL_LOCK_ASSERT(mh); + + if ((mh->mh_flags & MHF_FWHANG) && + (mh->mh_debug & MWL_HAL_DEBUG_IGNHANG) == 0) { +#ifdef MWLHAL_DEBUG + device_printf(mh->mh_dev, "firmware hung, skipping cmd %s\n", + mwlcmdname(cmd)); +#else + device_printf(mh->mh_dev, "firmware hung, skipping cmd 0x%x\n", + cmd); +#endif + return ENXIO; + } + if (RD4(mh, MACREG_REG_INT_CODE) == 0xffffffff) { + device_printf(mh->mh_dev, "%s: device not present!\n", + __func__); + return EIO; + } +#ifdef MWLHAL_DEBUG + if (mh->mh_debug & MWL_HAL_DEBUG_SENDCMD) + dumpresult(mh, 0); +#endif + mwlSendCmd(mh); + if (!mwlWaitForCmdComplete(mh, 0x8000 | cmd)) { +#ifdef MWLHAL_DEBUG + device_printf(mh->mh_dev, + "timeout waiting for f/w cmd %s\n", mwlcmdname(cmd)); +#else + device_printf(mh->mh_dev, + "timeout waiting for f/w cmd 0x%x\n", cmd); +#endif + mh->mh_flags |= MHF_FWHANG; + return ETIMEDOUT; + } + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); +#ifdef MWLHAL_DEBUG + if (mh->mh_debug & MWL_HAL_DEBUG_CMDDONE) + dumpresult(mh, 1); +#endif + return 0; +} + +/* + * Firmware download support. + */ +#define FW_DOWNLOAD_BLOCK_SIZE 256 +#define FW_CHECK_USECS (5*1000) /* 5ms */ +#define FW_MAX_NUM_CHECKS 200 + +#if 0 +/* XXX read f/w from file */ +#include <dev/mwl/mwlbootfw.h> +#include <dev/mwl/mwl88W8363fw.h> +#endif + +static void +mwlFwReset(struct mwl_hal_priv *mh) +{ + if (RD4(mh, MACREG_REG_INT_CODE) == 0xffffffff) { + device_printf(mh->mh_dev, "%s: device not present!\n", + __func__); + return; + } + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, ISR_RESET); + mh->mh_flags &= ~MHF_FWHANG; +} + +static void +mwlTriggerPciCmd(struct mwl_hal_priv *mh) +{ + uint32_t dummy; + + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, BUS_DMASYNC_PREWRITE); + + WR4(mh, MACREG_REG_GEN_PTR, mh->mh_cmdaddr); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_INT_CODE, 0x00); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_DOOR_BELL); + dummy = RD4(mh, MACREG_REG_INT_CODE); +} + +static int +mwlWaitFor(struct mwl_hal_priv *mh, uint32_t val) +{ + int i; + + for (i = 0; i < FW_MAX_NUM_CHECKS; i++) { + DELAY(FW_CHECK_USECS); + if (RD4(mh, MACREG_REG_INT_CODE) == val) + return 1; + } + return 0; +} + +/* + * Firmware block xmit when talking to the boot-rom. + */ +static int +mwlSendBlock(struct mwl_hal_priv *mh, int bsize, const void *data, size_t dsize) +{ + mh->mh_cmdbuf[0] = htole16(HostCmd_CMD_CODE_DNLD); + mh->mh_cmdbuf[1] = htole16(bsize); + memcpy(&mh->mh_cmdbuf[4], data , dsize); + mwlTriggerPciCmd(mh); + /* XXX 2000 vs 200 */ + if (mwlWaitFor(mh, MACREG_INT_CODE_CMD_FINISHED)) { + WR4(mh, MACREG_REG_INT_CODE, 0); + return 1; + } + device_printf(mh->mh_dev, + "%s: timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n", + __func__, RD4(mh, MACREG_REG_INT_CODE)); + return 0; +} + +/* + * Firmware block xmit when talking to the 1st-stage loader. + */ +static int +mwlSendBlock2(struct mwl_hal_priv *mh, const void *data, size_t dsize) +{ + memcpy(&mh->mh_cmdbuf[0], data, dsize); + mwlTriggerPciCmd(mh); + if (mwlWaitFor(mh, MACREG_INT_CODE_CMD_FINISHED)) { + WR4(mh, MACREG_REG_INT_CODE, 0); + return 1; + } + device_printf(mh->mh_dev, + "%s: timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n", + __func__, RD4(mh, MACREG_REG_INT_CODE)); + return 0; +} + +static void +mwlPokeSdramController(struct mwl_hal_priv *mh, int SDRAMSIZE_Addr) +{ + /** Set up sdram controller for superflyv2 **/ + WR4(mh, 0x00006014, 0x33); + WR4(mh, 0x00006018, 0xa3a2632); + WR4(mh, 0x00006010, SDRAMSIZE_Addr); +} + +int +mwl_hal_fwload(struct mwl_hal *mh0, void *fwargs) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + const char *fwname = "mw88W8363fw"; + const char *fwbootname = "mwlboot"; + const struct firmware *fwboot = NULL; + const struct firmware *fw; + /* XXX get from firmware header */ + uint32_t FwReadySignature = HostCmd_SOFTAP_FWRDY_SIGNATURE; + uint32_t OpMode = HostCmd_SOFTAP_MODE; + const uint8_t *fp, *ep; + const uint8_t *fmdata; + uint32_t blocksize, nbytes, fmsize; + int i, error, ntries; + + fw = firmware_get(fwname); + if (fw == NULL) { + device_printf(mh->mh_dev, + "could not load firmware image %s\n", fwname); + return ENXIO; + } + fmdata = fw->data; + fmsize = fw->datasize; + if (fmsize < 4) { + device_printf(mh->mh_dev, "firmware image %s too small\n", + fwname); + error = ENXIO; + goto bad2; + } + if (fmdata[0] == 0x01 && fmdata[1] == 0x00 && + fmdata[2] == 0x00 && fmdata[3] == 0x00) { + /* + * 2-stage load, get the boot firmware. + */ + fwboot = firmware_get(fwbootname); + if (fwboot == NULL) { + device_printf(mh->mh_dev, + "could not load firmware image %s\n", fwbootname); + error = ENXIO; + goto bad2; + } + } else + fwboot = NULL; + + mwlFwReset(mh); + + WR4(mh, MACREG_REG_A2H_INTERRUPT_CLEAR_SEL, MACREG_A2HRIC_BIT_MASK); + WR4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE, 0x00); + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, 0x00); + WR4(mh, MACREG_REG_A2H_INTERRUPT_STATUS_MASK, MACREG_A2HRIC_BIT_MASK); + if (mh->mh_SDRAMSIZE_Addr != 0) { + /** Set up sdram controller for superflyv2 **/ + mwlPokeSdramController(mh, mh->mh_SDRAMSIZE_Addr); + } + device_printf(mh->mh_dev, "load %s firmware image (%u bytes)\n", + fwname, fmsize); + if (fwboot != NULL) { + /* + * Do 2-stage load. The 1st stage loader is setup + * with the bootrom loader then we load the real + * image using a different handshake. With this + * mechanism the firmware is segmented into chunks + * that have a CRC. If a chunk is incorrect we'll + * be told to retransmit. + */ + /* XXX assumes hlpimage fits in a block */ + /* NB: zero size block indicates download is finished */ + if (!mwlSendBlock(mh, fwboot->datasize, fwboot->data, fwboot->datasize) || + !mwlSendBlock(mh, 0, NULL, 0)) { + error = ETIMEDOUT; + goto bad; + } + DELAY(200*FW_CHECK_USECS); + if (mh->mh_SDRAMSIZE_Addr != 0) { + /** Set up sdram controller for superflyv2 **/ + mwlPokeSdramController(mh, mh->mh_SDRAMSIZE_Addr); + } + nbytes = ntries = 0; /* NB: silence compiler */ + for (fp = fmdata, ep = fp + fmsize; fp < ep; ) { + WR4(mh, MACREG_REG_INT_CODE, 0); + blocksize = RD4(mh, MACREG_REG_SCRATCH); + if (blocksize == 0) /* download complete */ + break; + if (blocksize > 0x00000c00) { + error = EINVAL; + goto bad; + } + if ((blocksize & 0x1) == 0) { + /* block successfully downloaded, advance */ + fp += nbytes; + ntries = 0; + } else { + if (++ntries > 2) { + /* + * Guard against f/w telling us to + * retry infinitely. + */ + error = ELOOP; + goto bad; + } + /* clear NAK bit/flag */ + blocksize &= ~0x1; + } + if (blocksize > ep - fp) { + /* XXX this should not happen, what to do? */ + blocksize = ep - fp; + } + nbytes = blocksize; + if (!mwlSendBlock2(mh, fp, nbytes)) { + error = ETIMEDOUT; + goto bad; + } + } + } else { + for (fp = fmdata, ep = fp + fmsize; fp < ep;) { + nbytes = ep - fp; + if (nbytes > FW_DOWNLOAD_BLOCK_SIZE) + nbytes = FW_DOWNLOAD_BLOCK_SIZE; + if (!mwlSendBlock(mh, FW_DOWNLOAD_BLOCK_SIZE, fp, nbytes)) { + error = EIO; + goto bad; + } + fp += nbytes; + } + } + /* done with firmware... */ + if (fwboot != NULL) + firmware_put(fwboot, FIRMWARE_UNLOAD); + firmware_put(fw, FIRMWARE_UNLOAD); + /* + * Wait for firmware to startup; we monitor the + * INT_CODE register waiting for a signature to + * written back indicating it's ready to go. + */ + mh->mh_cmdbuf[1] = 0; + /* + * XXX WAR for mfg fw download + */ + if (OpMode != HostCmd_STA_MODE) + mwlTriggerPciCmd(mh); + for (i = 0; i < FW_MAX_NUM_CHECKS; i++) { + WR4(mh, MACREG_REG_GEN_PTR, OpMode); + DELAY(FW_CHECK_USECS); + if (RD4(mh, MACREG_REG_INT_CODE) == FwReadySignature) { + WR4(mh, MACREG_REG_INT_CODE, 0x00); + return mwlResetHalState(mh); + } + } + return ETIMEDOUT; +bad: + mwlFwReset(mh); +bad2: + /* done with firmware... */ + if (fwboot != NULL) + firmware_put(fwboot, FIRMWARE_UNLOAD); + firmware_put(fw, FIRMWARE_UNLOAD); + return error; +} + +#ifdef MWLHAL_DEBUG +static const char * +mwlcmdname(int cmd) +{ + static char buf[12]; +#define CMD(x) case HostCmd_CMD_##x: return #x + switch (cmd) { + CMD(CODE_DNLD); + CMD(GET_HW_SPEC); + CMD(SET_HW_SPEC); + CMD(MAC_MULTICAST_ADR); + CMD(802_11_GET_STAT); + CMD(MAC_REG_ACCESS); + CMD(BBP_REG_ACCESS); + CMD(RF_REG_ACCESS); + CMD(802_11_RADIO_CONTROL); + CMD(802_11_RF_TX_POWER); + CMD(802_11_RF_ANTENNA); + CMD(SET_BEACON); + CMD(SET_RF_CHANNEL); + CMD(SET_AID); + CMD(SET_INFRA_MODE); + CMD(SET_G_PROTECT_FLAG); + CMD(802_11_RTS_THSD); + CMD(802_11_SET_SLOT); + CMD(SET_EDCA_PARAMS); + CMD(802_11H_DETECT_RADAR); + CMD(SET_WMM_MODE); + CMD(HT_GUARD_INTERVAL); + CMD(SET_FIXED_RATE); + CMD(SET_LINKADAPT_CS_MODE); + CMD(SET_MAC_ADDR); + CMD(SET_RATE_ADAPT_MODE); + CMD(BSS_START); + CMD(SET_NEW_STN); + CMD(SET_KEEP_ALIVE); + CMD(SET_APMODE); + CMD(SET_SWITCH_CHANNEL); + CMD(UPDATE_ENCRYPTION); + CMD(BASTREAM); + CMD(SET_RIFS); + CMD(SET_N_PROTECT_FLAG); + CMD(SET_N_PROTECT_OPMODE); + CMD(SET_OPTIMIZATION_LEVEL); + CMD(GET_CALTABLE); + CMD(SET_MIMOPSHT); + CMD(GET_BEACON); + CMD(SET_REGION_CODE); + CMD(SET_POWERSAVESTATION); + CMD(SET_TIM); + CMD(GET_TIM); + CMD(GET_SEQNO); + } + snprintf(buf, sizeof(buf), "0x%x", cmd); + return buf; +#undef CMD +} + +static void +dumpresult(struct mwl_hal_priv *mh, int showresult) +{ + const FWCmdHdr *h = (const FWCmdHdr *)mh->mh_cmdbuf; + const uint8_t *cp; + int len, i; + + len = le16toh(h->Length); +#ifdef MWL_MBSS_SUPPORT + device_printf(mh->mh_dev, "Cmd %s Length %d SeqNum %d MacId %d", + mwlcmdname(le16toh(h->Cmd) &~ 0x8000), len, h->SeqNum, h->MacId); +#else + device_printf(mh->mh_dev, "Cmd %s Length %d SeqNum %d", + mwlcmdname(le16toh(h->Cmd) &~ 0x8000), len, le16toh(h->SeqNum)); +#endif + if (showresult) { + const char *results[] = + { "OK", "ERROR", "NOT_SUPPORT", "PENDING", "BUSY", + "PARTIAL_DATA" }; + int result = le16toh(h->Result); + + if (result <= HostCmd_RESULT_PARTIAL_DATA) + printf(" Result %s", results[result]); + else + printf(" Result %d", result); + } + cp = (const uint8_t *)h; + for (i = 0; i < len; i++) { + if ((i % 16) == 0) + printf("\n%02x", cp[i]); + else + printf(" %02x", cp[i]); + } + printf("\n"); +} +#endif /* MWLHAL_DEBUG */ diff --git a/sys/dev/mwl/mwlhal.h b/sys/dev/mwl/mwlhal.h new file mode 100644 index 0000000000000..c82b427f00791 --- /dev/null +++ b/sys/dev/mwl/mwlhal.h @@ -0,0 +1,666 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +#ifndef _MWL_HAL_H_ +#define _MWL_HAL_H_ +/* + * Hardware Access Layer for Marvell Wireless Devices. + */ + +#define MWL_MBSS_SUPPORT /* enable multi-bss support */ + +/* + * Define total number of TX queues in the shared memory. + * This count includes the EDCA queues, Block Ack queues, and HCCA queues + * In addition to this, there could be a management packet queue some + * time in the future + */ +#define MWL_NUM_EDCA_QUEUES 4 +#define MWL_NUM_HCCA_QUEUES 0 +#define MWL_NUM_BA_QUEUES 0 +#define MWL_NUM_MGMT_QUEUES 0 +#define MWL_NUM_TX_QUEUES \ + (MWL_NUM_EDCA_QUEUES + MWL_NUM_HCCA_QUEUES + MWL_NUM_BA_QUEUES + \ + MWL_NUM_MGMT_QUEUES) +#define MWL_MAX_RXWCB_QUEUES 1 + +#define MWL_MAX_SUPPORTED_RATES 12 +#define MWL_MAX_SUPPORTED_MCS 32 + +typedef enum { + MWL_HAL_OK +} MWL_HAL_STATUS; + +/* + * Transmit queue assignment. + */ +enum { + MWL_WME_AC_BK = 0, /* background access category */ + MWL_WME_AC_BE = 1, /* best effort access category*/ + MWL_WME_AC_VI = 2, /* video access category */ + MWL_WME_AC_VO = 3, /* voice access category */ +}; + +struct device; + +struct mwl_hal { + bus_space_handle_t mh_ioh; /* BAR 1 copied from softc */ + bus_space_tag_t mh_iot; + uint32_t mh_imask; /* interrupt mask */ + /* remainder is opaque to driver */ +}; +struct mwl_hal *mwl_hal_attach(struct device *dev, uint16_t devid, + bus_space_handle_t ioh, bus_space_tag_t iot, bus_dma_tag_t tag); +void mwl_hal_detach(struct mwl_hal *); + +/* + * Query whether multi-bss support is available/enabled. + */ +int mwl_hal_ismbsscapable(struct mwl_hal *); + +typedef enum { + MWL_HAL_AP, + MWL_HAL_STA, /* infrastructure mode */ + MWL_HAL_IBSS /* ibss/adhoc mode */ +} MWL_HAL_BSSTYPE; +struct mwl_hal_vap; + +struct mwl_hal_vap *mwl_hal_newvap(struct mwl_hal *, MWL_HAL_BSSTYPE, + const uint8_t mac[6]); +void mwl_hal_delvap(struct mwl_hal_vap *); + +enum { + MWL_HAL_DEBUG_SENDCMD = 0x00000001, + MWL_HAL_DEBUG_CMDDONE = 0x00000002, + MWL_HAL_DEBUG_IGNHANG = 0x00000004, +}; +void mwl_hal_setdebug(struct mwl_hal *, int); +int mwl_hal_getdebug(struct mwl_hal *); + +typedef struct { + uint16_t freqLow, freqHigh; + int nchannels; + struct mwl_hal_channel { + uint16_t freq; /* channel center */ + uint8_t ieee; /* channel number */ + int8_t maxTxPow; /* max tx power (dBm) */ + uint8_t targetPowers[4];/* target powers (dBm) */ +#define MWL_HAL_MAXCHAN 40 + } channels[MWL_HAL_MAXCHAN]; +} MWL_HAL_CHANNELINFO; +int mwl_hal_getchannelinfo(struct mwl_hal *, int band, int chw, + const MWL_HAL_CHANNELINFO **); + +/* + * Return the current ISR setting and clear the cause. + */ +static __inline void +mwl_hal_getisr(struct mwl_hal *mh, uint32_t *status) +{ +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 // (From ARM to host) +#define MACREG_REG_INT_CODE 0x00000C14 + uint32_t cause; + + cause = bus_space_read_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_A2H_INTERRUPT_CAUSE); + if (cause == 0xffffffff) { /* card removed */ + cause = 0; + } else if (cause != 0) { + /* clear cause bits */ + bus_space_write_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_A2H_INTERRUPT_CAUSE, cause &~ mh->mh_imask); + (void) bus_space_read_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_INT_CODE); + cause &= mh->mh_imask; + } + *status = cause; +#undef MACREG_REG_INT_CODE +#undef MACREG_REG_A2H_INTERRUPT_CAUSE +} + +void mwl_hal_intrset(struct mwl_hal *mh, uint32_t mask); + +/* + * Kick the firmware to tell it there are new tx descriptors + * for processing. The driver says what h/w q has work in + * case the f/w ever gets smarter. + */ +static __inline void +mwl_hal_txstart(struct mwl_hal *mh, int qnum) +{ +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 // (From host to ARM) +#define MACREG_H2ARIC_BIT_PPA_READY 0x00000001 // bit 0 +#define MACREG_REG_INT_CODE 0x00000C14 + + bus_space_write_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_PPA_READY); + (void) bus_space_read_4(mh->mh_iot, mh->mh_ioh, MACREG_REG_INT_CODE); +#undef MACREG_REG_INT_CODE +#undef MACREG_H2ARIC_BIT_PPA_READY +#undef MACREG_REG_H2A_INTERRUPT_EVENTS +} + +void mwl_hal_cmddone(struct mwl_hal *mh); + +typedef struct { + uint32_t FreqBand : 6, +#define MWL_FREQ_BAND_2DOT4GHZ 0x1 +#define MWL_FREQ_BAND_5GHZ 0x4 + ChnlWidth: 5, +#define MWL_CH_10_MHz_WIDTH 0x1 +#define MWL_CH_20_MHz_WIDTH 0x2 +#define MWL_CH_40_MHz_WIDTH 0x4 + ExtChnlOffset: 2, +#define MWL_EXT_CH_NONE 0x0 +#define MWL_EXT_CH_ABOVE_CTRL_CH 0x1 +#define MWL_EXT_CH_BELOW_CTRL_CH 0x3 + : 19; /* reserved */ +} MWL_HAL_CHANNEL_FLAGS; + +typedef struct { + uint32_t channel; + MWL_HAL_CHANNEL_FLAGS channelFlags; +} MWL_HAL_CHANNEL; + +/* + * Get Hardware/Firmware capabilities. + */ +struct mwl_hal_hwspec { + uint8_t hwVersion; /* version of the HW */ + uint8_t hostInterface; /* host interface */ + uint16_t maxNumWCB; /* max # of WCB FW handles */ + uint16_t maxNumMCAddr; /* max # of mcast addresses FW handles*/ + uint16_t maxNumTxWcb; /* max # of tx descs per WCB */ + uint8_t macAddr[6]; /* MAC address programmed in HW */ + uint16_t regionCode; /* EEPROM region code */ + uint16_t numAntennas; /* Number of antenna used */ + uint32_t fwReleaseNumber; /* firmware release number */ + uint32_t wcbBase0; + uint32_t rxDescRead; + uint32_t rxDescWrite; + uint32_t ulFwAwakeCookie; + uint32_t wcbBase[4]; +}; +int mwl_hal_gethwspecs(struct mwl_hal *mh, struct mwl_hal_hwspec *); + +/* + * Supply tx/rx dma-related settings to the firmware. + */ +struct mwl_hal_txrxdma { + uint32_t maxNumWCB; /* max # of WCB FW handles */ + uint32_t maxNumTxWcb; /* max # of tx descs per WCB */ + uint32_t rxDescRead; + uint32_t rxDescWrite; + uint32_t wcbBase[4]; +}; +int mwl_hal_sethwdma(struct mwl_hal *mh, const struct mwl_hal_txrxdma *); + +/* + * Get Hardware Statistics. + * + * Items marked with ! are deprecated and not ever updated. In + * some cases this is because work has been moved to the host (e.g. + * rx defragmentation). + */ +struct mwl_hal_hwstats { + uint32_t TxRetrySuccesses; /* tx success w/ 1 retry */ + uint32_t TxMultipleRetrySuccesses;/* tx success w/ >1 retry */ + uint32_t TxFailures; /* tx fail due to no ACK */ + uint32_t RTSSuccesses; /* CTS rx'd for RTS */ + uint32_t RTSFailures; /* CTS not rx'd for RTS */ + uint32_t AckFailures; /* same as TxFailures */ + uint32_t RxDuplicateFrames; /* rx discard for dup seqno */ + uint32_t FCSErrorCount; /* rx discard for bad FCS */ + uint32_t TxWatchDogTimeouts; /* MAC tx hang (f/w recovery) */ + uint32_t RxOverflows; /* no f/w buffer for rx data */ + uint32_t RxFragErrors; /* !rx fail due to defrag */ + uint32_t RxMemErrors; /* out of mem or desc corrupted + in some way */ + uint32_t RxPointerErrors; /* MAC internal ptr problem */ + uint32_t TxUnderflows; /* !tx underflow on dma */ + uint32_t TxDone; /* MAC tx ops completed + (possibly w/ error) */ + uint32_t TxDoneBufTryPut; /* ! */ + uint32_t TxDoneBufPut; /* same as TxDone */ + uint32_t Wait4TxBuf; /* !no f/w buf avail when + supplied a tx descriptor */ + uint32_t TxAttempts; /* tx descriptors processed */ + uint32_t TxSuccesses; /* tx attempts successful */ + uint32_t TxFragments; /* tx with fragmentation */ + uint32_t TxMulticasts; /* tx multicast frames */ + uint32_t RxNonCtlPkts; /* rx non-control frames */ + uint32_t RxMulticasts; /* rx multicast frames */ + uint32_t RxUndecryptableFrames; /* rx failed due to crypto */ + uint32_t RxICVErrors; /* rx failed due to ICV check */ + uint32_t RxExcludedFrames; /* rx discarded, e.g. bssid */ +}; +int mwl_hal_gethwstats(struct mwl_hal *mh, struct mwl_hal_hwstats *); + +/* + * Set HT Guard Interval. + * + * GIType = 0: enable long and short GI + * GIType = 1: enable short GI + * GIType = 2: enable long GI + */ +int mwl_hal_sethtgi(struct mwl_hal_vap *, int GIType); + +/* + * Set Radio Configuration. + * + * onoff != 0 turns radio on; otherwise off. + * if radio is enabled, the preamble is set too. + */ +typedef enum { + WL_LONG_PREAMBLE = 1, + WL_SHORT_PREAMBLE = 3, + WL_AUTO_PREAMBLE = 5, +} MWL_HAL_PREAMBLE; +int mwl_hal_setradio(struct mwl_hal *mh, int onoff, MWL_HAL_PREAMBLE preamble); + +/* + * Set Antenna Configuration (legacy operation). + * + * The RX antenna can be selected using the the bitmask + * ant (bit 0 = antenna 1, bit 1 = antenna 2, etc.) + * (diversity?XXX) + */ +typedef enum { + WL_ANTENNATYPE_RX = 1, + WL_ANTENNATYPE_TX = 2, +} MWL_HAL_ANTENNA; +int mwl_hal_setantenna(struct mwl_hal *mh, MWL_HAL_ANTENNA dirSet, int ant); + +/* + * Set the threshold for using RTS on TX. + */ +int mwl_hal_setrtsthreshold(struct mwl_hal_vap *, int threshold); + +/* + * Set the adapter to operate in infrastructure mode. + */ +int mwl_hal_setinframode(struct mwl_hal_vap *); + +/* + * Set Radar Detection Configuration. + */ +typedef enum { + DR_DFS_DISABLE = 0, + DR_CHK_CHANNEL_AVAILABLE_START = 1, + DR_CHK_CHANNEL_AVAILABLE_STOP = 2, + DR_IN_SERVICE_MONITOR_START = 3 +} MWL_HAL_RADAR; +int mwl_hal_setradardetection(struct mwl_hal *mh, MWL_HAL_RADAR action); +/* + * Set the region code that selects the radar bin'ing agorithm. + */ +int mwl_hal_setregioncode(struct mwl_hal *mh, int regionCode); + +/* + * Initiate an 802.11h-based channel switch. The CSA ie + * is included in the next beacon(s) using the specified + * information and the firmware counts down until switch + * time after which it notifies the driver by delivering + * an interrupt with MACREG_A2HRIC_BIT_CHAN_SWITCH set in + * the cause register. + */ +int mwl_hal_setchannelswitchie(struct mwl_hal *, + const MWL_HAL_CHANNEL *nextchan, uint32_t mode, uint32_t count); + +/* + * Set regdomain code (IEEE SKU). + */ +enum { + DOMAIN_CODE_FCC = 0x10, /* USA */ + DOMAIN_CODE_IC = 0x20, /* Canda */ + DOMAIN_CODE_ETSI = 0x30, /* Europe */ + DOMAIN_CODE_SPAIN = 0x31, /* Spain */ + DOMAIN_CODE_FRANCE = 0x32, /* France */ + DOMAIN_CODE_ETSI_131 = 0x130,/* ETSI w/ 1.3.1 radar type */ + DOMAIN_CODE_MKK = 0x40, /* Japan */ + DOMAIN_CODE_MKK2 = 0x41, /* Japan w/ 10MHz chan spacing */ + DOMAIN_CODE_DGT = 0x80, /* Taiwan */ + DOMAIN_CODE_AUS = 0x81, /* Australia */ +}; + +/* + * Transmit rate control. Rate codes with bit 0x80 set are + * interpreted as MCS codes (this limits us to 0-127). The + * transmit rate can be set to a single fixed rate or can + * be configured to start at an initial rate and drop based + * on retry counts. + */ +typedef enum { + RATE_AUTO = 0, /* rate selected by firmware */ + RATE_FIXED = 2, /* rate fixed */ + RATE_FIXED_DROP = 1, /* rate starts fixed but may drop */ +} MWL_HAL_TXRATE_HANDLING; + +typedef struct { + uint8_t McastRate; /* rate for multicast frames */ +#define RATE_MCS 0x80 /* rate is an MCS index */ + uint8_t MgtRate; /* rate for management frames */ + struct { + uint8_t TryCount; /* try this many times */ + uint8_t Rate; /* use this tx rate */ + } RateSeries[4]; /* rate series */ +} MWL_HAL_TXRATE; + +int mwl_hal_settxrate(struct mwl_hal_vap *, + MWL_HAL_TXRATE_HANDLING handling, const MWL_HAL_TXRATE *rate); +/* NB: hack for setting rates while scanning */ +int mwl_hal_settxrate_auto(struct mwl_hal *, const MWL_HAL_TXRATE *rate); + +/* + * Set the Slot Time Configuration. + * NB: usecs must either be 9 or 20 for now. + */ +int mwl_hal_setslottime(struct mwl_hal *mh, int usecs); + +/* + * Adjust current transmit power settings according to powerLevel. + * This translates to low/medium/high use of the current tx power rate tables. + */ +int mwl_hal_adjusttxpower(struct mwl_hal *, uint32_t powerLevel); +/* + * Set the transmit power for the specified channel; the power + * is taken from the calibration data and capped according to + * the specified max tx power (in dBm). + */ +int mwl_hal_settxpower(struct mwl_hal *, const MWL_HAL_CHANNEL *, + uint8_t maxtxpow); + +/* + * Set the Multicast Address Filter. + * A packed array addresses is specified. + */ +#define MWL_HAL_MCAST_MAX 32 +int mwl_hal_setmcast(struct mwl_hal *mh, int nmc, const uint8_t macs[]); + +/* + * Crypto Configuration. + */ +typedef struct { + uint16_t pad; + uint16_t keyTypeId; +#define KEY_TYPE_ID_WEP 0 +#define KEY_TYPE_ID_TKIP 1 +#define KEY_TYPE_ID_AES 2 /* AES-CCMP */ + uint32_t keyFlags; +#define KEY_FLAG_INUSE 0x00000001 /* indicate key is in use */ +#define KEY_FLAG_RXGROUPKEY 0x00000002 /* Group key for RX only */ +#define KEY_FLAG_TXGROUPKEY 0x00000004 /* Group key for TX */ +#define KEY_FLAG_PAIRWISE 0x00000008 /* pairwise */ +#define KEY_FLAG_RXONLY 0x00000010 /* only used for RX */ +#define KEY_FLAG_AUTHENTICATOR 0x00000020 /* Key is for Authenticator */ +#define KEY_FLAG_TSC_VALID 0x00000040 /* Sequence counters valid */ +#define KEY_FLAG_WEP_TXKEY 0x01000000 /* Tx key for WEP */ +#define KEY_FLAG_MICKEY_VALID 0x02000000 /* Tx/Rx MIC keys are valid */ + uint32_t keyIndex; /* for WEP only; actual key index */ + uint16_t keyLen; /* key size in bytes */ + union { /* key material, keyLen gives size */ + uint8_t wep[16]; /* enough for 128 bits */ + uint8_t aes[16]; + struct { + /* NB: group or pairwise key is determined by keyFlags */ + uint8_t keyMaterial[16]; + uint8_t txMic[8]; + uint8_t rxMic[8]; + struct { + uint16_t low; + uint32_t high; + } rsc; + struct { + uint16_t low; + uint32_t high; + } tsc; + } __packed tkip; + }__packed key; +} __packed MWL_HAL_KEYVAL; + +/* + * Plumb a unicast/group key. The mac address identifies + * the station, use the broadcast address for group keys. + */ +int mwl_hal_keyset(struct mwl_hal_vap *, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[6]); + +/* + * Plumb a unicast/group key. The mac address identifies + * the station, use the broadcast address for group keys. + */ +int mwl_hal_keyreset(struct mwl_hal_vap *, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[6]); + +/* + * Set the MAC address. + */ +int mwl_hal_setmac(struct mwl_hal_vap *, const uint8_t addr[6]); + +/* + * Set the beacon frame contents. The firmware will modify the + * frame only to add CSA and WME ie's and to fill in dynamic fields + * such as the sequence #.. + */ +int mwl_hal_setbeacon(struct mwl_hal_vap *, const void *, size_t); + +/* + * Handle power save operation for AP operation when offloaded to + * the host (SET_HW_SPEC_HOST_POWERSAVE). mwl_hal_setbss_powersave + * informs the firmware whether 1+ associated stations are in power + * save mode (it will then buffer mcast traffic). mwl_hal_setsta_powersave + * specifies a change in power save state for an associated station. + */ +int mwl_hal_setpowersave_bss(struct mwl_hal_vap *, uint8_t nsta); +int mwl_hal_setpowersave_sta(struct mwl_hal_vap *, uint16_t aid, int ena); + +/* + * Set Association Configuration for station operation. + */ +int mwl_hal_setassocid(struct mwl_hal_vap *, const uint8_t bssId[6], + uint16_t assocId); + +/* + * Set the current channel. + */ +int mwl_hal_setchannel(struct mwl_hal *mh, const MWL_HAL_CHANNEL *c); + +/* + * A-MPDU Block Ack (BA) stream support. There are several + * streams that the driver must multiplex. Once assigned + * to a station the driver queues frames to a corresponding + * transmit queue and the firmware handles all the work. + * + * XXX no way to find out how many streams are supported + */ +typedef struct { + void *data[2]; /* opaque data */ + int txq; +} MWL_HAL_BASTREAM; + +const MWL_HAL_BASTREAM *mwl_hal_bastream_alloc(struct mwl_hal *mh, + int ba_type, const uint8_t Macaddr[16], uint8_t Tid, + uint8_t ParamInfo, void *, void *); +const MWL_HAL_BASTREAM *mwl_hal_bastream_lookup(struct mwl_hal *mh, int s); +int mwl_hal_bastream_create(struct mwl_hal *mh, const MWL_HAL_BASTREAM *, + int BarThrs, int WindowSize, uint16_t seqno); +int mwl_hal_bastream_destroy(struct mwl_hal *mh, const MWL_HAL_BASTREAM *); +int mwl_hal_bastream_get_seqno(struct mwl_hal *mh, const MWL_HAL_BASTREAM *, + uint16_t *pseqno); +int mwl_hal_getwatchdogbitmap(struct mwl_hal *mh, uint8_t bitmap[1]); +/* for sysctl hookup for debugging */ +void mwl_hal_setbastreams(struct mwl_hal *mh, int mask); +int mwl_hal_getbastreams(struct mwl_hal *mh); + +/* + * Inform the firmware of a new association station. + * The address is the MAC address of the peer station. + * The AID is supplied sans the 0xc000 bits. The station + * ID is defined by the caller. The peer information must + * be supplied. + * + * NB: All values are in host byte order; any byte swapping + * is handled by the hal. + */ +typedef struct { + uint32_t LegacyRateBitMap; + uint32_t HTRateBitMap; + uint16_t CapInfo; + uint16_t HTCapabilitiesInfo; + uint8_t MacHTParamInfo; + uint8_t Rev; + struct { + uint8_t ControlChan; + uint8_t AddChan; + uint8_t OpMode; + uint8_t stbc; + } __packed AddHtInfo; +} __packed MWL_HAL_PEERINFO; +int mwl_hal_newstation(struct mwl_hal_vap *, const uint8_t addr[6], + uint16_t aid, uint16_t sid, const MWL_HAL_PEERINFO *, + int isQosSta, int wmeInfo); +int mwl_hal_delstation(struct mwl_hal_vap *, const uint8_t addr[6]); + +/* + * Prod the firmware to age packets on station power + * save queues and reap frames on the tx aggregation q's. + */ +int mwl_hal_setkeepalive(struct mwl_hal *mh); + +typedef enum { + AP_MODE_B_ONLY = 1, + AP_MODE_G_ONLY = 2, + AP_MODE_MIXED = 3, + AP_MODE_N_ONLY = 4, + AP_MODE_BandN = 5, + AP_MODE_GandN = 6, + AP_MODE_BandGandN = 7, + AP_MODE_A_ONLY = 8, + AP_MODE_AandG = 10, + AP_MODE_AandN = 12, +} MWL_HAL_APMODE; +int mwl_hal_setapmode(struct mwl_hal_vap *, MWL_HAL_APMODE); + +/* + * Enable/disable firmware operation. mwl_hal_start is + * also used to sync state updates, e.g. beacon frame + * reconstruction after content changes. + */ +int mwl_hal_stop(struct mwl_hal_vap *); +int mwl_hal_start(struct mwl_hal_vap *); + +/* + * Enable/disable 11g protection use. This call specifies + * the ERP information element flags to use. + */ +int mwl_hal_setgprot(struct mwl_hal *, int); + +/* + * Enable/disable WMM support. + */ +int mwl_hal_setwmm(struct mwl_hal *mh, int onoff); + +/* + * Configure WMM EDCA parameters for the specified h/w ring. + */ +int mwl_hal_setedcaparams(struct mwl_hal *mh, uint8_t qnum, + uint32_t CWmin, uint32_t CWmax, uint8_t AIFSN, uint16_t TXOPLimit); + +/* + * Configure rate adaptation for indooor/outdoor operation. + * XXX wtf? + */ +int mwl_hal_setrateadaptmode(struct mwl_hal *mh, uint16_t mode); + +typedef enum { + CSMODE_CONSERVATIVE = 0, + CSMODE_AGGRESSIVE = 1, + CSMODE_AUTO_ENA = 2, + CSMODE_AUTO_DIS = 3, +} MWL_HAL_CSMODE; +int mwl_hal_setcsmode(struct mwl_hal *mh, MWL_HAL_CSMODE csmode); + +/* + * Configure 11n protection on/off. + */ +typedef enum { + HTPROTECT_NONE = 0, /* disable */ + HTPROTECT_OPT = 1, /* optional */ + HTPROTECT_HT20 = 2, /* protect only HT20 */ + HTPROTECT_HT2040 = 3, /* protect HT20/40 */ + HTPROTECT_AUTO = 4, /* automatic */ +} MWL_HAL_HTPROTECT; +int mwl_hal_setnprot(struct mwl_hal_vap *, MWL_HAL_HTPROTECT mode); +/* + * Configure 11n protection mechanism for when protection is enabled. + */ +int mwl_hal_setnprotmode(struct mwl_hal_vap *, uint8_t mode); + +/* + * Enable/disable Marvell "turbo mode"". + */ +int mwl_hal_setoptimizationlevel(struct mwl_hal *mh, int onoff); + +/* + * Set MIMO Power Save handling for a station; the enable and mode + * values come directly from the Action frame. + */ +int mwl_hal_setmimops(struct mwl_hal *mh, const uint8_t addr[6], + uint8_t enable, uint8_t mode); + +/* + * Retrieve the region/country code from the EEPROM. + */ +int mwl_hal_getregioncode(struct mwl_hal *mh, uint8_t *countryCode); +int mwl_hal_GetBeacon(struct mwl_hal *mh, uint8_t *pBcn, uint16_t *pLen); +int mwl_hal_SetRifs(struct mwl_hal *mh, uint8_t QNum); + +/* + * Set/get promiscuous mode. + */ +int mwl_hal_setpromisc(struct mwl_hal *, int ena); +int mwl_hal_getpromisc(struct mwl_hal *); + +/* + * Diagnostic interface. This is an open-ended interface that + * is opaque to applications. Diagnostic programs use this to + * retrieve internal data structures, etc. There is no guarantee + * that calling conventions for calls other than MWL_DIAG_REVS + * are stable between HAL releases; a diagnostic application must + * use the HAL revision information to deal with ABI/API differences. + */ +int mwl_hal_getdiagstate(struct mwl_hal *mh, int request, + const void *args, uint32_t argsize, + void **result, uint32_t *resultsize); + +int mwl_hal_fwload(struct mwl_hal *mh, void *fwargs); +#endif /* _MWL_HAL_H_ */ diff --git a/sys/dev/mwl/mwlreg.h b/sys/dev/mwl/mwlreg.h new file mode 100644 index 0000000000000..17ebd61580906 --- /dev/null +++ b/sys/dev/mwl/mwlreg.h @@ -0,0 +1,1352 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 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. + * + * $FreeBSD$ + */ + +/* + * Definitions for the Marvell Wireless LAN controller Hardware Access Layer. + */ +#ifndef _MWL_HALREG_H_ +#define _MWL_HALREG_H_ + +#define MWL_ANT_INFO_SUPPORT /* per-antenna data in rx descriptor */ + +#define MACREG_REG_TSF_LOW 0xa600 /* TSF lo */ +#define MACREG_REG_TSF_HIGH 0xa604 /* TSF hi */ +#define MACREG_REG_CHIP_REV 0xa814 /* chip rev */ + +// Map to 0x80000000 (Bus control) on BAR0 +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 // (From host to ARM) + +#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C // (From ARM to host) + + +// Map to 0x80000000 on BAR1 +#define MACREG_REG_GEN_PTR 0x00000C10 +#define MACREG_REG_INT_CODE 0x00000C14 +#define MACREG_REG_SCRATCH 0x00000C40 +#define MACREG_REG_FW_PRESENT 0x0000BFFC + +#define MACREG_REG_PROMISCUOUS 0xA300 + +// Bit definitio for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) +#define MACREG_A2HRIC_BIT_TX_DONE 0x00000001 // bit 0 +#define MACREG_A2HRIC_BIT_RX_RDY 0x00000002 // bit 1 +#define MACREG_A2HRIC_BIT_OPC_DONE 0x00000004 // bit 2 +#define MACREG_A2HRIC_BIT_MAC_EVENT 0x00000008 // bit 3 +#define MACREG_A2HRIC_BIT_RX_PROBLEM 0x00000010 // bit 4 + +#define MACREG_A2HRIC_BIT_RADIO_OFF 0x00000020 // bit 5 +#define MACREG_A2HRIC_BIT_RADIO_ON 0x00000040 // bit 6 + +#define MACREG_A2HRIC_BIT_RADAR_DETECT 0x00000080 // bit 7 + +#define MACREG_A2HRIC_BIT_ICV_ERROR 0x00000100 // bit 8 +#define MACREG_A2HRIC_BIT_MIC_ERROR 0x00000200 // bit 9 +#define MACREG_A2HRIC_BIT_QUEUE_EMPTY 0x00004000 +#define MACREG_A2HRIC_BIT_QUEUE_FULL 0x00000800 +#define MACREG_A2HRIC_BIT_CHAN_SWITCH 0x00001000 +#define MACREG_A2HRIC_BIT_TX_WATCHDOG 0x00002000 +#define MACREG_A2HRIC_BIT_BA_WATCHDOG 0x00000400 +#define ISR_SRC_BITS ((MACREG_A2HRIC_BIT_RX_RDY) | \ + (MACREG_A2HRIC_BIT_TX_DONE) | \ + (MACREG_A2HRIC_BIT_OPC_DONE) | \ + (MACREG_A2HRIC_BIT_MAC_EVENT)| \ + (MACREG_A2HRIC_BIT_MIC_ERROR)| \ + (MACREG_A2HRIC_BIT_ICV_ERROR)| \ + (MACREG_A2HRIC_BIT_RADAR_DETECT)| \ + (MACREG_A2HRIC_BIT_CHAN_SWITCH)| \ + (MACREG_A2HRIC_BIT_TX_WATCHDOG)| \ + (MACREG_A2HRIC_BIT_QUEUE_EMPTY)| \ + (MACREG_A2HRIC_BIT_BA_WATCHDOG)) + +#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS + + +// Bit definitio for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) +#define MACREG_H2ARIC_BIT_PPA_READY 0x00000001 // bit 0 +#define MACREG_H2ARIC_BIT_DOOR_BELL 0x00000002 // bit 1 +#define ISR_RESET (1<<15) + +// INT code register event definition +#define MACREG_INT_CODE_CMD_FINISHED 0x00000005 + +/* + * Host/Firmware Interface definitions. + */ + +/** + * Define total number of TX queues in the shared memory. + * This count includes the EDCA queues, Block Ack queues, and HCCA queues + * In addition to this, there could be a management packet queue some + * time in the future + */ +#define NUM_EDCA_QUEUES 4 +#define NUM_HCCA_QUEUES 0 +#define NUM_BA_QUEUES 0 +#define NUM_MGMT_QUEUES 0 +#define TOTAL_TX_QUEUES \ + (NUM_EDCA_QUEUES + NUM_HCCA_QUEUES + NUM_BA_QUEUES + NUM_MGMT_QUEUES) +#define MAX_TXWCB_QUEUES TOTAL_TX_QUEUES +#define MAX_RXWCB_QUEUES 1 + +//============================================================================= +// PUBLIC DEFINITIONS +//============================================================================= + +#define RATE_INDEX_MAX_ARRAY 14 +#define WOW_MAX_STATION 32 + +/* + * Hardware tx/rx descriptors. + * + * NB: tx descriptor size must match f/w expected size + * because f/w prefetch's the next descriptor linearly + * and doesn't chase the next pointer. + */ +struct mwl_txdesc { + uint32_t Status; +#define EAGLE_TXD_STATUS_IDLE 0x00000000 +#define EAGLE_TXD_STATUS_USED 0x00000001 +#define EAGLE_TXD_STATUS_OK 0x00000001 +#define EAGLE_TXD_STATUS_OK_RETRY 0x00000002 +#define EAGLE_TXD_STATUS_OK_MORE_RETRY 0x00000004 +#define EAGLE_TXD_STATUS_MULTICAST_TX 0x00000008 +#define EAGLE_TXD_STATUS_BROADCAST_TX 0x00000010 +#define EAGLE_TXD_STATUS_FAILED_LINK_ERROR 0x00000020 +#define EAGLE_TXD_STATUS_FAILED_EXCEED_LIMIT 0x00000040 +#define EAGLE_TXD_STATUS_FAILED_XRETRY EAGLE_TXD_STATUS_FAILED_EXCEED_LIMIT +#define EAGLE_TXD_STATUS_FAILED_AGING 0x00000080 +#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 + uint8_t DataRate; + uint8_t TxPriority; + uint16_t QosCtrl; + uint32_t PktPtr; + uint16_t PktLen; + uint8_t DestAddr[6]; + uint32_t pPhysNext; + uint32_t SapPktInfo; +#define EAGLE_TXD_MODE_BONLY 1 +#define EAGLE_TXD_MODE_GONLY 2 +#define EAGLE_TXD_MODE_BG 3 +#define EAGLE_TXD_MODE_NONLY 4 +#define EAGLE_TXD_MODE_BN 5 +#define EAGLE_TXD_MODE_GN 6 +#define EAGLE_TXD_MODE_BGN 7 +#define EAGLE_TXD_MODE_AONLY 8 +#define EAGLE_TXD_MODE_AG 10 +#define EAGLE_TXD_MODE_AN 12 + uint16_t Format; +#define EAGLE_TXD_FORMAT 0x0001 /* frame format/rate */ +#define EAGLE_TXD_FORMAT_LEGACY 0x0000 /* legacy rate frame */ +#define EAGLE_TXD_FORMAT_HT 0x0001 /* HT rate frame */ +#define EAGLE_TXD_GI 0x0002 /* guard interval */ +#define EAGLE_TXD_GI_SHORT 0x0002 /* short guard interval */ +#define EAGLE_TXD_GI_LONG 0x0000 /* long guard interval */ +#define EAGLE_TXD_CHW 0x0004 /* channel width */ +#define EAGLE_TXD_CHW_20 0x0000 /* 20MHz channel width */ +#define EAGLE_TXD_CHW_40 0x0004 /* 40MHz channel width */ +#define EAGLE_TXD_RATE 0x01f8 /* tx rate (legacy)/ MCS */ +#define EAGLE_TXD_RATE_S 3 +#define EAGLE_TXD_ADV 0x0600 /* advanced coding */ +#define EAGLE_TXD_ADV_S 9 +#define EAGLE_TXD_ADV_NONE 0x0000 +#define EAGLE_TXD_ADV_LDPC 0x0200 +#define EAGLE_TXD_ADV_RS 0x0400 +/* NB: 3 is reserved */ +#define EAGLE_TXD_ANTENNA 0x1800 /* antenna select */ +#define EAGLE_TXD_ANTENNA_S 11 +#define EAGLE_TXD_EXTCHAN 0x6000 /* extension channel */ +#define EAGLE_TXD_EXTCHAN_S 13 +#define EAGLE_TXD_EXTCHAN_HI 0x0000 /* above */ +#define EAGLE_TXD_EXTCHAN_LO 0x2000 /* below */ +#define EAGLE_TXD_PREAMBLE 0x8000 +#define EAGLE_TXD_PREAMBLE_SHORT 0x8000 /* short preamble */ +#define EAGLE_TXD_PREAMBLE_LONG 0x0000 /* long preamble */ + uint16_t pad; /* align to 4-byte boundary */ +#define EAGLE_TXD_FIXED_RATE 0x0100 /* get tx rate from Format */ +#define EAGLE_TXD_DONT_AGGR 0x0200 /* don't aggregate frame */ +} __packed; + +struct mwl_ant_info { + uint8_t rssi_a; /* RSSI for antenna A */ + uint8_t rssi_b; /* RSSI for antenna B */ + uint8_t rssi_c; /* RSSI for antenna C */ + uint8_t rsvd1; /* Reserved */ + uint8_t nf_a; /* Noise floor for antenna A */ + uint8_t nf_b; /* Noise floor for antenna B */ + uint8_t nf_c; /* Noise floor for antenna C */ + uint8_t rsvd2; /* Reserved */ + uint8_t nf; /* Noise floor */ +} __packed; + +struct mwl_rxdesc { + uint8_t RxControl; /* control element */ +#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 +#define EAGLE_RXD_CTRL_OS_OWN 0x04 +#define EAGLE_RXD_CTRL_DMA_OWN 0x80 + uint8_t RSSI; /* received signal strengt indication */ + uint8_t Status; /* status field w/ USED bit */ +#define EAGLE_RXD_STATUS_IDLE 0x00 +#define EAGLE_RXD_STATUS_OK 0x01 +#define EAGLE_RXD_STATUS_MULTICAST_RX 0x02 +#define EAGLE_RXD_STATUS_BROADCAST_RX 0x04 +#define EAGLE_RXD_STATUS_FRAGMENT_RX 0x08 +#define EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR 0xff +#define EAGLE_RXD_STATUS_DECRYPT_ERR_MASK 0x80 +#define EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR 0x02 +#define EAGLE_RXD_STATUS_WEP_ICV_DECRYPT_ERR 0x04 +#define EAGLE_RXD_STATUS_TKIP_ICV_DECRYPT_ERR 0x08 + uint8_t Channel; /* channel # pkt received on */ + uint16_t PktLen; /* total length of received data */ + uint8_t SQ2; /* not used */ + uint8_t Rate; /* received data rate */ + uint32_t pPhysBuffData; /* physical address of payload data */ + uint32_t pPhysNext; /* physical address of next RX desc */ + uint16_t QosCtrl; /* received QosCtrl field variable */ + uint16_t HtSig2; /* like name states */ +#ifdef MWL_ANT_INFO_SUPPORT + struct mwl_ant_info ai; /* antenna info */ +#endif +} __packed; + +/* +// Define OpMode for SoftAP/Station mode +// +// The following mode signature has to be written to PCI scratch register#0 +// right after successfully downloading the last block of firmware and +// before waiting for firmware ready signature + */ +#define HostCmd_STA_MODE 0x5A +#define HostCmd_SOFTAP_MODE 0xA5 + +#define HostCmd_STA_FWRDY_SIGNATURE 0xF0F1F2F4 +#define HostCmd_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 + +//*************************************************************************** +//*************************************************************************** + +//*************************************************************************** + +#define HostCmd_CMD_CODE_DNLD 0x0001 +#define HostCmd_CMD_GET_HW_SPEC 0x0003 +#define HostCmd_CMD_SET_HW_SPEC 0x0004 +#define HostCmd_CMD_MAC_MULTICAST_ADR 0x0010 +#define HostCmd_CMD_802_11_GET_STAT 0x0014 +#define HostCmd_CMD_MAC_REG_ACCESS 0x0019 +#define HostCmd_CMD_BBP_REG_ACCESS 0x001a +#define HostCmd_CMD_RF_REG_ACCESS 0x001b +#define HostCmd_CMD_802_11_RADIO_CONTROL 0x001c +#define HostCmd_CMD_802_11_RF_TX_POWER 0x001e +#define HostCmd_CMD_802_11_RF_ANTENNA 0x0020 +#define HostCmd_CMD_SET_BEACON 0x0100 +#define HostCmd_CMD_SET_AID 0x010d +#define HostCmd_CMD_SET_RF_CHANNEL 0x010a +#define HostCmd_CMD_SET_INFRA_MODE 0x010e +#define HostCmd_CMD_SET_G_PROTECT_FLAG 0x010f +#define HostCmd_CMD_802_11_RTS_THSD 0x0113 +#define HostCmd_CMD_802_11_SET_SLOT 0x0114 + +#define HostCmd_CMD_802_11H_DETECT_RADAR 0x0120 +#define HostCmd_CMD_SET_WMM_MODE 0x0123 +#define HostCmd_CMD_HT_GUARD_INTERVAL 0x0124 +#define HostCmd_CMD_SET_FIXED_RATE 0x0126 +#define HostCmd_CMD_SET_LINKADAPT_CS_MODE 0x0129 +#define HostCmd_CMD_SET_MAC_ADDR 0x0202 +#define HostCmd_CMD_SET_RATE_ADAPT_MODE 0x0203 +#define HostCmd_CMD_GET_WATCHDOG_BITMAP 0x0205 + +//SoftAP command code +#define HostCmd_CMD_BSS_START 0x1100 +#define HostCmd_CMD_SET_NEW_STN 0x1111 +#define HostCmd_CMD_SET_KEEP_ALIVE 0x1112 +#define HostCmd_CMD_SET_APMODE 0x1114 +#define HostCmd_CMD_SET_SWITCH_CHANNEL 0x1121 + +/* + @HWENCR@ + Command to update firmware encryption keys. +*/ +#define HostCmd_CMD_UPDATE_ENCRYPTION 0x1122 +/* + @11E-BA@ + Command to create/destroy block ACK +*/ +#define HostCmd_CMD_BASTREAM 0x1125 +#define HostCmd_CMD_SET_RIFS 0x1126 +#define HostCmd_CMD_SET_N_PROTECT_FLAG 0x1131 +#define HostCmd_CMD_SET_N_PROTECT_OPMODE 0x1132 +#define HostCmd_CMD_SET_OPTIMIZATION_LEVEL 0x1133 +#define HostCmd_CMD_GET_CALTABLE 0x1134 +#define HostCmd_CMD_SET_MIMOPSHT 0x1135 +#define HostCmd_CMD_GET_BEACON 0x1138 +#define HostCmd_CMD_SET_REGION_CODE 0x1139 +#define HostCmd_CMD_SET_POWERSAVESTATION 0x1140 +#define HostCmd_CMD_SET_TIM 0x1141 +#define HostCmd_CMD_GET_TIM 0x1142 +#define HostCmd_CMD_GET_SEQNO 0x1143 + +/* +// Define general result code for each command + */ +#define HostCmd_RESULT_OK 0x0000 // OK +#define HostCmd_RESULT_ERROR 0x0001 // Genenral error +#define HostCmd_RESULT_NOT_SUPPORT 0x0002 // Command is not valid +#define HostCmd_RESULT_PENDING 0x0003 // Command is pending (will be processed) +#define HostCmd_RESULT_BUSY 0x0004 // System is busy (command ignored) +#define HostCmd_RESULT_PARTIAL_DATA 0x0005 // Data buffer is not big enough + + +/* +// Definition of action or option for each command +// +// Define general purpose action + */ +#define HostCmd_ACT_GEN_READ 0x0000 +#define HostCmd_ACT_GEN_WRITE 0x0001 +#define HostCmd_ACT_GEN_GET 0x0000 +#define HostCmd_ACT_GEN_SET 0x0001 +#define HostCmd_ACT_GEN_OFF 0x0000 +#define HostCmd_ACT_GEN_ON 0x0001 + +#define HostCmd_ACT_DIFF_CHANNEL 0x0002 +#define HostCmd_ACT_GEN_SET_LIST 0x0002 + +// Define action or option for HostCmd_FW_USE_FIXED_RATE +#define HostCmd_ACT_USE_FIXED_RATE 0x0001 +#define HostCmd_ACT_NOT_USE_FIXED_RATE 0x0002 + +// Define action or option for HostCmd_CMD_802_11_SET_WEP +//#define HostCmd_ACT_ENABLE 0x0001 // Use MAC control for WEP on/off +//#define HostCmd_ACT_DISABLE 0x0000 +#define HostCmd_ACT_ADD 0x0002 +#define HostCmd_ACT_REMOVE 0x0004 +#define HostCmd_ACT_USE_DEFAULT 0x0008 + +#define HostCmd_TYPE_WEP_40_BIT 0x0001 // 40 bit +#define HostCmd_TYPE_WEP_104_BIT 0x0002 // 104 bit +#define HostCmd_TYPE_WEP_128_BIT 0x0003 // 128 bit +#define HostCmd_TYPE_WEP_TX_KEY 0x0004 // TX WEP + +#define HostCmd_NUM_OF_WEP_KEYS 4 + +#define HostCmd_WEP_KEY_INDEX_MASK 0x3fffffff + + +// Define action or option for HostCmd_CMD_802_11_RESET +#define HostCmd_ACT_HALT 0x0001 +#define HostCmd_ACT_RESTART 0x0002 + +// Define action or option for HostCmd_CMD_802_11_RADIO_CONTROL +#define HostCmd_TYPE_AUTO_PREAMBLE 0x0001 +#define HostCmd_TYPE_SHORT_PREAMBLE 0x0002 +#define HostCmd_TYPE_LONG_PREAMBLE 0x0003 + +// Define action or option for CMD_802_11_RF_CHANNEL +#define HostCmd_TYPE_802_11A 0x0001 +#define HostCmd_TYPE_802_11B 0x0002 + +// Define action or option for HostCmd_CMD_802_11_RF_TX_POWER +#define HostCmd_ACT_TX_POWER_OPT_SET_HIGH 0x0003 +#define HostCmd_ACT_TX_POWER_OPT_SET_MID 0x0002 +#define HostCmd_ACT_TX_POWER_OPT_SET_LOW 0x0001 +#define HostCmd_ACT_TX_POWER_OPT_SET_AUTO 0x0000 + +#define HostCmd_ACT_TX_POWER_LEVEL_MIN 0x000e // in dbm +#define HostCmd_ACT_TX_POWER_LEVEL_GAP 0x0001 // in dbm +// Define action or option for HostCmd_CMD_802_11_DATA_RATE +#define HostCmd_ACT_SET_TX_AUTO 0x0000 +#define HostCmd_ACT_SET_TX_FIX_RATE 0x0001 +#define HostCmd_ACT_GET_TX_RATE 0x0002 + +#define HostCmd_ACT_SET_RX 0x0001 +#define HostCmd_ACT_SET_TX 0x0002 +#define HostCmd_ACT_SET_BOTH 0x0003 +#define HostCmd_ACT_GET_RX 0x0004 +#define HostCmd_ACT_GET_TX 0x0008 +#define HostCmd_ACT_GET_BOTH 0x000c + +#define TYPE_ANTENNA_DIVERSITY 0xffff + +// Define action or option for HostCmd_CMD_802_11_PS_MODE +#define HostCmd_TYPE_CAM 0x0000 +#define HostCmd_TYPE_MAX_PSP 0x0001 +#define HostCmd_TYPE_FAST_PSP 0x0002 + +#define HostCmd_CMD_SET_EDCA_PARAMS 0x0115 + +//============================================================================= +// HOST COMMAND DEFINITIONS +//============================================================================= + +// +// Definition of data structure for each command +// +// Define general data structure +typedef struct { + uint16_t Cmd; + uint16_t Length; +#ifdef MWL_MBSS_SUPPORT + uint8_t SeqNum; + uint8_t MacId; +#else + uint16_t SeqNum; +#endif + uint16_t Result; +} __packed FWCmdHdr; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Version; // HW revision + uint8_t HostIf; // Host interface + uint16_t NumOfMCastAdr; // Max. number of Multicast address FW can handle + uint8_t PermanentAddr[6]; // MAC address + uint16_t RegionCode; // Region Code + uint32_t FWReleaseNumber; // 4 byte of FW release number + uint32_t ulFwAwakeCookie; // Firmware awake cookie + uint32_t DeviceCaps; // Device capabilities (see above) + uint32_t RxPdWrPtr; // Rx shared memory queue + uint32_t NumTxQueues; // # TX queues in WcbBase array + uint32_t WcbBase[MAX_TXWCB_QUEUES]; // TX WCB Rings + uint32_t Flags; +#define SET_HW_SPEC_DISABLEMBSS 0x08 +#define SET_HW_SPEC_HOSTFORM_BEACON 0x10 +#define SET_HW_SPEC_HOSTFORM_PROBERESP 0x20 +#define SET_HW_SPEC_HOST_POWERSAVE 0x40 +#define SET_HW_SPEC_HOSTENCRDECR_MGMT 0x80 + uint32_t TxWcbNumPerQueue; + uint32_t TotalRxWcb; +} __packed HostCmd_DS_SET_HW_SPEC; + +typedef struct { + FWCmdHdr CmdHdr; + u_int8_t Version; /* version of the HW */ + u_int8_t HostIf; /* host interface */ + u_int16_t NumOfWCB; /* Max. number of WCB FW can handle */ + u_int16_t NumOfMCastAddr; /* MaxNbr of MC addresses FW can handle */ + u_int8_t PermanentAddr[6]; /* MAC address programmed in HW */ + u_int16_t RegionCode; + u_int16_t NumberOfAntenna; /* Number of antenna used */ + u_int32_t FWReleaseNumber; /* 4 byte of FW release number */ + u_int32_t WcbBase0; + u_int32_t RxPdWrPtr; + u_int32_t RxPdRdPtr; + u_int32_t ulFwAwakeCookie; + u_int32_t WcbBase1; + u_int32_t WcbBase2; + u_int32_t WcbBase3; +} __packed HostCmd_DS_GET_HW_SPEC; + +typedef struct { + FWCmdHdr CmdHdr; + u_int32_t Enable; /* FALSE: Disable or TRUE: Enable */ +} __packed HostCmd_DS_BSS_START; + + +typedef struct { + u_int8_t ElemId; + u_int8_t Len; + u_int8_t OuiType[4]; /* 00:50:f2:01 */ + u_int8_t Ver[2]; + u_int8_t GrpKeyCipher[4]; + u_int8_t PwsKeyCnt[2]; + u_int8_t PwsKeyCipherList[4]; + u_int8_t AuthKeyCnt[2]; + u_int8_t AuthKeyList[4]; +} __packed RsnIE_t; + +typedef struct { + u_int8_t ElemId; + u_int8_t Len; + u_int8_t Ver[2]; + u_int8_t GrpKeyCipher[4]; + u_int8_t PwsKeyCnt[2]; + u_int8_t PwsKeyCipherList[4]; + u_int8_t AuthKeyCnt[2]; + u_int8_t AuthKeyList[4]; + u_int8_t RsnCap[2]; +} __packed Rsn48IE_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CfpCnt; + u_int8_t CfpPeriod; + u_int16_t CfpMaxDuration; + u_int16_t CfpDurationRemaining; +} __packed CfParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int16_t AtimWindow; +} __packed IbssParams_t; + +typedef union { + CfParams_t CfParamSet; + IbssParams_t IbssParamSet; +} __packed SsParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int16_t DwellTime; + u_int8_t HopSet; + u_int8_t HopPattern; + u_int8_t HopIndex; +} __packed FhParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CurrentChan; +} __packed DsParams_t; + +typedef union { + FhParams_t FhParamSet; + DsParams_t DsParamSet; +} __packed PhyParams_t; + +typedef struct { + u_int8_t FirstChannelNum; + u_int8_t NumOfChannels; + u_int8_t MaxTxPwrLevel; +} __packed ChannelInfo_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CountryStr[3]; + ChannelInfo_t ChannelInfo[40]; +} __packed Country_t; + +typedef struct { + u_int8_t AIFSN : 4; + u_int8_t ACM : 1; + u_int8_t ACI : 2; + u_int8_t rsvd : 1; + +}__packed ACIAIFSN_field_t; + +typedef struct { + u_int8_t ECW_min : 4; + u_int8_t ECW_max : 4; +}__packed ECWmin_max_field_t; + +typedef struct { + ACIAIFSN_field_t ACI_AIFSN; + ECWmin_max_field_t ECW_min_max; + u_int16_t TXOP_lim; +}__packed ACparam_rcd_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t OUI[3]; + u_int8_t Type; + u_int8_t Subtype; + u_int8_t version; + u_int8_t rsvd; + ACparam_rcd_t AC_BE; + ACparam_rcd_t AC_BK; + ACparam_rcd_t AC_VI; + ACparam_rcd_t AC_VO; +} __packed WMM_param_elem_t ; + +typedef struct { +#ifdef MWL_MBSS_SUPPORT + u_int8_t StaMacAddr[6]; +#endif + u_int8_t SsId[32]; + u_int8_t BssType; + u_int16_t BcnPeriod; + u_int8_t DtimPeriod; + SsParams_t SsParamSet; + PhyParams_t PhyParamSet; + u_int16_t ProbeDelay; + u_int16_t CapInfo; /* see below */ + u_int8_t BssBasicRateSet[14]; + u_int8_t OpRateSet[14]; + RsnIE_t RsnIE; + Rsn48IE_t Rsn48IE; + WMM_param_elem_t WMMParam; + Country_t Country; + u_int32_t ApRFType; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ +} __packed StartCmd_t; + +#define HostCmd_CAPINFO_DEFAULT 0x0000 +#define HostCmd_CAPINFO_ESS 0x0001 +#define HostCmd_CAPINFO_IBSS 0x0002 +#define HostCmd_CAPINFO_CF_POLLABLE 0x0004 +#define HostCmd_CAPINFO_CF_REQUEST 0x0008 +#define HostCmd_CAPINFO_PRIVACY 0x0010 +#define HostCmd_CAPINFO_SHORT_PREAMBLE 0x0020 +#define HostCmd_CAPINFO_PBCC 0x0040 +#define HostCmd_CAPINFO_CHANNEL_AGILITY 0x0080 +#define HostCmd_CAPINFO_SHORT_SLOT 0x0400 +#define HostCmd_CAPINFO_DSSS_OFDM 0x2000 + +typedef struct { + FWCmdHdr CmdHdr; + StartCmd_t StartCmd; +} __packed HostCmd_DS_AP_BEACON; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t FrmBodyLen; + uint8_t FrmBody[1]; /* NB: variable length */ +} __packed HostCmd_DS_SET_BEACON; + +// Define data structure for HostCmd_CMD_MAC_MULTICAST_ADR +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t NumOfAdrs; +#define MWL_HAL_MCAST_MAX 32 + uint8_t MACList[6*32]; +} __packed HostCmd_DS_MAC_MULTICAST_ADR; + +// Indicate to FW the current state of AP ERP info +typedef struct { + FWCmdHdr CmdHdr; + uint32_t GProtectFlag; +} __packed HostCmd_FW_SET_G_PROTECT_FLAG; + +typedef struct { + FWCmdHdr CmdHdr; +} __packed HostCmd_FW_SET_INFRA_MODE; + +// Define data structure for HostCmd_CMD_802_11_RF_CHANNEL +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint8_t CurrentChannel; /* channel # */ + uint32_t ChannelFlags; /* see below */ +} __packed HostCmd_FW_SET_RF_CHANNEL; + +/* bits 0-5 specify frequency band */ +#define FREQ_BAND_2DOT4GHZ 0x0001 +#define FREQ_BAND_4DOT9GHZ 0x0002 /* XXX not implemented */ +#define FREQ_BAND_5GHZ 0x0004 +#define FREQ_BAND_5DOT2GHZ 0x0008 /* XXX not implemented */ +/* bits 6-10 specify channel width */ +#define CH_AUTO_WIDTH 0x0000 /* XXX not used? */ +#define CH_10_MHz_WIDTH 0x0040 +#define CH_20_MHz_WIDTH 0x0080 +#define CH_40_MHz_WIDTH 0x0100 +/* bits 11-12 specify extension channel */ +#define EXT_CH_NONE 0x0000 /* no extension channel */ +#define EXT_CH_ABOVE_CTRL_CH 0x0800 /* extension channel above */ +#define EXT_CH_AUTO 0x1000 /* XXX not used? */ +#define EXT_CH_BELOW_CTRL_CH 0x1800 /* extension channel below */ +/* bits 13-31 are reserved */ + +#define FIXED_RATE_WITH_AUTO_RATE_DROP 0 +#define FIXED_RATE_WITHOUT_AUTORATE_DROP 1 + +#define LEGACY_RATE_TYPE 0 +#define HT_RATE_TYPE 1 + +#define RETRY_COUNT_VALID 0 +#define RETRY_COUNT_INVALID 1 + +typedef struct { + // lower rate after the retry count + uint32_t FixRateType; //0: legacy, 1: HT + uint32_t RetryCountValid; //0: retry count is not valid, 1: use retry count specified +} __packed FIX_RATE_FLAG; + +typedef struct { + FIX_RATE_FLAG FixRateTypeFlags; + uint32_t FixedRate; // legacy rate(not index) or an MCS code. + uint32_t RetryCount; +} __packed FIXED_RATE_ENTRY; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; //HostCmd_ACT_GEN_GET 0x0000 + //HostCmd_ACT_GEN_SET 0x0001 + //HostCmd_ACT_NOT_USE_FIXED_RATE 0x0002 + uint32_t AllowRateDrop; // use fixed rate specified but firmware can drop to + uint32_t EntryCount; + FIXED_RATE_ENTRY FixedRateTable[4]; + uint8_t MulticastRate; + uint8_t MultiRateTxType; + uint8_t ManagementRate; +} __packed HostCmd_FW_USE_FIXED_RATE; + +typedef struct { + uint32_t AllowRateDrop; + uint32_t EntryCount; + FIXED_RATE_ENTRY FixedRateTable[4]; +} __packed USE_FIXED_RATE_INFO; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; + uint32_t GIType; +#define GI_TYPE_LONG 0x0001 +#define GI_TYPE_SHORT 0x0002 +} __packed HostCmd_FW_HT_GUARD_INTERVAL; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; + uint8_t RxAntennaMap; + uint8_t TxAntennaMap; +} __packed HostCmd_FW_HT_MIMO_CONFIG; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint8_t Slot; // Slot=0 if regular, Slot=1 if short. +} __packed HostCmd_FW_SET_SLOT; + + +// Define data structure for HostCmd_CMD_802_11_GET_STAT +typedef struct { + FWCmdHdr CmdHdr; + uint32_t TxRetrySuccesses; + uint32_t TxMultipleRetrySuccesses; + uint32_t TxFailures; + uint32_t RTSSuccesses; + uint32_t RTSFailures; + uint32_t AckFailures; + uint32_t RxDuplicateFrames; + uint32_t FCSErrorCount; + uint32_t TxWatchDogTimeouts; + uint32_t RxOverflows; //used + uint32_t RxFragErrors; //used + uint32_t RxMemErrors; //used + uint32_t PointerErrors; //used + uint32_t TxUnderflows; //used + uint32_t TxDone; + uint32_t TxDoneBufTryPut; + uint32_t TxDoneBufPut; + uint32_t Wait4TxBuf; // Put size of requested buffer in here + uint32_t TxAttempts; + uint32_t TxSuccesses; + uint32_t TxFragments; + uint32_t TxMulticasts; + uint32_t RxNonCtlPkts; + uint32_t RxMulticasts; + uint32_t RxUndecryptableFrames; + uint32_t RxICVErrors; + uint32_t RxExcludedFrames; +} __packed HostCmd_DS_802_11_GET_STAT; + + +// Define data structure for HostCmd_CMD_MAC_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint32_t Value; + uint16_t Reserved; +} __packed HostCmd_DS_MAC_REG_ACCESS; + +// Define data structure for HostCmd_CMD_BBP_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint8_t Value; + uint8_t Reserverd[3]; +} __packed HostCmd_DS_BBP_REG_ACCESS; + +// Define data structure for HostCmd_CMD_RF_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint8_t Value; + uint8_t Reserverd[3]; +} __packed HostCmd_DS_RF_REG_ACCESS; + + +// Define data structure for HostCmd_CMD_802_11_RADIO_CONTROL +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Control; // @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix + uint16_t RadioOn; +} __packed HostCmd_DS_802_11_RADIO_CONTROL; + + +#define TX_POWER_LEVEL_TOTAL 8 +// Define data structure for HostCmd_CMD_802_11_RF_TX_POWER +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t SupportTxPowerLevel; + uint16_t CurrentTxPowerLevel; + uint16_t Reserved; + uint16_t PowerLevelList[TX_POWER_LEVEL_TOTAL]; +} __packed HostCmd_DS_802_11_RF_TX_POWER; + +// Define data structure for HostCmd_CMD_802_11_RF_ANTENNA +typedef struct _HostCmd_DS_802_11_RF_ANTENNA { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t AntennaMode; // Number of antennas or 0xffff(diversity) +} __packed HostCmd_DS_802_11_RF_ANTENNA; + +// Define data structure for HostCmd_CMD_802_11_PS_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t PowerMode; // CAM, Max.PSP or Fast PSP +} __packed HostCmd_DS_802_11_PS_MODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Threshold; +} __packed HostCmd_DS_802_11_RTS_THSD; + +// used for stand alone bssid sets/clears +typedef struct { + FWCmdHdr CmdHdr; +#ifdef MWL_MBSS_SUPPORT + uint16_t MacType; +#define WL_MAC_TYPE_PRIMARY_CLIENT 0 +#define WL_MAC_TYPE_SECONDARY_CLIENT 1 +#define WL_MAC_TYPE_PRIMARY_AP 2 +#define WL_MAC_TYPE_SECONDARY_AP 3 +#endif + uint8_t MacAddr[6]; +} __packed HostCmd_DS_SET_MAC, + HostCmd_FW_SET_BSSID, + HostCmd_FW_SET_MAC; + +// Indicate to FW to send out PS Poll +typedef struct { + FWCmdHdr CmdHdr; + uint32_t PSPoll; +} __packed HostCmd_FW_TX_POLL; + +// used for AID sets/clears +typedef struct { + FWCmdHdr CmdHdr; + uint16_t AssocID; + uint8_t MacAddr[6]; //AP's Mac Address(BSSID) + uint32_t GProtection; + uint8_t ApRates[ RATE_INDEX_MAX_ARRAY]; +} __packed HostCmd_FW_SET_AID; + +typedef struct { + uint32_t LegacyRateBitMap; + uint32_t HTRateBitMap; + uint16_t CapInfo; + uint16_t HTCapabilitiesInfo; + uint8_t MacHTParamInfo; + uint8_t Rev; + struct { + uint8_t ControlChan; + uint8_t AddChan; + uint16_t OpMode; + uint16_t stbc; + } __packed AddHtInfo; +} __packed PeerInfo_t; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t AID; + uint8_t MacAddr[6]; + uint16_t StnId; + uint16_t Action; + uint16_t Reserved; + PeerInfo_t PeerInfo; + uint8_t Qosinfo; + uint8_t isQosSta; +} __packed HostCmd_FW_SET_NEW_STN; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t tick; +} __packed HostCmd_FW_SET_KEEP_ALIVE_TICK; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t QNum; +} __packed HostCmd_FW_SET_RIFS; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t ApMode; +} __packed HostCmd_FW_SET_APMODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // see following + uint16_t RadarTypeCode; +} __packed HostCmd_802_11h_Detect_Radar; + +#define DR_DFS_DISABLE 0 +#define DR_CHK_CHANNEL_AVAILABLE_START 1 +#define DR_CHK_CHANNEL_AVAILABLE_STOP 2 +#define DR_IN_SERVICE_MONITOR_START 3 + +//New Structure for Update Tim 30/9/2003 +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Aid; + uint32_t Set; +} __packed HostCmd_UpdateTIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t SsidBroadcastEnable; +} __packed HostCmd_SSID_BROADCAST; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t WdsEnable; +} __packed HostCmd_WDS; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Next11hChannel; + uint32_t Mode; + uint32_t InitialCount; + uint32_t ChannelFlags ; +} __packed HostCmd_SET_SWITCH_CHANNEL; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t SpectrumMgmt; +} __packed HostCmd_SET_SPECTRUM_MGMT; + +typedef struct { + FWCmdHdr CmdHdr; + int32_t PowerConstraint; +} __packed HostCmd_SET_POWER_CONSTRAINT; + +typedef struct { + uint8_t FirstChannelNo; + uint8_t NoofChannel; + uint8_t MaxTransmitPw; +} __packed DomainChannelEntry; + +typedef struct { + uint8_t CountryString[3]; + uint8_t GChannelLen; + DomainChannelEntry DomainEntryG[1]; /** Assume only 1 G zone **/ + uint8_t AChannelLen; + DomainChannelEntry DomainEntryA[20]; /** Assume max of 5 A zone **/ +} __packed DomainCountryInfo; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action ; // 0 -> unset, 1 ->set + DomainCountryInfo DomainInfo ; +} __packed HostCmd_SET_COUNTRY_INFO; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t regionCode ; +} __packed HostCmd_SET_REGIONCODE_INFO; + +// for HostCmd_CMD_SET_WMM_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // 0->unset, 1->set +} __packed HostCmd_FW_SetWMMMode; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // 0->unset, 1->set + uint16_t IeListLen; + uint8_t IeList[200]; +} __packed HostCmd_FW_SetIEs; + +#define EDCA_PARAM_SIZE 18 +#define BA_PARAM_SIZE 2 + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; //0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN + uint16_t TxOP; // in unit of 32 us + uint32_t CWMax; // 0~15 + uint32_t CWMin; // 0~15 + uint8_t AIFSN; + uint8_t TxQNum; // Tx Queue number. +} __packed HostCmd_FW_SET_EDCA_PARAMS; + +/****************************************************************************** + @HWENCR@ + Hardware Encryption related data structures and constant definitions. + Note that all related changes are marked with the @HWENCR@ tag. +*******************************************************************************/ + +#define MAX_ENCR_KEY_LENGTH 16 /* max 128 bits - depends on type */ +#define MIC_KEY_LENGTH 8 /* size of Tx/Rx MIC key - 8 bytes*/ + +#define ENCR_KEY_TYPE_ID_WEP 0x00 /* Key type is WEP */ +#define ENCR_KEY_TYPE_ID_TKIP 0x01 /* Key type is TKIP */ +#define ENCR_KEY_TYPE_ID_AES 0x02 /* Key type is AES-CCMP */ + +/* flags used in structure - same as driver EKF_XXX flags */ +#define ENCR_KEY_FLAG_INUSE 0x00000001 /* indicate key is in use */ +#define ENCR_KEY_FLAG_RXGROUPKEY 0x00000002 /* Group key for RX only */ +#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 /* Group key for TX */ +#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 /* pairwise */ +#define ENCR_KEY_FLAG_RXONLY 0x00000010 /* only used for RX */ +// These flags are new additions - for hardware encryption commands only. +#define ENCR_KEY_FLAG_AUTHENTICATOR 0x00000020 /* Key is for Authenticator */ +#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 /* Sequence counters valid */ +#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 /* Tx key for WEP */ +#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 /* Tx/Rx MIC keys are valid */ + +/* + UPDATE_ENCRYPTION command action type. +*/ +typedef enum { + // request to enable/disable HW encryption + EncrActionEnableHWEncryption, + // request to set encryption key + EncrActionTypeSetKey, + // request to remove one or more keys + EncrActionTypeRemoveKey, + EncrActionTypeSetGroupKey +} ENCR_ACTION_TYPE; + +/* + Key material definitions (for WEP, TKIP, & AES-CCMP) +*/ + +/* + WEP Key material definition + ---------------------------- + WEPKey --> An array of 'MAX_ENCR_KEY_LENGTH' bytes. + Note that we do not support 152bit WEP keys +*/ +typedef struct { + // WEP key material (max 128bit) + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; +} __packed WEP_TYPE_KEY; + +/* + TKIP Key material definition + ---------------------------- + This structure defines TKIP key material. Note that + the TxMicKey and RxMicKey may or may not be valid. +*/ +/* TKIP Sequence counter - 24 bits */ +/* Incremented on each fragment MPDU */ +typedef struct { + uint16_t low; + uint32_t high; +} __packed ENCR_TKIPSEQCNT; + +typedef struct { + // TKIP Key material. Key type (group or pairwise key) is + // determined by flags in KEY_PARAM_SET structure. + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; + uint8_t TkipTxMicKey[ MIC_KEY_LENGTH ]; + uint8_t TkipRxMicKey[ MIC_KEY_LENGTH ]; + ENCR_TKIPSEQCNT TkipRsc; + ENCR_TKIPSEQCNT TkipTsc; +} __packed TKIP_TYPE_KEY; + +/* + AES-CCMP Key material definition + -------------------------------- + This structure defines AES-CCMP key material. +*/ +typedef struct { + // AES Key material + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; +} __packed AES_TYPE_KEY; + +/* + Encryption key definition. + -------------------------- + This structure provides all required/essential + information about the key being set/removed. +*/ +typedef struct { + uint16_t Length; // Total length of this structure + uint16_t KeyTypeId; // Key type - WEP, TKIP or AES-CCMP. + uint32_t KeyInfo; // key flags (ENCR_KEY_FLAG_XXX_ + uint32_t KeyIndex; // For WEP only - actual key index + uint16_t KeyLen; // Size of the key + union { // Key material (variable size array) + WEP_TYPE_KEY WepKey; + TKIP_TYPE_KEY TkipKey; + AES_TYPE_KEY AesKey; + }__packed Key; +#ifdef MWL_MBSS_SUPPORT + uint8_t Macaddr[6]; +#endif +} __packed KEY_PARAM_SET; + +/* + HostCmd_FW_UPDATE_ENCRYPTION + ---------------------------- + Define data structure for updating firmware encryption keys. + +*/ +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; // ENCR_ACTION_TYPE + uint32_t DataLength; // size of the data buffer attached. +#ifdef MWL_MBSS_SUPPORT + uint8_t macaddr[6]; +#endif + uint8_t ActionData[1]; +} __packed HostCmd_FW_UPDATE_ENCRYPTION; + + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; // ENCR_ACTION_TYPE + uint32_t DataLength; // size of the data buffer attached. + KEY_PARAM_SET KeyParam; +#ifndef MWL_MBSS_SUPPORT + uint8_t Macaddr[8]; /* XXX? */ +#endif +} __packed HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY; + +typedef struct { + // Rate flags - see above. + uint32_t Flags; + // Rate in 500Kbps units. + uint8_t RateKbps; + // 802.11 rate to conversion table index value. + // This is the value required by the firmware/hardware. + uint16_t RateCodeToIndex; +}__packed RATE_INFO; + +/* + UPDATE_STADB command action type. +*/ +typedef enum { + // request to add entry to stainfo db + StaInfoDbActionAddEntry, + // request to modify peer entry + StaInfoDbActionModifyEntry, + // request to remove peer from stainfo db + StaInfoDbActionRemoveEntry +}__packed STADB_ACTION_TYPE; + +/* + @11E-BA@ + 802.11e/WMM Related command(s)/data structures +*/ + +// Flag to indicate if the stream is an immediate block ack stream. +// if this bit is not set, the stream is delayed block ack stream. +#define BASTREAM_FLAG_DELAYED_TYPE 0x00 +#define BASTREAM_FLAG_IMMEDIATE_TYPE 0x01 + +// Flag to indicate the direction of the stream (upstream/downstream). +// If this bit is not set, the direction is downstream. +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0x00 +#define BASTREAM_FLAG_DIRECTION_DOWNSTREAM 0x02 +#define BASTREAM_FLAG_DIRECTION_DLP 0x04 +#define BASTREAM_FLAG_DIRECTION_BOTH 0x06 + +typedef enum { + BaCreateStream, + BaUpdateStream, + BaDestroyStream, + BaFlushStream, + BaCheckCreateStream +} BASTREAM_ACTION_TYPE; + +typedef struct { + uint32_t Context; +} __packed BASTREAM_CONTEXT; + +// parameters for block ack creation +typedef struct { + // BA Creation flags - see above + uint32_t Flags; + // idle threshold + uint32_t IdleThrs; + // block ack transmit threshold (after how many pkts should we send BAR?) + uint32_t BarThrs; + // receiver window size + uint32_t WindowSize; + // MAC Address of the BA partner + uint8_t PeerMacAddr[6]; + // Dialog Token + uint8_t DialogToken; + //TID for the traffic stream in this BA + uint8_t Tid; + // shared memory queue ID (not sure if this is required) + uint8_t QueueId; + uint8_t ParamInfo; + // returned by firmware - firmware context pointer. + // this context pointer will be passed to firmware for all future commands. + BASTREAM_CONTEXT FwBaContext; + uint8_t ResetSeqNo; /** 0 or 1**/ + uint16_t StartSeqNo; +}__packed BASTREAM_CREATE_STREAM; + +// new transmit sequence number information +typedef struct { + // BA flags - see above + uint32_t Flags; + // returned by firmware in the create ba stream response + BASTREAM_CONTEXT FwBaContext; + // new sequence number for this block ack stream + uint16_t BaSeqNum; +}__packed BASTREAM_UPDATE_STREAM; + +typedef struct { + // BA Stream flags + uint32_t Flags; + // returned by firmware in the create ba stream response + BASTREAM_CONTEXT FwBaContext; +}__packed BASTREAM_STREAM_INFO; + +//Command to create/destroy block ACK +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; + union + { + // information required to create BA Stream... + BASTREAM_CREATE_STREAM CreateParams; + // update starting/new sequence number etc. + BASTREAM_UPDATE_STREAM UpdtSeqNum; + // destroy an existing stream... + BASTREAM_STREAM_INFO DestroyParams; + // destroy an existing stream... + BASTREAM_STREAM_INFO FlushParams; + }__packed BaInfo; +}__packed HostCmd_FW_BASTREAM; + +// Define data structure for HostCmd_CMD_GET_WATCHDOG_BITMAP +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Watchdogbitmap; // for SW/BA +} __packed HostCmd_FW_GET_WATCHDOG_BITMAP; + + + +// Define data structure for HostCmd_CMD_SET_REGION_POWER +typedef struct { + FWCmdHdr CmdHdr; + uint16_t MaxPowerLevel; + uint16_t Reserved; +} __packed HostCmd_DS_SET_REGION_POWER; + +// Define data structure for HostCmd_CMD_SET_RATE_ADAPT_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t RateAdaptMode; +} __packed HostCmd_DS_SET_RATE_ADAPT_MODE; + +// Define data structure for HostCmd_CMD_SET_LINKADAPT_CS_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t CSMode; +} __packed HostCmd_DS_SET_LINKADAPT_CS_MODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t NProtectFlag; +} __packed HostCmd_FW_SET_N_PROTECT_FLAG; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t NProtectOpMode; +} __packed HostCmd_FW_SET_N_PROTECT_OPMODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t OptLevel; +} __packed HostCmd_FW_SET_OPTIMIZATION_LEVEL; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t annex; + uint8_t index; + uint8_t len; + uint8_t Reserverd; +#define CAL_TBL_SIZE 160 + uint8_t calTbl[CAL_TBL_SIZE]; +} __packed HostCmd_FW_GET_CALTABLE; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Addr[6]; + uint8_t Enable; + uint8_t Mode; +} __packed HostCmd_FW_SET_MIMOPSHT; + +#define MAX_BEACON_SIZE 1024 +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Bcnlen; + uint8_t Reserverd[2]; + uint8_t Bcn[MAX_BEACON_SIZE]; +} __packed HostCmd_FW_GET_BEACON; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t NumberOfPowersave; + uint8_t reserved; +} __packed HostCmd_SET_POWERSAVESTATION; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Aid; + uint32_t Set; + uint8_t reserved; +} __packed HostCmd_SET_TIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t TrafficMap[251]; + uint8_t reserved; +} __packed HostCmd_GET_TIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t MacAddr[6]; + uint8_t TID; + uint16_t SeqNo; + uint8_t reserved; +} __packed HostCmd_GET_SEQNO; +#endif /* _MWL_HALREG_H_ */ |
