7aeac9ef18
Run on LLNW canaries and tested by pho@ gallatin: Using a 14-core, 28-HTT single socket E5-2697 v3 with a 40GbE MLX5 based ConnectX 4-LX NIC, I see an almost 12% improvement in received packet rate, and a larger improvement in bytes delivered all the way to userspace. When the host receiving 64 streams of netperf -H $DUT -t UDP_STREAM -- -m 1, I see, using nstat -I mce0 1 before the patch: InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 4.98 0.00 4.42 0.00 4235592 33 83.80 4720653 2149771 1235 247.32 4.73 0.00 4.20 0.00 4025260 33 82.99 4724900 2139833 1204 247.32 4.72 0.00 4.20 0.00 4035252 33 82.14 4719162 2132023 1264 247.32 4.71 0.00 4.21 0.00 4073206 33 83.68 4744973 2123317 1347 247.32 4.72 0.00 4.21 0.00 4061118 33 80.82 4713615 2188091 1490 247.32 4.72 0.00 4.21 0.00 4051675 33 85.29 4727399 2109011 1205 247.32 4.73 0.00 4.21 0.00 4039056 33 84.65 4724735 2102603 1053 247.32 After the patch InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 5.43 0.00 4.20 0.00 3313143 33 84.96 5434214 1900162 2656 245.51 5.43 0.00 4.20 0.00 3308527 33 85.24 5439695 1809382 2521 245.51 5.42 0.00 4.19 0.00 3316778 33 87.54 5416028 1805835 2256 245.51 5.42 0.00 4.19 0.00 3317673 33 90.44 5426044 1763056 2332 245.51 5.42 0.00 4.19 0.00 3314839 33 88.11 5435732 1792218 2499 245.52 5.44 0.00 4.19 0.00 3293228 33 91.84 5426301 1668597 2121 245.52 Similarly, netperf reports 230Mb/s before the patch, and 270Mb/s after the patch Reviewed by: gallatin Sponsored by: Limelight Networks Differential Revision: https://reviews.freebsd.org/D15366
3244 lines
80 KiB
C
3244 lines
80 KiB
C
/* $OpenBSD: if_otus.c,v 1.49 2015/11/24 13:33:18 mpi Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2009 Damien Bergamini <damien.bergamini@free.fr>
|
|
* Copyright (c) 2015 Adrian Chadd <adrian@FreeBSD.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* Driver for Atheros AR9001U chipset.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_wlan.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/firmware.h>
|
|
#include <sys/module.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <net/bpf.h>
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/if_ether.h>
|
|
#include <netinet/ip.h>
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
#include <net80211/ieee80211_regdomain.h>
|
|
#include <net80211/ieee80211_radiotap.h>
|
|
#include <net80211/ieee80211_ratectl.h>
|
|
#ifdef IEEE80211_SUPPORT_SUPERG
|
|
#include <net80211/ieee80211_superg.h>
|
|
#endif
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usbdi.h>
|
|
#include "usbdevs.h"
|
|
|
|
#define USB_DEBUG_VAR otus_debug
|
|
#include <dev/usb/usb_debug.h>
|
|
|
|
#include "if_otusreg.h"
|
|
|
|
static int otus_debug = 0;
|
|
static SYSCTL_NODE(_hw_usb, OID_AUTO, otus, CTLFLAG_RW, 0, "USB otus");
|
|
SYSCTL_INT(_hw_usb_otus, OID_AUTO, debug, CTLFLAG_RWTUN, &otus_debug, 0,
|
|
"Debug level");
|
|
#define OTUS_DEBUG_XMIT 0x00000001
|
|
#define OTUS_DEBUG_RECV 0x00000002
|
|
#define OTUS_DEBUG_TXDONE 0x00000004
|
|
#define OTUS_DEBUG_RXDONE 0x00000008
|
|
#define OTUS_DEBUG_CMD 0x00000010
|
|
#define OTUS_DEBUG_CMDDONE 0x00000020
|
|
#define OTUS_DEBUG_RESET 0x00000040
|
|
#define OTUS_DEBUG_STATE 0x00000080
|
|
#define OTUS_DEBUG_CMDNOTIFY 0x00000100
|
|
#define OTUS_DEBUG_REGIO 0x00000200
|
|
#define OTUS_DEBUG_IRQ 0x00000400
|
|
#define OTUS_DEBUG_TXCOMP 0x00000800
|
|
#define OTUS_DEBUG_ANY 0xffffffff
|
|
|
|
#define OTUS_DPRINTF(sc, dm, ...) \
|
|
do { \
|
|
if ((dm == OTUS_DEBUG_ANY) || (dm & otus_debug)) \
|
|
device_printf(sc->sc_dev, __VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define OTUS_DEV(v, p) { USB_VPI(v, p, 0) }
|
|
static const STRUCT_USB_HOST_ID otus_devs[] = {
|
|
OTUS_DEV(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_WN7512),
|
|
OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_3CRUSBN275),
|
|
OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_TG121N),
|
|
OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR9170),
|
|
OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_WN612),
|
|
OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_WN821NV2),
|
|
OTUS_DEV(USB_VENDOR_AVM, USB_PRODUCT_AVM_FRITZWLAN),
|
|
OTUS_DEV(USB_VENDOR_CACE, USB_PRODUCT_CACE_AIRPCAPNX),
|
|
OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA130D1),
|
|
OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA160A1),
|
|
OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA160A2),
|
|
OTUS_DEV(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_WNGDNUS2),
|
|
OTUS_DEV(USB_VENDOR_NEC, USB_PRODUCT_NEC_WL300NUG),
|
|
OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WN111V2),
|
|
OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WNA1000),
|
|
OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WNDA3100),
|
|
OTUS_DEV(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GW_US300),
|
|
OTUS_DEV(USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_O8494),
|
|
OTUS_DEV(USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_WNC0600),
|
|
OTUS_DEV(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_UB81),
|
|
OTUS_DEV(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_UB82),
|
|
OTUS_DEV(USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1221),
|
|
OTUS_DEV(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_NWD271N),
|
|
};
|
|
|
|
static device_probe_t otus_match;
|
|
static device_attach_t otus_attach;
|
|
static device_detach_t otus_detach;
|
|
|
|
static int otus_attachhook(struct otus_softc *);
|
|
void otus_get_chanlist(struct otus_softc *);
|
|
static void otus_getradiocaps(struct ieee80211com *, int, int *,
|
|
struct ieee80211_channel[]);
|
|
int otus_load_firmware(struct otus_softc *, const char *,
|
|
uint32_t);
|
|
int otus_open_pipes(struct otus_softc *);
|
|
void otus_close_pipes(struct otus_softc *);
|
|
|
|
static int otus_alloc_tx_cmd_list(struct otus_softc *);
|
|
static void otus_free_tx_cmd_list(struct otus_softc *);
|
|
|
|
static int otus_alloc_rx_list(struct otus_softc *);
|
|
static void otus_free_rx_list(struct otus_softc *);
|
|
static int otus_alloc_tx_list(struct otus_softc *);
|
|
static void otus_free_tx_list(struct otus_softc *);
|
|
static void otus_free_list(struct otus_softc *, struct otus_data [], int);
|
|
static struct otus_data *_otus_getbuf(struct otus_softc *);
|
|
static struct otus_data *otus_getbuf(struct otus_softc *);
|
|
static void otus_freebuf(struct otus_softc *, struct otus_data *);
|
|
|
|
static struct otus_tx_cmd *_otus_get_txcmd(struct otus_softc *);
|
|
static struct otus_tx_cmd *otus_get_txcmd(struct otus_softc *);
|
|
static void otus_free_txcmd(struct otus_softc *, struct otus_tx_cmd *);
|
|
|
|
void otus_next_scan(void *, int);
|
|
static void otus_tx_task(void *, int pending);
|
|
void otus_do_async(struct otus_softc *,
|
|
void (*)(struct otus_softc *, void *), void *, int);
|
|
int otus_newstate(struct ieee80211vap *, enum ieee80211_state,
|
|
int);
|
|
int otus_cmd(struct otus_softc *, uint8_t, const void *, int,
|
|
void *, int);
|
|
void otus_write(struct otus_softc *, uint32_t, uint32_t);
|
|
int otus_write_barrier(struct otus_softc *);
|
|
static struct ieee80211_node *otus_node_alloc(struct ieee80211vap *vap,
|
|
const uint8_t mac[IEEE80211_ADDR_LEN]);
|
|
int otus_media_change(struct ifnet *);
|
|
int otus_read_eeprom(struct otus_softc *);
|
|
void otus_newassoc(struct ieee80211_node *, int);
|
|
void otus_cmd_rxeof(struct otus_softc *, uint8_t *, int);
|
|
void otus_sub_rxeof(struct otus_softc *, uint8_t *, int,
|
|
struct mbufq *);
|
|
static int otus_tx(struct otus_softc *, struct ieee80211_node *,
|
|
struct mbuf *, struct otus_data *,
|
|
const struct ieee80211_bpf_params *);
|
|
int otus_ioctl(struct ifnet *, u_long, caddr_t);
|
|
int otus_set_multi(struct otus_softc *);
|
|
static int otus_updateedca(struct ieee80211com *);
|
|
static void otus_updateedca_locked(struct otus_softc *);
|
|
static void otus_updateslot(struct otus_softc *);
|
|
static void otus_set_operating_mode(struct otus_softc *sc);
|
|
static void otus_set_rx_filter(struct otus_softc *sc);
|
|
int otus_init_mac(struct otus_softc *);
|
|
uint32_t otus_phy_get_def(struct otus_softc *, uint32_t);
|
|
int otus_set_board_values(struct otus_softc *,
|
|
struct ieee80211_channel *);
|
|
int otus_program_phy(struct otus_softc *,
|
|
struct ieee80211_channel *);
|
|
int otus_set_rf_bank4(struct otus_softc *,
|
|
struct ieee80211_channel *);
|
|
void otus_get_delta_slope(uint32_t, uint32_t *, uint32_t *);
|
|
static int otus_set_chan(struct otus_softc *, struct ieee80211_channel *,
|
|
int);
|
|
int otus_set_key(struct ieee80211com *, struct ieee80211_node *,
|
|
struct ieee80211_key *);
|
|
void otus_set_key_cb(struct otus_softc *, void *);
|
|
void otus_delete_key(struct ieee80211com *, struct ieee80211_node *,
|
|
struct ieee80211_key *);
|
|
void otus_delete_key_cb(struct otus_softc *, void *);
|
|
void otus_calibrate_to(void *, int);
|
|
int otus_set_bssid(struct otus_softc *, const uint8_t *);
|
|
int otus_set_macaddr(struct otus_softc *, const uint8_t *);
|
|
void otus_led_newstate_type1(struct otus_softc *);
|
|
void otus_led_newstate_type2(struct otus_softc *);
|
|
void otus_led_newstate_type3(struct otus_softc *);
|
|
int otus_init(struct otus_softc *sc);
|
|
void otus_stop(struct otus_softc *sc);
|
|
|
|
static device_method_t otus_methods[] = {
|
|
DEVMETHOD(device_probe, otus_match),
|
|
DEVMETHOD(device_attach, otus_attach),
|
|
DEVMETHOD(device_detach, otus_detach),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t otus_driver = {
|
|
.name = "otus",
|
|
.methods = otus_methods,
|
|
.size = sizeof(struct otus_softc)
|
|
};
|
|
|
|
static devclass_t otus_devclass;
|
|
|
|
DRIVER_MODULE(otus, uhub, otus_driver, otus_devclass, NULL, 0);
|
|
MODULE_DEPEND(otus, wlan, 1, 1, 1);
|
|
MODULE_DEPEND(otus, usb, 1, 1, 1);
|
|
MODULE_DEPEND(otus, firmware, 1, 1, 1);
|
|
MODULE_VERSION(otus, 1);
|
|
|
|
static usb_callback_t otus_bulk_tx_callback;
|
|
static usb_callback_t otus_bulk_rx_callback;
|
|
static usb_callback_t otus_bulk_irq_callback;
|
|
static usb_callback_t otus_bulk_cmd_callback;
|
|
|
|
static const struct usb_config otus_config[OTUS_N_XFER] = {
|
|
[OTUS_BULK_TX] = {
|
|
.type = UE_BULK,
|
|
.endpoint = UE_ADDR_ANY,
|
|
.direction = UE_DIR_OUT,
|
|
.bufsize = 0x200,
|
|
.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
|
|
.callback = otus_bulk_tx_callback,
|
|
.timeout = 5000, /* ms */
|
|
},
|
|
[OTUS_BULK_RX] = {
|
|
.type = UE_BULK,
|
|
.endpoint = UE_ADDR_ANY,
|
|
.direction = UE_DIR_IN,
|
|
.bufsize = OTUS_RXBUFSZ,
|
|
.flags = { .ext_buffer = 1, .pipe_bof = 1,.short_xfer_ok = 1,},
|
|
.callback = otus_bulk_rx_callback,
|
|
},
|
|
[OTUS_BULK_IRQ] = {
|
|
.type = UE_INTERRUPT,
|
|
.endpoint = UE_ADDR_ANY,
|
|
.direction = UE_DIR_IN,
|
|
.bufsize = OTUS_MAX_CTRLSZ,
|
|
.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
|
|
.callback = otus_bulk_irq_callback,
|
|
},
|
|
[OTUS_BULK_CMD] = {
|
|
.type = UE_INTERRUPT,
|
|
.endpoint = UE_ADDR_ANY,
|
|
.direction = UE_DIR_OUT,
|
|
.bufsize = OTUS_MAX_CTRLSZ,
|
|
.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
|
|
.callback = otus_bulk_cmd_callback,
|
|
.timeout = 5000, /* ms */
|
|
},
|
|
};
|
|
|
|
static int
|
|
otus_match(device_t self)
|
|
{
|
|
struct usb_attach_arg *uaa = device_get_ivars(self);
|
|
|
|
if (uaa->usb_mode != USB_MODE_HOST ||
|
|
uaa->info.bIfaceIndex != 0 ||
|
|
uaa->info.bConfigIndex != 0)
|
|
return (ENXIO);
|
|
|
|
return (usbd_lookup_id_by_uaa(otus_devs, sizeof(otus_devs), uaa));
|
|
}
|
|
|
|
static int
|
|
otus_attach(device_t self)
|
|
{
|
|
struct usb_attach_arg *uaa = device_get_ivars(self);
|
|
struct otus_softc *sc = device_get_softc(self);
|
|
int error;
|
|
uint8_t iface_index;
|
|
|
|
device_set_usb_desc(self);
|
|
sc->sc_udev = uaa->device;
|
|
sc->sc_dev = self;
|
|
|
|
mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK,
|
|
MTX_DEF);
|
|
|
|
TIMEOUT_TASK_INIT(taskqueue_thread, &sc->scan_to, 0, otus_next_scan, sc);
|
|
TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_to, 0, otus_calibrate_to, sc);
|
|
TASK_INIT(&sc->tx_task, 0, otus_tx_task, sc);
|
|
mbufq_init(&sc->sc_snd, ifqmaxlen);
|
|
|
|
iface_index = 0;
|
|
error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
|
|
otus_config, OTUS_N_XFER, sc, &sc->sc_mtx);
|
|
if (error) {
|
|
device_printf(sc->sc_dev,
|
|
"could not allocate USB transfers, err=%s\n",
|
|
usbd_errstr(error));
|
|
goto fail_usb;
|
|
}
|
|
|
|
if ((error = otus_open_pipes(sc)) != 0) {
|
|
device_printf(sc->sc_dev, "%s: could not open pipes\n",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
/* XXX check return status; fail out if appropriate */
|
|
if (otus_attachhook(sc) != 0)
|
|
goto fail;
|
|
|
|
return (0);
|
|
|
|
fail:
|
|
otus_close_pipes(sc);
|
|
fail_usb:
|
|
mtx_destroy(&sc->sc_mtx);
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
otus_detach(device_t self)
|
|
{
|
|
struct otus_softc *sc = device_get_softc(self);
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
|
|
otus_stop(sc);
|
|
|
|
usbd_transfer_unsetup(sc->sc_xfer, OTUS_N_XFER);
|
|
|
|
taskqueue_drain_timeout(taskqueue_thread, &sc->scan_to);
|
|
taskqueue_drain_timeout(taskqueue_thread, &sc->calib_to);
|
|
taskqueue_drain(taskqueue_thread, &sc->tx_task);
|
|
|
|
otus_close_pipes(sc);
|
|
#if 0
|
|
/* Wait for all queued asynchronous commands to complete. */
|
|
usb_rem_wait_task(sc->sc_udev, &sc->sc_task);
|
|
|
|
usbd_ref_wait(sc->sc_udev);
|
|
#endif
|
|
|
|
ieee80211_ifdetach(ic);
|
|
mtx_destroy(&sc->sc_mtx);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
otus_delay_ms(struct otus_softc *sc, int ms)
|
|
{
|
|
|
|
DELAY(1000 * ms);
|
|
}
|
|
|
|
static struct ieee80211vap *
|
|
otus_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 mac[IEEE80211_ADDR_LEN])
|
|
{
|
|
struct otus_vap *uvp;
|
|
struct ieee80211vap *vap;
|
|
|
|
if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
|
|
return (NULL);
|
|
|
|
uvp = malloc(sizeof(struct otus_vap), M_80211_VAP, M_WAITOK | M_ZERO);
|
|
vap = &uvp->vap;
|
|
|
|
if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
|
|
flags, bssid) != 0) {
|
|
/* out of memory */
|
|
free(uvp, M_80211_VAP);
|
|
return (NULL);
|
|
}
|
|
|
|
/* override state transition machine */
|
|
uvp->newstate = vap->iv_newstate;
|
|
vap->iv_newstate = otus_newstate;
|
|
|
|
/* XXX TODO: double-check */
|
|
vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16;
|
|
vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K;
|
|
|
|
ieee80211_ratectl_init(vap);
|
|
|
|
/* complete setup */
|
|
ieee80211_vap_attach(vap, ieee80211_media_change,
|
|
ieee80211_media_status, mac);
|
|
ic->ic_opmode = opmode;
|
|
|
|
return (vap);
|
|
}
|
|
|
|
static void
|
|
otus_vap_delete(struct ieee80211vap *vap)
|
|
{
|
|
struct otus_vap *uvp = OTUS_VAP(vap);
|
|
|
|
ieee80211_ratectl_deinit(vap);
|
|
ieee80211_vap_detach(vap);
|
|
free(uvp, M_80211_VAP);
|
|
}
|
|
|
|
static void
|
|
otus_parent(struct ieee80211com *ic)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
int startall = 0;
|
|
|
|
if (ic->ic_nrunning > 0) {
|
|
if (!sc->sc_running) {
|
|
otus_init(sc);
|
|
startall = 1;
|
|
} else {
|
|
(void) otus_set_multi(sc);
|
|
}
|
|
} else if (sc->sc_running)
|
|
otus_stop(sc);
|
|
|
|
if (startall)
|
|
ieee80211_start_all(ic);
|
|
}
|
|
|
|
static void
|
|
otus_drain_mbufq(struct otus_softc *sc)
|
|
{
|
|
struct mbuf *m;
|
|
struct ieee80211_node *ni;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
|
|
ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
|
|
m->m_pkthdr.rcvif = NULL;
|
|
ieee80211_free_node(ni);
|
|
m_freem(m);
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_tx_start(struct otus_softc *sc)
|
|
{
|
|
|
|
taskqueue_enqueue(taskqueue_thread, &sc->tx_task);
|
|
}
|
|
|
|
static int
|
|
otus_transmit(struct ieee80211com *ic, struct mbuf *m)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
int error;
|
|
|
|
OTUS_LOCK(sc);
|
|
if (! sc->sc_running) {
|
|
OTUS_UNLOCK(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* XXX TODO: handle fragments */
|
|
error = mbufq_enqueue(&sc->sc_snd, m);
|
|
if (error) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: mbufq_enqueue failed: %d\n",
|
|
__func__,
|
|
error);
|
|
OTUS_UNLOCK(sc);
|
|
return (error);
|
|
}
|
|
OTUS_UNLOCK(sc);
|
|
|
|
/* Kick TX */
|
|
otus_tx_start(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
_otus_start(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211_node *ni;
|
|
struct otus_data *bf;
|
|
struct mbuf *m;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
|
|
bf = otus_getbuf(sc);
|
|
if (bf == NULL) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: failed to get buffer\n", __func__);
|
|
mbufq_prepend(&sc->sc_snd, m);
|
|
break;
|
|
}
|
|
|
|
ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
|
|
m->m_pkthdr.rcvif = NULL;
|
|
|
|
if (otus_tx(sc, ni, m, bf, NULL) != 0) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: failed to transmit\n", __func__);
|
|
if_inc_counter(ni->ni_vap->iv_ifp,
|
|
IFCOUNTER_OERRORS, 1);
|
|
otus_freebuf(sc, bf);
|
|
ieee80211_free_node(ni);
|
|
m_freem(m);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_tx_task(void *arg, int pending)
|
|
{
|
|
struct otus_softc *sc = arg;
|
|
|
|
OTUS_LOCK(sc);
|
|
_otus_start(sc);
|
|
OTUS_UNLOCK(sc);
|
|
}
|
|
|
|
static int
|
|
otus_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
|
|
const struct ieee80211_bpf_params *params)
|
|
{
|
|
struct ieee80211com *ic= ni->ni_ic;
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
struct otus_data *bf = NULL;
|
|
int error = 0;
|
|
|
|
/* Don't transmit if we're not running */
|
|
OTUS_LOCK(sc);
|
|
if (! sc->sc_running) {
|
|
error = ENETDOWN;
|
|
goto error;
|
|
}
|
|
|
|
bf = otus_getbuf(sc);
|
|
if (bf == NULL) {
|
|
error = ENOBUFS;
|
|
goto error;
|
|
}
|
|
|
|
if (otus_tx(sc, ni, m, bf, params) != 0) {
|
|
error = EIO;
|
|
goto error;
|
|
}
|
|
|
|
OTUS_UNLOCK(sc);
|
|
return (0);
|
|
error:
|
|
if (bf)
|
|
otus_freebuf(sc, bf);
|
|
OTUS_UNLOCK(sc);
|
|
m_freem(m);
|
|
return (ENXIO);
|
|
}
|
|
|
|
static void
|
|
otus_update_chw(struct ieee80211com *ic)
|
|
{
|
|
|
|
printf("%s: TODO\n", __func__);
|
|
}
|
|
|
|
static void
|
|
otus_set_channel(struct ieee80211com *ic)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "%s: set channel: %d\n",
|
|
__func__,
|
|
ic->ic_curchan->ic_freq);
|
|
|
|
OTUS_LOCK(sc);
|
|
(void) otus_set_chan(sc, ic->ic_curchan, 0);
|
|
OTUS_UNLOCK(sc);
|
|
}
|
|
|
|
static int
|
|
otus_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
|
|
{
|
|
|
|
/* For now, no A-MPDU TX support in the driver */
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
otus_scan_start(struct ieee80211com *ic)
|
|
{
|
|
|
|
// printf("%s: TODO\n", __func__);
|
|
}
|
|
|
|
static void
|
|
otus_scan_end(struct ieee80211com *ic)
|
|
{
|
|
|
|
// printf("%s: TODO\n", __func__);
|
|
}
|
|
|
|
static void
|
|
otus_update_mcast(struct ieee80211com *ic)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
|
|
(void) otus_set_multi(sc);
|
|
}
|
|
|
|
static int
|
|
otus_attachhook(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
usb_device_request_t req;
|
|
uint32_t in, out;
|
|
int error;
|
|
|
|
/* Not locked */
|
|
error = otus_load_firmware(sc, "otusfw_init", AR_FW_INIT_ADDR);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev, "%s: could not load %s firmware\n",
|
|
__func__, "init");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* XXX not locked? */
|
|
otus_delay_ms(sc, 1000);
|
|
|
|
/* Not locked */
|
|
error = otus_load_firmware(sc, "otusfw_main", AR_FW_MAIN_ADDR);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev, "%s: could not load %s firmware\n",
|
|
__func__, "main");
|
|
return (ENXIO);
|
|
}
|
|
|
|
OTUS_LOCK(sc);
|
|
|
|
/* Tell device that firmware transfer is complete. */
|
|
req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
|
|
req.bRequest = AR_FW_DOWNLOAD_COMPLETE;
|
|
USETW(req.wValue, 0);
|
|
USETW(req.wIndex, 0);
|
|
USETW(req.wLength, 0);
|
|
if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, NULL,
|
|
0, NULL, 250) != 0) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: firmware initialization failed\n",
|
|
__func__);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Send an ECHO command to check that everything is settled. */
|
|
in = 0xbadc0ffe;
|
|
if (otus_cmd(sc, AR_CMD_ECHO, &in, sizeof in, &out, sizeof(out)) != 0) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: echo command failed\n", __func__);
|
|
return (ENXIO);
|
|
}
|
|
if (in != out) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: echo reply mismatch: 0x%08x!=0x%08x\n",
|
|
__func__, in, out);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Read entire EEPROM. */
|
|
if (otus_read_eeprom(sc) != 0) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not read EEPROM\n",
|
|
__func__);
|
|
return (ENXIO);
|
|
}
|
|
|
|
OTUS_UNLOCK(sc);
|
|
|
|
sc->txmask = sc->eeprom.baseEepHeader.txMask;
|
|
sc->rxmask = sc->eeprom.baseEepHeader.rxMask;
|
|
sc->capflags = sc->eeprom.baseEepHeader.opCapFlags;
|
|
IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->eeprom.baseEepHeader.macAddr);
|
|
sc->sc_led_newstate = otus_led_newstate_type3; /* XXX */
|
|
|
|
device_printf(sc->sc_dev,
|
|
"MAC/BBP AR9170, RF AR%X, MIMO %dT%dR, address %s\n",
|
|
(sc->capflags & AR5416_OPFLAGS_11A) ?
|
|
0x9104 : ((sc->txmask == 0x5) ? 0x9102 : 0x9101),
|
|
(sc->txmask == 0x5) ? 2 : 1, (sc->rxmask == 0x5) ? 2 : 1,
|
|
ether_sprintf(ic->ic_macaddr));
|
|
|
|
ic->ic_softc = sc;
|
|
ic->ic_name = device_get_nameunit(sc->sc_dev);
|
|
ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
|
|
ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */
|
|
|
|
/* Set device capabilities. */
|
|
ic->ic_caps =
|
|
IEEE80211_C_STA | /* station mode */
|
|
#if 0
|
|
IEEE80211_C_BGSCAN | /* Background scan. */
|
|
#endif
|
|
IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */
|
|
IEEE80211_C_WME | /* WME/QoS */
|
|
IEEE80211_C_SHSLOT | /* Short slot time supported. */
|
|
IEEE80211_C_FF | /* Atheros fast-frames supported. */
|
|
IEEE80211_C_MONITOR |
|
|
IEEE80211_C_WPA; /* WPA/RSN. */
|
|
|
|
/* XXX TODO: 11n */
|
|
|
|
#if 0
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) {
|
|
/* Set supported .11b and .11g rates. */
|
|
ic->ic_sup_rates[IEEE80211_MODE_11B] =
|
|
ieee80211_std_rateset_11b;
|
|
ic->ic_sup_rates[IEEE80211_MODE_11G] =
|
|
ieee80211_std_rateset_11g;
|
|
}
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) {
|
|
/* Set supported .11a rates. */
|
|
ic->ic_sup_rates[IEEE80211_MODE_11A] =
|
|
ieee80211_std_rateset_11a;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* Build the list of supported channels. */
|
|
otus_get_chanlist(sc);
|
|
#else
|
|
otus_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
|
|
ic->ic_channels);
|
|
#endif
|
|
|
|
ieee80211_ifattach(ic);
|
|
ic->ic_raw_xmit = otus_raw_xmit;
|
|
ic->ic_scan_start = otus_scan_start;
|
|
ic->ic_scan_end = otus_scan_end;
|
|
ic->ic_set_channel = otus_set_channel;
|
|
ic->ic_getradiocaps = otus_getradiocaps;
|
|
ic->ic_vap_create = otus_vap_create;
|
|
ic->ic_vap_delete = otus_vap_delete;
|
|
ic->ic_update_mcast = otus_update_mcast;
|
|
ic->ic_update_promisc = otus_update_mcast;
|
|
ic->ic_parent = otus_parent;
|
|
ic->ic_transmit = otus_transmit;
|
|
ic->ic_update_chw = otus_update_chw;
|
|
ic->ic_ampdu_enable = otus_ampdu_enable;
|
|
ic->ic_wme.wme_update = otus_updateedca;
|
|
ic->ic_newassoc = otus_newassoc;
|
|
ic->ic_node_alloc = otus_node_alloc;
|
|
|
|
#ifdef notyet
|
|
ic->ic_set_key = otus_set_key;
|
|
ic->ic_delete_key = otus_delete_key;
|
|
#endif
|
|
|
|
ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr,
|
|
sizeof(sc->sc_txtap), OTUS_TX_RADIOTAP_PRESENT,
|
|
&sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
|
|
OTUS_RX_RADIOTAP_PRESENT);
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
otus_get_chanlist(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
uint16_t domain;
|
|
uint8_t chan;
|
|
int i;
|
|
|
|
/* XXX regulatory domain. */
|
|
domain = le16toh(sc->eeprom.baseEepHeader.regDmn[0]);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "regdomain=0x%04x\n", domain);
|
|
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) {
|
|
for (i = 0; i < 14; i++) {
|
|
chan = ar_chans[i];
|
|
ic->ic_channels[chan].ic_freq =
|
|
ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ);
|
|
ic->ic_channels[chan].ic_flags =
|
|
IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
|
|
IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
|
|
}
|
|
}
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) {
|
|
for (i = 14; i < nitems(ar_chans); i++) {
|
|
chan = ar_chans[i];
|
|
ic->ic_channels[chan].ic_freq =
|
|
ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ);
|
|
ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_A;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_getradiocaps(struct ieee80211com *ic,
|
|
int maxchans, int *nchans, struct ieee80211_channel chans[])
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
uint8_t bands[IEEE80211_MODE_BYTES];
|
|
|
|
/* Set supported .11b and .11g rates. */
|
|
memset(bands, 0, sizeof(bands));
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) {
|
|
setbit(bands, IEEE80211_MODE_11B);
|
|
setbit(bands, IEEE80211_MODE_11G);
|
|
#if 0
|
|
if (sc->sc_ht)
|
|
setbit(bands, IEEE80211_MODE_11NG);
|
|
#endif
|
|
ieee80211_add_channel_list_2ghz(chans, maxchans, nchans,
|
|
ar_chans, 14, bands, 0);
|
|
}
|
|
if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) {
|
|
setbit(bands, IEEE80211_MODE_11A);
|
|
ieee80211_add_channel_list_5ghz(chans, maxchans, nchans,
|
|
&ar_chans[14], nitems(ar_chans) - 14, bands, 0);
|
|
}
|
|
}
|
|
|
|
int
|
|
otus_load_firmware(struct otus_softc *sc, const char *name, uint32_t addr)
|
|
{
|
|
usb_device_request_t req;
|
|
char *ptr;
|
|
const struct firmware *fw;
|
|
int mlen, error, size;
|
|
|
|
error = 0;
|
|
|
|
/* Read firmware image from the filesystem. */
|
|
if ((fw = firmware_get(name)) == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: failed loadfirmware of file %s\n", __func__, name);
|
|
return (ENXIO);
|
|
}
|
|
req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
|
|
req.bRequest = AR_FW_DOWNLOAD;
|
|
USETW(req.wIndex, 0);
|
|
|
|
OTUS_LOCK(sc);
|
|
|
|
/* XXX const */
|
|
ptr = __DECONST(char *, fw->data);
|
|
size = fw->datasize;
|
|
addr >>= 8;
|
|
while (size > 0) {
|
|
mlen = MIN(size, 4096);
|
|
|
|
USETW(req.wValue, addr);
|
|
USETW(req.wLength, mlen);
|
|
if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
|
|
&req, ptr, 0, NULL, 250) != 0) {
|
|
error = EIO;
|
|
break;
|
|
}
|
|
addr += mlen >> 8;
|
|
ptr += mlen;
|
|
size -= mlen;
|
|
}
|
|
|
|
OTUS_UNLOCK(sc);
|
|
|
|
firmware_put(fw, FIRMWARE_UNLOAD);
|
|
if (error != 0)
|
|
device_printf(sc->sc_dev,
|
|
"%s: %s: error=%d\n", __func__, name, error);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
otus_open_pipes(struct otus_softc *sc)
|
|
{
|
|
#if 0
|
|
int isize, error;
|
|
int i;
|
|
#endif
|
|
int error;
|
|
|
|
OTUS_UNLOCK_ASSERT(sc);
|
|
|
|
if ((error = otus_alloc_tx_cmd_list(sc)) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not allocate command xfer\n",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
if ((error = otus_alloc_tx_list(sc)) != 0) {
|
|
device_printf(sc->sc_dev, "%s: could not allocate Tx xfers\n",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
if ((error = otus_alloc_rx_list(sc)) != 0) {
|
|
device_printf(sc->sc_dev, "%s: could not allocate Rx xfers\n",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
/* Enable RX transfers; needed for initial firmware messages */
|
|
OTUS_LOCK(sc);
|
|
usbd_transfer_start(sc->sc_xfer[OTUS_BULK_RX]);
|
|
usbd_transfer_start(sc->sc_xfer[OTUS_BULK_IRQ]);
|
|
OTUS_UNLOCK(sc);
|
|
return 0;
|
|
|
|
fail: otus_close_pipes(sc);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
otus_close_pipes(struct otus_softc *sc)
|
|
{
|
|
|
|
OTUS_LOCK(sc);
|
|
otus_free_tx_cmd_list(sc);
|
|
otus_free_tx_list(sc);
|
|
otus_free_rx_list(sc);
|
|
OTUS_UNLOCK(sc);
|
|
|
|
usbd_transfer_unsetup(sc->sc_xfer, OTUS_N_XFER);
|
|
}
|
|
|
|
static void
|
|
otus_free_cmd_list(struct otus_softc *sc, struct otus_tx_cmd cmd[], int ndata)
|
|
{
|
|
int i;
|
|
|
|
/* XXX TODO: someone has to have waken up waiters! */
|
|
for (i = 0; i < ndata; i++) {
|
|
struct otus_tx_cmd *dp = &cmd[i];
|
|
|
|
if (dp->buf != NULL) {
|
|
free(dp->buf, M_USBDEV);
|
|
dp->buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
otus_alloc_cmd_list(struct otus_softc *sc, struct otus_tx_cmd cmd[],
|
|
int ndata, int maxsz)
|
|
{
|
|
int i, error;
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
struct otus_tx_cmd *dp = &cmd[i];
|
|
dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT | M_ZERO);
|
|
dp->odata = NULL;
|
|
if (dp->buf == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"could not allocate buffer\n");
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
fail:
|
|
otus_free_cmd_list(sc, cmd, ndata);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
otus_alloc_tx_cmd_list(struct otus_softc *sc)
|
|
{
|
|
int error, i;
|
|
|
|
error = otus_alloc_cmd_list(sc, sc->sc_cmd, OTUS_CMD_LIST_COUNT,
|
|
OTUS_MAX_TXCMDSZ);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
STAILQ_INIT(&sc->sc_cmd_active);
|
|
STAILQ_INIT(&sc->sc_cmd_inactive);
|
|
STAILQ_INIT(&sc->sc_cmd_pending);
|
|
STAILQ_INIT(&sc->sc_cmd_waiting);
|
|
|
|
for (i = 0; i < OTUS_CMD_LIST_COUNT; i++)
|
|
STAILQ_INSERT_HEAD(&sc->sc_cmd_inactive, &sc->sc_cmd[i],
|
|
next_cmd);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
otus_free_tx_cmd_list(struct otus_softc *sc)
|
|
{
|
|
|
|
/*
|
|
* XXX TODO: something needs to wake up any pending/sleeping
|
|
* waiters!
|
|
*/
|
|
STAILQ_INIT(&sc->sc_cmd_active);
|
|
STAILQ_INIT(&sc->sc_cmd_inactive);
|
|
STAILQ_INIT(&sc->sc_cmd_pending);
|
|
STAILQ_INIT(&sc->sc_cmd_waiting);
|
|
|
|
otus_free_cmd_list(sc, sc->sc_cmd, OTUS_CMD_LIST_COUNT);
|
|
}
|
|
|
|
static int
|
|
otus_alloc_list(struct otus_softc *sc, struct otus_data data[],
|
|
int ndata, int maxsz)
|
|
{
|
|
int i, error;
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
struct otus_data *dp = &data[i];
|
|
dp->sc = sc;
|
|
dp->m = NULL;
|
|
dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT | M_ZERO);
|
|
if (dp->buf == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"could not allocate buffer\n");
|
|
error = ENOMEM;
|
|
goto fail;
|
|
}
|
|
dp->ni = NULL;
|
|
}
|
|
|
|
return (0);
|
|
fail:
|
|
otus_free_list(sc, data, ndata);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
otus_alloc_rx_list(struct otus_softc *sc)
|
|
{
|
|
int error, i;
|
|
|
|
error = otus_alloc_list(sc, sc->sc_rx, OTUS_RX_LIST_COUNT,
|
|
OTUS_RXBUFSZ);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
STAILQ_INIT(&sc->sc_rx_active);
|
|
STAILQ_INIT(&sc->sc_rx_inactive);
|
|
|
|
for (i = 0; i < OTUS_RX_LIST_COUNT; i++)
|
|
STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
otus_alloc_tx_list(struct otus_softc *sc)
|
|
{
|
|
int error, i;
|
|
|
|
error = otus_alloc_list(sc, sc->sc_tx, OTUS_TX_LIST_COUNT,
|
|
OTUS_TXBUFSZ);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
STAILQ_INIT(&sc->sc_tx_inactive);
|
|
|
|
for (i = 0; i != OTUS_N_XFER; i++) {
|
|
STAILQ_INIT(&sc->sc_tx_active[i]);
|
|
STAILQ_INIT(&sc->sc_tx_pending[i]);
|
|
}
|
|
|
|
for (i = 0; i < OTUS_TX_LIST_COUNT; i++) {
|
|
STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
otus_free_tx_list(struct otus_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
/* prevent further allocations from TX list(s) */
|
|
STAILQ_INIT(&sc->sc_tx_inactive);
|
|
|
|
for (i = 0; i != OTUS_N_XFER; i++) {
|
|
STAILQ_INIT(&sc->sc_tx_active[i]);
|
|
STAILQ_INIT(&sc->sc_tx_pending[i]);
|
|
}
|
|
|
|
otus_free_list(sc, sc->sc_tx, OTUS_TX_LIST_COUNT);
|
|
}
|
|
|
|
static void
|
|
otus_free_rx_list(struct otus_softc *sc)
|
|
{
|
|
/* prevent further allocations from RX list(s) */
|
|
STAILQ_INIT(&sc->sc_rx_inactive);
|
|
STAILQ_INIT(&sc->sc_rx_active);
|
|
|
|
otus_free_list(sc, sc->sc_rx, OTUS_RX_LIST_COUNT);
|
|
}
|
|
|
|
static void
|
|
otus_free_list(struct otus_softc *sc, struct otus_data data[], int ndata)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
struct otus_data *dp = &data[i];
|
|
|
|
if (dp->buf != NULL) {
|
|
free(dp->buf, M_USBDEV);
|
|
dp->buf = NULL;
|
|
}
|
|
if (dp->ni != NULL) {
|
|
ieee80211_free_node(dp->ni);
|
|
dp->ni = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct otus_data *
|
|
_otus_getbuf(struct otus_softc *sc)
|
|
{
|
|
struct otus_data *bf;
|
|
|
|
bf = STAILQ_FIRST(&sc->sc_tx_inactive);
|
|
if (bf != NULL)
|
|
STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next);
|
|
else
|
|
bf = NULL;
|
|
/* XXX bzero? */
|
|
return (bf);
|
|
}
|
|
|
|
static struct otus_data *
|
|
otus_getbuf(struct otus_softc *sc)
|
|
{
|
|
struct otus_data *bf;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
bf = _otus_getbuf(sc);
|
|
return (bf);
|
|
}
|
|
|
|
static void
|
|
otus_freebuf(struct otus_softc *sc, struct otus_data *bf)
|
|
{
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next);
|
|
}
|
|
|
|
static struct otus_tx_cmd *
|
|
_otus_get_txcmd(struct otus_softc *sc)
|
|
{
|
|
struct otus_tx_cmd *bf;
|
|
|
|
bf = STAILQ_FIRST(&sc->sc_cmd_inactive);
|
|
if (bf != NULL)
|
|
STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next_cmd);
|
|
else
|
|
bf = NULL;
|
|
return (bf);
|
|
}
|
|
|
|
static struct otus_tx_cmd *
|
|
otus_get_txcmd(struct otus_softc *sc)
|
|
{
|
|
struct otus_tx_cmd *bf;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
bf = _otus_get_txcmd(sc);
|
|
if (bf == NULL) {
|
|
device_printf(sc->sc_dev, "%s: no tx cmd buffers\n",
|
|
__func__);
|
|
}
|
|
return (bf);
|
|
}
|
|
|
|
static void
|
|
otus_free_txcmd(struct otus_softc *sc, struct otus_tx_cmd *bf)
|
|
{
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, bf, next_cmd);
|
|
}
|
|
|
|
void
|
|
otus_next_scan(void *arg, int pending)
|
|
{
|
|
#if 0
|
|
struct otus_softc *sc = arg;
|
|
|
|
if (usbd_is_dying(sc->sc_udev))
|
|
return;
|
|
|
|
usbd_ref_incr(sc->sc_udev);
|
|
|
|
if (sc->sc_ic.ic_state == IEEE80211_S_SCAN)
|
|
ieee80211_next_scan(&sc->sc_ic.ic_if);
|
|
|
|
usbd_ref_decr(sc->sc_udev);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
otus_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
|
|
{
|
|
struct otus_vap *uvp = OTUS_VAP(vap);
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
enum ieee80211_state ostate;
|
|
|
|
ostate = vap->iv_state;
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_STATE, "%s: %s -> %s\n", __func__,
|
|
ieee80211_state_name[ostate],
|
|
ieee80211_state_name[nstate]);
|
|
|
|
IEEE80211_UNLOCK(ic);
|
|
|
|
OTUS_LOCK(sc);
|
|
|
|
/* XXX TODO: more fleshing out! */
|
|
|
|
switch (nstate) {
|
|
case IEEE80211_S_INIT:
|
|
otus_set_operating_mode(sc);
|
|
otus_set_rx_filter(sc);
|
|
break;
|
|
case IEEE80211_S_RUN:
|
|
if (ic->ic_opmode == IEEE80211_M_STA) {
|
|
otus_updateslot(sc);
|
|
otus_set_operating_mode(sc);
|
|
otus_set_rx_filter(sc);
|
|
|
|
/* Start calibration timer. */
|
|
taskqueue_enqueue_timeout(taskqueue_thread,
|
|
&sc->calib_to, hz);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* XXX TODO: calibration? */
|
|
|
|
sc->sc_led_newstate(sc);
|
|
|
|
OTUS_UNLOCK(sc);
|
|
IEEE80211_LOCK(ic);
|
|
return (uvp->newstate(vap, nstate, arg));
|
|
}
|
|
|
|
int
|
|
otus_cmd(struct otus_softc *sc, uint8_t code, const void *idata, int ilen,
|
|
void *odata, int odatalen)
|
|
{
|
|
struct otus_tx_cmd *cmd;
|
|
struct ar_cmd_hdr *hdr;
|
|
int xferlen, error;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
/* Always bulk-out a multiple of 4 bytes. */
|
|
xferlen = (sizeof (*hdr) + ilen + 3) & ~3;
|
|
if (xferlen > OTUS_MAX_TXCMDSZ) {
|
|
device_printf(sc->sc_dev, "%s: command (0x%02x) size (%d) > %d\n",
|
|
__func__,
|
|
code,
|
|
xferlen,
|
|
OTUS_MAX_TXCMDSZ);
|
|
return (EIO);
|
|
}
|
|
|
|
cmd = otus_get_txcmd(sc);
|
|
if (cmd == NULL) {
|
|
device_printf(sc->sc_dev, "%s: failed to get buf\n",
|
|
__func__);
|
|
return (EIO);
|
|
}
|
|
|
|
hdr = (struct ar_cmd_hdr *)cmd->buf;
|
|
hdr->code = code;
|
|
hdr->len = ilen;
|
|
hdr->token = ++sc->token; /* Don't care about endianness. */
|
|
cmd->token = hdr->token;
|
|
/* XXX TODO: check max cmd length? */
|
|
memcpy((uint8_t *)&hdr[1], idata, ilen);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMD,
|
|
"%s: sending command code=0x%02x len=%d token=%d\n",
|
|
__func__, code, ilen, hdr->token);
|
|
|
|
cmd->odata = odata;
|
|
cmd->odatalen = odatalen;
|
|
cmd->buflen = xferlen;
|
|
|
|
/* Queue the command to the endpoint */
|
|
STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next_cmd);
|
|
usbd_transfer_start(sc->sc_xfer[OTUS_BULK_CMD]);
|
|
|
|
/* Sleep on the command; wait for it to complete */
|
|
error = msleep(cmd, &sc->sc_mtx, PCATCH, "otuscmd", hz);
|
|
|
|
/*
|
|
* At this point we don't own cmd any longer; it'll be
|
|
* freed by the cmd bulk path or the RX notification
|
|
* path. If the data is made available then it'll be copied
|
|
* to the caller. All that is left to do is communicate
|
|
* status back to the caller.
|
|
*/
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: timeout waiting for command 0x%02x reply\n",
|
|
__func__, code);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void
|
|
otus_write(struct otus_softc *sc, uint32_t reg, uint32_t val)
|
|
{
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
sc->write_buf[sc->write_idx].reg = htole32(reg);
|
|
sc->write_buf[sc->write_idx].val = htole32(val);
|
|
|
|
if (++sc->write_idx > (AR_MAX_WRITE_IDX-1))
|
|
(void)otus_write_barrier(sc);
|
|
}
|
|
|
|
int
|
|
otus_write_barrier(struct otus_softc *sc)
|
|
{
|
|
int error;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
if (sc->write_idx == 0)
|
|
return 0; /* Nothing to flush. */
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_REGIO, "%s: called; %d updates\n",
|
|
__func__,
|
|
sc->write_idx);
|
|
|
|
error = otus_cmd(sc, AR_CMD_WREG, sc->write_buf,
|
|
sizeof (sc->write_buf[0]) * sc->write_idx, NULL, 0);
|
|
sc->write_idx = 0;
|
|
return error;
|
|
}
|
|
|
|
static struct ieee80211_node *
|
|
otus_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
|
|
{
|
|
|
|
return malloc(sizeof (struct otus_node), M_80211_NODE,
|
|
M_NOWAIT | M_ZERO);
|
|
}
|
|
|
|
#if 0
|
|
int
|
|
otus_media_change(struct ifnet *ifp)
|
|
{
|
|
struct otus_softc *sc = ifp->if_softc;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
uint8_t rate, ridx;
|
|
int error;
|
|
|
|
error = ieee80211_media_change(ifp);
|
|
if (error != ENETRESET)
|
|
return error;
|
|
|
|
if (ic->ic_fixed_rate != -1) {
|
|
rate = ic->ic_sup_rates[ic->ic_curmode].
|
|
rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL;
|
|
for (ridx = 0; ridx <= OTUS_RIDX_MAX; ridx++)
|
|
if (otus_rates[ridx].rate == rate)
|
|
break;
|
|
sc->fixed_ridx = ridx;
|
|
}
|
|
|
|
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING))
|
|
error = otus_init(sc);
|
|
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
otus_read_eeprom(struct otus_softc *sc)
|
|
{
|
|
uint32_t regs[8], reg;
|
|
uint8_t *eep;
|
|
int i, j, error;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
/* Read EEPROM by blocks of 32 bytes. */
|
|
eep = (uint8_t *)&sc->eeprom;
|
|
reg = AR_EEPROM_OFFSET;
|
|
for (i = 0; i < sizeof (sc->eeprom) / 32; i++) {
|
|
for (j = 0; j < 8; j++, reg += 4)
|
|
regs[j] = htole32(reg);
|
|
error = otus_cmd(sc, AR_CMD_RREG, regs, sizeof regs, eep, 32);
|
|
if (error != 0)
|
|
break;
|
|
eep += 32;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void
|
|
otus_newassoc(struct ieee80211_node *ni, int isnew)
|
|
{
|
|
struct ieee80211com *ic = ni->ni_ic;
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
struct otus_node *on = OTUS_NODE(ni);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_STATE, "new assoc isnew=%d addr=%s\n",
|
|
isnew, ether_sprintf(ni->ni_macaddr));
|
|
|
|
on->tx_done = 0;
|
|
on->tx_err = 0;
|
|
on->tx_retries = 0;
|
|
}
|
|
|
|
static void
|
|
otus_cmd_handle_response(struct otus_softc *sc, struct ar_cmd_hdr *hdr)
|
|
{
|
|
struct otus_tx_cmd *cmd;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"%s: received reply code=0x%02x len=%d token=%d\n",
|
|
__func__,
|
|
hdr->code, hdr->len, hdr->token);
|
|
|
|
/*
|
|
* Walk the list, freeing items that aren't ours,
|
|
* stopping when we hit our token.
|
|
*/
|
|
while ((cmd = STAILQ_FIRST(&sc->sc_cmd_waiting)) != NULL) {
|
|
STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next_cmd);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"%s: cmd=%p; hdr.token=%d, cmd.token=%d\n",
|
|
__func__,
|
|
cmd,
|
|
(int) hdr->token,
|
|
(int) cmd->token);
|
|
if (hdr->token == cmd->token) {
|
|
/* Copy answer into caller's supplied buffer. */
|
|
if (cmd->odata != NULL) {
|
|
if (hdr->len != cmd->odatalen) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: code 0x%02x, len=%d, olen=%d\n",
|
|
__func__,
|
|
(int) hdr->code,
|
|
(int) hdr->len,
|
|
(int) cmd->odatalen);
|
|
}
|
|
memcpy(cmd->odata, &hdr[1],
|
|
MIN(cmd->odatalen, hdr->len));
|
|
}
|
|
wakeup(cmd);
|
|
}
|
|
|
|
STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next_cmd);
|
|
}
|
|
}
|
|
|
|
void
|
|
otus_cmd_rxeof(struct otus_softc *sc, uint8_t *buf, int len)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ar_cmd_hdr *hdr;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
if (__predict_false(len < sizeof (*hdr))) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"cmd too small %d\n", len);
|
|
return;
|
|
}
|
|
hdr = (struct ar_cmd_hdr *)buf;
|
|
if (__predict_false(sizeof (*hdr) + hdr->len > len ||
|
|
sizeof (*hdr) + hdr->len > 64)) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"cmd too large %d\n", hdr->len);
|
|
return;
|
|
}
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE,
|
|
"%s: code=%.02x\n",
|
|
__func__,
|
|
hdr->code);
|
|
|
|
/*
|
|
* This has to reach into the cmd queue "waiting for
|
|
* an RX response" list, grab the head entry and check
|
|
* if we need to wake anyone up.
|
|
*/
|
|
if ((hdr->code & 0xc0) != 0xc0) {
|
|
otus_cmd_handle_response(sc, hdr);
|
|
return;
|
|
}
|
|
|
|
/* Received unsolicited notification. */
|
|
switch (hdr->code & 0x3f) {
|
|
case AR_EVT_BEACON:
|
|
break;
|
|
case AR_EVT_TX_COMP:
|
|
{
|
|
struct ar_evt_tx_comp *tx = (struct ar_evt_tx_comp *)&hdr[1];
|
|
struct ieee80211_node *ni;
|
|
|
|
ni = ieee80211_find_node(&ic->ic_sta, tx->macaddr);
|
|
if (ni == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: txcomp on unknown node (%s)\n",
|
|
__func__,
|
|
ether_sprintf(tx->macaddr));
|
|
break;
|
|
}
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_TXCOMP,
|
|
"tx completed %s status=%d phy=0x%x\n",
|
|
ether_sprintf(tx->macaddr), le16toh(tx->status),
|
|
le32toh(tx->phy));
|
|
|
|
switch (le16toh(tx->status)) {
|
|
case AR_TX_STATUS_COMP:
|
|
#if 0
|
|
ackfailcnt = 0;
|
|
ieee80211_ratectl_tx_complete(ni->ni_vap, ni,
|
|
IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL);
|
|
#endif
|
|
/*
|
|
* We don't get the above; only error notifications.
|
|
* Sigh. So, don't worry about this.
|
|
*/
|
|
break;
|
|
case AR_TX_STATUS_RETRY_COMP:
|
|
OTUS_NODE(ni)->tx_retries++;
|
|
break;
|
|
case AR_TX_STATUS_FAILED:
|
|
OTUS_NODE(ni)->tx_err++;
|
|
break;
|
|
}
|
|
ieee80211_free_node(ni);
|
|
break;
|
|
}
|
|
case AR_EVT_TBTT:
|
|
break;
|
|
case AR_EVT_DO_BB_RESET:
|
|
/*
|
|
* This is "tell driver to reset baseband" from ar9170-fw.
|
|
*
|
|
* I'm not sure what we should do here, so I'm going to
|
|
* fall through; it gets generated when RTSRetryCnt internally
|
|
* reaches '5' - I guess the firmware authors thought that
|
|
* meant that the BB may have gone deaf or something.
|
|
*/
|
|
default:
|
|
device_printf(sc->sc_dev,
|
|
"%s: received notification code=0x%02x len=%d\n",
|
|
__func__,
|
|
hdr->code, hdr->len);
|
|
}
|
|
}
|
|
|
|
void
|
|
otus_sub_rxeof(struct otus_softc *sc, uint8_t *buf, int len, struct mbufq *rxq)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211_rx_stats rxs;
|
|
#if 0
|
|
struct ieee80211_node *ni;
|
|
#endif
|
|
struct ar_rx_tail *tail;
|
|
struct ieee80211_frame *wh;
|
|
struct mbuf *m;
|
|
uint8_t *plcp;
|
|
// int s;
|
|
int mlen;
|
|
|
|
if (__predict_false(len < AR_PLCP_HDR_LEN)) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE,
|
|
"sub-xfer too short %d\n", len);
|
|
return;
|
|
}
|
|
plcp = buf;
|
|
|
|
/* All bits in the PLCP header are set to 1 for non-MPDU. */
|
|
if (memcmp(plcp, AR_PLCP_HDR_INTR, AR_PLCP_HDR_LEN) == 0) {
|
|
otus_cmd_rxeof(sc, plcp + AR_PLCP_HDR_LEN,
|
|
len - AR_PLCP_HDR_LEN);
|
|
return;
|
|
}
|
|
|
|
/* Received MPDU. */
|
|
if (__predict_false(len < AR_PLCP_HDR_LEN + sizeof (*tail))) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "MPDU too short %d\n", len);
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
return;
|
|
}
|
|
tail = (struct ar_rx_tail *)(plcp + len - sizeof (*tail));
|
|
|
|
/* Discard error frames; don't discard BAD_RA (eg monitor mode); let net80211 do that */
|
|
if (__predict_false((tail->error & ~AR_RX_ERROR_BAD_RA) != 0)) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "error frame 0x%02x\n", tail->error);
|
|
if (tail->error & AR_RX_ERROR_FCS) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "bad FCS\n");
|
|
} else if (tail->error & AR_RX_ERROR_MMIC) {
|
|
/* Report Michael MIC failures to net80211. */
|
|
#if 0
|
|
ieee80211_notify_michael_failure(ni->ni_vap, wh, keyidx);
|
|
#endif
|
|
device_printf(sc->sc_dev, "%s: MIC failure\n", __func__);
|
|
}
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
return;
|
|
}
|
|
/* Compute MPDU's length. */
|
|
mlen = len - AR_PLCP_HDR_LEN - sizeof (*tail);
|
|
/* Make sure there's room for an 802.11 header + FCS. */
|
|
if (__predict_false(mlen < IEEE80211_MIN_LEN)) {
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
return;
|
|
}
|
|
mlen -= IEEE80211_CRC_LEN; /* strip 802.11 FCS */
|
|
|
|
wh = (struct ieee80211_frame *)(plcp + AR_PLCP_HDR_LEN);
|
|
|
|
/*
|
|
* TODO: I see > 2KiB buffers in this path; is it A-MSDU or something?
|
|
*/
|
|
m = m_get2(mlen, M_NOWAIT, MT_DATA, M_PKTHDR);
|
|
if (m == NULL) {
|
|
device_printf(sc->sc_dev, "%s: failed m_get2() (mlen=%d)\n", __func__, mlen);
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
return;
|
|
}
|
|
|
|
/* Finalize mbuf. */
|
|
memcpy(mtod(m, uint8_t *), wh, mlen);
|
|
m->m_pkthdr.len = m->m_len = mlen;
|
|
|
|
#if 0
|
|
if (__predict_false(sc->sc_drvbpf != NULL)) {
|
|
struct otus_rx_radiotap_header *tap = &sc->sc_rxtap;
|
|
struct mbuf mb;
|
|
|
|
tap->wr_flags = 0;
|
|
tap->wr_chan_freq = htole16(ic->ic_ibss_chan->ic_freq);
|
|
tap->wr_chan_flags = htole16(ic->ic_ibss_chan->ic_flags);
|
|
tap->wr_antsignal = tail->rssi;
|
|
tap->wr_rate = 2; /* In case it can't be found below. */
|
|
switch (tail->status & AR_RX_STATUS_MT_MASK) {
|
|
case AR_RX_STATUS_MT_CCK:
|
|
switch (plcp[0]) {
|
|
case 10: tap->wr_rate = 2; break;
|
|
case 20: tap->wr_rate = 4; break;
|
|
case 55: tap->wr_rate = 11; break;
|
|
case 110: tap->wr_rate = 22; break;
|
|
}
|
|
if (tail->status & AR_RX_STATUS_SHPREAMBLE)
|
|
tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
|
|
break;
|
|
case AR_RX_STATUS_MT_OFDM:
|
|
switch (plcp[0] & 0xf) {
|
|
case 0xb: tap->wr_rate = 12; break;
|
|
case 0xf: tap->wr_rate = 18; break;
|
|
case 0xa: tap->wr_rate = 24; break;
|
|
case 0xe: tap->wr_rate = 36; break;
|
|
case 0x9: tap->wr_rate = 48; break;
|
|
case 0xd: tap->wr_rate = 72; break;
|
|
case 0x8: tap->wr_rate = 96; break;
|
|
case 0xc: tap->wr_rate = 108; break;
|
|
}
|
|
break;
|
|
}
|
|
mb.m_data = (caddr_t)tap;
|
|
mb.m_next = m;
|
|
mb.m_nextpkt = NULL;
|
|
mb.m_type = 0;
|
|
mb.m_flags = 0;
|
|
bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_IN);
|
|
}
|
|
#endif
|
|
|
|
/* Add RSSI/NF to this mbuf */
|
|
bzero(&rxs, sizeof(rxs));
|
|
rxs.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI;
|
|
rxs.c_nf = sc->sc_nf[0]; /* XXX chain 0 != combined rssi/nf */
|
|
rxs.c_rssi = tail->rssi;
|
|
/* XXX TODO: add MIMO RSSI/NF as well */
|
|
if (ieee80211_add_rx_params(m, &rxs) == 0) {
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
return;
|
|
}
|
|
|
|
/* XXX make a method */
|
|
STAILQ_INSERT_TAIL(&rxq->mq_head, m, m_stailqpkt);
|
|
|
|
#if 0
|
|
OTUS_UNLOCK(sc);
|
|
ni = ieee80211_find_rxnode(ic, wh);
|
|
rxi.rxi_flags = 0;
|
|
rxi.rxi_rssi = tail->rssi;
|
|
rxi.rxi_tstamp = 0; /* unused */
|
|
ieee80211_input(ifp, m, ni, &rxi);
|
|
|
|
/* Node is no longer needed. */
|
|
ieee80211_release_node(ic, ni);
|
|
OTUS_LOCK(sc);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
otus_rxeof(struct usb_xfer *xfer, struct otus_data *data, struct mbufq *rxq)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
caddr_t buf = data->buf;
|
|
struct ar_rx_head *head;
|
|
uint16_t hlen;
|
|
int len;
|
|
|
|
usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
|
|
|
|
while (len >= sizeof (*head)) {
|
|
head = (struct ar_rx_head *)buf;
|
|
if (__predict_false(head->tag != htole16(AR_RX_HEAD_TAG))) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE,
|
|
"tag not valid 0x%x\n", le16toh(head->tag));
|
|
break;
|
|
}
|
|
hlen = le16toh(head->len);
|
|
if (__predict_false(sizeof (*head) + hlen > len)) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE,
|
|
"xfer too short %d/%d\n", len, hlen);
|
|
break;
|
|
}
|
|
/* Process sub-xfer. */
|
|
otus_sub_rxeof(sc, (uint8_t *)&head[1], hlen, rxq);
|
|
|
|
/* Next sub-xfer is aligned on a 32-bit boundary. */
|
|
hlen = (sizeof (*head) + hlen + 3) & ~3;
|
|
buf += hlen;
|
|
len -= hlen;
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_node *ni;
|
|
struct mbuf *m;
|
|
struct mbufq scrx;
|
|
struct otus_data *data;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
mbufq_init(&scrx, 1024);
|
|
|
|
#if 0
|
|
device_printf(sc->sc_dev, "%s: called; state=%d; error=%d\n",
|
|
__func__,
|
|
USB_GET_STATE(xfer),
|
|
error);
|
|
#endif
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
data = STAILQ_FIRST(&sc->sc_rx_active);
|
|
if (data == NULL)
|
|
goto tr_setup;
|
|
STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
|
|
otus_rxeof(xfer, data, &scrx);
|
|
STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
|
|
/* FALLTHROUGH */
|
|
case USB_ST_SETUP:
|
|
tr_setup:
|
|
/*
|
|
* XXX TODO: what if sc_rx isn't empty, but data
|
|
* is empty? Then we leak mbufs.
|
|
*/
|
|
data = STAILQ_FIRST(&sc->sc_rx_inactive);
|
|
if (data == NULL) {
|
|
//KASSERT(m == NULL, ("mbuf isn't NULL"));
|
|
return;
|
|
}
|
|
STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next);
|
|
STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next);
|
|
usbd_xfer_set_frame_data(xfer, 0, data->buf,
|
|
usbd_xfer_max_len(xfer));
|
|
usbd_transfer_submit(xfer);
|
|
/*
|
|
* To avoid LOR we should unlock our private mutex here to call
|
|
* ieee80211_input() because here is at the end of a USB
|
|
* callback and safe to unlock.
|
|
*/
|
|
OTUS_UNLOCK(sc);
|
|
while ((m = mbufq_dequeue(&scrx)) != NULL) {
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
ni = ieee80211_find_rxnode(ic,
|
|
(struct ieee80211_frame_min *)wh);
|
|
if (ni != NULL) {
|
|
if (ni->ni_flags & IEEE80211_NODE_HT)
|
|
m->m_flags |= M_AMPDU;
|
|
(void)ieee80211_input_mimo(ni, m);
|
|
ieee80211_free_node(ni);
|
|
} else
|
|
(void)ieee80211_input_mimo_all(ic, m);
|
|
}
|
|
#ifdef IEEE80211_SUPPORT_SUPERG
|
|
ieee80211_ff_age_all(ic, 100);
|
|
#endif
|
|
OTUS_LOCK(sc);
|
|
break;
|
|
default:
|
|
/* needs it to the inactive queue due to a error. */
|
|
data = STAILQ_FIRST(&sc->sc_rx_active);
|
|
if (data != NULL) {
|
|
STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
|
|
STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
|
|
}
|
|
if (error != USB_ERR_CANCELLED) {
|
|
usbd_xfer_set_stall(xfer);
|
|
counter_u64_add(ic->ic_ierrors, 1);
|
|
goto tr_setup;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_txeof(struct usb_xfer *xfer, struct otus_data *data)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_TXDONE,
|
|
"%s: called; data=%p\n", __func__, data);
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
if (sc->sc_tx_n_active == 0) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: completed but tx_active=0\n",
|
|
__func__);
|
|
} else {
|
|
sc->sc_tx_n_active--;
|
|
}
|
|
|
|
if (data->m) {
|
|
/* XXX status? */
|
|
/* XXX we get TX status via the RX path.. */
|
|
ieee80211_tx_complete(data->ni, data->m, 0);
|
|
data->m = NULL;
|
|
data->ni = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_txcmdeof(struct usb_xfer *xfer, struct otus_tx_cmd *cmd)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"%s: called; data=%p; odata=%p\n",
|
|
__func__, cmd, cmd->odata);
|
|
|
|
/*
|
|
* Non-response commands still need wakeup so the caller
|
|
* knows it was submitted and completed OK; response commands should
|
|
* wait until they're ACKed by the firmware with a response.
|
|
*/
|
|
if (cmd->odata) {
|
|
STAILQ_INSERT_TAIL(&sc->sc_cmd_waiting, cmd, next_cmd);
|
|
} else {
|
|
wakeup(cmd);
|
|
otus_free_txcmd(sc, cmd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
otus_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
uint8_t which = OTUS_BULK_TX;
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct otus_data *data;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
data = STAILQ_FIRST(&sc->sc_tx_active[which]);
|
|
if (data == NULL)
|
|
goto tr_setup;
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_TXDONE,
|
|
"%s: transfer done %p\n", __func__, data);
|
|
STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next);
|
|
otus_txeof(xfer, data);
|
|
otus_freebuf(sc, data);
|
|
/* FALLTHROUGH */
|
|
case USB_ST_SETUP:
|
|
tr_setup:
|
|
data = STAILQ_FIRST(&sc->sc_tx_pending[which]);
|
|
if (data == NULL) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: empty pending queue sc %p\n", __func__, sc);
|
|
sc->sc_tx_n_active = 0;
|
|
goto finish;
|
|
}
|
|
STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next);
|
|
STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next);
|
|
usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: submitting transfer %p\n", __func__, data);
|
|
usbd_transfer_submit(xfer);
|
|
sc->sc_tx_n_active++;
|
|
break;
|
|
default:
|
|
data = STAILQ_FIRST(&sc->sc_tx_active[which]);
|
|
if (data != NULL) {
|
|
STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next);
|
|
otus_txeof(xfer, data);
|
|
otus_freebuf(sc, data);
|
|
}
|
|
counter_u64_add(ic->ic_oerrors, 1);
|
|
|
|
if (error != USB_ERR_CANCELLED) {
|
|
usbd_xfer_set_stall(xfer);
|
|
goto tr_setup;
|
|
}
|
|
break;
|
|
}
|
|
|
|
finish:
|
|
#ifdef IEEE80211_SUPPORT_SUPERG
|
|
/*
|
|
* If the TX active queue drops below a certain
|
|
* threshold, ensure we age fast-frames out so they're
|
|
* transmitted.
|
|
*/
|
|
if (sc->sc_tx_n_active < 2) {
|
|
/* XXX ew - net80211 should defer this for us! */
|
|
OTUS_UNLOCK(sc);
|
|
ieee80211_ff_flush(ic, WME_AC_VO);
|
|
ieee80211_ff_flush(ic, WME_AC_VI);
|
|
ieee80211_ff_flush(ic, WME_AC_BE);
|
|
ieee80211_ff_flush(ic, WME_AC_BK);
|
|
OTUS_LOCK(sc);
|
|
}
|
|
#endif
|
|
/* Kick TX */
|
|
otus_tx_start(sc);
|
|
}
|
|
|
|
static void
|
|
otus_bulk_cmd_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
#if 0
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
#endif
|
|
struct otus_tx_cmd *cmd;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
cmd = STAILQ_FIRST(&sc->sc_cmd_active);
|
|
if (cmd == NULL)
|
|
goto tr_setup;
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE,
|
|
"%s: transfer done %p\n", __func__, cmd);
|
|
STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next_cmd);
|
|
otus_txcmdeof(xfer, cmd);
|
|
/* FALLTHROUGH */
|
|
case USB_ST_SETUP:
|
|
tr_setup:
|
|
cmd = STAILQ_FIRST(&sc->sc_cmd_pending);
|
|
if (cmd == NULL) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMD,
|
|
"%s: empty pending queue sc %p\n", __func__, sc);
|
|
return;
|
|
}
|
|
STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next_cmd);
|
|
STAILQ_INSERT_TAIL(&sc->sc_cmd_active, cmd, next_cmd);
|
|
usbd_xfer_set_frame_data(xfer, 0, cmd->buf, cmd->buflen);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_CMD,
|
|
"%s: submitting transfer %p; buf=%p, buflen=%d\n", __func__, cmd, cmd->buf, cmd->buflen);
|
|
usbd_transfer_submit(xfer);
|
|
break;
|
|
default:
|
|
cmd = STAILQ_FIRST(&sc->sc_cmd_active);
|
|
if (cmd != NULL) {
|
|
STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next_cmd);
|
|
otus_txcmdeof(xfer, cmd);
|
|
}
|
|
|
|
if (error != USB_ERR_CANCELLED) {
|
|
usbd_xfer_set_stall(xfer);
|
|
goto tr_setup;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This isn't used by carl9170; it however may be used by the
|
|
* initial bootloader.
|
|
*/
|
|
static void
|
|
otus_bulk_irq_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
struct otus_softc *sc = usbd_xfer_softc(xfer);
|
|
int actlen;
|
|
int sumlen;
|
|
|
|
usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ,
|
|
"%s: called; state=%d\n", __func__, USB_GET_STATE(xfer));
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
/*
|
|
* Read usb frame data, if any.
|
|
* "actlen" has the total length for all frames
|
|
* transferred.
|
|
*/
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ,
|
|
"%s: comp; %d bytes\n",
|
|
__func__,
|
|
actlen);
|
|
#if 0
|
|
pc = usbd_xfer_get_frame(xfer, 0);
|
|
otus_dump_usb_rx_page(sc, pc, actlen);
|
|
#endif
|
|
/* XXX fallthrough */
|
|
case USB_ST_SETUP:
|
|
/*
|
|
* Setup xfer frame lengths/count and data
|
|
*/
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: setup\n", __func__);
|
|
usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
|
|
usbd_transfer_submit(xfer);
|
|
break;
|
|
|
|
default: /* Error */
|
|
/*
|
|
* Print error message and clear stall
|
|
* for example.
|
|
*/
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: ERROR?\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Map net80211 rate to hw rate for otus MAC/PHY.
|
|
*/
|
|
static uint8_t
|
|
otus_rate_to_hw_rate(struct otus_softc *sc, uint8_t rate)
|
|
{
|
|
int is_2ghz;
|
|
|
|
is_2ghz = !! (IEEE80211_IS_CHAN_2GHZ(sc->sc_ic.ic_curchan));
|
|
|
|
switch (rate) {
|
|
/* CCK */
|
|
case 2:
|
|
return (0x0);
|
|
case 4:
|
|
return (0x1);
|
|
case 11:
|
|
return (0x2);
|
|
case 22:
|
|
return (0x3);
|
|
/* OFDM */
|
|
case 12:
|
|
return (0xb);
|
|
case 18:
|
|
return (0xf);
|
|
case 24:
|
|
return (0xa);
|
|
case 36:
|
|
return (0xe);
|
|
case 48:
|
|
return (0x9);
|
|
case 72:
|
|
return (0xd);
|
|
case 96:
|
|
return (0x8);
|
|
case 108:
|
|
return (0xc);
|
|
default:
|
|
device_printf(sc->sc_dev, "%s: unknown rate '%d'\n",
|
|
__func__, (int) rate);
|
|
case 0:
|
|
if (is_2ghz)
|
|
return (0x0); /* 1MB CCK */
|
|
else
|
|
return (0xb); /* 6MB OFDM */
|
|
|
|
/* XXX TODO: HT */
|
|
}
|
|
}
|
|
|
|
static int
|
|
otus_hw_rate_is_ofdm(struct otus_softc *sc, uint8_t hw_rate)
|
|
{
|
|
|
|
switch (hw_rate) {
|
|
case 0x0:
|
|
case 0x1:
|
|
case 0x2:
|
|
case 0x3:
|
|
return (0);
|
|
default:
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
otus_tx_update_ratectl(struct otus_softc *sc, struct ieee80211_node *ni)
|
|
{
|
|
struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs;
|
|
struct otus_node *on = OTUS_NODE(ni);
|
|
|
|
txs->flags = IEEE80211_RATECTL_TX_STATS_NODE |
|
|
IEEE80211_RATECTL_TX_STATS_RETRIES;
|
|
txs->ni = ni;
|
|
txs->nframes = on->tx_done;
|
|
txs->nsuccess = on->tx_done - on->tx_err;
|
|
txs->nretries = on->tx_retries;
|
|
|
|
ieee80211_ratectl_tx_update(ni->ni_vap, txs);
|
|
on->tx_done = on->tx_err = on->tx_retries = 0;
|
|
}
|
|
|
|
/*
|
|
* XXX TODO: support tx bpf parameters for configuration!
|
|
*
|
|
* Relevant pieces:
|
|
*
|
|
* ac = params->ibp_pri & 3;
|
|
* rate = params->ibp_rate0;
|
|
* params->ibp_flags & IEEE80211_BPF_NOACK
|
|
* params->ibp_flags & IEEE80211_BPF_RTS
|
|
* params->ibp_flags & IEEE80211_BPF_CTS
|
|
* tx->rts_ntries = params->ibp_try1;
|
|
* tx->data_ntries = params->ibp_try0;
|
|
*/
|
|
static int
|
|
otus_tx(struct otus_softc *sc, struct ieee80211_node *ni, struct mbuf *m,
|
|
struct otus_data *data, const struct ieee80211_bpf_params *params)
|
|
{
|
|
const struct ieee80211_txparam *tp = ni->ni_txparms;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_key *k;
|
|
struct ar_tx_head *head;
|
|
uint32_t phyctl;
|
|
uint16_t macctl, qos;
|
|
uint8_t qid, rate;
|
|
int hasqos, xferlen, type, ismcast;
|
|
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
|
|
k = ieee80211_crypto_encap(ni, m);
|
|
if (k == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: m=%p: ieee80211_crypto_encap returns NULL\n",
|
|
__func__,
|
|
m);
|
|
return (ENOBUFS);
|
|
}
|
|
wh = mtod(m, struct ieee80211_frame *);
|
|
}
|
|
|
|
/* Calculate transfer length; ensure data buffer is large enough */
|
|
xferlen = sizeof (*head) + m->m_pkthdr.len;
|
|
if (xferlen > OTUS_TXBUFSZ) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: 802.11 TX frame is %d bytes, max %d bytes\n",
|
|
__func__,
|
|
xferlen,
|
|
OTUS_TXBUFSZ);
|
|
return (ENOBUFS);
|
|
}
|
|
|
|
hasqos = !! IEEE80211_QOS_HAS_SEQ(wh);
|
|
|
|
if (hasqos) {
|
|
uint8_t tid;
|
|
qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0];
|
|
tid = qos & IEEE80211_QOS_TID;
|
|
qid = TID_TO_WME_AC(tid);
|
|
} else {
|
|
qos = 0;
|
|
qid = WME_AC_BE;
|
|
}
|
|
|
|
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
|
|
ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
|
|
|
|
/* Pickup a rate index. */
|
|
if (params != NULL)
|
|
rate = otus_rate_to_hw_rate(sc, params->ibp_rate0);
|
|
else if (!!(m->m_flags & M_EAPOL) || type != IEEE80211_FC0_TYPE_DATA)
|
|
rate = otus_rate_to_hw_rate(sc, tp->mgmtrate);
|
|
else if (ismcast)
|
|
rate = otus_rate_to_hw_rate(sc, tp->mcastrate);
|
|
else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
|
|
rate = otus_rate_to_hw_rate(sc, tp->ucastrate);
|
|
else {
|
|
(void) ieee80211_ratectl_rate(ni, NULL, 0);
|
|
rate = otus_rate_to_hw_rate(sc, ni->ni_txrate);
|
|
}
|
|
|
|
phyctl = 0;
|
|
macctl = AR_TX_MAC_BACKOFF | AR_TX_MAC_HW_DUR | AR_TX_MAC_QID(qid);
|
|
|
|
/*
|
|
* XXX TODO: params for NOACK, ACK, RTS, CTS, etc
|
|
*/
|
|
if (ismcast ||
|
|
(hasqos && ((qos & IEEE80211_QOS_ACKPOLICY) ==
|
|
IEEE80211_QOS_ACKPOLICY_NOACK)))
|
|
macctl |= AR_TX_MAC_NOACK;
|
|
|
|
if (!ismcast) {
|
|
if (m->m_pkthdr.len + IEEE80211_CRC_LEN >= vap->iv_rtsthreshold)
|
|
macctl |= AR_TX_MAC_RTS;
|
|
else if (ic->ic_flags & IEEE80211_F_USEPROT) {
|
|
if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
|
|
macctl |= AR_TX_MAC_CTS;
|
|
else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
|
|
macctl |= AR_TX_MAC_RTS;
|
|
}
|
|
}
|
|
|
|
phyctl |= AR_TX_PHY_MCS(rate);
|
|
if (otus_hw_rate_is_ofdm(sc, rate)) {
|
|
phyctl |= AR_TX_PHY_MT_OFDM;
|
|
/* Always use all tx antennas for now, just to be safe */
|
|
phyctl |= AR_TX_PHY_ANTMSK(sc->txmask);
|
|
} else { /* CCK */
|
|
phyctl |= AR_TX_PHY_MT_CCK;
|
|
phyctl |= AR_TX_PHY_ANTMSK(sc->txmask);
|
|
}
|
|
|
|
/* Update net80211 with the current counters */
|
|
otus_tx_update_ratectl(sc, ni);
|
|
|
|
/* Update rate control stats for frames that are ACK'ed. */
|
|
if (!(macctl & AR_TX_MAC_NOACK))
|
|
OTUS_NODE(ni)->tx_done++;
|
|
|
|
|
|
/* Fill Tx descriptor. */
|
|
head = (struct ar_tx_head *)data->buf;
|
|
head->len = htole16(m->m_pkthdr.len + IEEE80211_CRC_LEN);
|
|
head->macctl = htole16(macctl);
|
|
head->phyctl = htole32(phyctl);
|
|
|
|
m_copydata(m, 0, m->m_pkthdr.len, (caddr_t)&head[1]);
|
|
|
|
data->buflen = xferlen;
|
|
data->ni = ni;
|
|
data->m = m;
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT,
|
|
"%s: tx: m=%p; data=%p; len=%d mac=0x%04x phy=0x%08x rate=0x%02x, ni_txrate=%d\n",
|
|
__func__, m, data, le16toh(head->len), macctl, phyctl,
|
|
(int) rate, (int) ni->ni_txrate);
|
|
|
|
/* Submit transfer */
|
|
STAILQ_INSERT_TAIL(&sc->sc_tx_pending[OTUS_BULK_TX], data, next);
|
|
usbd_transfer_start(sc->sc_xfer[OTUS_BULK_TX]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
otus_set_multi(struct otus_softc *sc)
|
|
{
|
|
uint32_t lo, hi;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
int r;
|
|
|
|
if (ic->ic_allmulti > 0 || ic->ic_promisc > 0 ||
|
|
ic->ic_opmode == IEEE80211_M_MONITOR) {
|
|
lo = 0xffffffff;
|
|
hi = 0xffffffff;
|
|
} else {
|
|
struct ieee80211vap *vap;
|
|
struct ifnet *ifp;
|
|
struct ifmultiaddr *ifma;
|
|
|
|
lo = hi = 0;
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
ifp = vap->iv_ifp;
|
|
if_maddr_rlock(ifp);
|
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
|
caddr_t dl;
|
|
uint32_t val;
|
|
|
|
dl = LLADDR((struct sockaddr_dl *) ifma->ifma_addr);
|
|
val = le32dec(dl + 4);
|
|
/* Get address byte 5 */
|
|
val = val & 0x0000ff00;
|
|
val = val >> 8;
|
|
|
|
/* As per below, shift it >> 2 to get only 6 bits */
|
|
val = val >> 2;
|
|
if (val < 32)
|
|
lo |= 1 << val;
|
|
else
|
|
hi |= 1 << (val - 32);
|
|
}
|
|
if_maddr_runlock(ifp);
|
|
}
|
|
}
|
|
#if 0
|
|
/* XXX openbsd code */
|
|
while (enm != NULL) {
|
|
bit = enm->enm_addrlo[5] >> 2;
|
|
if (bit < 32)
|
|
lo |= 1 << bit;
|
|
else
|
|
hi |= 1 << (bit - 32);
|
|
ETHER_NEXT_MULTI(step, enm);
|
|
}
|
|
#endif
|
|
|
|
hi |= 1U << 31; /* Make sure the broadcast bit is set. */
|
|
|
|
OTUS_LOCK(sc);
|
|
otus_write(sc, AR_MAC_REG_GROUP_HASH_TBL_L, lo);
|
|
otus_write(sc, AR_MAC_REG_GROUP_HASH_TBL_H, hi);
|
|
r = otus_write_barrier(sc);
|
|
/* XXX operating mode? filter? */
|
|
OTUS_UNLOCK(sc);
|
|
return (r);
|
|
}
|
|
|
|
static int
|
|
otus_updateedca(struct ieee80211com *ic)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
|
|
OTUS_LOCK(sc);
|
|
/*
|
|
* XXX TODO: take temporary copy of EDCA information
|
|
* when scheduling this so we have a more time-correct view
|
|
* of things.
|
|
* XXX TODO: this can be done on the net80211 level
|
|
*/
|
|
otus_updateedca_locked(sc);
|
|
OTUS_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
otus_updateedca_locked(struct otus_softc *sc)
|
|
{
|
|
#define EXP2(val) ((1 << (val)) - 1)
|
|
#define AIFS(val) ((val) * 9 + 10)
|
|
struct chanAccParams chp;
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
const struct wmeParams *edca;
|
|
|
|
ieee80211_wme_ic_getparams(ic, &chp);
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
edca = chp.cap_wmeParams;
|
|
|
|
/* Set CWmin/CWmax values. */
|
|
otus_write(sc, AR_MAC_REG_AC0_CW,
|
|
EXP2(edca[WME_AC_BE].wmep_logcwmax) << 16 |
|
|
EXP2(edca[WME_AC_BE].wmep_logcwmin));
|
|
otus_write(sc, AR_MAC_REG_AC1_CW,
|
|
EXP2(edca[WME_AC_BK].wmep_logcwmax) << 16 |
|
|
EXP2(edca[WME_AC_BK].wmep_logcwmin));
|
|
otus_write(sc, AR_MAC_REG_AC2_CW,
|
|
EXP2(edca[WME_AC_VI].wmep_logcwmax) << 16 |
|
|
EXP2(edca[WME_AC_VI].wmep_logcwmin));
|
|
otus_write(sc, AR_MAC_REG_AC3_CW,
|
|
EXP2(edca[WME_AC_VO].wmep_logcwmax) << 16 |
|
|
EXP2(edca[WME_AC_VO].wmep_logcwmin));
|
|
otus_write(sc, AR_MAC_REG_AC4_CW, /* Special TXQ. */
|
|
EXP2(edca[WME_AC_VO].wmep_logcwmax) << 16 |
|
|
EXP2(edca[WME_AC_VO].wmep_logcwmin));
|
|
|
|
/* Set AIFSN values. */
|
|
otus_write(sc, AR_MAC_REG_AC1_AC0_AIFS,
|
|
AIFS(edca[WME_AC_VI].wmep_aifsn) << 24 |
|
|
AIFS(edca[WME_AC_BK].wmep_aifsn) << 12 |
|
|
AIFS(edca[WME_AC_BE].wmep_aifsn));
|
|
otus_write(sc, AR_MAC_REG_AC3_AC2_AIFS,
|
|
AIFS(edca[WME_AC_VO].wmep_aifsn) << 16 | /* Special TXQ. */
|
|
AIFS(edca[WME_AC_VO].wmep_aifsn) << 4 |
|
|
AIFS(edca[WME_AC_VI].wmep_aifsn) >> 8);
|
|
|
|
/* Set TXOP limit. */
|
|
otus_write(sc, AR_MAC_REG_AC1_AC0_TXOP,
|
|
edca[WME_AC_BK].wmep_txopLimit << 16 |
|
|
edca[WME_AC_BE].wmep_txopLimit);
|
|
otus_write(sc, AR_MAC_REG_AC3_AC2_TXOP,
|
|
edca[WME_AC_VO].wmep_txopLimit << 16 |
|
|
edca[WME_AC_VI].wmep_txopLimit);
|
|
|
|
/* XXX ACK policy? */
|
|
|
|
(void)otus_write_barrier(sc);
|
|
|
|
#undef AIFS
|
|
#undef EXP2
|
|
}
|
|
|
|
static void
|
|
otus_updateslot(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
uint32_t slottime;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
slottime = IEEE80211_GET_SLOTTIME(ic);
|
|
otus_write(sc, AR_MAC_REG_SLOT_TIME, slottime << 10);
|
|
(void)otus_write_barrier(sc);
|
|
}
|
|
|
|
int
|
|
otus_init_mac(struct otus_softc *sc)
|
|
{
|
|
int error;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
otus_write(sc, AR_MAC_REG_ACK_EXTENSION, 0x40);
|
|
otus_write(sc, AR_MAC_REG_RETRY_MAX, 0);
|
|
otus_write(sc, AR_MAC_REG_RX_THRESHOLD, 0xc1f80);
|
|
otus_write(sc, AR_MAC_REG_RX_PE_DELAY, 0x70);
|
|
otus_write(sc, AR_MAC_REG_EIFS_AND_SIFS, 0xa144000);
|
|
otus_write(sc, AR_MAC_REG_SLOT_TIME, 9 << 10);
|
|
otus_write(sc, AR_MAC_REG_TID_CFACK_CFEND_RATE, 0x19000000);
|
|
/* NAV protects ACK only (in TXOP). */
|
|
otus_write(sc, AR_MAC_REG_TXOP_DURATION, 0x201);
|
|
/* Set beacon Tx power to 0x7. */
|
|
otus_write(sc, AR_MAC_REG_BCN_HT1, 0x8000170);
|
|
otus_write(sc, AR_MAC_REG_BACKOFF_PROTECT, 0x105);
|
|
otus_write(sc, AR_MAC_REG_AMPDU_FACTOR, 0x10000a);
|
|
|
|
otus_set_rx_filter(sc);
|
|
|
|
otus_write(sc, AR_MAC_REG_BASIC_RATE, 0x150f);
|
|
otus_write(sc, AR_MAC_REG_MANDATORY_RATE, 0x150f);
|
|
otus_write(sc, AR_MAC_REG_RTS_CTS_RATE, 0x10b01bb);
|
|
otus_write(sc, AR_MAC_REG_ACK_TPC, 0x4003c1e);
|
|
|
|
/* Enable LED0 and LED1. */
|
|
otus_write(sc, AR_GPIO_REG_PORT_TYPE, 0x3);
|
|
otus_write(sc, AR_GPIO_REG_PORT_DATA, 0x3);
|
|
/* Switch MAC to OTUS interface. */
|
|
otus_write(sc, 0x1c3600, 0x3);
|
|
otus_write(sc, AR_MAC_REG_AMPDU_RX_THRESH, 0xffff);
|
|
otus_write(sc, AR_MAC_REG_MISC_680, 0xf00008);
|
|
/* Disable Rx timeout (workaround). */
|
|
otus_write(sc, AR_MAC_REG_RX_TIMEOUT, 0);
|
|
|
|
/* Set USB Rx stream mode maximum frame number to 2. */
|
|
otus_write(sc, 0x1e1110, 0x4);
|
|
/* Set USB Rx stream mode timeout to 10us. */
|
|
otus_write(sc, 0x1e1114, 0x80);
|
|
|
|
/* Set clock frequency to 88/80MHz. */
|
|
otus_write(sc, AR_PWR_REG_CLOCK_SEL, 0x73);
|
|
/* Set WLAN DMA interrupt mode: generate intr per packet. */
|
|
otus_write(sc, AR_MAC_REG_TXRX_MPI, 0x110011);
|
|
otus_write(sc, AR_MAC_REG_FCS_SELECT, 0x4);
|
|
otus_write(sc, AR_MAC_REG_TXOP_NOT_ENOUGH_INDICATION, 0x141e0f48);
|
|
|
|
/* Disable HW decryption for now. */
|
|
otus_write(sc, AR_MAC_REG_ENCRYPTION, 0x78);
|
|
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
return error;
|
|
|
|
/* Set default EDCA parameters. */
|
|
otus_updateedca_locked(sc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Return default value for PHY register based on current operating mode.
|
|
*/
|
|
uint32_t
|
|
otus_phy_get_def(struct otus_softc *sc, uint32_t reg)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nitems(ar5416_phy_regs); i++)
|
|
if (AR_PHY(ar5416_phy_regs[i]) == reg)
|
|
return sc->phy_vals[i];
|
|
return 0; /* Register not found. */
|
|
}
|
|
|
|
/*
|
|
* Update PHY's programming based on vendor-specific data stored in EEPROM.
|
|
* This is for FEM-type devices only.
|
|
*/
|
|
int
|
|
otus_set_board_values(struct otus_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
const struct ModalEepHeader *eep;
|
|
uint32_t tmp, offset;
|
|
|
|
if (IEEE80211_IS_CHAN_5GHZ(c))
|
|
eep = &sc->eeprom.modalHeader[0];
|
|
else
|
|
eep = &sc->eeprom.modalHeader[1];
|
|
|
|
/* Offset of chain 2. */
|
|
offset = 2 * 0x1000;
|
|
|
|
tmp = le32toh(eep->antCtrlCommon);
|
|
otus_write(sc, AR_PHY_SWITCH_COM, tmp);
|
|
|
|
tmp = le32toh(eep->antCtrlChain[0]);
|
|
otus_write(sc, AR_PHY_SWITCH_CHAIN_0, tmp);
|
|
|
|
tmp = le32toh(eep->antCtrlChain[1]);
|
|
otus_write(sc, AR_PHY_SWITCH_CHAIN_0 + offset, tmp);
|
|
|
|
if (1 /* sc->sc_sco == AR_SCO_SCN */) {
|
|
tmp = otus_phy_get_def(sc, AR_PHY_SETTLING);
|
|
tmp &= ~(0x7f << 7);
|
|
tmp |= (eep->switchSettling & 0x7f) << 7;
|
|
otus_write(sc, AR_PHY_SETTLING, tmp);
|
|
}
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_DESIRED_SZ);
|
|
tmp &= ~0xffff;
|
|
tmp |= eep->pgaDesiredSize << 8 | eep->adcDesiredSize;
|
|
otus_write(sc, AR_PHY_DESIRED_SZ, tmp);
|
|
|
|
tmp = eep->txEndToXpaOff << 24 | eep->txEndToXpaOff << 16 |
|
|
eep->txFrameToXpaOn << 8 | eep->txFrameToXpaOn;
|
|
otus_write(sc, AR_PHY_RF_CTL4, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_RF_CTL3);
|
|
tmp &= ~(0xff << 16);
|
|
tmp |= eep->txEndToRxOn << 16;
|
|
otus_write(sc, AR_PHY_RF_CTL3, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_CCA);
|
|
tmp &= ~(0x7f << 12);
|
|
tmp |= (eep->thresh62 & 0x7f) << 12;
|
|
otus_write(sc, AR_PHY_CCA, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_RXGAIN);
|
|
tmp &= ~(0x3f << 12);
|
|
tmp |= (eep->txRxAttenCh[0] & 0x3f) << 12;
|
|
otus_write(sc, AR_PHY_RXGAIN, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_RXGAIN + offset);
|
|
tmp &= ~(0x3f << 12);
|
|
tmp |= (eep->txRxAttenCh[1] & 0x3f) << 12;
|
|
otus_write(sc, AR_PHY_RXGAIN + offset, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_GAIN_2GHZ);
|
|
tmp &= ~(0x3f << 18);
|
|
tmp |= (eep->rxTxMarginCh[0] & 0x3f) << 18;
|
|
if (IEEE80211_IS_CHAN_5GHZ(c)) {
|
|
tmp &= ~(0xf << 10);
|
|
tmp |= (eep->bswMargin[0] & 0xf) << 10;
|
|
}
|
|
otus_write(sc, AR_PHY_GAIN_2GHZ, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_GAIN_2GHZ + offset);
|
|
tmp &= ~(0x3f << 18);
|
|
tmp |= (eep->rxTxMarginCh[1] & 0x3f) << 18;
|
|
otus_write(sc, AR_PHY_GAIN_2GHZ + offset, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_TIMING_CTRL4);
|
|
tmp &= ~(0x3f << 5 | 0x1f);
|
|
tmp |= (eep->iqCalICh[0] & 0x3f) << 5 | (eep->iqCalQCh[0] & 0x1f);
|
|
otus_write(sc, AR_PHY_TIMING_CTRL4, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_TIMING_CTRL4 + offset);
|
|
tmp &= ~(0x3f << 5 | 0x1f);
|
|
tmp |= (eep->iqCalICh[1] & 0x3f) << 5 | (eep->iqCalQCh[1] & 0x1f);
|
|
otus_write(sc, AR_PHY_TIMING_CTRL4 + offset, tmp);
|
|
|
|
tmp = otus_phy_get_def(sc, AR_PHY_TPCRG1);
|
|
tmp &= ~(0xf << 16);
|
|
tmp |= (eep->xpd & 0xf) << 16;
|
|
otus_write(sc, AR_PHY_TPCRG1, tmp);
|
|
|
|
return otus_write_barrier(sc);
|
|
}
|
|
|
|
int
|
|
otus_program_phy(struct otus_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
const uint32_t *vals;
|
|
int error, i;
|
|
|
|
/* Select PHY programming based on band and bandwidth. */
|
|
if (IEEE80211_IS_CHAN_2GHZ(c))
|
|
vals = ar5416_phy_vals_2ghz_20mhz;
|
|
else
|
|
vals = ar5416_phy_vals_5ghz_20mhz;
|
|
for (i = 0; i < nitems(ar5416_phy_regs); i++)
|
|
otus_write(sc, AR_PHY(ar5416_phy_regs[i]), vals[i]);
|
|
sc->phy_vals = vals;
|
|
|
|
if (sc->eeprom.baseEepHeader.deviceType == 0x80) /* FEM */
|
|
if ((error = otus_set_board_values(sc, c)) != 0)
|
|
return error;
|
|
|
|
/* Initial Tx power settings. */
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE_MAX, 0x7f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE1, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE2, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE3, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE4, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE5, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE6, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE7, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE8, 0x3f3f3f3f);
|
|
otus_write(sc, AR_PHY_POWER_TX_RATE9, 0x3f3f3f3f);
|
|
|
|
if (IEEE80211_IS_CHAN_2GHZ(c))
|
|
otus_write(sc, AR_PWR_REG_PLL_ADDAC, 0x5163);
|
|
else
|
|
otus_write(sc, AR_PWR_REG_PLL_ADDAC, 0x5143);
|
|
|
|
return otus_write_barrier(sc);
|
|
}
|
|
|
|
static __inline uint8_t
|
|
otus_reverse_bits(uint8_t v)
|
|
{
|
|
v = ((v >> 1) & 0x55) | ((v & 0x55) << 1);
|
|
v = ((v >> 2) & 0x33) | ((v & 0x33) << 2);
|
|
v = ((v >> 4) & 0x0f) | ((v & 0x0f) << 4);
|
|
return v;
|
|
}
|
|
|
|
int
|
|
otus_set_rf_bank4(struct otus_softc *sc, struct ieee80211_channel *c)
|
|
{
|
|
uint8_t chansel, d0, d1;
|
|
uint16_t data;
|
|
int error;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
d0 = 0;
|
|
if (IEEE80211_IS_CHAN_5GHZ(c)) {
|
|
chansel = (c->ic_freq - 4800) / 5;
|
|
if (chansel & 1)
|
|
d0 |= AR_BANK4_AMODE_REFSEL(2);
|
|
else
|
|
d0 |= AR_BANK4_AMODE_REFSEL(1);
|
|
} else {
|
|
d0 |= AR_BANK4_AMODE_REFSEL(2);
|
|
if (c->ic_freq == 2484) { /* CH 14 */
|
|
d0 |= AR_BANK4_BMODE_LF_SYNTH_FREQ;
|
|
chansel = 10 + (c->ic_freq - 2274) / 5;
|
|
} else
|
|
chansel = 16 + (c->ic_freq - 2272) / 5;
|
|
chansel <<= 2;
|
|
}
|
|
d0 |= AR_BANK4_ADDR(1) | AR_BANK4_CHUP;
|
|
d1 = otus_reverse_bits(chansel);
|
|
|
|
/* Write bits 0-4 of d0 and d1. */
|
|
data = (d1 & 0x1f) << 5 | (d0 & 0x1f);
|
|
otus_write(sc, AR_PHY(44), data);
|
|
/* Write bits 5-7 of d0 and d1. */
|
|
data = (d1 >> 5) << 5 | (d0 >> 5);
|
|
otus_write(sc, AR_PHY(58), data);
|
|
|
|
if ((error = otus_write_barrier(sc)) == 0)
|
|
otus_delay_ms(sc, 10);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
otus_get_delta_slope(uint32_t coeff, uint32_t *exponent, uint32_t *mantissa)
|
|
{
|
|
#define COEFF_SCALE_SHIFT 24
|
|
uint32_t exp, man;
|
|
|
|
/* exponent = 14 - floor(log2(coeff)) */
|
|
for (exp = 31; exp > 0; exp--)
|
|
if (coeff & (1 << exp))
|
|
break;
|
|
KASSERT(exp != 0, ("exp"));
|
|
exp = 14 - (exp - COEFF_SCALE_SHIFT);
|
|
|
|
/* mantissa = floor(coeff * 2^exponent + 0.5) */
|
|
man = coeff + (1 << (COEFF_SCALE_SHIFT - exp - 1));
|
|
|
|
*mantissa = man >> (COEFF_SCALE_SHIFT - exp);
|
|
*exponent = exp - 16;
|
|
#undef COEFF_SCALE_SHIFT
|
|
}
|
|
|
|
static int
|
|
otus_set_chan(struct otus_softc *sc, struct ieee80211_channel *c, int assoc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ar_cmd_frequency cmd;
|
|
struct ar_rsp_frequency rsp;
|
|
const uint32_t *vals;
|
|
uint32_t coeff, exp, man, tmp;
|
|
uint8_t code;
|
|
int error, chan, i;
|
|
|
|
error = 0;
|
|
chan = ieee80211_chan2ieee(ic, c);
|
|
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET,
|
|
"setting channel %d (%dMHz)\n", chan, c->ic_freq);
|
|
|
|
tmp = IEEE80211_IS_CHAN_2GHZ(c) ? 0x105 : 0x104;
|
|
otus_write(sc, AR_MAC_REG_DYNAMIC_SIFS_ACK, tmp);
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
goto finish;
|
|
|
|
/* Disable BB Heavy Clip. */
|
|
otus_write(sc, AR_PHY_HEAVY_CLIP_ENABLE, 0x200);
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
goto finish;
|
|
|
|
/* XXX Is that FREQ_START ? */
|
|
error = otus_cmd(sc, AR_CMD_FREQ_STRAT, NULL, 0, NULL, 0);
|
|
if (error != 0)
|
|
goto finish;
|
|
|
|
/* Reprogram PHY and RF on channel band or bandwidth changes. */
|
|
if (sc->bb_reset || c->ic_flags != sc->sc_curchan->ic_flags) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "band switch\n");
|
|
|
|
/* Cold/Warm reset BB/ADDA. */
|
|
otus_write(sc, AR_PWR_REG_RESET, sc->bb_reset ? 0x800 : 0x400);
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
goto finish;
|
|
otus_write(sc, AR_PWR_REG_RESET, 0);
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
goto finish;
|
|
sc->bb_reset = 0;
|
|
|
|
if ((error = otus_program_phy(sc, c)) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not program PHY\n",
|
|
__func__);
|
|
goto finish;
|
|
}
|
|
|
|
/* Select RF programming based on band. */
|
|
if (IEEE80211_IS_CHAN_5GHZ(c))
|
|
vals = ar5416_banks_vals_5ghz;
|
|
else
|
|
vals = ar5416_banks_vals_2ghz;
|
|
for (i = 0; i < nitems(ar5416_banks_regs); i++)
|
|
otus_write(sc, AR_PHY(ar5416_banks_regs[i]), vals[i]);
|
|
if ((error = otus_write_barrier(sc)) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not program RF\n",
|
|
__func__);
|
|
goto finish;
|
|
}
|
|
code = AR_CMD_RF_INIT;
|
|
} else {
|
|
code = AR_CMD_FREQUENCY;
|
|
}
|
|
|
|
if ((error = otus_set_rf_bank4(sc, c)) != 0)
|
|
goto finish;
|
|
|
|
tmp = (sc->txmask == 0x5) ? 0x340 : 0x240;
|
|
otus_write(sc, AR_PHY_TURBO, tmp);
|
|
if ((error = otus_write_barrier(sc)) != 0)
|
|
goto finish;
|
|
|
|
/* Send firmware command to set channel. */
|
|
cmd.freq = htole32((uint32_t)c->ic_freq * 1000);
|
|
cmd.dynht2040 = htole32(0);
|
|
cmd.htena = htole32(1);
|
|
/* Set Delta Slope (exponent and mantissa). */
|
|
coeff = (100 << 24) / c->ic_freq;
|
|
otus_get_delta_slope(coeff, &exp, &man);
|
|
cmd.dsc_exp = htole32(exp);
|
|
cmd.dsc_man = htole32(man);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET,
|
|
"ds coeff=%u exp=%u man=%u\n", coeff, exp, man);
|
|
/* For Short GI, coeff is 9/10 that of normal coeff. */
|
|
coeff = (9 * coeff) / 10;
|
|
otus_get_delta_slope(coeff, &exp, &man);
|
|
cmd.dsc_shgi_exp = htole32(exp);
|
|
cmd.dsc_shgi_man = htole32(man);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET,
|
|
"ds shgi coeff=%u exp=%u man=%u\n", coeff, exp, man);
|
|
/* Set wait time for AGC and noise calibration (100 or 200ms). */
|
|
cmd.check_loop_count = assoc ? htole32(2000) : htole32(1000);
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET,
|
|
"%s\n", (code == AR_CMD_RF_INIT) ? "RF_INIT" : "FREQUENCY");
|
|
error = otus_cmd(sc, code, &cmd, sizeof cmd, &rsp, sizeof(rsp));
|
|
if (error != 0)
|
|
goto finish;
|
|
if ((rsp.status & htole32(AR_CAL_ERR_AGC | AR_CAL_ERR_NF_VAL)) != 0) {
|
|
OTUS_DPRINTF(sc, OTUS_DEBUG_RESET,
|
|
"status=0x%x\n", le32toh(rsp.status));
|
|
/* Force cold reset on next channel. */
|
|
sc->bb_reset = 1;
|
|
}
|
|
#ifdef USB_DEBUG
|
|
if (otus_debug & OTUS_DEBUG_RESET) {
|
|
device_printf(sc->sc_dev, "calibration status=0x%x\n",
|
|
le32toh(rsp.status));
|
|
for (i = 0; i < 2; i++) { /* 2 Rx chains */
|
|
/* Sign-extend 9-bit NF values. */
|
|
device_printf(sc->sc_dev,
|
|
"noisefloor chain %d=%d\n", i,
|
|
(((int32_t)le32toh(rsp.nf[i])) << 4) >> 23);
|
|
device_printf(sc->sc_dev,
|
|
"noisefloor ext chain %d=%d\n", i,
|
|
((int32_t)le32toh(rsp.nf_ext[i])) >> 23);
|
|
}
|
|
}
|
|
#endif
|
|
for (i = 0; i < OTUS_NUM_CHAINS; i++) {
|
|
sc->sc_nf[i] = ((((int32_t)le32toh(rsp.nf[i])) << 4) >> 23);
|
|
}
|
|
sc->sc_curchan = c;
|
|
finish:
|
|
return (error);
|
|
}
|
|
|
|
#ifdef notyet
|
|
int
|
|
otus_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
|
|
struct ieee80211_key *k)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
struct otus_cmd_key cmd;
|
|
|
|
/* Defer setting of WEP keys until interface is brought up. */
|
|
if ((ic->ic_if.if_flags & (IFF_UP | IFF_RUNNING)) !=
|
|
(IFF_UP | IFF_RUNNING))
|
|
return 0;
|
|
|
|
/* Do it in a process context. */
|
|
cmd.key = *k;
|
|
cmd.associd = (ni != NULL) ? ni->ni_associd : 0;
|
|
otus_do_async(sc, otus_set_key_cb, &cmd, sizeof cmd);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
otus_set_key_cb(struct otus_softc *sc, void *arg)
|
|
{
|
|
struct otus_cmd_key *cmd = arg;
|
|
struct ieee80211_key *k = &cmd->key;
|
|
struct ar_cmd_ekey key;
|
|
uint16_t cipher;
|
|
int error;
|
|
|
|
memset(&key, 0, sizeof key);
|
|
if (k->k_flags & IEEE80211_KEY_GROUP) {
|
|
key.uid = htole16(k->k_id);
|
|
IEEE80211_ADDR_COPY(key.macaddr, sc->sc_ic.ic_myaddr);
|
|
key.macaddr[0] |= 0x80;
|
|
} else {
|
|
key.uid = htole16(OTUS_UID(cmd->associd));
|
|
IEEE80211_ADDR_COPY(key.macaddr, ni->ni_macaddr);
|
|
}
|
|
key.kix = htole16(0);
|
|
/* Map net80211 cipher to hardware. */
|
|
switch (k->k_cipher) {
|
|
case IEEE80211_CIPHER_WEP40:
|
|
cipher = AR_CIPHER_WEP64;
|
|
break;
|
|
case IEEE80211_CIPHER_WEP104:
|
|
cipher = AR_CIPHER_WEP128;
|
|
break;
|
|
case IEEE80211_CIPHER_TKIP:
|
|
cipher = AR_CIPHER_TKIP;
|
|
break;
|
|
case IEEE80211_CIPHER_CCMP:
|
|
cipher = AR_CIPHER_AES;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
key.cipher = htole16(cipher);
|
|
memcpy(key.key, k->k_key, MIN(k->k_len, 16));
|
|
error = otus_cmd(sc, AR_CMD_EKEY, &key, sizeof key, NULL, 0);
|
|
if (error != 0 || k->k_cipher != IEEE80211_CIPHER_TKIP)
|
|
return;
|
|
|
|
/* TKIP: set Tx/Rx MIC Key. */
|
|
key.kix = htole16(1);
|
|
memcpy(key.key, k->k_key + 16, 16);
|
|
(void)otus_cmd(sc, AR_CMD_EKEY, &key, sizeof key, NULL, 0);
|
|
}
|
|
|
|
void
|
|
otus_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
|
|
struct ieee80211_key *k)
|
|
{
|
|
struct otus_softc *sc = ic->ic_softc;
|
|
struct otus_cmd_key cmd;
|
|
|
|
if (!(ic->ic_if.if_flags & IFF_RUNNING) ||
|
|
ic->ic_state != IEEE80211_S_RUN)
|
|
return; /* Nothing to do. */
|
|
|
|
/* Do it in a process context. */
|
|
cmd.key = *k;
|
|
cmd.associd = (ni != NULL) ? ni->ni_associd : 0;
|
|
otus_do_async(sc, otus_delete_key_cb, &cmd, sizeof cmd);
|
|
}
|
|
|
|
void
|
|
otus_delete_key_cb(struct otus_softc *sc, void *arg)
|
|
{
|
|
struct otus_cmd_key *cmd = arg;
|
|
struct ieee80211_key *k = &cmd->key;
|
|
uint32_t uid;
|
|
|
|
if (k->k_flags & IEEE80211_KEY_GROUP)
|
|
uid = htole32(k->k_id);
|
|
else
|
|
uid = htole32(OTUS_UID(cmd->associd));
|
|
(void)otus_cmd(sc, AR_CMD_DKEY, &uid, sizeof uid, NULL, 0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* XXX TODO: check if we have to be doing any calibration in the host
|
|
* or whether it's purely a firmware thing.
|
|
*/
|
|
void
|
|
otus_calibrate_to(void *arg, int pending)
|
|
{
|
|
#if 0
|
|
struct otus_softc *sc = arg;
|
|
|
|
device_printf(sc->sc_dev, "%s: called\n", __func__);
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211_node *ni;
|
|
int s;
|
|
|
|
if (usbd_is_dying(sc->sc_udev))
|
|
return;
|
|
|
|
usbd_ref_incr(sc->sc_udev);
|
|
|
|
s = splnet();
|
|
ni = ic->ic_bss;
|
|
ieee80211_amrr_choose(&sc->amrr, ni, &((struct otus_node *)ni)->amn);
|
|
splx(s);
|
|
|
|
if (!usbd_is_dying(sc->sc_udev))
|
|
timeout_add_sec(&sc->calib_to, 1);
|
|
|
|
usbd_ref_decr(sc->sc_udev);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
otus_set_bssid(struct otus_softc *sc, const uint8_t *bssid)
|
|
{
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
otus_write(sc, AR_MAC_REG_BSSID_L,
|
|
bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24);
|
|
otus_write(sc, AR_MAC_REG_BSSID_H,
|
|
bssid[4] | bssid[5] << 8);
|
|
return otus_write_barrier(sc);
|
|
}
|
|
|
|
int
|
|
otus_set_macaddr(struct otus_softc *sc, const uint8_t *addr)
|
|
{
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
otus_write(sc, AR_MAC_REG_MAC_ADDR_L,
|
|
addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24);
|
|
otus_write(sc, AR_MAC_REG_MAC_ADDR_H,
|
|
addr[4] | addr[5] << 8);
|
|
return otus_write_barrier(sc);
|
|
}
|
|
|
|
/* Default single-LED. */
|
|
void
|
|
otus_led_newstate_type1(struct otus_softc *sc)
|
|
{
|
|
/* TBD */
|
|
device_printf(sc->sc_dev, "%s: TODO\n", __func__);
|
|
}
|
|
|
|
/* NETGEAR, dual-LED. */
|
|
void
|
|
otus_led_newstate_type2(struct otus_softc *sc)
|
|
{
|
|
/* TBD */
|
|
device_printf(sc->sc_dev, "%s: TODO\n", __func__);
|
|
}
|
|
|
|
/* NETGEAR, single-LED/3 colors (blue, red, purple.) */
|
|
void
|
|
otus_led_newstate_type3(struct otus_softc *sc)
|
|
{
|
|
#if 0
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
|
|
|
|
uint32_t state = sc->led_state;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
if (!vap) {
|
|
state = 0; /* led off */
|
|
} else if (vap->iv_state == IEEE80211_S_INIT) {
|
|
state = 0; /* LED off. */
|
|
} else if (vap->iv_state == IEEE80211_S_RUN) {
|
|
/* Associated, LED always on. */
|
|
if (IEEE80211_IS_CHAN_2GHZ(sc->sc_curchan))
|
|
state = AR_LED0_ON; /* 2GHz=>Red. */
|
|
else
|
|
state = AR_LED1_ON; /* 5GHz=>Blue. */
|
|
} else {
|
|
/* Scanning, blink LED. */
|
|
state ^= AR_LED0_ON | AR_LED1_ON;
|
|
if (IEEE80211_IS_CHAN_2GHZ(sc->sc_curchan))
|
|
state &= ~AR_LED1_ON;
|
|
else
|
|
state &= ~AR_LED0_ON;
|
|
}
|
|
if (state != sc->led_state) {
|
|
otus_write(sc, AR_GPIO_REG_PORT_DATA, state);
|
|
if (otus_write_barrier(sc) == 0)
|
|
sc->led_state = state;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static uint8_t zero_macaddr[IEEE80211_ADDR_LEN] = { 0,0,0,0,0,0 };
|
|
|
|
/*
|
|
* Set up operating mode, MAC/BSS address and RX filter.
|
|
*/
|
|
static void
|
|
otus_set_operating_mode(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
struct ieee80211vap *vap;
|
|
uint32_t cam_mode = AR_MAC_CAM_DEFAULTS;
|
|
uint32_t rx_ctrl = AR_MAC_RX_CTRL_DEAGG | AR_MAC_RX_CTRL_SHORT_FILTER;
|
|
uint32_t sniffer = AR_MAC_SNIFFER_DEFAULTS;
|
|
uint32_t enc_mode = 0x78; /* XXX */
|
|
const uint8_t *macaddr;
|
|
uint8_t bssid[IEEE80211_ADDR_LEN];
|
|
struct ieee80211_node *ni;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
/*
|
|
* If we're in sniffer mode or we don't have a MAC
|
|
* address assigned, ensure it gets reset to all-zero.
|
|
*/
|
|
IEEE80211_ADDR_COPY(bssid, zero_macaddr);
|
|
vap = TAILQ_FIRST(&ic->ic_vaps);
|
|
macaddr = ic->ic_macaddr;
|
|
|
|
switch (ic->ic_opmode) {
|
|
case IEEE80211_M_STA:
|
|
if (vap) {
|
|
ni = ieee80211_ref_node(vap->iv_bss);
|
|
IEEE80211_ADDR_COPY(bssid, ni->ni_bssid);
|
|
ieee80211_free_node(ni);
|
|
}
|
|
cam_mode |= AR_MAC_CAM_STA;
|
|
rx_ctrl |= AR_MAC_RX_CTRL_PASS_TO_HOST;
|
|
break;
|
|
case IEEE80211_M_MONITOR:
|
|
/*
|
|
* Note: monitor mode ends up causing the MAC to
|
|
* generate ACK frames for everything it sees.
|
|
* So don't do that; instead just put it in STA mode
|
|
* and disable RX filters.
|
|
*/
|
|
default:
|
|
cam_mode |= AR_MAC_CAM_STA;
|
|
rx_ctrl |= AR_MAC_RX_CTRL_PASS_TO_HOST;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* TODO: if/when we do hardware encryption, ensure it's
|
|
* disabled if the NIC is in monitor mode.
|
|
*/
|
|
otus_write(sc, AR_MAC_REG_SNIFFER, sniffer);
|
|
otus_write(sc, AR_MAC_REG_CAM_MODE, cam_mode);
|
|
otus_write(sc, AR_MAC_REG_ENCRYPTION, enc_mode);
|
|
otus_write(sc, AR_MAC_REG_RX_CONTROL, rx_ctrl);
|
|
otus_set_macaddr(sc, macaddr);
|
|
otus_set_bssid(sc, bssid);
|
|
/* XXX barrier? */
|
|
}
|
|
|
|
static void
|
|
otus_set_rx_filter(struct otus_softc *sc)
|
|
{
|
|
// struct ieee80211com *ic = &sc->sc_ic;
|
|
|
|
OTUS_LOCK_ASSERT(sc);
|
|
|
|
#if 0
|
|
if (ic->ic_allmulti > 0 || ic->ic_promisc > 0 ||
|
|
ic->ic_opmode == IEEE80211_M_MONITOR) {
|
|
otus_write(sc, AR_MAC_REG_FRAMETYPE_FILTER, 0xff00ffff);
|
|
} else {
|
|
#endif
|
|
/* Filter any control frames, BAR is bit 24. */
|
|
otus_write(sc, AR_MAC_REG_FRAMETYPE_FILTER, 0x0500ffff);
|
|
#if 0
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int
|
|
otus_init(struct otus_softc *sc)
|
|
{
|
|
struct ieee80211com *ic = &sc->sc_ic;
|
|
int error;
|
|
|
|
OTUS_UNLOCK_ASSERT(sc);
|
|
|
|
OTUS_LOCK(sc);
|
|
|
|
/* Drain any pending TX frames */
|
|
otus_drain_mbufq(sc);
|
|
|
|
/* Init MAC */
|
|
if ((error = otus_init_mac(sc)) != 0) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not initialize MAC\n", __func__);
|
|
return error;
|
|
}
|
|
|
|
otus_set_operating_mode(sc);
|
|
otus_set_rx_filter(sc);
|
|
(void) otus_set_operating_mode(sc);
|
|
|
|
sc->bb_reset = 1; /* Force cold reset. */
|
|
|
|
if ((error = otus_set_chan(sc, ic->ic_curchan, 0)) != 0) {
|
|
OTUS_UNLOCK(sc);
|
|
device_printf(sc->sc_dev,
|
|
"%s: could not set channel\n", __func__);
|
|
return error;
|
|
}
|
|
|
|
/* Start Rx. */
|
|
otus_write(sc, AR_MAC_REG_DMA_TRIGGER, 0x100);
|
|
(void)otus_write_barrier(sc);
|
|
|
|
sc->sc_running = 1;
|
|
|
|
OTUS_UNLOCK(sc);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
otus_stop(struct otus_softc *sc)
|
|
{
|
|
#if 0
|
|
int s;
|
|
#endif
|
|
|
|
OTUS_UNLOCK_ASSERT(sc);
|
|
|
|
OTUS_LOCK(sc);
|
|
sc->sc_running = 0;
|
|
sc->sc_tx_timer = 0;
|
|
OTUS_UNLOCK(sc);
|
|
|
|
taskqueue_drain_timeout(taskqueue_thread, &sc->scan_to);
|
|
taskqueue_drain_timeout(taskqueue_thread, &sc->calib_to);
|
|
taskqueue_drain(taskqueue_thread, &sc->tx_task);
|
|
|
|
OTUS_LOCK(sc);
|
|
sc->sc_running = 0;
|
|
/* Stop Rx. */
|
|
otus_write(sc, AR_MAC_REG_DMA_TRIGGER, 0);
|
|
(void)otus_write_barrier(sc);
|
|
|
|
/* Drain any pending TX frames */
|
|
otus_drain_mbufq(sc);
|
|
|
|
OTUS_UNLOCK(sc);
|
|
}
|