freebsd-nq/sys/dev/mwl/if_mwl.c
2012-12-04 09:32:43 +00:00

5050 lines
139 KiB
C

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