#include <sys/cdefs.h>
#include "opt_inet.h"
#include "opt_mwl.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/errno.h>
#include <sys/callout.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kthread.h>
#include <sys/taskqueue.h>
#include <machine/bus.h>
#include <net/if.h>
#include <net/if_var.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_input.h>
#include <net80211/ieee80211_regdomain.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_ether.h>
#endif
#include <dev/mwl/if_mwlvar.h>
#include <dev/mwl/mwldiag.h>
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(struct mwl_softc *);
static void mwl_parent(struct ieee80211com *);
static int mwl_reset(struct ieee80211vap *, u_long);
static void mwl_stop(struct mwl_softc *);
static void mwl_start(struct mwl_softc *);
static int mwl_transmit(struct ieee80211com *, struct mbuf *);
static int mwl_raw_xmit(struct ieee80211_node *, struct mbuf *,
const struct ieee80211_bpf_params *);
static int mwl_media_change(if_t);
static void mwl_watchdog(void *);
static int mwl_ioctl(struct ieee80211com *, u_long, void *);
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 *);
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 ieee80211com *);
static void mwl_update_promisc(struct ieee80211com *);
static void mwl_updateslot(struct ieee80211com *);
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 | CTLFLAG_MPSAFE, 0,
"Marvell driver parameters");
static int mwl_rxdesc = MWL_RXDESC;
SYSCTL_INT(_hw_mwl, OID_AUTO, rxdesc, CTLFLAG_RW, &mwl_rxdesc,
0, "rx descriptors allocated");
static int mwl_rxbuf = MWL_RXBUF;
SYSCTL_INT(_hw_mwl, OID_AUTO, rxbuf, CTLFLAG_RWTUN, &mwl_rxbuf,
0, "rx buffers allocated");
static int mwl_txbuf = MWL_TXBUF;
SYSCTL_INT(_hw_mwl, OID_AUTO, txbuf, CTLFLAG_RWTUN, &mwl_txbuf,
0, "tx buffers allocated");
static int mwl_txcoalesce = 8;
SYSCTL_INT(_hw_mwl, OID_AUTO, txcoalesce, CTLFLAG_RWTUN, &mwl_txcoalesce,
0, "tx buffers to send at once");
static int mwl_rxquota = MWL_RXBUF;
SYSCTL_INT(_hw_mwl, OID_AUTO, rxquota, CTLFLAG_RWTUN, &mwl_rxquota,
0, "max rx buffers to process per interrupt");
static int mwl_rxdmalow = 3;
SYSCTL_INT(_hw_mwl, OID_AUTO, rxdmalow, CTLFLAG_RWTUN, &mwl_rxdmalow,
0, "min free rx buffers before restarting traffic");
#ifdef MWL_DEBUG
static int mwl_debug = 0;
SYSCTL_INT(_hw_mwl, OID_AUTO, debug, CTLFLAG_RWTUN, &mwl_debug,
0, "control debugging printfs");
enum {
MWL_DEBUG_XMIT = 0x00000001,
MWL_DEBUG_XMIT_DESC = 0x00000002,
MWL_DEBUG_RECV = 0x00000004,
MWL_DEBUG_RECV_DESC = 0x00000008,
MWL_DEBUG_RESET = 0x00000010,
MWL_DEBUG_BEACON = 0x00000020,
MWL_DEBUG_INTR = 0x00000040,
MWL_DEBUG_TX_PROC = 0x00000080,
MWL_DEBUG_RX_PROC = 0x00000100,
MWL_DEBUG_KEYCACHE = 0x00000200,
MWL_DEBUG_STATE = 0x00000400,
MWL_DEBUG_NODE = 0x00000800,
MWL_DEBUG_RECV_ALL = 0x00001000,
MWL_DEBUG_TSO = 0x00002000,
MWL_DEBUG_AMPDU = 0x00004000,
MWL_DEBUG_ANY = 0xffffffff
};
#define IFF_DUMPPKTS_RECV(sc, wh) \
((sc->sc_debug & MWL_DEBUG_RECV) && \
((sc->sc_debug & MWL_DEBUG_RECV_ALL) || !IEEE80211_IS_MGMT_BEACON(wh)))
#define IFF_DUMPPKTS_XMIT(sc) \
(sc->sc_debug & MWL_DEBUG_XMIT)
#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) 0
#define IFF_DUMPPKTS_XMIT(sc) 0
#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");
struct mwltxrec {
uint16_t fwlen;
struct ieee80211_frame_addr4 wh;
} __packed;
#ifdef MWL_DEBUG
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);
}
#endif
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 ieee80211com *ic = &sc->sc_ic;
struct mwl_hal *mh;
int error = 0;
DPRINTF(sc, MWL_DEBUG_ANY, "%s: devid 0x%x\n", __func__, devid);
MWL_RXFREE_INIT(sc);
mh = mwl_hal_attach(sc->sc_dev, devid,
sc->sc_io1h, sc->sc_io1t, sc->sc_dmat);
if (mh == NULL) {
device_printf(sc->sc_dev, "unable to attach HAL\n");
error = EIO;
goto bad;
}
sc->sc_mh = mh;
if (mwl_hal_fwload(mh, NULL) != 0) {
device_printf(sc->sc_dev, "unable to setup builtin firmware\n");
error = EIO;
goto bad1;
}
if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) {
device_printf(sc->sc_dev, "unable to fetch h/w specs\n");
error = EIO;
goto bad1;
}
error = mwl_getchannels(sc);
if (error != 0)
goto bad1;
sc->sc_txantenna = 0;
sc->sc_rxantenna = 0;
sc->sc_invalid = 0;
sc->sc_ageinterval = MWL_AGEINTERVAL;
error = mwl_dma_setup(sc);
if (error != 0) {
device_printf(sc->sc_dev, "failed to setup descriptors: %d\n",
error);
goto bad1;
}
error = mwl_setupdma(sc);
if (error != 0)
goto bad1;
callout_init(&sc->sc_timer, 1);
callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
mbufq_init(&sc->sc_snd, ifqmaxlen);
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", device_get_nameunit(sc->sc_dev));
NET_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);
if (!mwl_tx_setup(sc, WME_AC_BK, MWL_WME_AC_BK)) {
device_printf(sc->sc_dev,
"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)) {
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);
ic->ic_softc = sc;
ic->ic_name = device_get_nameunit(sc->sc_dev);
ic->ic_phytype = IEEE80211_T_OFDM;
ic->ic_opmode = IEEE80211_M_STA;
ic->ic_caps =
IEEE80211_C_STA
| IEEE80211_C_HOSTAP
| IEEE80211_C_MONITOR
#if 0
| IEEE80211_C_IBSS
| IEEE80211_C_AHDEMO
#endif
| IEEE80211_C_MBSS
| IEEE80211_C_WDS
| IEEE80211_C_SHPREAMBLE
| IEEE80211_C_SHSLOT
| IEEE80211_C_WME
| IEEE80211_C_BURST
| IEEE80211_C_WPA
| IEEE80211_C_BGSCAN
| IEEE80211_C_TXFRAG
| IEEE80211_C_TXPMGT
| IEEE80211_C_DFS
;
ic->ic_htcaps =
IEEE80211_HTCAP_SMPS_ENA
| IEEE80211_HTCAP_CHWIDTH40
| IEEE80211_HTCAP_SHORTGI20
| IEEE80211_HTCAP_SHORTGI40
| IEEE80211_HTCAP_RXSTBC_2STREAM
#if MWL_AGGR_SIZE == 7935
| IEEE80211_HTCAP_MAXAMSDU_7935
#else
| IEEE80211_HTCAP_MAXAMSDU_3839
#endif
#if 0
| IEEE80211_HTCAP_PSMP
| IEEE80211_HTCAP_40INTOLERANT
#endif
| IEEE80211_HTC_HT
| IEEE80211_HTC_AMPDU
| IEEE80211_HTC_AMSDU
| IEEE80211_HTC_SMPS
;
ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP
| IEEE80211_CRYPTO_AES_CCM
| IEEE80211_CRYPTO_TKIP
| IEEE80211_CRYPTO_TKIPMIC
;
ic->ic_headroom = sizeof(struct mwltxrec) -
sizeof(struct ieee80211_frame);
IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->sc_hwspecs.macAddr);
ieee80211_ifattach(ic);
ic->ic_setregdomain = mwl_setregdomain;
ic->ic_getradiocaps = mwl_getradiocaps;
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_transmit = mwl_transmit;
ic->ic_ioctl = mwl_ioctl;
ic->ic_parent = mwl_parent;
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);
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);
sc->sc_invalid = 1;
return error;
}
int
mwl_detach(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
MWL_LOCK(sc);
mwl_stop(sc);
MWL_UNLOCK(sc);
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);
mbufq_drain(&sc->sc_snd);
return 0;
}
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)) {
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, const 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 mwl_softc *sc = ic->ic_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;
}
flags |= IEEE80211_CLONE_NOBEACONS;
break;
case IEEE80211_M_WDS:
hvap = NULL;
if (sc->sc_napvaps == 0)
return NULL;
break;
case IEEE80211_M_MONITOR:
hvap = NULL;
break;
case IEEE80211_M_IBSS:
case IEEE80211_M_AHDEMO:
default:
return NULL;
}
mvp = malloc(sizeof(struct mwl_vap), M_80211_VAP, M_WAITOK | M_ZERO);
mvp->mv_hvap = hvap;
if (opmode == IEEE80211_M_WDS) {
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);
mvp->mv_newstate = vap->iv_newstate;
vap->iv_newstate = mwl_newstate;
vap->iv_max_keyix = 0;
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;
vap->iv_max_aid = MWL_MAXSTAID;
vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_4;
ieee80211_vap_attach(vap, mwl_media_change, ieee80211_media_status,
mac);
switch (vap->iv_opmode) {
case IEEE80211_M_HOSTAP:
case IEEE80211_M_MBSS:
case IEEE80211_M_STA:
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;
}
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 mwl_softc *sc = vap->iv_ic->ic_softc;
struct mwl_hal *mh = sc->sc_mh;
struct mwl_hal_vap *hvap = mvp->mv_hvap;
enum ieee80211_opmode opmode = vap->iv_opmode;
if (sc->sc_running) {
mwl_hal_intrset(mh, 0);
}
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--;
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 (sc->sc_running)
mwl_hal_intrset(mh, sc->sc_imask);
}
void
mwl_suspend(struct mwl_softc *sc)
{
MWL_LOCK(sc);
mwl_stop(sc);
MWL_UNLOCK(sc);
}
void
mwl_resume(struct mwl_softc *sc)
{
int error = EDOOFUS;
MWL_LOCK(sc);
if (sc->sc_ic.ic_nrunning > 0)
error = mwl_init(sc);
MWL_UNLOCK(sc);
if (error == 0)
ieee80211_start_all(&sc->sc_ic);
}
void
mwl_shutdown(void *arg)
{
struct mwl_softc *sc = arg;
MWL_LOCK(sc);
mwl_stop(sc);
MWL_UNLOCK(sc);
}
void
mwl_intr(void *arg)
{
struct mwl_softc *sc = arg;
struct mwl_hal *mh = sc->sc_mh;
uint32_t status;
#if !defined(__HAIKU__)
if (sc->sc_invalid) {
DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid; ignored\n", __func__);
return;
}
mwl_hal_getisr(mh, &status);
if (status == 0)
return;
#else
status = atomic_get((int32 *)&sc->sc_intr_status);
#endif
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) {
sc->sc_stats.mst_rx_badtkipicv++;
}
if (status & MACREG_A2HRIC_BIT_QUEUE_EMPTY) {
;
}
if (status & MACREG_A2HRIC_BIT_QUEUE_FULL) {
;
}
if (status & MACREG_A2HRIC_BIT_RADAR_DETECT) {
taskqueue_enqueue(sc->sc_tq, &sc->sc_radartask);
}
if (status & MACREG_A2HRIC_BIT_CHAN_SWITCH) {
taskqueue_enqueue(sc->sc_tq, &sc->sc_chanswitchtask);
}
}
static void
mwl_radar_proc(void *arg, int pending)
{
struct mwl_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
DPRINTF(sc, MWL_DEBUG_ANY, "%s: radar detected, pending %u\n",
__func__, pending);
sc->sc_stats.mst_radardetect++;
IEEE80211_LOCK(ic);
ieee80211_dfs_notify_radar(ic, ic->ic_curchan);
IEEE80211_UNLOCK(ic);
}
static void
mwl_chanswitch_proc(void *arg, int pending)
{
struct mwl_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
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];
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;
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) {
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++;
}
}
}
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;
}
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);
}
return error;
}
static int
mwl_setcurchanrates(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
const struct ieee80211_rateset *rs;
MWL_HAL_TXRATE rates;
memset(&rates, 0, sizeof(rates));
rs = ieee80211_get_suprates(ic, ic->ic_curchan);
rates.MgtRate = rs->rs_rates[0] & IEEE80211_RATE_VAL;
rates.McastRate = rates.MgtRate;
return mwl_hal_settxrate_auto(sc->sc_mh, &rates);
}
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));
memset(&rates, 0, sizeof(rates));
rates.MgtRate = tp->mgmtrate;
rates.McastRate = tp->mcastrate;
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);
}
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);
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));
}
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;
return DOMAIN_CODE_ETSI_131;
case SKU_JAPAN:
return DOMAIN_CODE_MKK;
case SKU_ROW:
return DOMAIN_CODE_DGT;
case SKU_APAC:
case SKU_APAC2:
case SKU_APAC3:
return DOMAIN_CODE_AUS;
}
return DOMAIN_CODE_FCC;
}
static int
mwl_hal_reset(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct mwl_hal *mh = sc->sc_mh;
mwl_hal_setantenna(mh, WL_ANTENNATYPE_RX, sc->sc_rxantenna);
mwl_hal_setantenna(mh, WL_ANTENNATYPE_TX, sc->sc_txantenna);
mwl_hal_setradio(mh, 1, WL_AUTO_PREAMBLE);
mwl_hal_setwmm(sc->sc_mh, (ic->ic_flags & IEEE80211_F_WME) != 0);
mwl_chan_set(sc, ic->ic_curchan);
mwl_hal_setrateadaptmode(mh, 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);
mwl_hal_setcfend(mh, 0);
return 1;
}
static int
mwl_init(struct mwl_softc *sc)
{
struct mwl_hal *mh = sc->sc_mh;
int error = 0;
MWL_LOCK_ASSERT(sc);
mwl_stop(sc);
if (!mwl_hal_reset(sc)) {
device_printf(sc->sc_dev, "unable to reset hardware\n");
return EIO;
}
error = mwl_startrecv(sc);
if (error != 0) {
device_printf(sc->sc_dev, "unable to start recv logic\n");
return error;
}
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
;
sc->sc_running = 1;
mwl_hal_intrset(mh, sc->sc_imask);
callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc);
return 0;
}
static void
mwl_stop(struct mwl_softc *sc)
{
MWL_LOCK_ASSERT(sc);
if (sc->sc_running) {
sc->sc_running = 0;
callout_stop(&sc->sc_watchdog);
sc->sc_tx_timer = 0;
mwl_draintxq(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);
mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold);
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);
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, _IEEE80211_MASKSHIFT(
ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE));
return mwl_beacon_setup(vap);
}
return 0;
}
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) {
struct ieee80211com *ic = vap->iv_ic;
struct mwl_softc *sc = ic->ic_softc;
struct mwl_hal *mh = sc->sc_mh;
mwl_hal_intrset(mh, 0);
error = mwl_reset_vap(vap, vap->iv_state);
mwl_hal_intrset(mh, sc->sc_imask);
}
return error;
}
static struct mwl_txbuf *
mwl_gettxbuf(struct mwl_softc *sc, struct mwl_txq *txq)
{
struct mwl_txbuf *bf;
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;
}
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 int
mwl_transmit(struct ieee80211com *ic, struct mbuf *m)
{
struct mwl_softc *sc = ic->ic_softc;
int error;
MWL_LOCK(sc);
if (!sc->sc_running) {
MWL_UNLOCK(sc);
return (ENXIO);
}
error = mbufq_enqueue(&sc->sc_snd, m);
if (error) {
MWL_UNLOCK(sc);
return (error);
}
mwl_start(sc);
MWL_UNLOCK(sc);
return (0);
}
static void
mwl_start(struct mwl_softc *sc)
{
struct ieee80211_node *ni;
struct mwl_txbuf *bf;
struct mbuf *m;
struct mwl_txq *txq = NULL;
int nqueued;
MWL_LOCK_ASSERT(sc);
if (!sc->sc_running || sc->sc_invalid)
return;
nqueued = 0;
while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
KASSERT(ni != NULL, ("no node"));
m->m_pkthdr.rcvif = NULL;
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++;
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
}
if (mwl_tx_start(sc, ni, bf, m)) {
if_inc_counter(ni->ni_vap->iv_ifp,
IFCOUNTER_OERRORS, 1);
mwl_puttxbuf_head(txq, bf);
ieee80211_free_node(ni);
continue;
}
nqueued++;
if (nqueued >= mwl_txcoalesce) {
nqueued = 0;
mwl_hal_txstart(sc->sc_mh, 0);
}
}
if (nqueued) {
mwl_hal_txstart(sc->sc_mh, 0);
}
}
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 mwl_softc *sc = ic->ic_softc;
struct mwl_txbuf *bf;
struct mwl_txq *txq;
if (!sc->sc_running || sc->sc_invalid) {
m_freem(m);
return ENETDOWN;
}
txq = sc->sc_ac2q[M_WME_GETAC(m)];
bf = mwl_gettxbuf(sc, txq);
if (bf == NULL) {
sc->sc_stats.mst_tx_qstop++;
m_freem(m);
return ENOBUFS;
}
if (mwl_tx_start(sc, ni, bf, m)) {
mwl_puttxbuf_head(txq, bf);
return EIO;
}
mwl_hal_txstart(sc->sc_mh, 0);
return 0;
}
static int
mwl_media_change(if_t ifp)
{
struct ieee80211vap *vap;
int error;
error = ieee80211_media_change(ifp);
if (error != 0)
return (error);
vap = if_getsoftc(ifp);
mwl_setrates(vap);
return (0);
}
#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
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_softc;
if (k->wk_keyix != IEEE80211_KEYIX_NONE ||
(k->wk_flags & IEEE80211_KEY_GROUP)) {
if (!ieee80211_is_key_global(vap, k)) {
DPRINTF(sc, MWL_DEBUG_KEYCACHE,
"%s: bogus group key\n", __func__);
return 0;
}
*keyix = *rxkeyix = ieee80211_crypto_get_key_wepidx(vap, k);
} else {
*keyix = *rxkeyix = 0;
}
return 1;
}
static int
mwl_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
{
struct mwl_softc *sc = vap->iv_ic->ic_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) {
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:
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);
}
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;
}
static int
mwl_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k)
{
return (_mwl_key_set(vap, k, k->wk_macaddr));
}
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)
#define IEEE80211_IS_STATICKEY(k) \
(((k)->wk_flags & (GRPXMIT|IEEE80211_KEY_RECV)) == \
(GRPXMIT|IEEE80211_KEY_RECV))
struct mwl_softc *sc = vap->iv_ic->ic_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) {
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)) {
(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:
DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n",
__func__, k->wk_cipher->ic_cipher);
return 0;
}
memcpy(hk.key.aes, k->wk_key, hk.keyLen);
if (vap->iv_opmode == IEEE80211_M_STA) {
macaddr = vap->iv_bss->ni_bssid;
if ((k->wk_flags & IEEE80211_KEY_GROUP) == 0) {
mwl_hal_keyset(hvap, &hk, vap->iv_myaddr);
}
} else if (vap->iv_opmode == IEEE80211_M_WDS &&
vap->iv_state != IEEE80211_S_RUN) {
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
}
static void
mwl_setmcastfilter(struct mwl_softc *sc)
{
#if 0
struct ether_multi *enm;
struct ether_multistep estep;
uint8_t macs[IEEE80211_ADDR_LEN*MWL_HAL_MCAST_MAX];
uint8_t *mp;
int nmc;
mp = macs;
nmc = 0;
ETHER_FIRST_MULTI(estep, &sc->sc_ec, enm);
while (enm != NULL) {
if (nmc == MWL_HAL_MCAST_MAX ||
!IEEE80211_ADDR_EQ(enm->enm_addrlo, enm->enm_addrhi)) {
if_setflagsbit(ifp, IFF_ALLMULTI, 0);
return;
}
IEEE80211_ADDR_COPY(mp, enm->enm_addrlo);
mp += IEEE80211_ADDR_LEN, nmc++;
ETHER_NEXT_MULTI(estep, enm);
}
if_setflagsbit(ifp, 0, IFF_ALLMULTI);
mwl_hal_setmcast(sc->sc_mh, nmc, macs);
#endif
}
static int
mwl_mode_init(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct mwl_hal *mh = sc->sc_mh;
mwl_hal_setpromisc(mh, ic->ic_promisc > 0);
mwl_setmcastfilter(sc);
return 0;
}
static void
mwl_update_mcast(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
mwl_setmcastfilter(sc);
}
static void
mwl_update_promisc(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
mwl_hal_setpromisc(sc->sc_mh, ic->ic_promisc > 0);
}
static void
mwl_updateslot(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
struct mwl_hal *mh = sc->sc_mh;
int prot;
if (!sc->sc_running)
return;
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);
}
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 mbuf *m;
m = ieee80211_beacon_alloc(ni);
if (m == NULL)
return ENOBUFS;
mwl_hal_setbeacon(hvap, mtod(m, const void *), m->m_len);
m_free(m);
return 0;
}
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);
break;
case IEEE80211_BEACON_HTINFO:
mwl_hal_setnprotmode(hvap, _IEEE80211_MASKSHIFT(
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:
return;
}
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
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;
}
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)) {
mwl_hal_setpowersave_sta(mvp->mv_hvap,
IEEE80211_AID(ni->ni_associd), set);
return 1;
} else
return 0;
}
#endif
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)
{
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;
error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev),
PAGE_SIZE, 0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
dd->dd_desc_len,
1,
dd->dd_desc_len,
BUS_DMA_ALLOCNOW,
NULL,
NULL,
&dd->dd_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "cannot allocate %s DMA tag\n", dd->dd_name);
return error;
}
error = bus_dmamem_alloc(dd->dd_dmat, (void**) &dd->dd_desc,
BUS_DMA_NOWAIT | BUS_DMA_COHERENT,
&dd->dd_dmamap);
if (error != 0) {
device_printf(sc->sc_dev, "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) {
device_printf(sc->sc_dev, "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) -> 0x%jx (%lu)\n",
__func__, dd->dd_name, ds, (u_long) dd->dd_desc_len,
(uintmax_t) dd->dd_desc_paddr, (u_long) dd->dd_desc_len);
return 0;
fail2:
bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap);
fail1:
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_dma_tag_destroy(dd->dd_dmat);
memset(dd, 0, sizeof(*dd));
}
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)
{
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;
bsize = mwl_txbuf * sizeof(struct mwl_txbuf);
bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO);
if (bf == NULL) {
device_printf(sc->sc_dev, "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) {
device_printf(sc->sc_dev, "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)
{
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;
if (mwl_rxbuf < 2*mwl_rxdesc) {
device_printf(sc->sc_dev,
"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,
PAGE_SIZE, 0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
sc->sc_rxmemsize,
1,
sc->sc_rxmemsize,
BUS_DMA_ALLOCNOW,
NULL,
NULL,
&sc->sc_rxdmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create rx DMA tag\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) {
device_printf(sc->sc_dev, "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) {
device_printf(sc->sc_dev, "could not load rx DMA map\n");
return error;
}
bsize = mwl_rxdesc * sizeof(struct mwl_rxbuf);
bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO);
if (bf == NULL) {
device_printf(sc->sc_dev, "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);
bf->bf_data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize);
STAILQ_INSERT_TAIL(&sc->sc_rxbuf, bf, bf_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_rxmem_paddr != 0) {
bus_dmamap_unload(sc->sc_rxdmat, sc->sc_rxmap);
sc->sc_rxmem_paddr = 0;
}
if (sc->sc_rxmem != NULL) {
bus_dmamem_free(sc->sc_rxdmat, sc->sc_rxmem, sc->sc_rxmap);
sc->sc_rxmem = 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_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) {
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_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);
}
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);
}
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--;
if ((m->m_flags & M_EXT) == 0 ||
m->m_ext.ext_free != mwl_ext_free)
continue;
off = m->m_data - m->m_ext.ext_buf;
if (off + m->m_pkthdr.len > MCLBYTES) {
continue;
}
cl = pool_cache_get_paddr(&mclpool_cache, 0,
&m->m_ext.ext_paddr);
if (cl != NULL) {
memcpy((caddr_t) cl + off, m->m_data, m->m_pkthdr.len);
MEXTREMOVE(m);
MEXTADD(m, cl, MCLBYTES, 0, NULL, &mclpool_cache);
m->m_flags |= M_CLUSTER | M_EXT_RW;
_MOWNERREF(m, M_EXT | M_CLUSTER);
m->m_data += off;
}
}
#endif
}
static void
mwl_node_drain(struct ieee80211_node *ni)
{
struct ieee80211com *ic = ni->ni_ic;
struct mwl_softc *sc = ic->ic_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);
sc->sc_node_drain(ni);
if (sc->sc_rxblocked && mn->mn_staid != 0 &&
(ni->ni_flags & IEEE80211_NODE_HT)) {
uint8_t tid;
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
*noise = -MWL_NODE_CONST(ni)->mn_ai.nf;
#else
*noise = -95;
#endif
#else
*noise = -95;
#endif
}
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;
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->ch[0].rssi[0], mn->mn_ai.rssi_a);
CVT(mi->ch[1].rssi[0], mn->mn_ai.rssi_b);
CVT(mi->ch[2].rssi[0], mn->mn_ai.rssi_c);
mi->ch[0].noise[0] = mn->mn_ai.nf_a;
mi->ch[1].noise[0] = mn->mn_ai.nf_b;
mi->ch[2].noise[0] = mn->mn_ai.nf_c;
#undef CVT
}
static __inline void *
mwl_getrxdma(struct mwl_softc *sc)
{
struct mwl_jumbo *buf;
void *data;
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;
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) {
ds->RxControl = EAGLE_RXD_CTRL_OS_OWN;
MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREWRITE);
sc->sc_stats.mst_rxbuf_failed++;
return ENOMEM;
}
}
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));
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(struct mbuf *m, void *data, void *arg)
{
struct mwl_softc *sc = arg;
mwl_putrxdma(sc, m->m_ext.ext_buf);
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];
} __packed;
static __inline int
mwl_anyhdrsize(const void *data)
{
const struct ieee80211_frame *wh = data;
if (IEEE80211_IS_CTL(wh)) {
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);
}
}
static __inline int
cvtrssi(uint8_t ssi)
{
int rssi = (int) ssi + 8;
rssi = 2*(87 - rssi);
return (rssi < 0 ? 0 : rssi > 127 ? 127 : rssi);
}
static void
mwl_rx_proc(void *arg, int npending)
{
struct mwl_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
struct mwl_rxbuf *bf;
struct mwl_rxdesc *ds;
struct mbuf *m;
struct ieee80211_qosframe *wh;
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;
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) {
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) {
counter_u64_add(ic->ic_ierrors, 1);
sc->sc_stats.mst_rx_crypto++;
if (status != EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR &&
(status & EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR)) {
bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap,
BUS_DMASYNC_POSTREAD);
mwl_handlemicerror(ic, data);
sc->sc_stats.mst_rx_tkipmic++;
}
goto rx_next;
}
len = le16toh(ds->PktLen);
bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, BUS_DMASYNC_POSTREAD);
hdrlen = mwl_anyhdrsize(data + sizeof(uint16_t));
off = sizeof(uint16_t) + sizeof(struct ieee80211_frame_addr4);
rssi = cvtrssi(ds->RSSI);
pktlen = hdrlen + (len - off);
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;
}
newdata = mwl_getrxdma(sc);
if (newdata == NULL) {
m_free(m);
mwl_hal_intrset(sc->sc_mh,
sc->sc_imask &~ MACREG_A2HRIC_BIT_RX_RDY);
sc->sc_rxblocked = 1;
ieee80211_drain(ic);
goto rx_stop;
}
bf->bf_data = newdata;
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;
wh = mtod(m, struct ieee80211_qosframe *);
ovbcopy(data + sizeof(uint16_t), wh, hdrlen);
if (IEEE80211_QOS_HAS_SEQ(wh))
*(uint16_t *)ieee80211_getqos(wh) = ds->QosCtrl;
if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED)
m->m_flags |= M_WEP;
#ifdef MWL_HOST_PS_SUPPORT
wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
#else
wh->i_fc[1] &= ~(IEEE80211_FC1_PROTECTED |
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);
}
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
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:
(void) mwl_rxbuf_init(sc, bf);
bf = STAILQ_NEXT(bf, bf_list);
}
rx_stop:
sc->sc_rxnext = bf;
if (mbufq_first(&sc->sc_snd) != NULL) {
mwl_hal_txstart(sc->sc_mh, 0);
mwl_start(sc);
}
}
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;
#if 0
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);
}
static int
mwl_tx_setup(struct mwl_softc *sc, int ac, int mvtype)
{
struct mwl_txq *txq;
if (ac >= nitems(sc->sc_ac2q)) {
device_printf(sc->sc_dev, "AC %u out of range, max %zu!\n",
ac, nitems(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;
}
static int
mwl_txq_update(struct mwl_softc *sc, int ac)
{
#define MWL_EXPONENT_TO_VALUE(v) ((1<<v)-1)
struct ieee80211com *ic = &sc->sc_ic;
struct chanAccParams chp;
struct mwl_txq *txq = sc->sc_ac2q[ac];
struct wmeParams *wmep;
struct mwl_hal *mh = sc->sc_mh;
int aifs, cwmin, cwmax, txoplim;
ieee80211_wme_ic_getparams(ic, &chp);
wmep = &chp.cap_wmeParams[ac];
aifs = wmep->wmep_aifsn;
cwmin = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmin);
cwmax = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmax);
txoplim = wmep->wmep_txopLimit;
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
}
static int
mwl_wme_update(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_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;
}
static void
mwl_tx_cleanupq(struct mwl_softc *sc, struct mwl_txq *txq)
{
MWL_TXQ_LOCK_DESTROY(txq);
}
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;
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) {
bf->bf_nseg = MWL_TXDESC+1;
} else if (error != 0) {
sc->sc_stats.mst_tx_busdma++;
m_freem(m0);
return error;
}
if (error == EFBIG) {
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) {
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;
}
static uint16_t
mwl_calcformat(uint8_t rate, const struct ieee80211_node *ni)
{
uint16_t fmt;
fmt = _IEEE80211_SHIFTMASK(3, EAGLE_TXD_ANTENNA)
| (IEEE80211_IS_CHAN_HT40D(ni->ni_chan) ?
EAGLE_TXD_EXTCHAN_LO : EAGLE_TXD_EXTCHAN_HI);
if (rate & IEEE80211_RATE_MCS) {
fmt |= EAGLE_TXD_FORMAT_HT
| _IEEE80211_SHIFTMASK(rate, EAGLE_TXD_RATE);
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 {
fmt |= EAGLE_TXD_FORMAT_LEGACY
| _IEEE80211_SHIFTMASK(mwl_cvtlegacyrate(rate),
EAGLE_TXD_RATE)
| EAGLE_TXD_CHW_20
| (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)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = ni->ni_vap;
int error, iswep, ismcast;
int hdrlen, 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_PROTECTED;
ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
hdrlen = ieee80211_anyhdrsize(wh);
pktlen = m0->m_pkthdr.len;
if (IEEE80211_QOS_HAS_SEQ(wh)) {
qos = *(uint16_t *)ieee80211_getqos(wh);
} else
qos = 0;
if (iswep) {
const struct ieee80211_cipher *cip;
struct ieee80211_key *k;
k = ieee80211_crypto_encap(ni, m0);
if (k == NULL) {
m_freem(m0);
return EIO;
}
cip = k->wk_cipher;
pktlen += cip->ic_header + cip->ic_miclen + cip->ic_trailer;
wh = mtod(m0, struct ieee80211_frame *);
}
if (ieee80211_radiotap_active_vap(vap)) {
sc->sc_tx_th.wt_flags = 0;
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);
}
if (hdrlen < sizeof(struct mwltxrec)) {
const int space = sizeof(struct mwltxrec) - hdrlen;
if (M_LEADINGSPACE(m0) < space) {
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);
tr->fwlen = htole16(pktlen - hdrlen);
error = mwl_tx_dmasetup(sc, bf, m0);
if (error != 0) {
DPRINTF(sc, MWL_DEBUG_XMIT,
"%s: unable to setup dma\n", __func__);
return error;
}
bf->bf_node = ni;
m0 = bf->bf_m;
tr = mtod(m0, struct mwltxrec *);
wh = (struct ieee80211_frame *)&tr->wh;
ds = bf->bf_desc;
txq = bf->bf_txq;
ds->QosCtrl = qos;
#if MWL_TXDESC == 1
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
ds->Format = 0;
ds->pad = 0;
ds->ack_wcb_addr = 0;
mn = MWL_NODE(ni);
switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) {
case IEEE80211_FC0_TYPE_MGT:
sc->sc_stats.mst_tx_mgmt++;
case IEEE80211_FC0_TYPE_CTL:
ds->TxPriority = MWL_WME_AC_BE;
break;
case IEEE80211_FC0_TYPE_DATA:
if (!ismcast) {
const struct ieee80211_txparam *tp = ni->ni_txparms;
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) {
ds->Format = htole16(
mwl_calcformat(tp->ucastrate, ni));
ds->pad = htole16(EAGLE_TXD_FIXED_RATE);
}
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:
device_printf(sc->sc_dev, "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);
sc->sc_tx_timer = 5;
MWL_TXQ_UNLOCK(txq);
return 0;
}
static __inline int
mwl_cvtlegacyrix(int rix)
{
static const int ieeerates[] =
{ 2, 4, 11, 22, 44, 12, 18, 24, 36, 48, 72, 96, 108 };
return (rix < nitems(ieeerates) ? ieeerates[rix] : 0);
}
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 ieee80211com *ic = &sc->sc_ic;
struct mwl_txbuf *bf;
struct mwl_txdesc *ds;
struct ieee80211_node *ni;
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) {
status = le32toh(ds->Status);
int rate;
if (status & EAGLE_TXD_STATUS_OK) {
uint16_t Format = le16toh(ds->Format);
uint8_t txant = _IEEE80211_MASKSHIFT(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++;
rate = _IEEE80211_MASKSHIFT(Format,
EAGLE_TXD_RATE);
if ((Format & EAGLE_TXD_FORMAT_HT) == 0) {
rate = mwl_cvtlegacyrix(rate);
} else
rate |= IEEE80211_RATE_MCS;
sc->sc_stats.mst_tx_rate = rate;
ieee80211_node_set_txrate_dot11rate(ni, rate);
} 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++;
}
if (bf->bf_m->m_flags & M_TXCB)
m_adj(bf->bf_m, sizeof(uint16_t));
ieee80211_tx_complete(ni, bf->bf_m,
(status & EAGLE_TXD_STATUS_OK) == 0);
} else
m_freem(bf->bf_m);
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);
mwl_puttxbuf_tail(txq, bf);
}
return nreaped;
#undef EAGLE_TXD_STATUS_MCAST
}
static void
mwl_tx_proc(void *arg, int npending)
{
struct mwl_softc *sc = arg;
int nreaped;
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) {
sc->sc_tx_timer = 0;
if (mbufq_first(&sc->sc_snd) != NULL) {
mwl_hal_txstart(sc->sc_mh, 0);
mwl_start(sc);
}
}
}
static void
mwl_tx_draintxq(struct mwl_softc *sc, struct mwl_txq *txq)
{
struct ieee80211_node *ni;
struct mwl_txbuf *bf;
u_int ix __unused;
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 ieee80211com *ic = &sc->sc_ic;
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
bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap);
ni = bf->bf_node;
if (ni != NULL) {
ieee80211_free_node(ni);
}
m_freem(bf->bf_m);
mwl_puttxbuf_tail(txq, bf);
}
}
static void
mwl_draintxq(struct mwl_softc *sc)
{
int i;
for (i = 0; i < MWL_NUM_TX_QUEUES; i++)
mwl_tx_draintxq(sc, &sc->sc_txq[i]);
sc->sc_tx_timer = 0;
}
#ifdef MWL_DIAGAPI
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
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_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,
_IEEE80211_MASKSHIFT(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_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;
#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
{
DPRINTF(sc, MWL_DEBUG_AMPDU,
"%s: already has max bastreams\n", __func__);
sc->sc_stats.mst_ampdu_reject++;
return 0;
}
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) {
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);
bas->bastream = sp;
tap->txa_private = bas;
}
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_softc;
struct mwl_bastate *bas;
bas = tap->txa_private;
if (bas == NULL) {
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;
bufsiz = _IEEE80211_MASKSHIFT(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) {
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;
}
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 {
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;
}
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_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);
}
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);
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;
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));
}
static int
mwl_chan_set(struct mwl_softc *sc, struct ieee80211_channel *chan)
{
struct mwl_hal *mh = sc->sc_mh;
struct ieee80211com *ic = &sc->sc_ic;
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);
mwl_mapchan(&hchan, chan);
mwl_hal_intrset(mh, 0);
#if 0
mwl_draintxq(sc);
#endif
mwl_hal_setchannel(mh, &hchan);
maxtxpow = 2*chan->ic_maxregpower;
if (maxtxpow > ic->ic_txpowlimit)
maxtxpow = ic->ic_txpowlimit;
mwl_hal_settxpower(mh, &hchan, maxtxpow / 2);
mwl_setcurchanrates(sc);
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 mwl_softc *sc = ic->ic_softc;
DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__);
}
static void
mwl_scan_end(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__);
}
static void
mwl_set_channel(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
(void) mwl_chan_set(sc, ic->ic_curchan);
}
static void
mwl_startcsa(struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
struct mwl_softc *sc = ic->ic_softc;
MWL_HAL_CHANNEL hchan;
if (sc->sc_csapending)
return;
mwl_mapchan(&hchan, ic->ic_csa_newchan);
mwl_hal_setchannelswitchie(sc->sc_mh, &hchan, 1, ic->ic_csa_count);
sc->sc_csapending = 1;
}
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) {
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) {
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);
}
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;
}
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;
}
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) {
pi->HTCapabilitiesInfo = ni->ni_htcap;
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;
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 != NET80211_STA_RX_BW_40)
pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_CHWIDTH40;
}
return pi;
}
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 mwl_softc *sc = ic->ic_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",
if_name(vap->iv_ifp), __func__,
ieee80211_state_name[ostate], ieee80211_state_name[nstate]);
callout_stop(&sc->sc_timer);
if (ostate == IEEE80211_S_CAC) {
mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_STOP);
} else if (sc->sc_radarena) {
mwl_hal_setradardetection(mh, DR_DFS_DISABLE);
sc->sc_radarena = 0;
}
if (nstate == IEEE80211_S_INIT) {
if (hvap != NULL)
mwl_hal_stop(hvap);
} else if (nstate == IEEE80211_S_SCAN) {
mwl_hal_start(hvap);
mwl_hal_setinframode(hvap);
} else if (nstate == IEEE80211_S_AUTH) {
ni = vap->iv_bss;
MWL_NODE(ni)->mn_hvap = hvap;
(void) mwl_peerstadb(ni, 0, 0, NULL);
} else if (nstate == IEEE80211_S_CSA) {
if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
vap->iv_opmode == IEEE80211_M_MBSS)
mwl_startcsa(vap);
} else if (nstate == IEEE80211_S_CAC) {
mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_START);
}
error = mvp->mv_newstate(vap, nstate, arg);
if (error == 0 && nstate == IEEE80211_S_RUN) {
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",
if_name(vap->iv_ifp), __func__, vap->iv_flags,
ni->ni_intval, ether_sprintf(ni->ni_bssid), ni->ni_capinfo,
ieee80211_chan2ieee(ic, ic->ic_curchan));
mwl_localstadb(vap);
switch (vap->iv_opmode) {
case IEEE80211_M_HOSTAP:
case IEEE80211_M_MBSS:
if (ostate == IEEE80211_S_CAC) {
mwl_hal_setradardetection(mh,
DR_IN_SERVICE_MONITOR_START);
sc->sc_radarena = 1;
}
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;
}
mwl_hal_start(hvap);
break;
case IEEE80211_M_STA:
DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: aid 0x%x\n",
if_name(vap->iv_ifp), __func__, ni->ni_associd);
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",
if_name(vap->iv_ifp), __func__,
ether_sprintf(ni->ni_bssid));
mwl_seteapolformat(vap);
break;
default:
break;
}
if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan))
mwl_hal_setcsmode(mh, CSMODE_AGGRESSIVE);
else
mwl_hal_setcsmode(mh, CSMODE_AUTO_ENA);
if (sc->sc_ageinterval != 0)
callout_reset(&sc->sc_timer, sc->sc_ageinterval*hz,
mwl_agestations, sc);
} else if (nstate == IEEE80211_S_SLEEP) {
} else if ((vap->iv_flags & IEEE80211_F_DWDS) &&
--sc->sc_ndwdsvaps == 0)
mwl_hal_setdwds(mh, 0);
bad:
return error;
}
static int
allocstaid(struct mwl_softc *sc, int aid)
{
int staid;
if (!(0 < aid && aid < MWL_MAXSTAID) || isset(sc->sc_staid, aid)) {
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);
}
static void
mwl_newassoc(struct ieee80211_node *ni, int isnew)
{
struct ieee80211vap *vap = ni->ni_vap;
struct mwl_softc *sc = vap->iv_ic->ic_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);
}
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);
}
}
static void
mwl_agestations(void *arg)
{
struct mwl_softc *sc = arg;
mwl_hal_setkeepalive(sc->sc_mh);
if (sc->sc_ageinterval != 0)
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_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 {
device_printf(sc->sc_dev,
"%s: channel %u freq %u/0x%x not 2.4/5GHz\n",
__func__, c->ic_ieee, c->ic_freq, c->ic_flags);
return EINVAL;
}
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)) {
hc = findhalchannel(ci, c->ic_extieee);
if (hc != NULL) {
if (c->ic_maxpower > 2*hc->maxTxPow)
c->ic_maxpower = 2*hc->maxTxPow;
goto next;
}
}
device_printf(sc->sc_dev,
"%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
addht40channels(struct ieee80211_channel chans[], int maxchans, int *nchans,
const MWL_HAL_CHANNELINFO *ci, int flags)
{
int i, error;
for (i = 0; i < ci->nchannels; i++) {
const struct mwl_hal_channel *hc = &ci->channels[i];
error = ieee80211_add_channel_ht40(chans, maxchans, nchans,
hc->ieee, hc->maxTxPow, flags);
if (error != 0 && error != ENOENT)
break;
}
}
static void
addchannels(struct ieee80211_channel chans[], int maxchans, int *nchans,
const MWL_HAL_CHANNELINFO *ci, const uint8_t bands[])
{
int i, error;
error = 0;
for (i = 0; i < ci->nchannels && error == 0; i++) {
const struct mwl_hal_channel *hc = &ci->channels[i];
error = ieee80211_add_channel(chans, maxchans, nchans,
hc->ieee, hc->freq, hc->maxTxPow, 0, bands);
}
}
static void
getchannels(struct mwl_softc *sc, int maxchans, int *nchans,
struct ieee80211_channel chans[])
{
const MWL_HAL_CHANNELINFO *ci;
uint8_t bands[IEEE80211_MODE_BYTES];
*nchans = 0;
if (mwl_hal_getchannelinfo(sc->sc_mh,
MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) {
memset(bands, 0, sizeof(bands));
setbit(bands, IEEE80211_MODE_11B);
setbit(bands, IEEE80211_MODE_11G);
setbit(bands, IEEE80211_MODE_11NG);
addchannels(chans, maxchans, nchans, ci, bands);
}
if (mwl_hal_getchannelinfo(sc->sc_mh,
MWL_FREQ_BAND_5GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) {
memset(bands, 0, sizeof(bands));
setbit(bands, IEEE80211_MODE_11A);
setbit(bands, IEEE80211_MODE_11NA);
addchannels(chans, maxchans, nchans, ci, bands);
}
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_softc;
getchannels(sc, maxchans, nchans, chans);
}
static int
mwl_getchannels(struct mwl_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
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] = ' ';
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:0x%jx) 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, (uintmax_t)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:0x%jx)\n", ds, (uintmax_t)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
#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 = arg;
callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc);
if (sc->sc_tx_timer == 0 || --sc->sc_tx_timer > 0)
return;
if (sc->sc_running && !sc->sc_invalid) {
if (mwl_hal_setkeepalive(sc->sc_mh))
device_printf(sc->sc_dev,
"transmit timeout (firmware hung?)\n");
else
device_printf(sc->sc_dev,
"transmit timeout\n");
#if 0
mwl_reset(sc);
mwl_txq_dump(&sc->sc_txq[0]);
#endif
counter_u64_add(sc->sc_ic.ic_oerrors, 1);
sc->sc_stats.mst_watchdog++;
}
}
#ifdef MWL_DIAGAPI
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) {
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) {
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) {
return error;
}
mwl_draintxq(sc);
mwl_resettxq(sc);
sc->sc_rxnext = NULL;
return 0;
}
#endif
static void
mwl_parent(struct ieee80211com *ic)
{
struct mwl_softc *sc = ic->ic_softc;
int startall = 0;
MWL_LOCK(sc);
if (ic->ic_nrunning > 0) {
if (sc->sc_running) {
mwl_mode_init(sc);
} else {
if (!sc->sc_invalid) {
mwl_init(sc);
startall = 1;
}
}
} else
mwl_stop(sc);
MWL_UNLOCK(sc);
if (startall)
ieee80211_start_all(ic);
}
static int
mwl_ioctl(struct ieee80211com *ic, u_long cmd, void *data)
{
struct mwl_softc *sc = ic->ic_softc;
struct ifreq *ifr = data;
int error = 0;
switch (cmd) {
case SIOCGMVSTATS:
mwl_hal_gethwstats(sc->sc_mh, &sc->sc_stats.hw_stats);
#if 0
sc->sc_stats.mst_tx_packets =
if_get_counter(ifp, IFCOUNTER_OPACKETS);
sc->sc_stats.mst_rx_packets =
if_get_counter(ifp, IFCOUNTER_IPACKETS);
#endif
return (copyout(&sc->sc_stats, ifr_data_get_ptr(ifr),
sizeof (sc->sc_stats)));
#ifdef MWL_DIAGAPI
case SIOCGMVDIAG:
return mwl_ioctl_diag(sc, (struct mwl_diag *) ifr);
case SIOCGMVRESET:
MWL_LOCK(sc);
error = mwl_ioctl_reset(sc,(struct mwl_diag *) ifr);
MWL_UNLOCK(sc);
break;
#endif
default:
error = ENOTTY;
break;
}
return (error);
}
#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
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 | CTLFLAG_NEEDGIANT, sc, 0,
mwl_sysctl_debug, "I", "control debugging printfs");
#endif
}
static void
mwl_announce(struct mwl_softc *sc)
{
device_printf(sc->sc_dev, "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];
device_printf(sc->sc_dev, "Use hw queue %u for %s traffic\n",
txq->qnum, ieee80211_wme_acnames[i]);
}
}
if (bootverbose || mwl_rxdesc != MWL_RXDESC)
device_printf(sc->sc_dev, "using %u rx descriptors\n", mwl_rxdesc);
if (bootverbose || mwl_rxbuf != MWL_RXBUF)
device_printf(sc->sc_dev, "using %u rx buffers\n", mwl_rxbuf);
if (bootverbose || mwl_txbuf != MWL_TXBUF)
device_printf(sc->sc_dev, "using %u tx buffers\n", mwl_txbuf);
if (bootverbose && mwl_hal_ismbsscapable(sc->sc_mh))
device_printf(sc->sc_dev, "multi-bss support\n");
#ifdef MWL_TX_NODROP
if (bootverbose)
device_printf(sc->sc_dev, "no tx drop\n");
#endif
}