diff options
Diffstat (limited to 'sys/dev/usb/wlan/if_uath.c')
-rw-r--r-- | sys/dev/usb/wlan/if_uath.c | 2862 |
1 files changed, 2862 insertions, 0 deletions
diff --git a/sys/dev/usb/wlan/if_uath.c b/sys/dev/usb/wlan/if_uath.c new file mode 100644 index 0000000..a19d3b0 --- /dev/null +++ b/sys/dev/usb/wlan/if_uath.c @@ -0,0 +1,2862 @@ +/*- + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2008-2009 Weongyo Jeong <weongyo@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +/* + * This driver is distantly derived from a driver of the same name + * by Damien Bergamini. The original copyright is included below: + * + * Copyright (c) 2006 + * Damien Bergamini <damien.bergamini@free.fr> + * + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Driver for Atheros AR5523 USB parts. + * + * The driver requires firmware to be loaded into the device. This + * is done on device discovery from a user application (uathload) + * that is launched by devd when a device with suitable product ID + * is recognized. Once firmware has been loaded the device will + * reset the USB port and re-attach with the original product ID+1 + * and this driver will be attached. The firmware is licensed for + * general use (royalty free) and may be incorporated in products. + * Note that the firmware normally packaged with the NDIS drivers + * for these devices does not work in this way and so does not work + * with this driver. + */ +#include <sys/param.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/kdb.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#endif + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_regdomain.h> +#include <net80211/ieee80211_radiotap.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_error.h> +#include <dev/usb/usb_lookup.h> +#include <dev/usb/usb_util.h> +#include "usbdevs.h" + +#include <dev/usb/wlan/if_uathreg.h> +#include <dev/usb/wlan/if_uathvar.h> + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uath, CTLFLAG_RW, 0, "USB Atheros"); + +static int uath_countrycode = CTRY_DEFAULT; /* country code */ +SYSCTL_INT(_hw_usb2_uath, OID_AUTO, countrycode, CTLFLAG_RW, &uath_countrycode, + 0, "country code"); +TUNABLE_INT("hw.usb2.uath.countrycode", &uath_countrycode); +static int uath_regdomain = 0; /* regulatory domain */ +SYSCTL_INT(_hw_usb2_uath, OID_AUTO, regdomain, CTLFLAG_RD, &uath_regdomain, + 0, "regulatory domain"); + +#ifdef UATH_DEBUG +int uath_debug = 0; +SYSCTL_INT(_hw_usb2_uath, OID_AUTO, debug, CTLFLAG_RW, &uath_debug, 0, + "uath debug level"); +TUNABLE_INT("hw.usb.uath.debug", &uath_debug); +enum { + UATH_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + UATH_DEBUG_XMIT_DUMP = 0x00000002, /* xmit dump */ + UATH_DEBUG_RECV = 0x00000004, /* basic recv operation */ + UATH_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ + UATH_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ + UATH_DEBUG_RECV_ALL = 0x00000020, /* trace all frames (beacons) */ + UATH_DEBUG_INIT = 0x00000040, /* initialization of dev */ + UATH_DEBUG_DEVCAP = 0x00000080, /* dev caps */ + UATH_DEBUG_CMDS = 0x00000100, /* commands */ + UATH_DEBUG_CMDS_DUMP = 0x00000200, /* command buffer dump */ + UATH_DEBUG_RESET = 0x00000400, /* reset processing */ + UATH_DEBUG_STATE = 0x00000800, /* 802.11 state transitions */ + UATH_DEBUG_MULTICAST = 0x00001000, /* multicast */ + UATH_DEBUG_WME = 0x00002000, /* WME */ + UATH_DEBUG_CHANNEL = 0x00004000, /* channel */ + UATH_DEBUG_RATES = 0x00008000, /* rates */ + UATH_DEBUG_CRYPTO = 0x00010000, /* crypto */ + UATH_DEBUG_LED = 0x00020000, /* LED */ + UATH_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +/* unaligned little endian access */ +#define LE_READ_2(p) \ + ((u_int16_t) \ + ((((u_int8_t *)(p))[0] ) | (((u_int8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((u_int32_t) \ + ((((u_int8_t *)(p))[0] ) | (((u_int8_t *)(p))[1] << 8) | \ + (((u_int8_t *)(p))[2] << 16) | (((u_int8_t *)(p))[3] << 24))) + +/* recognized device vendors/products */ +static const struct usb2_device_id uath_devs[] = { +#define UATH_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + UATH_DEV(ATHEROS, AR5523), + UATH_DEV(ATHEROS2, AR5523_1), + UATH_DEV(ATHEROS2, AR5523_2), + UATH_DEV(ATHEROS2, AR5523_3), + UATH_DEV(CONCEPTRONIC, AR5523_1), + UATH_DEV(CONCEPTRONIC, AR5523_2), + UATH_DEV(DLINK, DWLAG122), + UATH_DEV(DLINK, DWLAG132), + UATH_DEV(DLINK, DWLG132), + UATH_DEV(GIGASET, AR5523), + UATH_DEV(GIGASET, SMCWUSBTG), + UATH_DEV(GLOBALSUN, AR5523_1), + UATH_DEV(GLOBALSUN, AR5523_2), + UATH_DEV(NETGEAR, WG111U), + UATH_DEV(NETGEAR3, WG111T), + UATH_DEV(NETGEAR3, WPN111), + UATH_DEV(UMEDIA, TEW444UBEU), + UATH_DEV(UMEDIA, AR5523_2), + UATH_DEV(WISTRONNEWEB, AR5523_1), + UATH_DEV(WISTRONNEWEB, AR5523_2), + UATH_DEV(ZCOM, AR5523) +#undef UATH_DEV +}; + +static usb2_callback_t uath_intr_rx_callback; +static usb2_callback_t uath_intr_tx_callback; +static usb2_callback_t uath_bulk_rx_callback; +static usb2_callback_t uath_bulk_tx_callback; + +static const struct usb2_config uath_usbconfig[UATH_N_XFERS] = { + [UATH_INTR_RX] = { + .type = UE_BULK, + .endpoint = 0x1, + .direction = UE_DIR_IN, + .mh.bufsize = UATH_MAX_CMDSZ, + .mh.flags = { + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .mh.callback = uath_intr_rx_callback + }, + [UATH_INTR_TX] = { + .type = UE_BULK, + .endpoint = 0x1, + .direction = UE_DIR_OUT, + .mh.bufsize = UATH_MAX_CMDSZ, + .mh.flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .mh.callback = uath_intr_tx_callback, + .mh.timeout = UATH_CMD_TIMEOUT + }, + [UATH_BULK_RX] = { + .type = UE_BULK, + .endpoint = 0x2, + .direction = UE_DIR_IN, + .mh.bufsize = MCLBYTES, + .mh.flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .mh.callback = uath_bulk_rx_callback + }, + [UATH_BULK_TX] = { + .type = UE_BULK, + .endpoint = 0x2, + .direction = UE_DIR_OUT, + .mh.bufsize = UATH_MAX_TXBUFSZ, + .mh.flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1 + }, + .mh.callback = uath_bulk_tx_callback, + .mh.timeout = UATH_DATA_TIMEOUT + } +}; + +static struct ieee80211vap *uath_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void uath_vap_delete(struct ieee80211vap *); +static int uath_alloc_cmd_list(struct uath_softc *, struct uath_cmd [], + int, int); +static void uath_free_cmd_list(struct uath_softc *, struct uath_cmd [], + int); +static int uath_host_available(struct uath_softc *); +static int uath_get_capability(struct uath_softc *, uint32_t, uint32_t *); +static int uath_get_devcap(struct uath_softc *); +static struct uath_cmd * + uath_get_cmdbuf(struct uath_softc *); +static int uath_cmd_read(struct uath_softc *, uint32_t, const void *, + int, void *, int, int); +static int uath_cmd_write(struct uath_softc *, uint32_t, const void *, + int, int); +static void uath_stat(void *); +#ifdef UATH_DEBUG +static void uath_dump_cmd(const uint8_t *, int, char); +static const char * + uath_codename(int); +#endif +static int uath_get_devstatus(struct uath_softc *, + uint8_t macaddr[IEEE80211_ADDR_LEN]); +static int uath_get_status(struct uath_softc *, uint32_t, void *, int); +static int uath_alloc_rx_data_list(struct uath_softc *); +static int uath_alloc_tx_data_list(struct uath_softc *); +static void uath_free_rx_data_list(struct uath_softc *); +static void uath_free_tx_data_list(struct uath_softc *); +static int uath_init_locked(void *); +static void uath_init(void *); +static void uath_stop_locked(struct ifnet *); +static void uath_stop(struct ifnet *); +static int uath_ioctl(struct ifnet *, u_long, caddr_t); +static void uath_start(struct ifnet *); +static int uath_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void uath_scan_start(struct ieee80211com *); +static void uath_scan_end(struct ieee80211com *); +static void uath_set_channel(struct ieee80211com *); +static void uath_update_mcast(struct ifnet *); +static void uath_update_promisc(struct ifnet *); +static int uath_config(struct uath_softc *, uint32_t, uint32_t); +static int uath_config_multi(struct uath_softc *, uint32_t, const void *, + int); +static int uath_switch_channel(struct uath_softc *, + struct ieee80211_channel *); +static int uath_set_rxfilter(struct uath_softc *, uint32_t, uint32_t); +static void uath_watchdog(void *); +static void uath_abort_xfers(struct uath_softc *); +static int uath_dataflush(struct uath_softc *); +static int uath_cmdflush(struct uath_softc *); +static int uath_flush(struct uath_softc *); +static int uath_set_ledstate(struct uath_softc *, int); +static int uath_set_chan(struct uath_softc *, struct ieee80211_channel *); +static int uath_reset_tx_queues(struct uath_softc *); +static int uath_wme_init(struct uath_softc *); +static struct uath_data * + uath_getbuf(struct uath_softc *); +static int uath_newstate(struct ieee80211vap *, enum ieee80211_state, + int); +static int uath_set_key(struct uath_softc *, + const struct ieee80211_key *, int); +static int uath_set_keys(struct uath_softc *, struct ieee80211vap *); +static void uath_sysctl_node(struct uath_softc *); + +static int +uath_match(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != UATH_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != UATH_IFACE_INDEX) + return (ENXIO); + + return (usb2_lookup_id_by_uaa(uath_devs, sizeof(uath_devs), uaa)); +} + +static int +uath_attach(device_t dev) +{ + struct uath_softc *sc = device_get_softc(dev); + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t bands, iface_index = UATH_IFACE_INDEX; /* XXX */ + usb2_error_t error; + uint8_t macaddr[IEEE80211_ADDR_LEN]; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; +#ifdef UATH_DEBUG + sc->sc_debug = uath_debug; +#endif + + /* + * Only post-firmware devices here. + */ + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init(&sc->stat_ch, 0); + callout_init_mtx(&sc->watchdog_ch, &sc->sc_mtx, 0); + + /* + * Allocate xfers for firmware commands. + */ + error = uath_alloc_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT, + UATH_MAX_CMDSZ); + if (error != 0) { + device_printf(sc->sc_dev, + "could not allocate Tx command list\n"); + goto fail; + } + + error = usb2_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, + uath_usbconfig, UATH_N_XFERS, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usb2_errstr(error)); + goto fail1; + } + + /* + * We're now ready to send+receive firmware commands. + */ + UATH_LOCK(sc); + error = uath_host_available(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not initialize adapter\n"); + goto fail3; + } + error = uath_get_devcap(sc); + if (error != 0) { + device_printf(sc->sc_dev, + "could not get device capabilities\n"); + goto fail3; + } + UATH_UNLOCK(sc); + + /* Create device sysctl node. */ + uath_sysctl_node(sc); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not allocate ifnet\n"); + error = ENXIO; + goto fail2; + } + + UATH_LOCK(sc); + error = uath_get_devstatus(sc, macaddr); + if (error != 0) { + device_printf(sc->sc_dev, "could not get device status\n"); + goto fail4; + } + + /* + * Allocate xfers for Rx/Tx data pipes. + */ + error = uath_alloc_rx_data_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Rx data list\n"); + goto fail4; + } + error = uath_alloc_tx_data_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Tx data list\n"); + goto fail4; + } + UATH_UNLOCK(sc); + + ifp->if_softc = sc; + if_initname(ifp, "uath", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = uath_init; + ifp->if_ioctl = uath_ioctl; + ifp->if_start = uath_start; + /* XXX UATH_TX_DATA_LIST_COUNT */ + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + 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 */ + IEEE80211_C_MONITOR | /* monitor mode supported */ + IEEE80211_C_TXPMGT | /* tx power management */ + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ + IEEE80211_C_SHSLOT | /* short slot time supported */ + IEEE80211_C_WPA | /* 802.11i */ + IEEE80211_C_BGSCAN | /* capable of bg scanning */ + IEEE80211_C_TXFRAG; /* handle tx frags */ + + /* put a regulatory domain to reveal informations. */ + uath_regdomain = sc->sc_devcap.regDomain; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if ((sc->sc_devcap.analog5GhzRevision & 0xf0) == 0x30) + setbit(&bands, IEEE80211_MODE_11A); + /* XXX turbo */ + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, macaddr); + ic->ic_raw_xmit = uath_raw_xmit; + ic->ic_scan_start = uath_scan_start; + ic->ic_scan_end = uath_scan_end; + ic->ic_set_channel = uath_set_channel; + + ic->ic_vap_create = uath_vap_create; + ic->ic_vap_delete = uath_vap_delete; + ic->ic_update_mcast = uath_update_mcast; + ic->ic_update_promisc = uath_update_promisc; + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof (struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + sc->sc_rxtap_len = sizeof sc->sc_rxtap; + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(UATH_RX_RADIOTAP_PRESENT); + + sc->sc_txtap_len = sizeof sc->sc_txtap; + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(UATH_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +fail4: if_free(ifp); +fail3: UATH_UNLOCK(sc); +fail2: usb2_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); +fail1: uath_free_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT); +fail: + return (error); +} + +static int +uath_detach(device_t dev) +{ + struct uath_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return (0); + + sc->sc_flags |= UATH_FLAG_INVALID; + uath_stop(ifp); + ieee80211_ifdetach(ic); + + callout_drain(&sc->stat_ch); + callout_drain(&sc->watchdog_ch); + + usb2_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); + + /* free buffers */ + UATH_LOCK(sc); + uath_free_rx_data_list(sc); + uath_free_tx_data_list(sc); + uath_free_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT); + UATH_UNLOCK(sc); + + bpfdetach(ifp); + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + return (0); +} + +static void +uath_free_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[], int ncmd) +{ + int i; + + for (i = 0; i < ncmd; i++) + if (cmds[i].buf != NULL) + free(cmds[i].buf, M_USBDEV); +} + +static int +uath_alloc_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[], + int ncmd, int maxsz) +{ + int i, error; + + STAILQ_INIT(&sc->sc_cmd_active); + STAILQ_INIT(&sc->sc_cmd_pending); + STAILQ_INIT(&sc->sc_cmd_waiting); + STAILQ_INIT(&sc->sc_cmd_inactive); + + for (i = 0; i < ncmd; i++) { + struct uath_cmd *cmd = &cmds[i]; + + cmd->sc = sc; /* backpointer for callbacks */ + cmd->msgid = i; + cmd->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); + if (cmd->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate xfer buffer\n"); + error = ENOMEM; + goto fail; + } + STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); + UATH_STAT_INC(sc, st_cmd_inactive); + } + return (0); + +fail: uath_free_cmd_list(sc, cmds, ncmd); + return (error); +} + +static int +uath_host_available(struct uath_softc *sc) +{ + struct uath_cmd_host_available setup; + + UATH_ASSERT_LOCKED(sc); + + /* inform target the host is available */ + setup.sw_ver_major = htobe32(ATH_SW_VER_MAJOR); + setup.sw_ver_minor = htobe32(ATH_SW_VER_MINOR); + setup.sw_ver_patch = htobe32(ATH_SW_VER_PATCH); + setup.sw_ver_build = htobe32(ATH_SW_VER_BUILD); + return uath_cmd_read(sc, WDCMSG_HOST_AVAILABLE, + &setup, sizeof setup, NULL, 0, 0); +} + +#ifdef UATH_DEBUG +static void +uath_dump_cmd(const uint8_t *buf, int len, char prefix) +{ + const char *sep = ""; + int i; + + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + printf("%s%c ", sep, prefix); + sep = "\n"; + } + else if ((i % 4) == 0) + printf(" "); + printf("%02x", buf[i]); + } + printf("\n"); +} + +static const char * +uath_codename(int code) +{ +#define N(a) (sizeof(a)/sizeof(a[0])) + static const char *names[] = { + "0x00", + "HOST_AVAILABLE", + "BIND", + "TARGET_RESET", + "TARGET_GET_CAPABILITY", + "TARGET_SET_CONFIG", + "TARGET_GET_STATUS", + "TARGET_GET_STATS", + "TARGET_START", + "TARGET_STOP", + "TARGET_ENABLE", + "TARGET_DISABLE", + "CREATE_CONNECTION", + "UPDATE_CONNECT_ATTR", + "DELETE_CONNECT", + "SEND", + "FLUSH", + "STATS_UPDATE", + "BMISS", + "DEVICE_AVAIL", + "SEND_COMPLETE", + "DATA_AVAIL", + "SET_PWR_MODE", + "BMISS_ACK", + "SET_LED_STEADY", + "SET_LED_BLINK", + "SETUP_BEACON_DESC", + "BEACON_INIT", + "RESET_KEY_CACHE", + "RESET_KEY_CACHE_ENTRY", + "SET_KEY_CACHE_ENTRY", + "SET_DECOMP_MASK", + "SET_REGULATORY_DOMAIN", + "SET_LED_STATE", + "WRITE_ASSOCID", + "SET_STA_BEACON_TIMERS", + "GET_TSF", + "RESET_TSF", + "SET_ADHOC_MODE", + "SET_BASIC_RATE", + "MIB_CONTROL", + "GET_CHANNEL_DATA", + "GET_CUR_RSSI", + "SET_ANTENNA_SWITCH", + "0x2c", "0x2d", "0x2e", + "USE_SHORT_SLOT_TIME", + "SET_POWER_MODE", + "SETUP_PSPOLL_DESC", + "SET_RX_MULTICAST_FILTER", + "RX_FILTER", + "PER_CALIBRATION", + "RESET", + "DISABLE", + "PHY_DISABLE", + "SET_TX_POWER_LIMIT", + "SET_TX_QUEUE_PARAMS", + "SETUP_TX_QUEUE", + "RELEASE_TX_QUEUE", + }; + static char buf[8]; + + if (code < N(names)) + return names[code]; + if (code == WDCMSG_SET_DEFAULT_KEY) + return "SET_DEFAULT_KEY"; + snprintf(buf, sizeof(buf), "0x%02x", code); + return buf; +#undef N +} +#endif + +/* + * Low-level function to send read or write commands to the firmware. + */ +static int +uath_cmdsend(struct uath_softc *sc, uint32_t code, const void *idata, int ilen, + void *odata, int olen, int flags) +{ + struct uath_cmd_hdr *hdr; + struct uath_cmd *cmd; + int error; + + UATH_ASSERT_LOCKED(sc); + + /* grab a xfer */ + cmd = uath_get_cmdbuf(sc); + if (cmd == NULL) { + device_printf(sc->sc_dev, "%s: empty inactive queue\n", + __func__); + return (ENOBUFS); + } + cmd->flags = flags; + /* always bulk-out a multiple of 4 bytes */ + cmd->buflen = roundup2(sizeof(struct uath_cmd_hdr) + ilen, 4); + + hdr = (struct uath_cmd_hdr *)cmd->buf; + bzero(hdr, sizeof (struct uath_cmd_hdr)); /* XXX not needed */ + hdr->len = htobe32(cmd->buflen); + hdr->code = htobe32(code); + hdr->msgid = cmd->msgid; /* don't care about endianness */ + hdr->magic = htobe32((cmd->flags & UATH_CMD_FLAG_MAGIC) ? 1 << 24 : 0); + bcopy(idata, (uint8_t *)(hdr + 1), ilen); + +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + printf("%s: send %s [flags 0x%x] olen %d\n", + __func__, uath_codename(code), cmd->flags, olen); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(cmd->buf, cmd->buflen, '+'); + } +#endif + cmd->odata = odata; + KASSERT(odata == NULL || + olen < UATH_MAX_CMDSZ - sizeof(*hdr) + sizeof(uint32_t), + ("odata %p olen %u", odata, olen)); + cmd->olen = olen; + + STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next); + UATH_STAT_INC(sc, st_cmd_pending); + usb2_transfer_start(sc->sc_xfer[UATH_INTR_TX]); + + if (cmd->flags & UATH_CMD_FLAG_READ) { + usb2_transfer_start(sc->sc_xfer[UATH_INTR_RX]); + + /* wait at most two seconds for command reply */ + error = mtx_sleep(cmd, &sc->sc_mtx, 0, "uathcmd", 2 * hz); + cmd->odata = NULL; /* in case reply comes too late */ + if (error != 0) { + device_printf(sc->sc_dev, "timeout waiting for reply " + "to cmd 0x%x (%u)\n", code, code); + } else if (cmd->olen != olen) { + device_printf(sc->sc_dev, "unexpected reply data count " + "to cmd 0x%x (%u), got %u, expected %u\n", + code, code, cmd->olen, olen); + error = EINVAL; + } + return (error); + } + return (0); +} + +static int +uath_cmd_read(struct uath_softc *sc, uint32_t code, const void *idata, + int ilen, void *odata, int olen, int flags) +{ + + flags |= UATH_CMD_FLAG_READ; + return uath_cmdsend(sc, code, idata, ilen, odata, olen, flags); +} + +static int +uath_cmd_write(struct uath_softc *sc, uint32_t code, const void *data, int len, + int flags) +{ + + flags &= ~UATH_CMD_FLAG_READ; + return uath_cmdsend(sc, code, data, len, NULL, 0, flags); +} + +static struct uath_cmd * +uath_get_cmdbuf(struct uath_softc *sc) +{ + struct uath_cmd *uc; + + UATH_ASSERT_LOCKED(sc); + + uc = STAILQ_FIRST(&sc->sc_cmd_inactive); + if (uc != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next); + UATH_STAT_DEC(sc, st_cmd_inactive); + } else + uc = NULL; + if (uc == NULL) + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, + "out of command xmit buffers"); + return (uc); +} + +/* + * This function is called periodically (every second) when associated to + * query device statistics. + */ +static void +uath_stat(void *arg) +{ + struct uath_softc *sc = arg; + int error; + + UATH_LOCK(sc); + /* + * Send request for statistics asynchronously. The timer will be + * restarted when we'll get the stats notification. + */ + error = uath_cmd_write(sc, WDCMSG_TARGET_GET_STATS, NULL, 0, + UATH_CMD_FLAG_ASYNC); + if (error != 0) { + device_printf(sc->sc_dev, + "could not query stats, error %d\n", error); + } + UATH_UNLOCK(sc); +} + +static int +uath_get_capability(struct uath_softc *sc, uint32_t cap, uint32_t *val) +{ + int error; + + cap = htobe32(cap); + error = uath_cmd_read(sc, WDCMSG_TARGET_GET_CAPABILITY, + &cap, sizeof cap, val, sizeof(uint32_t), UATH_CMD_FLAG_MAGIC); + if (error != 0) { + device_printf(sc->sc_dev, "could not read capability %u\n", + be32toh(cap)); + return (error); + } + *val = be32toh(*val); + return (error); +} + +static int +uath_get_devcap(struct uath_softc *sc) +{ +#define GETCAP(x, v) do { \ + error = uath_get_capability(sc, x, &v); \ + if (error != 0) \ + return (error); \ + DPRINTF(sc, UATH_DEBUG_DEVCAP, \ + "%s: %s=0x%08x\n", __func__, #x, v); \ +} while (0) + struct uath_devcap *cap = &sc->sc_devcap; + int error; + + /* collect device capabilities */ + GETCAP(CAP_TARGET_VERSION, cap->targetVersion); + GETCAP(CAP_TARGET_REVISION, cap->targetRevision); + GETCAP(CAP_MAC_VERSION, cap->macVersion); + GETCAP(CAP_MAC_REVISION, cap->macRevision); + GETCAP(CAP_PHY_REVISION, cap->phyRevision); + GETCAP(CAP_ANALOG_5GHz_REVISION, cap->analog5GhzRevision); + GETCAP(CAP_ANALOG_2GHz_REVISION, cap->analog2GhzRevision); + + GETCAP(CAP_REG_DOMAIN, cap->regDomain); + GETCAP(CAP_REG_CAP_BITS, cap->regCapBits); +#if 0 + /* NB: not supported in rev 1.5 */ + GETCAP(CAP_COUNTRY_CODE, cap->countryCode); +#endif + GETCAP(CAP_WIRELESS_MODES, cap->wirelessModes); + GETCAP(CAP_CHAN_SPREAD_SUPPORT, cap->chanSpreadSupport); + GETCAP(CAP_COMPRESS_SUPPORT, cap->compressSupport); + GETCAP(CAP_BURST_SUPPORT, cap->burstSupport); + GETCAP(CAP_FAST_FRAMES_SUPPORT, cap->fastFramesSupport); + GETCAP(CAP_CHAP_TUNING_SUPPORT, cap->chapTuningSupport); + GETCAP(CAP_TURBOG_SUPPORT, cap->turboGSupport); + GETCAP(CAP_TURBO_PRIME_SUPPORT, cap->turboPrimeSupport); + GETCAP(CAP_DEVICE_TYPE, cap->deviceType); + GETCAP(CAP_WME_SUPPORT, cap->wmeSupport); + GETCAP(CAP_TOTAL_QUEUES, cap->numTxQueues); + GETCAP(CAP_CONNECTION_ID_MAX, cap->connectionIdMax); + + GETCAP(CAP_LOW_5GHZ_CHAN, cap->low5GhzChan); + GETCAP(CAP_HIGH_5GHZ_CHAN, cap->high5GhzChan); + GETCAP(CAP_LOW_2GHZ_CHAN, cap->low2GhzChan); + GETCAP(CAP_HIGH_2GHZ_CHAN, cap->high2GhzChan); + GETCAP(CAP_TWICE_ANTENNAGAIN_5G, cap->twiceAntennaGain5G); + GETCAP(CAP_TWICE_ANTENNAGAIN_2G, cap->twiceAntennaGain2G); + + GETCAP(CAP_CIPHER_AES_CCM, cap->supportCipherAES_CCM); + GETCAP(CAP_CIPHER_TKIP, cap->supportCipherTKIP); + GETCAP(CAP_MIC_TKIP, cap->supportMicTKIP); + + cap->supportCipherWEP = 1; /* NB: always available */ + + return (0); +} + +static int +uath_get_devstatus(struct uath_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) +{ + int error; + + /* retrieve MAC address */ + error = uath_get_status(sc, ST_MAC_ADDR, macaddr, IEEE80211_ADDR_LEN); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC address\n"); + return (error); + } + + error = uath_get_status(sc, ST_SERIAL_NUMBER, + &sc->sc_serial[0], sizeof(sc->sc_serial)); + if (error != 0) { + device_printf(sc->sc_dev, + "could not read device serial number\n"); + return (error); + } + return (0); +} + +static int +uath_get_status(struct uath_softc *sc, uint32_t which, void *odata, int olen) +{ + int error; + + which = htobe32(which); + error = uath_cmd_read(sc, WDCMSG_TARGET_GET_STATUS, + &which, sizeof(which), odata, olen, UATH_CMD_FLAG_MAGIC); + if (error != 0) + device_printf(sc->sc_dev, + "could not read EEPROM offset 0x%02x\n", be32toh(which)); + return (error); +} + +static void +uath_free_data_list(struct uath_softc *sc, struct uath_data data[], int ndata, + int fillmbuf) +{ + int i; + + for (i = 0; i < ndata; i++) { + struct uath_data *dp = &data[i]; + + if (fillmbuf == 1) { + if (dp->m != NULL) { + m_freem(dp->m); + dp->m = NULL; + dp->buf = NULL; + } + } else { + if (dp->buf != NULL) { + free(dp->buf, M_USBDEV); + dp->buf = NULL; + } + } +#ifdef UATH_DEBUG + if (dp->ni != NULL) + device_printf(sc->sc_dev, "Node isn't NULL\n"); +#endif + } +} + +static int +uath_alloc_data_list(struct uath_softc *sc, struct uath_data data[], + int ndata, int maxsz, int fillmbuf) +{ + int i, error; + + for (i = 0; i < ndata; i++) { + struct uath_data *dp = &data[i]; + + dp->sc = sc; + if (fillmbuf) { + /* XXX check maxsz */ + dp->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (dp->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + dp->buf = mtod(dp->m, uint8_t *); + } else { + dp->m = NULL; + dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); + if (dp->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate buffer\n"); + error = ENOMEM; + goto fail; + } + } + dp->ni = NULL; + } + + return (0); + +fail: uath_free_data_list(sc, data, ndata, fillmbuf); + return (error); +} + +static int +uath_alloc_rx_data_list(struct uath_softc *sc) +{ + int error, i; + + /* XXX is it enough to store the RX packet with MCLBYTES bytes? */ + error = uath_alloc_data_list(sc, + sc->sc_rx, UATH_RX_DATA_LIST_COUNT, MCLBYTES, + 1 /* setup mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + for (i = 0; i < UATH_RX_DATA_LIST_COUNT; i++) { + STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], + next); + UATH_STAT_INC(sc, st_rx_inactive); + } + + return (0); +} + +static int +uath_alloc_tx_data_list(struct uath_softc *sc) +{ + int error, i; + + error = uath_alloc_data_list(sc, + sc->sc_tx, UATH_TX_DATA_LIST_COUNT, UATH_MAX_TXBUFSZ, + 0 /* no mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + for (i = 0; i < UATH_TX_DATA_LIST_COUNT; i++) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], + next); + UATH_STAT_INC(sc, st_tx_inactive); + } + + return (0); +} + +static void +uath_free_rx_data_list(struct uath_softc *sc) +{ + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + uath_free_data_list(sc, sc->sc_rx, UATH_RX_DATA_LIST_COUNT, + 1 /* free mbufs */); +} + +static void +uath_free_tx_data_list(struct uath_softc *sc) +{ + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + uath_free_data_list(sc, sc->sc_tx, UATH_TX_DATA_LIST_COUNT, + 0 /* no mbufs */); +} + +static struct ieee80211vap * +uath_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct uath_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + uvp = (struct uath_vap *) malloc(sizeof(struct uath_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return (NULL); + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = uath_newstate; + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +uath_vap_delete(struct ieee80211vap *vap) +{ + struct uath_vap *uvp = UATH_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static int +uath_init_locked(void *arg) +{ + struct uath_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t val; + int error; + + UATH_ASSERT_LOCKED(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + uath_stop_locked(ifp); + + /* reset variables */ + sc->sc_intrx_nextnum = sc->sc_msgid = 0; + + val = htobe32(0); + uath_cmd_write(sc, WDCMSG_BIND, &val, sizeof val, 0); + + /* set MAC address */ + uath_config_multi(sc, CFG_MAC_ADDR, IF_LLADDR(ifp), IEEE80211_ADDR_LEN); + + /* XXX honor net80211 state */ + uath_config(sc, CFG_RATE_CONTROL_ENABLE, 0x00000001); + uath_config(sc, CFG_DIVERSITY_CTL, 0x00000001); + uath_config(sc, CFG_ABOLT, 0x0000003f); + uath_config(sc, CFG_WME_ENABLED, 0x00000001); + + uath_config(sc, CFG_SERVICE_TYPE, 1); + uath_config(sc, CFG_TP_SCALE, 0x00000000); + uath_config(sc, CFG_TPC_HALF_DBM5, 0x0000003c); + uath_config(sc, CFG_TPC_HALF_DBM2, 0x0000003c); + uath_config(sc, CFG_OVERRD_TX_POWER, 0x00000000); + uath_config(sc, CFG_GMODE_PROTECTION, 0x00000000); + uath_config(sc, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003); + uath_config(sc, CFG_PROTECTION_TYPE, 0x00000000); + uath_config(sc, CFG_MODE_CTS, 0x00000002); + + error = uath_cmd_read(sc, WDCMSG_TARGET_START, NULL, 0, + &val, sizeof(val), UATH_CMD_FLAG_MAGIC); + if (error) { + device_printf(sc->sc_dev, + "could not start target, error %d\n", error); + goto fail; + } + DPRINTF(sc, UATH_DEBUG_INIT, "%s returns handle: 0x%x\n", + uath_codename(WDCMSG_TARGET_START), be32toh(val)); + + /* set default channel */ + error = uath_switch_channel(sc, ic->ic_curchan); + if (error) { + device_printf(sc->sc_dev, + "could not switch channel, error %d\n", error); + goto fail; + } + + val = htobe32(TARGET_DEVICE_AWAKE); + uath_cmd_write(sc, WDCMSG_SET_PWR_MODE, &val, sizeof val, 0); + /* XXX? check */ + uath_cmd_write(sc, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0); + + usb2_transfer_start(sc->sc_xfer[UATH_BULK_RX]); + /* enable Rx */ + uath_set_rxfilter(sc, 0x0, UATH_FILTER_OP_INIT); + uath_set_rxfilter(sc, + UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON, + UATH_FILTER_OP_SET); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + sc->sc_flags |= UATH_FLAG_INITDONE; + + callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); + + return (0); + +fail: + uath_stop_locked(ifp); + return (error); +} + +static void +uath_init(void *arg) +{ + struct uath_softc *sc = arg; + + UATH_LOCK(sc); + (void)uath_init_locked(sc); + UATH_UNLOCK(sc); +} + +static void +uath_stop_locked(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_ASSERT_LOCKED(sc); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + sc->sc_flags &= ~UATH_FLAG_INITDONE; + + callout_stop(&sc->stat_ch); + callout_stop(&sc->watchdog_ch); + sc->sc_tx_timer = 0; + /* abort pending transmits */ + uath_abort_xfers(sc); + /* flush data & control requests into the target */ + (void)uath_flush(sc); + /* set a LED status to the disconnected. */ + uath_set_ledstate(sc, 0); + /* stop the target */ + uath_cmd_write(sc, WDCMSG_TARGET_STOP, NULL, 0, 0); +} + +static void +uath_stop(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + uath_stop_locked(ifp); + UATH_UNLOCK(sc); +} + +static int +uath_config(struct uath_softc *sc, uint32_t reg, uint32_t val) +{ + struct uath_write_mac write; + int error; + + write.reg = htobe32(reg); + write.len = htobe32(0); /* 0 = single write */ + *(uint32_t *)write.data = htobe32(val); + + error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, + 3 * sizeof (uint32_t), 0); + if (error != 0) { + device_printf(sc->sc_dev, "could not write register 0x%02x\n", + reg); + } + return (error); +} + +static int +uath_config_multi(struct uath_softc *sc, uint32_t reg, const void *data, + int len) +{ + struct uath_write_mac write; + int error; + + write.reg = htobe32(reg); + write.len = htobe32(len); + bcopy(data, write.data, len); + + /* properly handle the case where len is zero (reset) */ + error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, + (len == 0) ? sizeof (uint32_t) : 2 * sizeof (uint32_t) + len, 0); + if (error != 0) { + device_printf(sc->sc_dev, + "could not write %d bytes to register 0x%02x\n", len, reg); + } + return (error); +} + +static int +uath_switch_channel(struct uath_softc *sc, struct ieee80211_channel *c) +{ + int error; + + UATH_ASSERT_LOCKED(sc); + + /* set radio frequency */ + error = uath_set_chan(sc, c); + if (error) { + device_printf(sc->sc_dev, + "could not set channel, error %d\n", error); + goto failed; + } + /* reset Tx rings */ + error = uath_reset_tx_queues(sc); + if (error) { + device_printf(sc->sc_dev, + "could not reset Tx queues, error %d\n", error); + goto failed; + } + /* set Tx rings WME properties */ + error = uath_wme_init(sc); + if (error) { + device_printf(sc->sc_dev, + "could not init Tx queues, error %d\n", error); + goto failed; + } + error = uath_set_ledstate(sc, 0); + if (error) { + device_printf(sc->sc_dev, + "could not set led state, error %d\n", error); + goto failed; + } + error = uath_flush(sc); + if (error) { + device_printf(sc->sc_dev, + "could not flush pipes, error %d\n", error); + goto failed; + } +failed: + return (error); +} + +static int +uath_set_rxfilter(struct uath_softc *sc, uint32_t bits, uint32_t op) +{ + struct uath_cmd_rx_filter rxfilter; + + rxfilter.bits = htobe32(bits); + rxfilter.op = htobe32(op); + + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "setting Rx filter=0x%x flags=0x%x\n", bits, op); + return uath_cmd_write(sc, WDCMSG_RX_FILTER, &rxfilter, + sizeof rxfilter, 0); +} + +static void +uath_watchdog(void *arg) +{ + struct uath_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + /*uath_init(ifp); XXX needs a process context! */ + ifp->if_oerrors++; + return; + } + callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); + } +} + +static void +uath_abort_xfers(struct uath_softc *sc) +{ + int i; + + UATH_ASSERT_LOCKED(sc); + /* abort any pending transfers */ + for (i = 0; i < UATH_N_XFERS; i++) + usb2_transfer_stop(sc->sc_xfer[i]); +} + +static int +uath_flush(struct uath_softc *sc) +{ + int error; + + error = uath_dataflush(sc); + if (error != 0) + goto failed; + + error = uath_cmdflush(sc); + if (error != 0) + goto failed; + +failed: + return (error); +} + +static int +uath_cmdflush(struct uath_softc *sc) +{ + + return uath_cmd_write(sc, WDCMSG_FLUSH, NULL, 0, 0); +} + +static int +uath_dataflush(struct uath_softc *sc) +{ + struct uath_data *data; + struct uath_chunk *chunk; + struct uath_tx_desc *desc; + + UATH_ASSERT_LOCKED(sc); + + data = uath_getbuf(sc); + if (data == NULL) + return (ENOBUFS); + data->buflen = sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc); + data->m = NULL; + data->ni = NULL; + chunk = (struct uath_chunk *)data->buf; + desc = (struct uath_tx_desc *)(chunk + 1); + + /* one chunk only */ + chunk->seqnum = 0; + chunk->flags = UATH_CFLAGS_FINAL; + chunk->length = htobe16(sizeof (struct uath_tx_desc)); + + bzero(desc, sizeof(struct uath_tx_desc)); + desc->msglen = htobe32(sizeof(struct uath_tx_desc)); + desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ + desc->type = htobe32(WDCMSG_FLUSH); + desc->txqid = htobe32(0); + desc->connid = htobe32(0); + desc->flags = htobe32(0); + +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + DPRINTF(sc, UATH_DEBUG_RESET, "send flush ix %d\n", + desc->msgid); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(data->buf, data->buflen, '+'); + } +#endif + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + UATH_STAT_INC(sc, st_tx_pending); + sc->sc_tx_timer = 5; + usb2_transfer_start(sc->sc_xfer[UATH_BULK_TX]); + + return (0); +} + +static struct uath_data * +_uath_getbuf(struct uath_softc *sc) +{ + struct uath_data *bf; + + bf = STAILQ_FIRST(&sc->sc_tx_inactive); + if (bf != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); + UATH_STAT_DEC(sc, st_tx_inactive); + } else + bf = NULL; + if (bf == NULL) + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, + "out of xmit buffers"); + return (bf); +} + +static struct uath_data * +uath_getbuf(struct uath_softc *sc) +{ + struct uath_data *bf; + + UATH_ASSERT_LOCKED(sc); + + bf = _uath_getbuf(sc); + if (bf == NULL) { + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: stop queue\n", __func__); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + } + return (bf); +} + +static int +uath_set_ledstate(struct uath_softc *sc, int connected) +{ + + DPRINTF(sc, UATH_DEBUG_LED, + "set led state %sconnected\n", connected ? "" : "!"); + connected = htobe32(connected); + return uath_cmd_write(sc, WDCMSG_SET_LED_STATE, + &connected, sizeof connected, 0); +} + +static int +uath_set_chan(struct uath_softc *sc, struct ieee80211_channel *c) +{ +#ifdef UATH_DEBUG + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; +#endif + struct uath_cmd_reset reset; + + bzero(&reset, sizeof reset); + if (IEEE80211_IS_CHAN_2GHZ(c)) + reset.flags |= htobe32(UATH_CHAN_2GHZ); + if (IEEE80211_IS_CHAN_5GHZ(c)) + reset.flags |= htobe32(UATH_CHAN_5GHZ); + /* NB: 11g =>'s 11b so don't specify both OFDM and CCK */ + if (IEEE80211_IS_CHAN_G(c)) + reset.flags |= htobe32(UATH_CHAN_OFDM); + else if (IEEE80211_IS_CHAN_B(c)) + reset.flags |= htobe32(UATH_CHAN_CCK); + /* turbo can be used in either 2GHz or 5GHz */ + if (c->ic_flags & IEEE80211_CHAN_TURBO) + reset.flags |= htobe32(UATH_CHAN_TURBO); + reset.freq = htobe32(c->ic_freq); + reset.maxrdpower = htobe32(50); /* XXX */ + reset.channelchange = htobe32(1); + reset.keeprccontent = htobe32(0); + + DPRINTF(sc, UATH_DEBUG_CHANNEL, "set channel %d, flags 0x%x freq %u\n", + ieee80211_chan2ieee(ic, c), + be32toh(reset.flags), be32toh(reset.freq)); + return uath_cmd_write(sc, WDCMSG_RESET, &reset, sizeof reset, 0); +} + +static int +uath_reset_tx_queues(struct uath_softc *sc) +{ + int ac, error; + + DPRINTF(sc, UATH_DEBUG_RESET, "%s: reset Tx queues\n", __func__); + for (ac = 0; ac < 4; ac++) { + const uint32_t qid = htobe32(ac); + + error = uath_cmd_write(sc, WDCMSG_RELEASE_TX_QUEUE, &qid, + sizeof qid, 0); + if (error != 0) + break; + } + return (error); +} + +static int +uath_wme_init(struct uath_softc *sc) +{ + /* XXX get from net80211 */ + static const struct uath_wme_settings uath_wme_11g[4] = { + { 7, 4, 10, 0, 0 }, /* Background */ + { 3, 4, 10, 0, 0 }, /* Best-Effort */ + { 3, 3, 4, 26, 0 }, /* Video */ + { 2, 2, 3, 47, 0 } /* Voice */ + }; + struct uath_cmd_txq_setup qinfo; + int ac, error; + + DPRINTF(sc, UATH_DEBUG_WME, "%s: setup Tx queues\n", __func__); + for (ac = 0; ac < 4; ac++) { + qinfo.qid = htobe32(ac); + qinfo.len = htobe32(sizeof(qinfo.attr)); + qinfo.attr.priority = htobe32(ac); /* XXX */ + qinfo.attr.aifs = htobe32(uath_wme_11g[ac].aifsn); + qinfo.attr.logcwmin = htobe32(uath_wme_11g[ac].logcwmin); + qinfo.attr.logcwmax = htobe32(uath_wme_11g[ac].logcwmax); + qinfo.attr.bursttime = htobe32(UATH_TXOP_TO_US( + uath_wme_11g[ac].txop)); + qinfo.attr.mode = htobe32(uath_wme_11g[ac].acm);/*XXX? */ + qinfo.attr.qflags = htobe32(1); /* XXX? */ + + error = uath_cmd_write(sc, WDCMSG_SETUP_TX_QUEUE, &qinfo, + sizeof qinfo, 0); + if (error != 0) + break; + } + return (error); +} + +static int +uath_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + uath_init(ifp->if_softc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + uath_stop(ifp); + } + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + + return (error); +} + +static int +uath_tx_start(struct uath_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + struct uath_data *data) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct uath_chunk *chunk; + struct uath_tx_desc *desc; + const struct ieee80211_frame *wh; + struct ieee80211_key *k; + int framelen, msglen; + + UATH_ASSERT_LOCKED(sc); + + data->ni = ni; + data->m = m0; + chunk = (struct uath_chunk *)data->buf; + desc = (struct uath_tx_desc *)(chunk + 1); + + if (bpf_peers_present(ifp->if_bpf)) { + struct uath_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + if (m0->m_flags & M_FRAG) + tap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return (ENOBUFS); + } + + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(desc + 1)); + + framelen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; + msglen = framelen + sizeof (struct uath_tx_desc); + data->buflen = msglen + sizeof (struct uath_chunk); + + /* one chunk only for now */ + chunk->seqnum = sc->sc_seqnum++; + chunk->flags = (m0->m_flags & M_FRAG) ? 0 : UATH_CFLAGS_FINAL; + if (m0->m_flags & M_LASTFRAG) + chunk->flags |= UATH_CFLAGS_FINAL; + chunk->flags = UATH_CFLAGS_FINAL; + chunk->length = htobe16(msglen); + + /* fill Tx descriptor */ + desc->msglen = htobe32(msglen); + /* NB: to get UATH_TX_NOTIFY reply, `msgid' must be larger than 0 */ + desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ + desc->type = htobe32(WDCMSG_SEND); + switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { + case IEEE80211_FC0_TYPE_CTL: + case IEEE80211_FC0_TYPE_MGT: + /* NB: force all management frames to highest queue */ + if (ni->ni_flags & IEEE80211_NODE_QOS) { + /* NB: force all management frames to highest queue */ + desc->txqid = htobe32(WME_AC_VO | UATH_TXQID_MINRATE); + } else + desc->txqid = htobe32(WME_AC_BE | UATH_TXQID_MINRATE); + break; + case IEEE80211_FC0_TYPE_DATA: + /* XXX multicast frames should honor mcastrate */ + desc->txqid = htobe32(M_WME_GETAC(m0)); + break; + default: + device_printf(sc->sc_dev, "bogus frame type 0x%x (%s)\n", + wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__); + m_freem(m0); + return (EIO); + } + if (sc->sc_state == IEEE80211_S_AUTH || + sc->sc_state == IEEE80211_S_ASSOC || + sc->sc_state == IEEE80211_S_RUN) + desc->connid = htobe32(UATH_ID_BSS); + else + desc->connid = htobe32(UATH_ID_INVALID); + desc->flags = htobe32(0 /* no UATH_TX_NOTIFY */); + desc->buflen = htobe32(m0->m_pkthdr.len); + +#ifdef UATH_DEBUG + DPRINTF(sc, UATH_DEBUG_XMIT, + "send frame ix %u framelen %d msglen %d connid 0x%x txqid 0x%x\n", + desc->msgid, framelen, msglen, be32toh(desc->connid), + be32toh(desc->txqid)); + if (sc->sc_debug & UATH_DEBUG_XMIT_DUMP) + uath_dump_cmd(data->buf, data->buflen, '+'); +#endif + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + UATH_STAT_INC(sc, st_tx_pending); + usb2_transfer_start(sc->sc_xfer[UATH_BULK_TX]); + + return (0); +} + +/* + * Cleanup driver resources when we run out of buffers while processing + * fragments; return the tx buffers allocated and drop node references. + */ +static void +uath_txfrag_cleanup(struct uath_softc *sc, + uath_datahead *frags, struct ieee80211_node *ni) +{ + struct uath_data *bf, *next; + + UATH_ASSERT_LOCKED(sc); + + STAILQ_FOREACH_SAFE(bf, frags, next, next) { + /* NB: bf assumed clean */ + STAILQ_REMOVE_HEAD(frags, next); + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + ieee80211_node_decref(ni); + } +} + +/* + * Setup xmit of a fragmented frame. Allocate a buffer for each frag and bump + * the node reference count to reflect the held reference to be setup by + * uath_tx_start. + */ +static int +uath_txfrag_setup(struct uath_softc *sc, uath_datahead *frags, + struct mbuf *m0, struct ieee80211_node *ni) +{ + struct mbuf *m; + struct uath_data *bf; + + UATH_ASSERT_LOCKED(sc); + for (m = m0->m_nextpkt; m != NULL; m = m->m_nextpkt) { + bf = uath_getbuf(sc); + if (bf == NULL) { /* out of buffers, cleanup */ + uath_txfrag_cleanup(sc, frags, ni); + break; + } + ieee80211_node_incref(ni); + STAILQ_INSERT_TAIL(frags, bf, next); + } + + return !STAILQ_EMPTY(frags); +} + +/* + * Reclaim mbuf resources. For fragmented frames we need to claim each frag + * chained with m_nextpkt. + */ +static void +uath_freetx(struct mbuf *m) +{ + struct mbuf *next; + + do { + next = m->m_nextpkt; + m->m_nextpkt = NULL; + m_freem(m); + } while ((m = next) != NULL); +} + +static void +uath_start(struct ifnet *ifp) +{ + struct uath_data *bf; + struct uath_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m, *next; + uath_datahead frags; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || + (sc->sc_flags & UATH_FLAG_INVALID)) + return; + + UATH_LOCK(sc); + for (;;) { + bf = uath_getbuf(sc); + if (bf == NULL) + break; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + break; + } + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m->m_pkthdr.rcvif = NULL; + + /* + * Check for fragmentation. If this frame has been broken up + * verify we have enough buffers to send all the fragments + * so all go out or none... + */ + STAILQ_INIT(&frags); + if ((m->m_flags & M_FRAG) && + !uath_txfrag_setup(sc, &frags, m, ni)) { + DPRINTF(sc, UATH_DEBUG_XMIT, + "%s: out of txfrag buffers\n", __func__); + uath_freetx(m); + goto bad; + } + sc->sc_seqnum = 0; + nextfrag: + /* + * Pass the frame to the h/w for transmission. + * Fragmented frames have each frag chained together + * with m_nextpkt. We know there are sufficient uath_data's + * to send all the frags because of work done by + * uath_txfrag_setup. + */ + next = m->m_nextpkt; + if (uath_tx_start(sc, m, ni, bf) != 0) { + bad: + ifp->if_oerrors++; + reclaim: + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + uath_txfrag_cleanup(sc, &frags, ni); + ieee80211_free_node(ni); + continue; + } + + if (next != NULL) { + /* + * Beware of state changing between frags. + XXX check sta power-save state? + */ + if (ni->ni_vap->iv_state != IEEE80211_S_RUN) { + DPRINTF(sc, UATH_DEBUG_XMIT, + "%s: flush fragmented packet, state %s\n", + __func__, + ieee80211_state_name[ni->ni_vap->iv_state]); + uath_freetx(next); + goto reclaim; + } + m = next; + bf = STAILQ_FIRST(&frags); + KASSERT(bf != NULL, ("no buf for txfrag")); + STAILQ_REMOVE_HEAD(&frags, next); + goto nextfrag; + } + + sc->sc_tx_timer = 5; + } + UATH_UNLOCK(sc); +} + +static int +uath_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct uath_data *bf; + struct uath_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return (ENETDOWN); + } + + UATH_LOCK(sc); + /* grab a TX buffer */ + bf = uath_getbuf(sc); + if (bf == NULL) { + ieee80211_free_node(ni); + m_freem(m); + UATH_UNLOCK(sc); + return (ENOBUFS); + } + + sc->sc_seqnum = 0; + if (uath_tx_start(sc, m, ni, bf) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + UATH_UNLOCK(sc); + return (EIO); + } + UATH_UNLOCK(sc); + + sc->sc_tx_timer = 5; + return (0); +} + +static void +uath_scan_start(struct ieee80211com *ic) +{ + /* do nothing */ +} + +static void +uath_scan_end(struct ieee80211com *ic) +{ + /* do nothing */ +} + +static void +uath_set_channel(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + (void)uath_switch_channel(sc, ic->ic_curchan); + UATH_UNLOCK(sc); +} + +static int +uath_set_rxmulti_filter(struct uath_softc *sc) +{ + + return (0); +} +static void +uath_update_mcast(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + /* + * this is for avoiding the race condition when we're try to + * connect to the AP with WPA. + */ + if (!(sc->sc_flags & UATH_FLAG_INITDONE)) + return; + (void)uath_set_rxmulti_filter(sc); +} + +static void +uath_update_promisc(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + if (!(sc->sc_flags & UATH_FLAG_INITDONE)) + return; + uath_set_rxfilter(sc, + UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON | + UATH_FILTER_RX_PROM, UATH_FILTER_OP_SET); +} + +static int +uath_create_connection(struct uath_softc *sc, uint32_t connid) +{ + const struct ieee80211_rateset *rs; + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni = vap->iv_bss; + struct uath_cmd_create_connection create; + + bzero(&create, sizeof create); + create.connid = htobe32(connid); + create.bssid = htobe32(0); + /* XXX packed or not? */ + create.size = htobe32(sizeof(struct uath_cmd_rateset)); + + rs = &ni->ni_rates; + create.connattr.rateset.length = rs->rs_nrates; + bcopy(rs->rs_rates, &create.connattr.rateset.set[0], + rs->rs_nrates); + + /* XXX turbo */ + if (IEEE80211_IS_CHAN_A(ni->ni_chan)) + create.connattr.wlanmode = htobe32(WLAN_MODE_11a); + else if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) + create.connattr.wlanmode = htobe32(WLAN_MODE_11g); + else + create.connattr.wlanmode = htobe32(WLAN_MODE_11b); + + return uath_cmd_write(sc, WDCMSG_CREATE_CONNECTION, &create, + sizeof create, 0); +} + +static int +uath_set_rates(struct uath_softc *sc, const struct ieee80211_rateset *rs) +{ + struct uath_cmd_rates rates; + + bzero(&rates, sizeof rates); + rates.connid = htobe32(UATH_ID_BSS); /* XXX */ + rates.size = htobe32(sizeof(struct uath_cmd_rateset)); + /* XXX bounds check rs->rs_nrates */ + rates.rateset.length = rs->rs_nrates; + bcopy(rs->rs_rates, &rates.rateset.set[0], rs->rs_nrates); + + DPRINTF(sc, UATH_DEBUG_RATES, + "setting supported rates nrates=%d\n", rs->rs_nrates); + return uath_cmd_write(sc, WDCMSG_SET_BASIC_RATE, + &rates, sizeof rates, 0); +} + +static int +uath_write_associd(struct uath_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni = vap->iv_bss; + struct uath_cmd_set_associd associd; + + bzero(&associd, sizeof associd); + associd.defaultrateix = htobe32(1); /* XXX */ + associd.associd = htobe32(ni->ni_associd); + associd.timoffset = htobe32(0x3b); /* XXX */ + IEEE80211_ADDR_COPY(associd.bssid, ni->ni_bssid); + return uath_cmd_write(sc, WDCMSG_WRITE_ASSOCID, &associd, + sizeof associd, 0); +} + +static int +uath_set_ledsteady(struct uath_softc *sc, int lednum, int ledmode) +{ + struct uath_cmd_ledsteady led; + + led.lednum = htobe32(lednum); + led.ledmode = htobe32(ledmode); + + DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (steady)\n", + (lednum == UATH_LED_LINK) ? "link" : "activity", + ledmode ? "on" : "off"); + return uath_cmd_write(sc, WDCMSG_SET_LED_STEADY, &led, sizeof led, 0); +} + +static int +uath_set_ledblink(struct uath_softc *sc, int lednum, int ledmode, + int blinkrate, int slowmode) +{ + struct uath_cmd_ledblink led; + + led.lednum = htobe32(lednum); + led.ledmode = htobe32(ledmode); + led.blinkrate = htobe32(blinkrate); + led.slowmode = htobe32(slowmode); + + DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (blink)\n", + (lednum == UATH_LED_LINK) ? "link" : "activity", + ledmode ? "on" : "off"); + return uath_cmd_write(sc, WDCMSG_SET_LED_BLINK, &led, sizeof led, 0); +} + +static int +uath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + enum ieee80211_state ostate = vap->iv_state; + int error; + struct ieee80211_node *ni = vap->iv_bss; + struct ieee80211com *ic = vap->iv_ic; + struct uath_softc *sc = ic->ic_ifp->if_softc; + struct uath_vap *uvp = UATH_VAP(vap); + + DPRINTF(sc, UATH_DEBUG_STATE, + "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + UATH_LOCK(sc); + + callout_stop(&sc->stat_ch); + callout_stop(&sc->watchdog_ch); + sc->sc_state = nstate; + + switch (nstate) { + case IEEE80211_S_INIT: + if (ostate == IEEE80211_S_RUN) { + /* turn link and activity LEDs off */ + uath_set_ledstate(sc, 0); + } + break; + + case IEEE80211_S_SCAN: + break; + + case IEEE80211_S_AUTH: + /* XXX good place? set RTS threshold */ + uath_config(sc, CFG_USER_RTS_THRESHOLD, vap->iv_rtsthreshold); + /* XXX bad place */ + error = uath_set_keys(sc, vap); + if (error != 0) { + device_printf(sc->sc_dev, + "could not set crypto keys, error %d\n", error); + break; + } + if (uath_switch_channel(sc, ni->ni_chan) != 0) { + device_printf(sc->sc_dev, "could not switch channel\n"); + break; + } + if (uath_create_connection(sc, UATH_ID_BSS) != 0) { + device_printf(sc->sc_dev, + "could not create connection\n"); + break; + } + break; + + case IEEE80211_S_ASSOC: + if (uath_set_rates(sc, &ni->ni_rates) != 0) { + device_printf(sc->sc_dev, + "could not set negotiated rate set\n"); + break; + } + break; + + case IEEE80211_S_RUN: + /* XXX monitor mode doesn't be tested */ + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + uath_set_ledstate(sc, 1); + break; + } + + /* + * Tx rate is controlled by firmware, report the maximum + * negotiated rate in ifconfig output. + */ + ni->ni_txrate = ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates-1]; + + if (uath_write_associd(sc) != 0) { + device_printf(sc->sc_dev, + "could not write association id\n"); + break; + } + /* turn link LED on */ + uath_set_ledsteady(sc, UATH_LED_LINK, UATH_LED_ON); + /* make activity LED blink */ + uath_set_ledblink(sc, UATH_LED_ACTIVITY, UATH_LED_ON, 1, 2); + /* set state to associated */ + uath_set_ledstate(sc, 1); + + /* start statistics timer */ + callout_reset(&sc->stat_ch, hz, uath_stat, sc); + break; + default: + break; + } + UATH_UNLOCK(sc); + + IEEE80211_LOCK(ic); + uvp->newstate(vap, nstate, arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, nstate, arg); + IEEE80211_UNLOCK(ic); + + return (0); +} + +static int +uath_set_key(struct uath_softc *sc, const struct ieee80211_key *wk, + int index) +{ +#if 0 + struct uath_cmd_crypto crypto; + int i; + + bzero(&crypto, sizeof crypto); + crypto.keyidx = htobe32(index); + crypto.magic1 = htobe32(1); + crypto.size = htobe32(368); + crypto.mask = htobe32(0xffff); + crypto.flags = htobe32(0x80000068); + if (index != UATH_DEFAULT_KEY) + crypto.flags |= htobe32(index << 16); + memset(crypto.magic2, 0xff, sizeof crypto.magic2); + + /* + * Each byte of the key must be XOR'ed with 10101010 before being + * transmitted to the firmware. + */ + for (i = 0; i < wk->wk_keylen; i++) + crypto.key[i] = wk->wk_key[i] ^ 0xaa; + + DPRINTF(sc, UATH_DEBUG_CRYPTO, + "setting crypto key index=%d len=%d\n", index, wk->wk_keylen); + return uath_cmd_write(sc, WDCMSG_SET_KEY_CACHE_ENTRY, &crypto, + sizeof crypto, 0); +#else + /* XXX support H/W cryto */ + return (0); +#endif +} + +static int +uath_set_keys(struct uath_softc *sc, struct ieee80211vap *vap) +{ + int i, error; + + error = 0; + for (i = 0; i < IEEE80211_WEP_NKID; i++) { + const struct ieee80211_key *wk = &vap->iv_nw_keys[i]; + + if (wk->wk_flags & (IEEE80211_KEY_XMIT|IEEE80211_KEY_RECV)) { + error = uath_set_key(sc, wk, i); + if (error) + return (error); + } + } + if (vap->iv_def_txkey != IEEE80211_KEYIX_NONE) { + error = uath_set_key(sc, &vap->iv_nw_keys[vap->iv_def_txkey], + UATH_DEFAULT_KEY); + } + return (error); +} + +#define UATH_SYSCTL_STAT_ADD32(c, h, n, p, d) \ + SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) + +static void +uath_sysctl_node(struct uath_softc *sc) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *child; + struct sysctl_oid *tree; + struct uath_stat *stats; + + stats = &sc->sc_stat; + ctx = device_get_sysctl_ctx(sc->sc_dev); + child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); + + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, + NULL, "UATH statistics"); + child = SYSCTL_CHILDREN(tree); + UATH_SYSCTL_STAT_ADD32(ctx, child, "badchunkseqnum", + &stats->st_badchunkseqnum, "Bad chunk sequence numbers"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "invalidlen", &stats->st_invalidlen, + "Invalid length"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "multichunk", &stats->st_multichunk, + "Multi chunks"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "toobigrxpkt", + &stats->st_toobigrxpkt, "Too big rx packets"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "stopinprogress", + &stats->st_stopinprogress, "Stop in progress"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "crcerrs", &stats->st_crcerr, + "CRC errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "phyerr", &stats->st_phyerr, + "PHY errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_crcerr", + &stats->st_decrypt_crcerr, "Decryption CRC errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_micerr", + &stats->st_decrypt_micerr, "Decryption Misc errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decomperr", &stats->st_decomperr, + "Decomp errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "keyerr", &stats->st_keyerr, + "Key errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "err", &stats->st_err, + "Unknown errors"); + + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_active", + &stats->st_cmd_active, "Active numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_inactive", + &stats->st_cmd_inactive, "Inactive numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_pending", + &stats->st_cmd_pending, "Pending numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_waiting", + &stats->st_cmd_waiting, "Waiting numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_active", + &stats->st_rx_active, "Active numbers in RX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_inactive", + &stats->st_rx_inactive, "Inactive numbers in RX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_active", + &stats->st_tx_active, "Active numbers in TX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive", + &stats->st_tx_inactive, "Inactive numbers in TX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_pending", + &stats->st_tx_pending, "Pending numbers in TX queue"); +} + +#undef UATH_SYSCTL_STAT_ADD32 + +static void +uath_cmdeof(struct uath_softc *sc, struct uath_cmd *cmd) +{ + struct uath_cmd_hdr *hdr; + int dlen; + + hdr = (struct uath_cmd_hdr *)cmd->buf; + /* NB: msgid is passed thru w/o byte swapping */ +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + int len = be32toh(hdr->len); + printf("%s: %s [ix %u] len %u status %u\n", + __func__, uath_codename(be32toh(hdr->code)), + hdr->msgid, len, be32toh(hdr->magic)); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(cmd->buf, + len > UATH_MAX_CMDSZ ? sizeof(*hdr) : len, '-'); + } +#endif + hdr->code = be32toh(hdr->code); + hdr->len = be32toh(hdr->len); + hdr->magic = be32toh(hdr->magic); /* target status on return */ + + switch (hdr->code & 0xff) { + /* reply to a read command */ + default: + dlen = hdr->len - sizeof(*hdr); + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: code %d data len %u\n", + __func__, hdr->code & 0xff, dlen); + /* + * The first response from the target after the + * HOST_AVAILABLE has an invalid msgid so we must + * treat it specially. + */ + if (hdr->msgid < UATH_CMD_LIST_COUNT) { + uint32_t *rp = (uint32_t *)(hdr+1); + u_int olen; + + if (!(sizeof(*hdr) <= hdr->len && + hdr->len < UATH_MAX_CMDSZ)) { + device_printf(sc->sc_dev, + "%s: invalid WDC msg length %u; " + "msg ignored\n", __func__, hdr->len); + return; + } + /* + * Calculate return/receive payload size; the + * first word, if present, always gives the + * number of bytes--unless it's 0 in which + * case a single 32-bit word should be present. + */ + if (dlen >= sizeof(uint32_t)) { + olen = be32toh(rp[0]); + dlen -= sizeof(uint32_t); + if (olen == 0) { + /* convention is 0 =>'s one word */ + olen = sizeof(uint32_t); + /* XXX KASSERT(olen == dlen ) */ + } + } else + olen = 0; + if (cmd->odata != NULL) { + /* NB: cmd->olen validated in uath_cmd */ + if (olen > cmd->olen) { + /* XXX complain? */ + device_printf(sc->sc_dev, + "%s: cmd 0x%x olen %u cmd olen %u\n", + __func__, hdr->code, olen, + cmd->olen); + olen = cmd->olen; + } + if (olen > dlen) { + /* XXX complain, shouldn't happen */ + device_printf(sc->sc_dev, + "%s: cmd 0x%x olen %u dlen %u\n", + __func__, hdr->code, olen, dlen); + olen = dlen; + } + /* XXX have submitter do this */ + /* copy answer into caller's supplied buffer */ + bcopy(&rp[1], cmd->odata, olen); + cmd->olen = olen; + } + } + wakeup_one(cmd); /* wake up caller */ + break; + + case WDCMSG_TARGET_START: + if (hdr->msgid >= UATH_CMD_LIST_COUNT) { + /* XXX */ + return; + } + dlen = hdr->len - sizeof(*hdr); + if (dlen != sizeof(uint32_t)) { + /* XXX something wrong */ + return; + } + /* XXX have submitter do this */ + /* copy answer into caller's supplied buffer */ + bcopy(hdr+1, cmd->odata, sizeof(uint32_t)); + cmd->olen = sizeof(uint32_t); + wakeup_one(cmd); /* wake up caller */ + break; + + case WDCMSG_SEND_COMPLETE: + /* this notification is sent when UATH_TX_NOTIFY is set */ + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: received Tx notification\n", __func__); + break; + + case WDCMSG_TARGET_GET_STATS: + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: received device statistics\n", __func__); + callout_reset(&sc->stat_ch, hz, uath_stat, sc); + break; + } +} + +static void +uath_intr_rx_callback(struct usb2_xfer *xfer) +{ + struct uath_softc *sc = xfer->priv_sc; + struct uath_cmd *cmd; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + cmd = STAILQ_FIRST(&sc->sc_cmd_waiting); + if (cmd == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next); + UATH_STAT_DEC(sc, st_cmd_waiting); + STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); + UATH_STAT_INC(sc, st_cmd_inactive); + + KASSERT(xfer->actlen >= sizeof(struct uath_cmd_hdr), + ("short xfer error")); + usb2_copy_out(xfer->frbuffers, 0, cmd->buf, xfer->actlen); + uath_cmdeof(sc, cmd); + case USB_ST_SETUP: +setup: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + break; + default: + if (xfer->error != USB_ERR_CANCELLED) { + xfer->flags.stall_pipe = 1; + goto setup; + } + break; + } +} + +static void +uath_intr_tx_callback(struct usb2_xfer *xfer) +{ + struct uath_softc *sc = xfer->priv_sc; + struct uath_cmd *cmd; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + cmd = STAILQ_FIRST(&sc->sc_cmd_active); + if (cmd == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next); + UATH_STAT_DEC(sc, st_cmd_active); + STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_READ) ? + &sc->sc_cmd_waiting : &sc->sc_cmd_inactive, cmd, next); + if (cmd->flags & UATH_CMD_FLAG_READ) + UATH_STAT_INC(sc, st_cmd_waiting); + else + UATH_STAT_INC(sc, st_cmd_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + cmd = STAILQ_FIRST(&sc->sc_cmd_pending); + if (cmd == NULL) { + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", + __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next); + UATH_STAT_DEC(sc, st_cmd_pending); + STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_ASYNC) ? + &sc->sc_cmd_inactive : &sc->sc_cmd_active, cmd, next); + if (cmd->flags & UATH_CMD_FLAG_ASYNC) + UATH_STAT_INC(sc, st_cmd_inactive); + else + UATH_STAT_INC(sc, st_cmd_active); + + usb2_set_frame_data(xfer, cmd->buf, 0); + xfer->frlengths[0] = cmd->buflen; + usb2_start_hardware(xfer); + break; + default: + if (xfer->error != USB_ERR_CANCELLED) { + xfer->flags.stall_pipe = 1; + goto setup; + } + break; + } +} + +static void +uath_update_rxstat(struct uath_softc *sc, uint32_t status) +{ + + switch (status) { + case UATH_STATUS_STOP_IN_PROGRESS: + UATH_STAT_INC(sc, st_stopinprogress); + break; + case UATH_STATUS_CRC_ERR: + UATH_STAT_INC(sc, st_crcerr); + break; + case UATH_STATUS_PHY_ERR: + UATH_STAT_INC(sc, st_phyerr); + break; + case UATH_STATUS_DECRYPT_CRC_ERR: + UATH_STAT_INC(sc, st_decrypt_crcerr); + break; + case UATH_STATUS_DECRYPT_MIC_ERR: + UATH_STAT_INC(sc, st_decrypt_micerr); + break; + case UATH_STATUS_DECOMP_ERR: + UATH_STAT_INC(sc, st_decomperr); + break; + case UATH_STATUS_KEY_ERR: + UATH_STAT_INC(sc, st_keyerr); + break; + case UATH_STATUS_ERR: + UATH_STAT_INC(sc, st_err); + break; + default: + break; + } +} + +static struct mbuf * +uath_data_rxeof(struct usb2_xfer *xfer, struct uath_data *data, + struct uath_rx_desc **pdesc) +{ + struct uath_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct uath_chunk *chunk; + struct uath_rx_desc *desc; + struct mbuf *m = data->m, *mnew, *mp; + uint16_t chunklen; + + if (xfer->actlen < UATH_MIN_RXBUFSZ) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: wrong xfer size (len=%d)\n", __func__, xfer->actlen); + ifp->if_ierrors++; + return (NULL); + } + + chunk = (struct uath_chunk *)data->buf; + if (chunk->seqnum == 0 && chunk->flags == 0 && chunk->length == 0) { + device_printf(sc->sc_dev, "%s: strange response\n", __func__); + ifp->if_ierrors++; + UATH_RESET_INTRX(sc); + return (NULL); + } + + if (chunk->seqnum != sc->sc_intrx_nextnum) { + DPRINTF(sc, UATH_DEBUG_XMIT, "invalid seqnum %d, expected %d\n", + chunk->seqnum, sc->sc_intrx_nextnum); + UATH_STAT_INC(sc, st_badchunkseqnum); + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + /* check multi-chunk frames */ + if ((chunk->seqnum == 0 && !(chunk->flags & UATH_CFLAGS_FINAL)) || + (chunk->seqnum != 0 && (chunk->flags & UATH_CFLAGS_FINAL)) || + chunk->flags & UATH_CFLAGS_RXMSG) + UATH_STAT_INC(sc, st_multichunk); + + chunklen = be16toh(chunk->length); + if (chunk->flags & UATH_CFLAGS_FINAL) + chunklen -= sizeof(struct uath_rx_desc); + + if (chunklen > 0 && + (!(chunk->flags & UATH_CFLAGS_FINAL) || !(chunk->seqnum == 0))) { + /* we should use intermediate RX buffer */ + if (chunk->seqnum == 0) + UATH_RESET_INTRX(sc); + if ((sc->sc_intrx_len + sizeof(struct uath_rx_desc) + + chunklen) > UATH_MAX_INTRX_SIZE) { + UATH_STAT_INC(sc, st_invalidlen); + ifp->if_iqdrops++; + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + m->m_len = chunklen; + m->m_data += sizeof(struct uath_chunk); + + if (sc->sc_intrx_head == NULL) { + sc->sc_intrx_head = m; + sc->sc_intrx_tail = m; + } else { + m->m_flags &= ~M_PKTHDR; + sc->sc_intrx_tail->m_next = m; + sc->sc_intrx_tail = m; + } + } + sc->sc_intrx_len += chunklen; + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: can't get new mbuf, drop frame\n", __func__); + ifp->if_ierrors++; + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + data->m = mnew; + data->buf = mtod(mnew, uint8_t *); + + /* if the frame is not final continue the transfer */ + if (!(chunk->flags & UATH_CFLAGS_FINAL)) { + sc->sc_intrx_nextnum++; + UATH_RESET_INTRX(sc); + return (NULL); + } + + /* + * if the frame is not set UATH_CFLAGS_RXMSG, then rx descriptor is + * located at the end, 32-bit aligned + */ + desc = (chunk->flags & UATH_CFLAGS_RXMSG) ? + (struct uath_rx_desc *)(chunk + 1) : + (struct uath_rx_desc *)(((uint8_t *)chunk) + + sizeof(struct uath_chunk) + be16toh(chunk->length) - + sizeof(struct uath_rx_desc)); + *pdesc = desc; + + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: frame len %u code %u status %u rate %u antenna %u " + "rssi %d channel %u phyerror %u connix %u decrypterror %u " + "keycachemiss %u\n", __func__, be32toh(desc->framelen) + , be32toh(desc->code), be32toh(desc->status), be32toh(desc->rate) + , be32toh(desc->antenna), be32toh(desc->rssi), be32toh(desc->channel) + , be32toh(desc->phyerror), be32toh(desc->connix) + , be32toh(desc->decrypterror), be32toh(desc->keycachemiss)); + + if (be32toh(desc->len) > MCLBYTES) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: bad descriptor (len=%d)\n", __func__, + be32toh(desc->len)); + ifp->if_iqdrops++; + UATH_STAT_INC(sc, st_toobigrxpkt); + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + uath_update_rxstat(sc, be32toh(desc->status)); + + /* finalize mbuf */ + if (sc->sc_intrx_head == NULL) { + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = + be32toh(desc->framelen) - UATH_RX_DUMMYSIZE; + m->m_data += sizeof(struct uath_chunk); + } else { + mp = sc->sc_intrx_head; + mp->m_pkthdr.rcvif = ifp; + mp->m_flags |= M_PKTHDR; + mp->m_pkthdr.len = sc->sc_intrx_len; + m = mp; + } + + /* there are a lot more fields in the RX descriptor */ + if (bpf_peers_present(ifp->if_bpf)) { + struct uath_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_chan_freq = htole16(be32toh(desc->channel)); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_dbm_antsignal = (int8_t)be32toh(desc->rssi); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + + ifp->if_ipackets++; + UATH_RESET_INTRX(sc); + + return (m); +} + +static void +uath_bulk_rx_callback(struct usb2_xfer *xfer) +{ + struct uath_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct uath_data *data; + struct uath_rx_desc *desc = NULL; + int8_t nf; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + UATH_STAT_DEC(sc, st_rx_active); + m = uath_data_rxeof(xfer, data, &desc); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + UATH_STAT_INC(sc, st_rx_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_rx_inactive); + if (data == NULL) + return; + STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); + UATH_STAT_DEC(sc, st_rx_inactive); + STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); + UATH_STAT_INC(sc, st_rx_active); + usb2_set_frame_data(xfer, data->buf, 0); + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(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. + */ + UATH_UNLOCK(sc); + if (m != NULL && desc != NULL) { + wh = mtod(m, struct ieee80211_frame *); + ni = ieee80211_find_rxnode(ic, + (struct ieee80211_frame_min *)wh); + nf = -95; /* XXX */ + if (ni != NULL) { + (void) ieee80211_input(ni, m, + (int)be32toh(desc->rssi), nf, 0); + /* node is no longer needed */ + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, + (int)be32toh(desc->rssi), nf, 0); + m = NULL; + desc = NULL; + } + UATH_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); + UATH_STAT_DEC(sc, st_rx_active); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + UATH_STAT_INC(sc, st_rx_inactive); + } + if (xfer->error != USB_ERR_CANCELLED) { + xfer->flags.stall_pipe = 1; + ifp->if_ierrors++; + goto setup; + } + break; + } +} + +static void +uath_data_txeof(struct usb2_xfer *xfer, struct uath_data *data) +{ + struct uath_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + UATH_ASSERT_LOCKED(sc); + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + if (data->m) { + m = data->m; + if (m->m_flags & M_TXCB) { + /* XXX status? */ + ieee80211_process_callback(data->ni, m, 0); + } + m_freem(m); + data->m = NULL; + } + if (data->ni) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + sc->sc_tx_timer = 0; + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +static void +uath_bulk_tx_callback(struct usb2_xfer *xfer) +{ + struct uath_softc *sc = xfer->priv_sc; + struct ifnet *ifp = sc->sc_ifp; + struct uath_data *data; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); + UATH_STAT_DEC(sc, st_tx_active); + uath_data_txeof(xfer, data); + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); + UATH_STAT_INC(sc, st_tx_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_tx_pending); + if (data == NULL) { + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", + __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); + UATH_STAT_DEC(sc, st_tx_pending); + STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); + UATH_STAT_INC(sc, st_tx_active); + + usb2_set_frame_data(xfer, data->buf, 0); + xfer->frlengths[0] = data->buflen; + usb2_start_hardware(xfer); + + UATH_UNLOCK(sc); + uath_start(ifp); + UATH_LOCK(sc); + break; + default: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + ifp->if_oerrors++; + } + if (xfer->error != USB_ERR_CANCELLED) { + xfer->flags.stall_pipe = 1; + goto setup; + } + break; + } +} + +static device_method_t uath_methods[] = { + DEVMETHOD(device_probe, uath_match), + DEVMETHOD(device_attach, uath_attach), + DEVMETHOD(device_detach, uath_detach), + { 0, 0 } +}; +static driver_t uath_driver = { + "uath", + uath_methods, + sizeof(struct uath_softc) +}; +static devclass_t uath_devclass; + +DRIVER_MODULE(uath, uhub, uath_driver, uath_devclass, NULL, 0); +MODULE_DEPEND(uath, wlan, 1, 1, 1); +MODULE_DEPEND(uath, usb, 1, 1, 1); |