| /* |
| * Copyright 2010 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * 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/types.h> |
| #include <sys/cmn_err.h> |
| #include <sys/strsubr.h> |
| #include <sys/strsun.h> |
| #include <sys/modctl.h> |
| #include <sys/devops.h> |
| #include <sys/byteorder.h> |
| #include <sys/mac_provider.h> |
| #include <sys/mac_wifi.h> |
| #include <sys/net80211.h> |
| |
| #define USBDRV_MAJOR_VER 2 |
| #define USBDRV_MINOR_VER 0 |
| #include <sys/usb/usba.h> |
| #include <sys/usb/usba/usba_types.h> |
| |
| #include "uath_reg.h" |
| #include "uath_var.h" |
| |
| static void *uath_soft_state_p = NULL; |
| |
| /* |
| * Bit flags in the ral_dbg_flags |
| */ |
| #define UATH_DBG_MSG 0x000001 |
| #define UATH_DBG_ERR 0x000002 |
| #define UATH_DBG_USB 0x000004 |
| #define UATH_DBG_TX 0x000008 |
| #define UATH_DBG_RX 0x000010 |
| #define UATH_DBG_FW 0x000020 |
| #define UATH_DBG_TX_CMD 0x000040 |
| #define UATH_DBG_RX_CMD 0x000080 |
| #define UATH_DBG_ALL 0x000fff |
| |
| uint32_t uath_dbg_flags = 0; |
| |
| #ifdef DEBUG |
| #define UATH_DEBUG \ |
| uath_debug |
| #else |
| #define UATH_DEBUG |
| #endif |
| |
| /* |
| * Various supported device vendors/products. |
| * UB51: AR5005UG 802.11b/g, UB52: AR5005UX 802.11b/g |
| */ |
| #define UATH_FLAG_PRE_FIRMWARE (1 << 0) |
| #define UATH_FLAG_ABG (1 << 1) |
| #define UATH_FLAG_ERR (1 << 2) |
| #define UATH_DEV(v, p, f) \ |
| { { USB_VENDOR_##v, USB_PRODUCT_##v##_##p }, (f) }, \ |
| { { USB_VENDOR_##v, USB_PRODUCT_##v##_##p##_NF }, \ |
| (f) | UATH_FLAG_PRE_FIRMWARE } |
| #define UATH_DEV_UG(v, p) UATH_DEV(v, p, 0) |
| #define UATH_DEV_UX(v, p) UATH_DEV(v, p, UATH_FLAG_ABG) |
| |
| struct uath_devno { |
| uint16_t vendor_id; |
| uint16_t product_id; |
| }; |
| |
| static const struct uath_type { |
| struct uath_devno dev; |
| uint8_t flags; |
| } uath_devs[] = { |
| UATH_DEV_UG(ACCTON, SMCWUSBTG2), |
| UATH_DEV_UG(ATHEROS, AR5523), |
| UATH_DEV_UG(ATHEROS2, AR5523_1), |
| UATH_DEV_UG(ATHEROS2, AR5523_2), |
| UATH_DEV_UX(ATHEROS2, AR5523_3), |
| UATH_DEV_UG(CONCEPTRONIC, AR5523_1), |
| UATH_DEV_UX(CONCEPTRONIC, AR5523_2), |
| UATH_DEV_UX(DLINK, DWLAG122), |
| UATH_DEV_UX(DLINK, DWLAG132), |
| UATH_DEV_UG(DLINK, DWLG132), |
| UATH_DEV_UG(GIGASET, AR5523), |
| UATH_DEV_UG(GIGASET, SMCWUSBTG), |
| UATH_DEV_UG(GLOBALSUN, AR5523_1), |
| UATH_DEV_UX(GLOBALSUN, AR5523_2), |
| UATH_DEV_UG(IODATA, USBWNG54US), |
| UATH_DEV_UG(MELCO, WLIU2KAMG54), |
| UATH_DEV_UX(NETGEAR, WG111U), |
| UATH_DEV_UG(NETGEAR3, WG111T), |
| UATH_DEV_UG(NETGEAR3, WPN111), |
| UATH_DEV_UG(PHILIPS, SNU6500), |
| UATH_DEV_UX(UMEDIA, AR5523_2), |
| UATH_DEV_UG(UMEDIA, TEW444UBEU), |
| UATH_DEV_UG(WISTRONNEWEB, AR5523_1), |
| UATH_DEV_UX(WISTRONNEWEB, AR5523_2), |
| UATH_DEV_UG(ZCOM, AR5523) |
| }; |
| |
| static char uath_fwmod[] = "uathfw"; |
| static char uath_binmod[] = "uathbin"; |
| |
| /* |
| * Supported rates for 802.11b/g modes (in 500Kbps unit). |
| */ |
| static const struct ieee80211_rateset uath_rateset_11b = |
| { 4, { 2, 4, 11, 22 } }; |
| |
| static const struct ieee80211_rateset uath_rateset_11g = |
| { 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } }; |
| |
| /* |
| * device operations |
| */ |
| static int uath_attach(dev_info_t *, ddi_attach_cmd_t); |
| static int uath_detach(dev_info_t *, ddi_detach_cmd_t); |
| |
| /* |
| * Module Loading Data & Entry Points |
| */ |
| DDI_DEFINE_STREAM_OPS(uath_dev_ops, nulldev, nulldev, uath_attach, |
| uath_detach, nodev, NULL, D_MP, NULL, ddi_quiesce_not_needed); |
| |
| static struct modldrv uath_modldrv = { |
| &mod_driverops, /* Type of module. This one is a driver */ |
| "Atheros AR5523 USB Driver v1.1", /* short description */ |
| &uath_dev_ops /* driver specific ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, |
| (void *)&uath_modldrv, |
| NULL |
| }; |
| |
| static int uath_m_stat(void *, uint_t, uint64_t *); |
| static int uath_m_start(void *); |
| static void uath_m_stop(void *); |
| static int uath_m_promisc(void *, boolean_t); |
| static int uath_m_multicst(void *, boolean_t, const uint8_t *); |
| static int uath_m_unicst(void *, const uint8_t *); |
| static mblk_t *uath_m_tx(void *, mblk_t *); |
| static void uath_m_ioctl(void *, queue_t *, mblk_t *); |
| static int uath_m_setprop(void *, const char *, mac_prop_id_t, |
| uint_t, const void *); |
| static int uath_m_getprop(void *, const char *, mac_prop_id_t, |
| uint_t, void *); |
| static void uath_m_propinfo(void *, const char *, mac_prop_id_t, |
| mac_prop_info_handle_t); |
| |
| static mac_callbacks_t uath_m_callbacks = { |
| MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO, |
| uath_m_stat, |
| uath_m_start, |
| uath_m_stop, |
| uath_m_promisc, |
| uath_m_multicst, |
| uath_m_unicst, |
| uath_m_tx, |
| NULL, |
| uath_m_ioctl, |
| NULL, |
| NULL, |
| NULL, |
| uath_m_setprop, |
| uath_m_getprop, |
| uath_m_propinfo |
| }; |
| |
| static usb_alt_if_data_t * |
| uath_lookup_alt_if(usb_client_dev_data_t *, |
| uint_t, uint_t, uint_t); |
| static usb_ep_data_t * |
| uath_lookup_ep_data(dev_info_t *, |
| usb_client_dev_data_t *, uint_t, uint_t, uint8_t, uint8_t); |
| static const char * |
| uath_codename(int code); |
| |
| static uint_t uath_lookup(uint16_t, uint16_t); |
| static void uath_list_all_eps(usb_alt_if_data_t *); |
| static int uath_open_pipes(struct uath_softc *); |
| static void uath_close_pipes(struct uath_softc *); |
| static int uath_fw_send(struct uath_softc *, |
| usb_pipe_handle_t, const void *, size_t); |
| static int uath_fw_ack(struct uath_softc *, int); |
| static int uath_loadsym(ddi_modhandle_t, char *, char **, size_t *); |
| static int uath_loadfirmware(struct uath_softc *); |
| static int uath_alloc_cmd_list(struct uath_softc *, |
| struct uath_cmd *, int, int); |
| static int uath_init_cmd_list(struct uath_softc *); |
| static void uath_free_cmd_list(struct uath_cmd *, int); |
| static int uath_host_available(struct uath_softc *); |
| static void uath_get_capability(struct uath_softc *, uint32_t, uint32_t *); |
| static int uath_get_devcap(struct uath_softc *); |
| static int uath_get_devstatus(struct uath_softc *, uint8_t *); |
| static int uath_get_status(struct uath_softc *, uint32_t, void *, int); |
| |
| static void uath_cmd_lock_init(struct uath_cmd_lock *); |
| static void uath_cmd_lock_destroy(struct uath_cmd_lock *); |
| static int uath_cmd_lock_wait(struct uath_cmd_lock *, clock_t); |
| static void uath_cmd_lock_signal(struct uath_cmd_lock *); |
| |
| 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 int uath_cmdsend(struct uath_softc *, uint32_t, |
| const void *, int, void *, int, int); |
| static int uath_rx_cmd_xfer(struct uath_softc *); |
| static int uath_tx_cmd_xfer(struct uath_softc *, |
| usb_pipe_handle_t, const void *, uint_t); |
| static void uath_cmd_txeof(usb_pipe_handle_t, struct usb_bulk_req *); |
| static void uath_cmd_rxeof(usb_pipe_handle_t, usb_bulk_req_t *); |
| static void uath_cmdeof(struct uath_softc *, struct uath_cmd *); |
| |
| static void uath_init_data_queue(struct uath_softc *); |
| static int uath_rx_data_xfer(struct uath_softc *sc); |
| static int uath_tx_data_xfer(struct uath_softc *, mblk_t *); |
| static void uath_data_txeof(usb_pipe_handle_t, usb_bulk_req_t *); |
| static void uath_data_rxeof(usb_pipe_handle_t, usb_bulk_req_t *); |
| |
| static int uath_create_connection(struct uath_softc *, uint32_t); |
| static int uath_set_rates(struct uath_softc *, |
| const struct ieee80211_rateset *); |
| static int uath_write_associd(struct uath_softc *); |
| static int uath_set_ledsteady(struct uath_softc *, int, int); |
| static int uath_set_ledblink(struct uath_softc *, int, int, int, int); |
| static void uath_update_rxstat(struct uath_softc *, uint32_t); |
| static int uath_send(ieee80211com_t *, mblk_t *, uint8_t); |
| static int uath_reconnect(dev_info_t *); |
| static int uath_disconnect(dev_info_t *); |
| static int uath_newstate(struct ieee80211com *, enum ieee80211_state, int); |
| |
| 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 int uath_config_multi(struct uath_softc *, |
| uint32_t, const void *, int); |
| static void uath_config(struct uath_softc *, uint32_t, uint32_t); |
| static int uath_switch_channel(struct uath_softc *, |
| struct ieee80211_channel *); |
| static int uath_set_rxfilter(struct uath_softc *, uint32_t, uint32_t); |
| static int uath_init_locked(void *); |
| static void uath_stop_locked(void *); |
| static int uath_init(struct uath_softc *); |
| static void uath_stop(struct uath_softc *); |
| static void uath_resume(struct uath_softc *); |
| |
| static void |
| uath_debug(uint32_t dbg_flags, const int8_t *fmt, ...) |
| { |
| va_list args; |
| |
| if (dbg_flags & uath_dbg_flags) { |
| va_start(args, fmt); |
| vcmn_err(CE_CONT, fmt, args); |
| va_end(args); |
| } |
| } |
| |
| static uint_t |
| uath_lookup(uint16_t vendor_id, uint16_t product_id) |
| { |
| int i, size; |
| |
| size = sizeof (uath_devs) / sizeof (struct uath_type); |
| |
| for (i = 0; i < size; i++) { |
| if ((vendor_id == uath_devs[i].dev.vendor_id) && |
| (product_id == uath_devs[i].dev.product_id)) |
| return (uath_devs[i].flags); |
| } |
| return (UATH_FLAG_ERR); |
| } |
| |
| /* |
| * Return a specific alt_if from the device descriptor tree. |
| */ |
| static usb_alt_if_data_t * |
| uath_lookup_alt_if(usb_client_dev_data_t *dev_data, uint_t config, |
| uint_t interface, uint_t alt) |
| { |
| usb_cfg_data_t *cfg_data; |
| usb_if_data_t *if_data; |
| usb_alt_if_data_t *if_alt_data; |
| |
| /* |
| * Assume everything is in the tree for now, |
| * (USB_PARSE_LVL_ALL) |
| * so we can directly index the array. |
| */ |
| |
| /* Descend to configuration, configs are 1-based */ |
| if (config < 1 || config > dev_data->dev_n_cfg) |
| return (NULL); |
| cfg_data = &dev_data->dev_cfg[config - 1]; |
| |
| /* Descend to interface */ |
| if (interface > cfg_data->cfg_n_if - 1) |
| return (NULL); |
| if_data = &cfg_data->cfg_if[interface]; |
| |
| /* Descend to alt */ |
| if (alt > if_data->if_n_alt - 1) |
| return (NULL); |
| if_alt_data = &if_data->if_alt[alt]; |
| |
| return (if_alt_data); |
| } |
| |
| /* |
| * Print all endpoints of an alt_if. |
| */ |
| static void |
| uath_list_all_eps(usb_alt_if_data_t *ifalt) |
| { |
| usb_ep_data_t *ep_data; |
| usb_ep_descr_t *ep_descr; |
| int i; |
| |
| for (i = 0; i < ifalt->altif_n_ep; i++) { |
| ep_data = &ifalt->altif_ep[i]; |
| ep_descr = &ep_data->ep_descr; |
| UATH_DEBUG(UATH_DBG_USB, |
| "uath: uath_list_all_endpoint: " |
| "ep addresa[%x] is %x", |
| i, ep_descr->bEndpointAddress); |
| } |
| } |
| |
| static usb_ep_data_t * |
| uath_lookup_ep_data(dev_info_t *dip, |
| usb_client_dev_data_t *dev_datap, |
| uint_t interface, |
| uint_t alternate, |
| uint8_t address, |
| uint8_t type) |
| { |
| usb_alt_if_data_t *altif_data; |
| int i; |
| |
| if ((dip == NULL) || (dev_datap == NULL)) |
| return (NULL); |
| |
| altif_data = &dev_datap->dev_curr_cfg-> |
| cfg_if[interface].if_alt[alternate]; |
| |
| for (i = 0; i < altif_data->altif_n_ep; i++) { |
| usb_ep_descr_t *ept = &altif_data->altif_ep[i].ep_descr; |
| uint8_t ept_type = ept->bmAttributes & USB_EP_ATTR_MASK; |
| uint8_t ept_address = ept->bEndpointAddress; |
| |
| if (ept->bLength == 0) |
| continue; |
| if ((ept_type == type) && |
| ((type == USB_EP_ATTR_CONTROL) || (address == ept_address))) |
| return (&altif_data->altif_ep[i]); |
| } |
| return (NULL); |
| } |
| |
| /* |
| * Open communication pipes. |
| * The following pipes are used by the AR5523: |
| * ep0: 0x81 IN Rx cmd |
| * ep1: 0x01 OUT Tx cmd |
| * ep2: 0x82 IN Rx data |
| * ep3: 0x02 OUT Tx data |
| */ |
| static int |
| uath_open_pipes(struct uath_softc *sc) |
| { |
| usb_ep_data_t *ep_node; |
| usb_ep_descr_t *ep_descr; |
| usb_pipe_policy_t policy; |
| int err; |
| |
| #ifdef DEBUG |
| usb_alt_if_data_t *altif_data; |
| |
| altif_data = uath_lookup_alt_if(sc->sc_udev, UATH_CONFIG_NO, |
| UATH_IFACE_INDEX, UATH_ALT_IF_INDEX); |
| if (altif_data == NULL) { |
| UATH_DEBUG(UATH_DBG_ERR, "alt_if not found"); |
| return (USB_FAILURE); |
| } |
| |
| uath_list_all_eps(altif_data); |
| #endif |
| |
| /* |
| * XXX pipes numbers are hardcoded because we don't have any way |
| * to distinguish the data pipes from the firmware command pipes |
| * (both are bulk pipes) using the endpoints descriptors. |
| */ |
| ep_node = uath_lookup_ep_data(sc->sc_dev, sc->sc_udev, |
| 0, 0, 0x81, USB_EP_ATTR_BULK); |
| ep_descr = &ep_node->ep_descr; |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_open_pipes(): " |
| "find pipe %x\n", ep_descr->bEndpointAddress); |
| |
| bzero(&policy, sizeof (usb_pipe_policy_t)); |
| policy.pp_max_async_reqs = UATH_CMD_LIST_COUNT; |
| |
| err = usb_pipe_open(sc->sc_dev, &ep_node->ep_descr, |
| &policy, USB_FLAGS_SLEEP, &sc->rx_cmd_pipe); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "failed to open rx data pipe, err = %x\n", |
| err); |
| goto fail; |
| } |
| |
| |
| ep_node = uath_lookup_ep_data(sc->sc_dev, sc->sc_udev, |
| 0, 0, 0x01, USB_EP_ATTR_BULK); |
| ep_descr = &ep_node->ep_descr; |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "find pipe %x\n", |
| ep_descr->bEndpointAddress); |
| |
| bzero(&policy, sizeof (usb_pipe_policy_t)); |
| policy.pp_max_async_reqs = UATH_CMD_LIST_COUNT; |
| |
| err = usb_pipe_open(sc->sc_dev, &ep_node->ep_descr, |
| &policy, USB_FLAGS_SLEEP, &sc->tx_cmd_pipe); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "failed to open tx command pipe, err = %x\n", |
| err); |
| goto fail; |
| } |
| |
| ep_node = uath_lookup_ep_data(sc->sc_dev, sc->sc_udev, |
| 0, 0, 0x82, USB_EP_ATTR_BULK); |
| ep_descr = &ep_node->ep_descr; |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "find pipe %x\n", |
| ep_descr->bEndpointAddress); |
| |
| bzero(&policy, sizeof (usb_pipe_policy_t)); |
| policy.pp_max_async_reqs = UATH_RX_DATA_LIST_COUNT; |
| |
| err = usb_pipe_open(sc->sc_dev, &ep_node->ep_descr, |
| &policy, USB_FLAGS_SLEEP, &sc->rx_data_pipe); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "failed to open tx pipe, err = %x\n", |
| err); |
| goto fail; |
| } |
| |
| ep_node = uath_lookup_ep_data(sc->sc_dev, sc->sc_udev, |
| 0, 0, 0x02, USB_EP_ATTR_BULK); |
| ep_descr = &ep_node->ep_descr; |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "find pipe %x\n", |
| ep_descr->bEndpointAddress); |
| |
| bzero(&policy, sizeof (usb_pipe_policy_t)); |
| policy.pp_max_async_reqs = UATH_TX_DATA_LIST_COUNT; |
| |
| err = usb_pipe_open(sc->sc_dev, &ep_node->ep_descr, |
| &policy, USB_FLAGS_SLEEP, &sc->tx_data_pipe); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_open_pipes(): " |
| "failed to open rx command pipe, err = %x\n", |
| err); |
| goto fail; |
| } |
| |
| return (UATH_SUCCESS); |
| fail: |
| uath_close_pipes(sc); |
| return (err); |
| } |
| |
| static void |
| uath_close_pipes(struct uath_softc *sc) |
| { |
| usb_flags_t flags = USB_FLAGS_SLEEP; |
| |
| if (sc->rx_cmd_pipe != NULL) { |
| usb_pipe_reset(sc->sc_dev, sc->rx_cmd_pipe, flags, NULL, 0); |
| usb_pipe_close(sc->sc_dev, sc->rx_cmd_pipe, flags, NULL, 0); |
| sc->rx_cmd_pipe = NULL; |
| } |
| |
| if (sc->tx_cmd_pipe != NULL) { |
| usb_pipe_reset(sc->sc_dev, sc->tx_cmd_pipe, flags, NULL, 0); |
| usb_pipe_close(sc->sc_dev, sc->tx_cmd_pipe, flags, NULL, 0); |
| sc->tx_cmd_pipe = NULL; |
| } |
| |
| if (sc->rx_data_pipe != NULL) { |
| usb_pipe_reset(sc->sc_dev, sc->rx_data_pipe, flags, NULL, 0); |
| usb_pipe_close(sc->sc_dev, sc->rx_data_pipe, flags, NULL, 0); |
| sc->rx_data_pipe = NULL; |
| } |
| |
| if (sc->tx_data_pipe != NULL) { |
| usb_pipe_reset(sc->sc_dev, sc->tx_data_pipe, flags, NULL, 0); |
| usb_pipe_close(sc->sc_dev, sc->tx_data_pipe, flags, NULL, 0); |
| sc->tx_data_pipe = NULL; |
| } |
| |
| } |
| |
| 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"); |
| |
| (void) snprintf(buf, sizeof (buf), "0x%02x", code); |
| return (buf); |
| #undef N |
| } |
| |
| static int |
| uath_fw_send(struct uath_softc *sc, usb_pipe_handle_t pipe, |
| const void *data, size_t len) |
| { |
| usb_bulk_req_t *send_req; |
| mblk_t *mblk; |
| int res; |
| |
| send_req = usb_alloc_bulk_req(sc->sc_dev, len, USB_FLAGS_SLEEP); |
| |
| send_req->bulk_len = (int)len; |
| send_req->bulk_attributes = USB_ATTRS_AUTOCLEARING; |
| send_req->bulk_timeout = UATH_CMD_TIMEOUT; |
| |
| mblk = send_req->bulk_data; |
| bcopy(data, mblk->b_wptr, len); |
| mblk->b_wptr += len; |
| |
| res = usb_pipe_bulk_xfer(pipe, send_req, USB_FLAGS_SLEEP); |
| if (res != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_fw_send(): " |
| "Error %x writing data to bulk/out pipe", res); |
| return (UATH_FAILURE); |
| } |
| |
| usb_free_bulk_req(send_req); |
| return (UATH_SUCCESS); |
| } |
| |
| static int |
| uath_fw_ack(struct uath_softc *sc, int len) |
| { |
| struct uath_fwblock *rxblock; |
| usb_bulk_req_t *req; |
| mblk_t *mp; |
| int err; |
| |
| req = usb_alloc_bulk_req(sc->sc_dev, len, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| UATH_DEBUG(UATH_DBG_FW, |
| "uath: uath_fw_ack(): " |
| "uath_rx_transfer(): failed to allocate req"); |
| return (UATH_FAILURE); |
| } |
| |
| req->bulk_len = len; |
| req->bulk_client_private = (usb_opaque_t)sc; |
| req->bulk_timeout = 0; |
| req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK |
| | USB_ATTRS_AUTOCLEARING; |
| |
| err = usb_pipe_bulk_xfer(sc->rx_cmd_pipe, req, USB_FLAGS_SLEEP); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_fw_ack(): " |
| "failed to do rx xfer, %d", err); |
| usb_free_bulk_req(req); |
| return (UATH_FAILURE); |
| } |
| |
| mp = req->bulk_data; |
| req->bulk_data = NULL; |
| |
| rxblock = (struct uath_fwblock *)mp->b_rptr; |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_fw_ack() " |
| "rxblock flags=0x%x total=%d\n", |
| BE_32(rxblock->flags), BE_32(rxblock->rxtotal)); |
| |
| freemsg(mp); |
| usb_free_bulk_req(req); |
| |
| return (UATH_SUCCESS); |
| } |
| |
| /* |
| * find uath firmware module's "_start" "_end" symbols |
| * and get its size. |
| */ |
| static int |
| uath_loadsym(ddi_modhandle_t modp, char *sym, char **start, size_t *len) |
| { |
| char start_sym[64]; |
| char end_sym[64]; |
| char *p, *end; |
| int rv; |
| size_t n; |
| |
| (void) snprintf(start_sym, sizeof (start_sym), "%s_start", sym); |
| (void) snprintf(end_sym, sizeof (end_sym), "%s_end", sym); |
| |
| p = (char *)ddi_modsym(modp, start_sym, &rv); |
| if (p == NULL || rv != 0) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_loadsym(): " |
| "mod %s: symbol %s not found\n", uath_fwmod, start_sym); |
| return (UATH_FAILURE); |
| } |
| |
| end = (char *)ddi_modsym(modp, end_sym, &rv); |
| if (end == NULL || rv != 0) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_loadsym(): " |
| "mod %s: symbol %s not found\n", uath_fwmod, end_sym); |
| return (UATH_FAILURE); |
| } |
| |
| n = _PTRDIFF(end, p); |
| *start = p; |
| *len = n; |
| |
| return (UATH_SUCCESS); |
| } |
| |
| /* |
| * Load the MIPS R4000 microcode into the device. Once the image is loaded, |
| * the device will detach itself from the bus and reattach later with a new |
| * product Id (a la ezusb). XXX this could also be implemented in userland |
| * through /dev/ugen. |
| */ |
| static int |
| uath_loadfirmware(struct uath_softc *sc) |
| { |
| struct uath_fwblock txblock; |
| ddi_modhandle_t modp; |
| char *fw_index, *fw_image = NULL; |
| size_t fw_size, len; |
| int err = DDI_SUCCESS, rv = 0; |
| |
| modp = ddi_modopen(uath_fwmod, KRTLD_MODE_FIRST, &rv); |
| if (modp == NULL) { |
| cmn_err(CE_WARN, "uath: uath_loadfirmware(): " |
| "module %s not found\n", uath_fwmod); |
| goto label; |
| } |
| |
| err = uath_loadsym(modp, uath_binmod, &fw_index, &fw_size); |
| if (err != UATH_SUCCESS) { |
| cmn_err(CE_WARN, "uath: uath_loadfirmware(): " |
| "could not get firmware\n"); |
| goto label; |
| } |
| |
| fw_image = (char *)kmem_alloc(fw_size, KM_SLEEP); |
| if (fw_image == NULL) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_loadfirmware(): " |
| "failed to alloc firmware memory\n"); |
| err = UATH_FAILURE; |
| goto label; |
| } |
| |
| (void) memcpy(fw_image, fw_index, fw_size); |
| fw_index = fw_image; |
| len = fw_size; |
| UATH_DEBUG(UATH_DBG_MSG, "loading firmware size = %lu\n", fw_size); |
| |
| /* bzero(txblock, sizeof (struct uath_fwblock)); */ |
| txblock.flags = BE_32(UATH_WRITE_BLOCK); |
| txblock.total = BE_32(fw_size); |
| |
| while (len > 0) { |
| size_t mlen = min(len, UATH_MAX_FWBLOCK_SIZE); |
| |
| txblock.remain = BE_32(len - mlen); |
| txblock.len = BE_32(mlen); |
| |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_loadfirmware(): " |
| "sending firmware block: %d bytes sending\n", mlen); |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_loadfirmware(): " |
| "sending firmware block: %d bytes remaining\n", |
| len - mlen); |
| |
| /* send firmware block meta-data */ |
| err = uath_fw_send(sc, sc->tx_cmd_pipe, &txblock, |
| sizeof (struct uath_fwblock)); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_loadfirmware(): " |
| "send block meta-data error"); |
| goto label; |
| } |
| |
| /* send firmware block data */ |
| err = uath_fw_send(sc, sc->tx_data_pipe, fw_index, mlen); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_loadfirmware() " |
| "send block data err"); |
| goto label; |
| } |
| |
| /* wait for ack from firmware */ |
| err = uath_fw_ack(sc, sizeof (struct uath_fwblock)); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_FW, "uath: uath_loadfirmware() " |
| "rx block ack err"); |
| goto label; |
| } |
| |
| fw_index += mlen; |
| len -= mlen; |
| } |
| |
| label: |
| if (fw_image != NULL) |
| kmem_free(fw_image, fw_size); |
| fw_image = fw_index = NULL; |
| if (modp != NULL) |
| (void) ddi_modclose(modp); |
| return (err); |
| } |
| |
| static int |
| uath_alloc_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[], |
| int ncmd, int maxsz) |
| { |
| int i, err; |
| |
| for (i = 0; i < ncmd; i++) { |
| struct uath_cmd *cmd = &cmds[i]; |
| |
| cmd->sc = sc; /* backpointer for callbacks */ |
| cmd->msgid = i; |
| cmd->buf = kmem_zalloc(maxsz, KM_NOSLEEP); |
| if (cmd->buf == NULL) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_alloc_cmd_list(): " |
| "could not allocate xfer buffer\n"); |
| err = DDI_ENOMEM; |
| goto fail; |
| } |
| } |
| return (UATH_SUCCESS); |
| |
| fail: |
| uath_free_cmd_list(cmds, ncmd); |
| return (err); |
| } |
| |
| static int |
| uath_init_cmd_list(struct uath_softc *sc) |
| { |
| int i; |
| |
| sc->sc_cmdid = sc->rx_cmd_queued = sc->tx_cmd_queued = 0; |
| for (i = 0; i < UATH_CMD_LIST_COUNT; i++) { |
| if (uath_rx_cmd_xfer(sc) != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_init_cmd_list(): " |
| "failed to init cmd list %x\n", i); |
| return (UATH_FAILURE); |
| } |
| } |
| return (UATH_SUCCESS); |
| } |
| |
| static void |
| uath_free_cmd_list(struct uath_cmd cmds[], int ncmd) |
| { |
| int i; |
| |
| for (i = 0; i < ncmd; i++) |
| if (cmds[i].buf != NULL) { |
| kmem_free(cmds[i].buf, UATH_MAX_CMDSZ); |
| cmds[i].buf = NULL; |
| } |
| } |
| |
| static int |
| uath_host_available(struct uath_softc *sc) |
| { |
| struct uath_cmd_host_available setup; |
| |
| /* inform target the host is available */ |
| setup.sw_ver_major = BE_32(ATH_SW_VER_MAJOR); |
| setup.sw_ver_minor = BE_32(ATH_SW_VER_MINOR); |
| setup.sw_ver_patch = BE_32(ATH_SW_VER_PATCH); |
| setup.sw_ver_build = BE_32(ATH_SW_VER_BUILD); |
| return (uath_cmd_read(sc, WDCMSG_HOST_AVAILABLE, |
| &setup, sizeof (setup), NULL, 0, 0)); |
| } |
| |
| static void |
| uath_get_capability(struct uath_softc *sc, uint32_t cap, uint32_t *val) |
| { |
| int err; |
| |
| cap = BE_32(cap); |
| err = uath_cmd_read(sc, WDCMSG_TARGET_GET_CAPABILITY, &cap, |
| sizeof (cap), val, sizeof (uint32_t), UATH_CMD_FLAG_MAGIC); |
| if (err == UATH_SUCCESS) |
| *val = BE_32(*val); |
| else |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_get_capability(): " |
| "could not read capability %u\n", BE_32(cap)); |
| } |
| |
| static int |
| uath_get_devcap(struct uath_softc *sc) |
| { |
| struct uath_devcap *cap = &sc->sc_devcap; |
| |
| /* collect device capabilities */ |
| uath_get_capability(sc, CAP_TARGET_VERSION, |
| &cap->targetVersion); |
| uath_get_capability(sc, CAP_TARGET_REVISION, |
| &cap->targetRevision); |
| uath_get_capability(sc, CAP_MAC_VERSION, |
| &cap->macVersion); |
| uath_get_capability(sc, CAP_MAC_REVISION, |
| &cap->macRevision); |
| uath_get_capability(sc, CAP_PHY_REVISION, |
| &cap->phyRevision); |
| uath_get_capability(sc, CAP_ANALOG_5GHz_REVISION, |
| &cap->analog5GhzRevision); |
| uath_get_capability(sc, CAP_ANALOG_2GHz_REVISION, |
| &cap->analog2GhzRevision); |
| uath_get_capability(sc, CAP_REG_DOMAIN, |
| &cap->regDomain); |
| uath_get_capability(sc, CAP_REG_CAP_BITS, |
| &cap->regCapBits); |
| |
| /* NB: not supported in rev 1.5 */ |
| /* uath_get_capability(sc, CAP_COUNTRY_CODE, cap->countryCode); */ |
| |
| uath_get_capability(sc, CAP_WIRELESS_MODES, |
| &cap->wirelessModes); |
| uath_get_capability(sc, CAP_CHAN_SPREAD_SUPPORT, |
| &cap->chanSpreadSupport); |
| uath_get_capability(sc, CAP_COMPRESS_SUPPORT, |
| &cap->compressSupport); |
| uath_get_capability(sc, CAP_BURST_SUPPORT, |
| &cap->burstSupport); |
| uath_get_capability(sc, CAP_FAST_FRAMES_SUPPORT, |
| &cap->fastFramesSupport); |
| uath_get_capability(sc, CAP_CHAP_TUNING_SUPPORT, |
| &cap->chapTuningSupport); |
| uath_get_capability(sc, CAP_TURBOG_SUPPORT, |
| &cap->turboGSupport); |
| uath_get_capability(sc, CAP_TURBO_PRIME_SUPPORT, |
| &cap->turboPrimeSupport); |
| uath_get_capability(sc, CAP_DEVICE_TYPE, |
| &cap->deviceType); |
| uath_get_capability(sc, CAP_WME_SUPPORT, |
| &cap->wmeSupport); |
| uath_get_capability(sc, CAP_TOTAL_QUEUES, |
| &cap->numTxQueues); |
| uath_get_capability(sc, CAP_CONNECTION_ID_MAX, |
| &cap->connectionIdMax); |
| |
| uath_get_capability(sc, CAP_LOW_5GHZ_CHAN, |
| &cap->low5GhzChan); |
| uath_get_capability(sc, CAP_HIGH_5GHZ_CHAN, |
| &cap->high5GhzChan); |
| uath_get_capability(sc, CAP_LOW_2GHZ_CHAN, |
| &cap->low2GhzChan); |
| uath_get_capability(sc, CAP_HIGH_2GHZ_CHAN, |
| &cap->high2GhzChan); |
| uath_get_capability(sc, CAP_TWICE_ANTENNAGAIN_5G, |
| &cap->twiceAntennaGain5G); |
| uath_get_capability(sc, CAP_TWICE_ANTENNAGAIN_2G, |
| &cap->twiceAntennaGain2G); |
| |
| uath_get_capability(sc, CAP_CIPHER_AES_CCM, |
| &cap->supportCipherAES_CCM); |
| uath_get_capability(sc, CAP_CIPHER_TKIP, |
| &cap->supportCipherTKIP); |
| uath_get_capability(sc, CAP_MIC_TKIP, |
| &cap->supportMicTKIP); |
| |
| cap->supportCipherWEP = 1; /* NB: always available */ |
| return (UATH_SUCCESS); |
| } |
| |
| static int |
| uath_get_status(struct uath_softc *sc, uint32_t which, void *odata, int olen) |
| { |
| int err; |
| |
| which = BE_32(which); |
| err = uath_cmd_read(sc, WDCMSG_TARGET_GET_STATUS, |
| &which, sizeof (which), odata, olen, UATH_CMD_FLAG_MAGIC); |
| if (err != UATH_SUCCESS) |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_get_status(): " |
| "could not read EEPROM offset 0x%02x\n", BE_32(which)); |
| return (err); |
| } |
| |
| static int |
| uath_get_devstatus(struct uath_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) |
| { |
| int err; |
| |
| /* retrieve MAC address */ |
| err = uath_get_status(sc, ST_MAC_ADDR, macaddr, IEEE80211_ADDR_LEN); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_get_devstatus(): " |
| "could not read MAC address\n"); |
| return (err); |
| } |
| |
| err = uath_get_status(sc, ST_SERIAL_NUMBER, |
| &sc->sc_serial[0], sizeof (sc->sc_serial)); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_get_devstatus(): " |
| "could not read device serial number\n"); |
| return (err); |
| } |
| |
| return (UATH_SUCCESS); |
| } |
| |
| /* |
| * uath_cmd_lock: a special signal structure that is used for notification |
| * that a callback function has been called. |
| */ |
| |
| /* Initializes the uath_cmd_lock structure. */ |
| static void |
| uath_cmd_lock_init(struct uath_cmd_lock *lock) |
| { |
| ASSERT(lock != NULL); |
| mutex_init(&lock->mutex, NULL, MUTEX_DRIVER, NULL); |
| cv_init(&lock->cv, NULL, CV_DRIVER, NULL); |
| lock->done = B_FALSE; |
| } |
| |
| /* Deinitalizes the uath_cb_lock structure. */ |
| void |
| uath_cmd_lock_destroy(struct uath_cmd_lock *lock) |
| { |
| ASSERT(lock != NULL); |
| mutex_destroy(&lock->mutex); |
| cv_destroy(&lock->cv); |
| } |
| |
| /* |
| * Wait on lock until someone calls the "signal" function or the timeout |
| * expires. Note: timeout is in microseconds. |
| */ |
| static int |
| uath_cmd_lock_wait(struct uath_cmd_lock *lock, clock_t timeout) |
| { |
| int res, cv_res; |
| clock_t etime; |
| |
| ASSERT(lock != NULL); |
| mutex_enter(&lock->mutex); |
| |
| if (timeout < 0) { |
| /* no timeout - wait as long as needed */ |
| while (lock->done == B_FALSE) |
| cv_wait(&lock->cv, &lock->mutex); |
| } else { |
| /* wait with timeout (given in usec) */ |
| etime = ddi_get_lbolt() + drv_usectohz(timeout); |
| while (lock->done == B_FALSE) { |
| cv_res = cv_timedwait_sig(&lock->cv, |
| &lock->mutex, etime); |
| if (cv_res <= 0) break; |
| } |
| } |
| |
| res = (lock->done == B_TRUE) ? UATH_SUCCESS : UATH_FAILURE; |
| mutex_exit(&lock->mutex); |
| |
| return (res); |
| } |
| |
| /* Signal that the job (eg. callback) is done and unblock anyone who waits. */ |
| static void |
| uath_cmd_lock_signal(struct uath_cmd_lock *lock) |
| { |
| ASSERT(lock != NULL); |
| |
| mutex_enter(&lock->mutex); |
| lock->done = B_TRUE; |
| cv_broadcast(&lock->cv); |
| mutex_exit(&lock->mutex); |
| } |
| |
| 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)); |
| } |
| |
| /* |
| * 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 err; |
| |
| /* grab a xfer */ |
| cmd = &sc->sc_cmd[sc->sc_cmdid]; |
| |
| cmd->flags = flags; |
| /* always bulk-out a multiple of 4 bytes */ |
| cmd->buflen = (sizeof (struct uath_cmd_hdr) + ilen + 3) & ~3; |
| |
| hdr = (struct uath_cmd_hdr *)cmd->buf; |
| bzero(hdr, sizeof (struct uath_cmd_hdr)); |
| hdr->len = BE_32(cmd->buflen); |
| hdr->code = BE_32(code); |
| hdr->msgid = cmd->msgid; /* don't care about endianness */ |
| hdr->magic = BE_32((cmd->flags & UATH_CMD_FLAG_MAGIC) ? 1 << 24 : 0); |
| bcopy(idata, (uint8_t *)(hdr + 1), ilen); |
| |
| UATH_DEBUG(UATH_DBG_TX_CMD, "uath: uath_cmdsend(): " |
| "queue %x send %s [flags 0x%x] olen %d\n", |
| cmd->msgid, uath_codename(code), cmd->flags, olen); |
| |
| cmd->odata = odata; |
| if (odata == NULL) |
| UATH_DEBUG(UATH_DBG_TX_CMD, "uath: uath_cmdsend(): " |
| "warning - odata is NULL\n"); |
| else if (olen < UATH_MAX_CMDSZ - sizeof (*hdr) + sizeof (uint32_t)) |
| UATH_DEBUG(UATH_DBG_TX_CMD, "uath: uath_cmdsend(): " |
| "warning - olen %x is short\n, olen"); |
| cmd->olen = olen; |
| |
| err = uath_tx_cmd_xfer(sc, sc->tx_cmd_pipe, cmd->buf, cmd->buflen); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_cmdsend(): " |
| "Error writing command\n"); |
| return (UATH_FAILURE); |
| } |
| |
| sc->sc_cmdid = (sc->sc_cmdid + 1) % UATH_CMD_LIST_COUNT; |
| |
| if (cmd->flags & UATH_CMD_FLAG_READ) { |
| /* wait at most two seconds for command reply */ |
| uath_cmd_lock_init(&sc->rlock); |
| err = uath_cmd_lock_wait(&sc->rlock, 2000000); |
| cmd->odata = NULL; /* in case reply comes too late */ |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_cmdsend(): " |
| "timeout waiting for reply, " |
| "to cmd 0x%x (%u), queue %x\n", |
| code, code, cmd->msgid); |
| err = UATH_FAILURE; |
| } else if (cmd->olen != olen) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_cmdsend(): " |
| "unexpected reply data count " |
| "to cmd 0x%x (%x), got %u, expected %u\n", |
| code, cmd->msgid, cmd->olen, olen); |
| err = UATH_FAILURE; |
| } |
| uath_cmd_lock_destroy(&sc->rlock); |
| return (err); |
| } |
| |
| return (UATH_SUCCESS); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| uath_cmd_txeof(usb_pipe_handle_t pipe, struct usb_bulk_req *req) |
| { |
| struct uath_softc *sc = (struct uath_softc *)req->bulk_client_private; |
| |
| UATH_DEBUG(UATH_DBG_TX_CMD, "uath: uath_cmd_txeof(): " |
| "cr:%s(%d), flags:0x%x, tx queued %d\n", |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason, |
| req->bulk_cb_flags, |
| sc->tx_cmd_queued); |
| |
| if (req->bulk_completion_reason != USB_CR_OK) |
| sc->sc_tx_err++; |
| |
| mutex_enter(&sc->sc_txlock_cmd); |
| sc->tx_cmd_queued--; |
| mutex_exit(&sc->sc_txlock_cmd); |
| usb_free_bulk_req(req); |
| } |
| |
| static int |
| uath_tx_cmd_xfer(struct uath_softc *sc, |
| usb_pipe_handle_t pipe, const void *data, uint_t len) |
| { |
| usb_bulk_req_t *send_req; |
| mblk_t *mblk; |
| int res; |
| |
| send_req = usb_alloc_bulk_req(sc->sc_dev, len, USB_FLAGS_SLEEP); |
| |
| send_req->bulk_client_private = (usb_opaque_t)sc; |
| send_req->bulk_len = (int)len; |
| send_req->bulk_attributes = USB_ATTRS_AUTOCLEARING; |
| send_req->bulk_timeout = UATH_CMD_TIMEOUT; |
| send_req->bulk_cb = uath_cmd_txeof; |
| send_req->bulk_exc_cb = uath_cmd_txeof; |
| send_req->bulk_completion_reason = 0; |
| send_req->bulk_cb_flags = 0; |
| |
| mblk = send_req->bulk_data; |
| bcopy(data, mblk->b_rptr, len); |
| mblk->b_wptr += len; |
| |
| res = usb_pipe_bulk_xfer(pipe, send_req, USB_FLAGS_NOSLEEP); |
| if (res != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_tx_cmd_xfer(): " |
| "Error %x writing cmd to bulk/out pipe", res); |
| return (UATH_FAILURE); |
| } |
| |
| mutex_enter(&sc->sc_txlock_cmd); |
| sc->tx_cmd_queued++; |
| mutex_exit(&sc->sc_txlock_cmd); |
| return (UATH_SUCCESS); |
| } |
| |
| 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; |
| |
| hdr->code = BE_32(hdr->code); |
| hdr->len = BE_32(hdr->len); |
| hdr->magic = BE_32(hdr->magic); /* target status on return */ |
| |
| /* NB: msgid is passed thru w/o byte swapping */ |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmdeof(): " |
| "%s: [ix %x] len=%x status %x\n", |
| uath_codename(hdr->code), |
| hdr->msgid, |
| hdr->len, |
| hdr->magic); |
| |
| switch (hdr->code & 0xff) { |
| /* reply to a read command */ |
| default: |
| dlen = hdr->len - sizeof (*hdr); |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmdeof(): " |
| "code %x data len %u\n", |
| 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) && (hdr->code != 0x13)) { |
| uint32_t *rp = (uint32_t *)(hdr + 1); |
| uint_t olen; |
| |
| if (!(sizeof (*hdr) <= hdr->len && |
| hdr->len < UATH_MAX_CMDSZ)) { |
| UATH_DEBUG(UATH_DBG_RX_CMD, |
| "uath: uath_cmdeof(): " |
| "invalid WDC msg length %u; " |
| "msg ignored\n", |
| 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 = BE_32(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? */ |
| UATH_DEBUG(UATH_DBG_RX_CMD, |
| "uath: uath_cmdeof(): " |
| "cmd 0x%x olen %u cmd olen %u\n", |
| hdr->code, olen, cmd->olen); |
| olen = cmd->olen; |
| } |
| if (olen > dlen) { |
| /* XXX complain, shouldn't happen */ |
| UATH_DEBUG(UATH_DBG_RX_CMD, |
| "uath: uath_cmdeof(): " |
| "cmd 0x%x olen %u dlen %u\n", |
| 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; |
| } |
| } |
| |
| /* Just signal that something happened */ |
| uath_cmd_lock_signal(&sc->rlock); |
| break; |
| |
| case WDCMSG_TARGET_START: |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmdeof(): " |
| "receive TARGET STAERT\n"); |
| |
| 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); |
| |
| /* wake up caller */ |
| uath_cmd_lock_signal(&sc->rlock); |
| break; |
| |
| case WDCMSG_SEND_COMPLETE: |
| /* this notification is sent when UATH_TX_NOTIFY is set */ |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmdeof(): " |
| "receive Tx notification\n"); |
| break; |
| |
| case WDCMSG_TARGET_GET_STATS: |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmdeof(): " |
| "received device statistics\n"); |
| break; |
| } |
| } |
| |
| /* ARGSUSED */ |
| static void |
| uath_cmd_rxeof(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct uath_softc *sc = (struct uath_softc *)req->bulk_client_private; |
| struct uath_cmd_hdr *hdr; |
| struct uath_cmd *cmd; |
| mblk_t *m, *mp; |
| int len; |
| |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmd_rxeof(): " |
| "cr:%s(%d), flags:0x%x, rx queued %d\n", |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason, |
| req->bulk_cb_flags, |
| sc->rx_cmd_queued); |
| |
| m = req->bulk_data; |
| req->bulk_data = NULL; |
| |
| if (req->bulk_completion_reason != USB_CR_OK) { |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_cmd_rxeof(): " |
| "USB CR is not OK\n"); |
| goto fail; |
| } |
| |
| if (m->b_cont != NULL) { |
| /* Fragmented message, concatenate */ |
| mp = msgpullup(m, -1); |
| freemsg(m); |
| m = mp; |
| mp = NULL; |
| } |
| |
| len = msgdsize(m); |
| if (len < sizeof (struct uath_cmd_hdr)) { |
| UATH_DEBUG(UATH_DBG_RX_CMD, "uath: uath_rx_cmdeof(): " |
| "short xfer error\n"); |
| goto fail; |
| } |
| |
| hdr = (struct uath_cmd_hdr *)m->b_rptr; |
| if (BE_32(hdr->code) == 0x13) |
| cmd = &sc->sc_cmd[0]; |
| else |
| cmd = &sc->sc_cmd[hdr->msgid]; |
| |
| bcopy(m->b_rptr, cmd->buf, len); |
| uath_cmdeof(sc, cmd); |
| (void) uath_rx_cmd_xfer(sc); |
| fail: |
| mutex_enter(&sc->sc_rxlock_cmd); |
| sc->rx_cmd_queued--; |
| mutex_exit(&sc->sc_rxlock_cmd); |
| if (m) freemsg(m); |
| usb_free_bulk_req(req); |
| } |
| |
| static int |
| uath_rx_cmd_xfer(struct uath_softc *sc) |
| { |
| usb_bulk_req_t *req; |
| int err; |
| |
| req = usb_alloc_bulk_req(sc->sc_dev, UATH_MAX_CMDSZ, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_rx_cmd_xfer(): " |
| "failed to allocate req"); |
| return (UATH_FAILURE); |
| } |
| |
| req->bulk_len = UATH_MAX_CMDSZ; |
| req->bulk_client_private = (usb_opaque_t)sc; |
| req->bulk_cb = uath_cmd_rxeof; |
| req->bulk_exc_cb = uath_cmd_rxeof; |
| req->bulk_timeout = 0; |
| req->bulk_completion_reason = 0; |
| req->bulk_cb_flags = 0; |
| req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK |
| | USB_ATTRS_AUTOCLEARING; |
| |
| err = usb_pipe_bulk_xfer(sc->rx_cmd_pipe, req, USB_FLAGS_NOSLEEP); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_rx_cmd_xfer(): " |
| "failed to do rx xfer, %d", err); |
| usb_free_bulk_req(req); |
| return (UATH_FAILURE); |
| } |
| |
| mutex_enter(&sc->sc_rxlock_cmd); |
| sc->rx_cmd_queued++; |
| mutex_exit(&sc->sc_rxlock_cmd); |
| return (UATH_SUCCESS); |
| } |
| |
| static void |
| uath_init_data_queue(struct uath_softc *sc) |
| { |
| sc->tx_data_queued = sc->rx_data_queued = 0; |
| } |
| |
| /* ARGSUSED */ |
| static void |
| uath_data_txeof(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct uath_softc *sc = (struct uath_softc *)req->bulk_client_private; |
| struct ieee80211com *ic = &sc->sc_ic; |
| |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_data_txeof(): " |
| "uath_txeof(): cr:%s(%d), flags:0x%x, tx_data_queued %d\n", |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason, |
| req->bulk_cb_flags, |
| sc->tx_data_queued); |
| |
| if (req->bulk_completion_reason != USB_CR_OK) |
| sc->sc_tx_err++; |
| |
| mutex_enter(&sc->sc_txlock_data); |
| sc->tx_data_queued--; |
| |
| if (sc->sc_need_sched) { |
| sc->sc_need_sched = 0; |
| mac_tx_update(ic->ic_mach); |
| } |
| |
| mutex_exit(&sc->sc_txlock_data); |
| usb_free_bulk_req(req); |
| } |
| |
| static int |
| uath_tx_data_xfer(struct uath_softc *sc, mblk_t *mp) |
| { |
| usb_bulk_req_t *req; |
| int err; |
| |
| req = usb_alloc_bulk_req(sc->sc_dev, 0, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_tx_data_xfer(): " |
| "uath_tx_data_xfer(): failed to allocate req"); |
| freemsg(mp); |
| return (UATH_FAILURE); |
| } |
| |
| req->bulk_len = msgdsize(mp); |
| req->bulk_data = mp; |
| req->bulk_client_private = (usb_opaque_t)sc; |
| req->bulk_timeout = UATH_DATA_TIMEOUT; |
| req->bulk_attributes = USB_ATTRS_AUTOCLEARING; |
| req->bulk_cb = uath_data_txeof; |
| req->bulk_exc_cb = uath_data_txeof; |
| req->bulk_completion_reason = 0; |
| req->bulk_cb_flags = 0; |
| |
| if ((err = usb_pipe_bulk_xfer(sc->tx_data_pipe, req, 0)) != |
| USB_SUCCESS) { |
| |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_tx_data_xfer(): " |
| "failed to do tx xfer, %d", err); |
| usb_free_bulk_req(req); |
| return (UATH_FAILURE); |
| } |
| |
| sc->tx_data_queued++; |
| return (UATH_SUCCESS); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| uath_data_rxeof(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| struct uath_softc *sc = (struct uath_softc *)req->bulk_client_private; |
| struct ieee80211com *ic = &sc->sc_ic; |
| struct uath_chunk *chunk; |
| struct uath_rx_desc *desc; |
| struct ieee80211_frame *wh; |
| struct ieee80211_node *ni; |
| |
| mblk_t *m, *mp; |
| uint8_t *rxbuf; |
| int actlen, pktlen; |
| |
| mutex_enter(&sc->sc_rxlock_data); |
| |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "cr:%s(%d), flags:0x%x, rx_data_queued %d\n", |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason, |
| req->bulk_cb_flags, |
| sc->rx_data_queued); |
| |
| mp = req->bulk_data; |
| req->bulk_data = NULL; |
| |
| if (req->bulk_completion_reason != USB_CR_OK) { |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "USB CR is not OK\n"); |
| sc->sc_rx_err++; |
| goto fail; |
| } |
| |
| rxbuf = (uint8_t *)mp->b_rptr; |
| actlen = (uintptr_t)mp->b_wptr - (uintptr_t)mp->b_rptr; |
| if (actlen < UATH_MIN_RXBUFSZ) { |
| UATH_DEBUG(UATH_DBG_RX, "uath_data_rxeof(): " |
| "wrong recv size %d\n", actlen); |
| sc->sc_rx_err++; |
| goto fail; |
| } |
| |
| chunk = (struct uath_chunk *)rxbuf; |
| if (chunk->seqnum == 0 && chunk->flags == 0 && chunk->length == 0) { |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "strange response\n"); |
| UATH_RESET_INTRX(sc); |
| sc->sc_rx_err++; |
| goto fail; |
| } |
| |
| if (chunk->seqnum != sc->sc_intrx_nextnum) { |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "invalid seqnum %d, expected %d\n", |
| chunk->seqnum, sc->sc_intrx_nextnum); |
| UATH_STAT_INC(sc, st_badchunkseqnum); |
| UATH_RESET_INTRX(sc); |
| sc->sc_rx_err++; |
| goto fail; |
| } |
| |
| /* 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_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "receive multi-chunk frames " |
| "chunk seqnum %x, flags %x, length %u\n", |
| chunk->seqnum, chunk->flags, BE_16(chunk->length)); |
| UATH_STAT_INC(sc, st_multichunk); |
| } |
| |
| |
| /* if the frame is not final continue the transfer */ |
| if (!(chunk->flags & UATH_CFLAGS_FINAL)) |
| sc->sc_intrx_nextnum++; |
| |
| /* |
| * 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) + BE_16(chunk->length) - |
| sizeof (struct uath_rx_desc)); |
| |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "frame len %u code %u status %u rate %u antenna %u " |
| "rssi %d channel %u phyerror %u connix %u " |
| "decrypterror %u keycachemiss %u\n", |
| BE_32(desc->framelen), BE_32(desc->code), BE_32(desc->status), |
| BE_32(desc->rate), BE_32(desc->antenna), BE_32(desc->rssi), |
| BE_32(desc->channel), BE_32(desc->phyerror), BE_32(desc->connix), |
| BE_32(desc->decrypterror), BE_32(desc->keycachemiss)); |
| |
| if (BE_32(desc->len) > IEEE80211_MAX_LEN) { |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "bad descriptor (len=%d)\n", BE_32(desc->len)); |
| UATH_STAT_INC(sc, st_toobigrxpkt); |
| goto fail; |
| } |
| |
| uath_update_rxstat(sc, BE_32(desc->status)); |
| |
| pktlen = BE_32(desc->framelen) - UATH_RX_DUMMYSIZE; |
| |
| if ((m = allocb(pktlen, BPRI_MED)) == NULL) { |
| UATH_DEBUG(UATH_DBG_RX, "uath: uath_data_rxeof(): " |
| "allocate mblk failed.\n"); |
| sc->sc_rx_nobuf++; |
| goto fail; |
| } |
| bcopy((rxbuf + sizeof (struct uath_chunk)), m->b_rptr, pktlen); |
| |
| m->b_wptr = m->b_rptr + pktlen; |
| wh = (struct ieee80211_frame *)m->b_rptr; |
| ni = ieee80211_find_rxnode(ic, wh); |
| |
| /* send the frame to the 802.11 layer */ |
| (void) ieee80211_input(ic, m, ni, (int)BE_32(desc->rssi), 0); |
| |
| /* node is no longer needed */ |
| ieee80211_free_node(ni); |
| fail: |
| sc->rx_data_queued--; |
| if (mp) freemsg(mp); |
| usb_free_bulk_req(req); |
| mutex_exit(&sc->sc_rxlock_data); |
| if (UATH_IS_RUNNING(sc) && !UATH_IS_SUSPEND(sc)) { |
| (void) uath_rx_data_xfer(sc); |
| } |
| } |
| |
| static int |
| uath_rx_data_xfer(struct uath_softc *sc) |
| { |
| usb_bulk_req_t *req; |
| int err; |
| |
| req = usb_alloc_bulk_req(sc->sc_dev, |
| IEEE80211_MAX_LEN, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_rx_data_xfer(): " |
| "failed to allocate req"); |
| return (UATH_SUCCESS); |
| } |
| |
| req->bulk_len = IEEE80211_MAX_LEN; |
| req->bulk_cb = uath_data_rxeof; |
| req->bulk_exc_cb = uath_data_rxeof; |
| req->bulk_client_private = (usb_opaque_t)sc; |
| req->bulk_timeout = 0; |
| req->bulk_completion_reason = 0; |
| req->bulk_cb_flags = 0; |
| req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK |
| | USB_ATTRS_AUTOCLEARING; |
| |
| err = usb_pipe_bulk_xfer(sc->rx_data_pipe, req, 0); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_rx_data_xfer(): " |
| "failed to do rx xfer, %d", err); |
| usb_free_bulk_req(req); |
| return (UATH_FAILURE); |
| } |
| |
| mutex_enter(&sc->sc_rxlock_data); |
| sc->rx_data_queued++; |
| mutex_exit(&sc->sc_rxlock_data); |
| return (UATH_SUCCESS); |
| } |
| |
| 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 void |
| uath_next_scan(void *arg) |
| { |
| struct uath_softc *sc = arg; |
| struct ieee80211com *ic = &sc->sc_ic; |
| |
| if (ic->ic_state == IEEE80211_S_SCAN) |
| ieee80211_next_scan(ic); |
| |
| sc->sc_scan_id = 0; |
| } |
| |
| static int |
| uath_create_connection(struct uath_softc *sc, uint32_t connid) |
| { |
| const struct ieee80211_rateset *rs; |
| struct ieee80211com *ic = &sc->sc_ic; |
| struct ieee80211_node *ni = ic->ic_bss; |
| struct uath_cmd_create_connection create; |
| int err; |
| |
| bzero(&create, sizeof (create)); |
| create.connid = BE_32(connid); |
| create.bssid = BE_32(0); |
| /* XXX packed or not? */ |
| create.size = BE_32(sizeof (struct uath_cmd_rateset)); |
| |
| rs = &ni->in_rates; |
| create.connattr.rateset.length = rs->ir_nrates; |
| bcopy(rs->ir_rates, &create.connattr.rateset.set[0], |
| rs->ir_nrates); |
| |
| /* XXX turbo */ |
| if (UATH_IS_CHAN_A(ni->in_chan)) |
| create.connattr.wlanmode = BE_32(WLAN_MODE_11a); |
| else if (UATH_IS_CHAN_ANYG(ni->in_chan)) |
| create.connattr.wlanmode = BE_32(WLAN_MODE_11g); |
| else |
| create.connattr.wlanmode = BE_32(WLAN_MODE_11b); |
| |
| err = uath_cmd_write(sc, WDCMSG_CREATE_CONNECTION, &create, |
| sizeof (create), 0); |
| return (err); |
| } |
| |
| static int |
| uath_set_rates(struct uath_softc *sc, const struct ieee80211_rateset *rs) |
| { |
| struct uath_cmd_rates rates; |
| int err; |
| |
| bzero(&rates, sizeof (rates)); |
| rates.connid = BE_32(UATH_ID_BSS); /* XXX */ |
| rates.size = BE_32(sizeof (struct uath_cmd_rateset)); |
| /* XXX bounds check rs->rs_nrates */ |
| rates.rateset.length = rs->ir_nrates; |
| bcopy(rs->ir_rates, &rates.rateset.set[0], rs->ir_nrates); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_rates(): " |
| "setting supported rates nrates=%d\n", rs->ir_nrates); |
| err = uath_cmd_write(sc, WDCMSG_SET_BASIC_RATE, |
| &rates, sizeof (rates), 0); |
| return (err); |
| } |
| |
| static int |
| uath_write_associd(struct uath_softc *sc) |
| { |
| struct ieee80211com *ic = &sc->sc_ic; |
| struct ieee80211_node *ni = ic->ic_bss; |
| struct uath_cmd_set_associd associd; |
| int err; |
| |
| bzero(&associd, sizeof (associd)); |
| associd.defaultrateix = BE_32(1); /* XXX */ |
| associd.associd = BE_32(ni->in_associd); |
| associd.timoffset = BE_32(0x3b); /* XXX */ |
| IEEE80211_ADDR_COPY(associd.bssid, ni->in_bssid); |
| err = uath_cmd_write(sc, WDCMSG_WRITE_ASSOCID, &associd, |
| sizeof (associd), 0); |
| return (err); |
| } |
| |
| static int |
| uath_set_ledsteady(struct uath_softc *sc, int lednum, int ledmode) |
| { |
| struct uath_cmd_ledsteady led; |
| int err; |
| |
| led.lednum = BE_32(lednum); |
| led.ledmode = BE_32(ledmode); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_ledsteady(): " |
| "set %s led %s (steady)\n", |
| (lednum == UATH_LED_LINK) ? "link" : "activity", |
| ledmode ? "on" : "off"); |
| err = uath_cmd_write(sc, WDCMSG_SET_LED_STEADY, &led, sizeof (led), 0); |
| return (err); |
| } |
| |
| static int |
| uath_set_ledblink(struct uath_softc *sc, int lednum, int ledmode, |
| int blinkrate, int slowmode) |
| { |
| struct uath_cmd_ledblink led; |
| int err; |
| |
| led.lednum = BE_32(lednum); |
| led.ledmode = BE_32(ledmode); |
| led.blinkrate = BE_32(blinkrate); |
| led.slowmode = BE_32(slowmode); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_ledblink(): " |
| "set %s led %s (blink)\n", |
| (lednum == UATH_LED_LINK) ? "link" : "activity", |
| ledmode ? "on" : "off"); |
| |
| err = uath_cmd_write(sc, WDCMSG_SET_LED_BLINK, |
| &led, sizeof (led), 0); |
| return (err); |
| } |
| |
| |
| static int |
| uath_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg) |
| { |
| struct uath_softc *sc = (struct uath_softc *)ic; |
| struct ieee80211_node *ni = ic->ic_bss; |
| enum ieee80211_state ostate; |
| int err; |
| |
| ostate = ic->ic_state; |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_newstate(): " |
| "%d -> %d\n", ostate, nstate); |
| |
| if (sc->sc_scan_id != 0) { |
| (void) untimeout(sc->sc_scan_id); |
| sc->sc_scan_id = 0; |
| } |
| |
| UATH_LOCK(sc); |
| |
| if (UATH_IS_DISCONNECT(sc) && (nstate != IEEE80211_S_INIT)) { |
| UATH_UNLOCK(sc); |
| return (DDI_SUCCESS); |
| } |
| |
| if (UATH_IS_SUSPEND(sc) && (nstate != IEEE80211_S_INIT)) { |
| UATH_UNLOCK(sc); |
| return (DDI_SUCCESS); |
| } |
| |
| switch (nstate) { |
| case IEEE80211_S_INIT: |
| if (ostate == IEEE80211_S_RUN) { |
| /* turn link and activity LEDs off */ |
| (void) uath_set_ledstate(sc, 0); |
| } |
| break; |
| case IEEE80211_S_SCAN: |
| if (uath_switch_channel(sc, ic->ic_curchan) != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_newstate(): " |
| "could not switch channel\n"); |
| break; |
| } |
| sc->sc_scan_id = timeout(uath_next_scan, (void *)sc, |
| drv_usectohz(250000)); |
| break; |
| case IEEE80211_S_AUTH: |
| /* XXX good place? set RTS threshold */ |
| uath_config(sc, CFG_USER_RTS_THRESHOLD, ic->ic_rtsthreshold); |
| |
| if (uath_switch_channel(sc, ni->in_chan) != 0) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_newstate(): " |
| "could not switch channel\n"); |
| break; |
| } |
| if (uath_create_connection(sc, UATH_ID_BSS) != 0) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_newstate(): " |
| "could not create connection\n"); |
| break; |
| } |
| break; |
| case IEEE80211_S_ASSOC: |
| if (uath_set_rates(sc, &ni->in_rates) != 0) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_newstate(): " |
| "could not set negotiated rate set\n"); |
| break; |
| } |
| break; |
| case IEEE80211_S_RUN: |
| /* XXX monitor mode doesn't be supported */ |
| if (ic->ic_opmode == IEEE80211_M_MONITOR) { |
| (void) uath_set_ledstate(sc, 1); |
| break; |
| } |
| |
| /* |
| * Tx rate is controlled by firmware, report the maximum |
| * negotiated rate in ifconfig output. |
| */ |
| ni->in_txrate = ni->in_rates.ir_nrates - 1; |
| |
| if (uath_write_associd(sc) != 0) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_newstate(): " |
| "could not write association id\n"); |
| break; |
| } |
| /* turn link LED on */ |
| (void) uath_set_ledsteady(sc, UATH_LED_LINK, UATH_LED_ON); |
| /* make activity LED blink */ |
| (void) uath_set_ledblink(sc, UATH_LED_ACTIVITY, |
| UATH_LED_ON, 1, 2); |
| /* set state to associated */ |
| (void) uath_set_ledstate(sc, 1); |
| break; |
| } |
| |
| UATH_UNLOCK(sc); |
| |
| err = sc->sc_newstate(ic, nstate, arg); |
| return (err); |
| } |
| |
| static int |
| uath_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type) |
| { |
| struct uath_softc *sc = (struct uath_softc *)ic; |
| struct uath_chunk *chunk; |
| struct uath_tx_desc *desc; |
| struct ieee80211_frame *wh; |
| struct ieee80211_node *ni = NULL; |
| struct ieee80211_key *k; |
| |
| mblk_t *m, *m0; |
| int err, off, mblen; |
| int pktlen, framelen, msglen; |
| |
| err = UATH_SUCCESS; |
| |
| mutex_enter(&sc->sc_txlock_data); |
| |
| if (UATH_IS_SUSPEND(sc)) { |
| err = 0; |
| goto fail; |
| } |
| |
| if (sc->tx_data_queued > UATH_TX_DATA_LIST_COUNT) { |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_send(): " |
| "no TX buffer available!\n"); |
| if ((type & IEEE80211_FC0_TYPE_MASK) == |
| IEEE80211_FC0_TYPE_DATA) { |
| sc->sc_need_sched = 1; |
| } |
| sc->sc_tx_nobuf++; |
| err = ENOMEM; |
| goto fail; |
| } |
| |
| m = allocb(UATH_MAX_TXBUFSZ, BPRI_MED); |
| if (m == NULL) { |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_send(): " |
| "can't alloc mblk.\n"); |
| err = DDI_FAILURE; |
| goto fail; |
| } |
| |
| /* skip TX descriptor */ |
| m->b_rptr += sizeof (struct uath_chunk) + sizeof (struct uath_tx_desc); |
| m->b_wptr += sizeof (struct uath_chunk) + sizeof (struct uath_tx_desc); |
| |
| for (off = 0, m0 = mp; m0 != NULL; m0 = m0->b_cont) { |
| mblen = (uintptr_t)m0->b_wptr - (uintptr_t)m0->b_rptr; |
| (void) memcpy(m->b_rptr + off, m0->b_rptr, mblen); |
| off += mblen; |
| } |
| m->b_wptr += off; |
| |
| wh = (struct ieee80211_frame *)m->b_rptr; |
| |
| ni = ieee80211_find_txnode(ic, wh->i_addr1); |
| if (ni == NULL) { |
| err = DDI_FAILURE; |
| freemsg(m); |
| goto fail; |
| } |
| |
| if ((type & IEEE80211_FC0_TYPE_MASK) == |
| IEEE80211_FC0_TYPE_DATA) { |
| (void) ieee80211_encap(ic, m, ni); |
| } |
| |
| if (wh->i_fc[1] & IEEE80211_FC1_WEP) { |
| k = ieee80211_crypto_encap(ic, m); |
| if (k == NULL) { |
| freemsg(m); |
| err = DDI_FAILURE; |
| goto fail; |
| } |
| /* packet header may have moved, reset our local pointer */ |
| wh = (struct ieee80211_frame *)m->b_rptr; |
| } |
| |
| pktlen = (uintptr_t)m->b_wptr - (uintptr_t)m->b_rptr; |
| framelen = pktlen + IEEE80211_CRC_LEN; |
| msglen = framelen + sizeof (struct uath_tx_desc); |
| |
| m->b_rptr -= sizeof (struct uath_chunk) + sizeof (struct uath_tx_desc); |
| |
| chunk = (struct uath_chunk *)m->b_rptr; |
| desc = (struct uath_tx_desc *)(chunk + 1); |
| |
| /* one chunk only for now */ |
| chunk->seqnum = 0; |
| chunk->flags = UATH_CFLAGS_FINAL; |
| chunk->length = BE_16(msglen); |
| |
| /* fill Tx descriptor */ |
| desc->msglen = BE_32(msglen); |
| /* NB: to get UATH_TX_NOTIFY reply, `msgid' must be larger than 0 */ |
| desc->msgid = sc->sc_msgid; /* don't care about endianness */ |
| desc->type = BE_32(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->in_flags & UATH_NODE_QOS) { |
| /* NB: force all management frames to highest queue */ |
| desc->txqid = BE_32(WME_AC_VO | UATH_TXQID_MINRATE); |
| } else |
| desc->txqid = BE_32(WME_AC_BE | UATH_TXQID_MINRATE); |
| break; |
| case IEEE80211_FC0_TYPE_DATA: |
| /* XXX multicast frames should honor mcastrate */ |
| desc->txqid = BE_32(WME_AC_BE); |
| break; |
| default: |
| UATH_DEBUG(UATH_DBG_TX, "uath: uath_send(): " |
| "bogus frame type 0x%x (%s)\n", |
| wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK); |
| err = EIO; |
| goto fail; |
| } |
| |
| if (ic->ic_state == IEEE80211_S_AUTH || |
| ic->ic_state == IEEE80211_S_ASSOC || |
| ic->ic_state == IEEE80211_S_RUN) |
| desc->connid = BE_32(UATH_ID_BSS); |
| else |
| desc->connid = BE_32(UATH_ID_INVALID); |
| desc->flags = BE_32(0 /* no UATH_TX_NOTIFY */); |
| desc->buflen = BE_32(pktlen); |
| |
| (void) uath_tx_data_xfer(sc, m); |
| |
| sc->sc_msgid = (sc->sc_msgid + 1) % UATH_TX_DATA_LIST_COUNT; |
| |
| ic->ic_stats.is_tx_frags++; |
| ic->ic_stats.is_tx_bytes += pktlen; |
| |
| fail: |
| if (ni != NULL) |
| ieee80211_free_node(ni); |
| if ((mp) && |
| ((type & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_DATA || |
| err == 0)) { |
| freemsg(mp); |
| } |
| mutex_exit(&sc->sc_txlock_data); |
| return (err); |
| } |
| |
| static int |
| uath_reconnect(dev_info_t *devinfo) |
| { |
| struct uath_softc *sc; |
| struct ieee80211com *ic; |
| int err; |
| uint16_t vendor_id, product_id; |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_reconnect(): " |
| "uath online\n"); |
| |
| sc = ddi_get_soft_state(uath_soft_state_p, ddi_get_instance(devinfo)); |
| ASSERT(sc != NULL); |
| ic = (struct ieee80211com *)&sc->sc_ic; |
| |
| if (!UATH_IS_RECONNECT(sc)) { |
| err = uath_open_pipes(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not open pipes\n"); |
| return (DDI_FAILURE); |
| } |
| |
| err = uath_loadfirmware(sc); |
| if (err != DDI_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not download firmware\n"); |
| return (DDI_FAILURE); |
| } |
| |
| uath_close_pipes(sc); |
| usb_client_detach(sc->sc_dev, sc->sc_udev); |
| |
| /* reset device */ |
| err = usb_reset_device(sc->sc_dev, USB_RESET_LVL_DEFAULT); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not reset device %x\n", err); |
| } |
| |
| err = usb_client_attach(devinfo, USBDRV_VERSION, 0); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "usb_client_attach failed\n"); |
| } |
| |
| err = usb_get_dev_data(devinfo, &sc->sc_udev, |
| USB_PARSE_LVL_ALL, 0); |
| if (err != USB_SUCCESS) { |
| sc->sc_udev = NULL; |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "usb_get_dev_data failed\n"); |
| } |
| |
| vendor_id = sc->sc_udev->dev_descr->idVendor; |
| product_id = sc->sc_udev->dev_descr->idProduct; |
| sc->dev_flags = uath_lookup(vendor_id, product_id); |
| if (sc->dev_flags == UATH_FLAG_ERR) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "HW does not match\n"); |
| } |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_reconnect(): " |
| "vendorId = %x,deviceID = %x, flags = %x\n", |
| vendor_id, product_id, sc->dev_flags); |
| |
| UATH_LOCK(sc); |
| sc->sc_flags |= UATH_FLAG_RECONNECT; |
| UATH_UNLOCK(sc); |
| |
| } else { |
| err = uath_open_pipes(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not open pipes\n"); |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * Allocate xfers for firmware commands. |
| */ |
| err = uath_alloc_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT, |
| UATH_MAX_CMDSZ); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not allocate Tx command list\n"); |
| return (DDI_FAILURE); |
| } |
| |
| err = uath_init_cmd_list(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not init RX command list\n"); |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * We're now ready to send+receive firmware commands. |
| */ |
| err = uath_host_available(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not initialize adapter\n"); |
| return (DDI_FAILURE); |
| } |
| |
| err = uath_get_devcap(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not get device capabilities\n"); |
| return (DDI_FAILURE); |
| } |
| |
| err = uath_get_devstatus(sc, ic->ic_macaddr); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "could not get dev status\n"); |
| return (DDI_FAILURE); |
| } |
| |
| err = usb_check_same_device(sc->sc_dev, NULL, USB_LOG_L2, -1, |
| USB_CHK_BASIC, NULL); |
| if (err != USB_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "different device connected %x\n", err); |
| return (DDI_FAILURE); |
| } |
| |
| err = uath_init(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_reconnect(): " |
| "device re-connect failed\n"); |
| return (DDI_FAILURE); |
| } |
| |
| UATH_LOCK(sc); |
| sc->sc_flags &= ~UATH_FLAG_RECONNECT; |
| sc->sc_flags &= ~UATH_FLAG_DISCONNECT; |
| sc->sc_flags |= UATH_FLAG_RUNNING; |
| UATH_UNLOCK(sc); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| static int |
| uath_disconnect(dev_info_t *devinfo) |
| { |
| struct uath_softc *sc; |
| struct ieee80211com *ic; |
| |
| /* |
| * We can't call uath_stop() here, since the hardware is removed, |
| * we can't access the register anymore. |
| */ |
| sc = ddi_get_soft_state(uath_soft_state_p, ddi_get_instance(devinfo)); |
| ASSERT(sc != NULL); |
| |
| if (sc->sc_flags & UATH_FLAG_RECONNECT) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_disconnect(): " |
| "stage 0 in re-connect\n"); |
| uath_close_pipes(sc); |
| return (DDI_SUCCESS); |
| } |
| |
| UATH_LOCK(sc); |
| sc->sc_flags |= UATH_FLAG_DISCONNECT; |
| UATH_UNLOCK(sc); |
| |
| ic = (struct ieee80211com *)&sc->sc_ic; |
| ieee80211_new_state(ic, IEEE80211_S_INIT, -1); |
| |
| UATH_LOCK(sc); |
| sc->sc_flags &= ~UATH_FLAG_RUNNING; /* STOP */ |
| UATH_UNLOCK(sc); |
| |
| /* abort and free xfers */ |
| uath_free_cmd_list(sc->sc_cmd, UATH_CMD_LIST_COUNT); |
| |
| /* close Tx/Rx pipes */ |
| uath_close_pipes(sc); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_disconnect(): " |
| "offline success\n"); |
| |
| return (DDI_SUCCESS); |
| } |
| |
| static int |
| uath_dataflush(struct uath_softc *sc) |
| { |
| struct uath_chunk *chunk; |
| struct uath_tx_desc *desc; |
| uint8_t *buf; |
| int err; |
| |
| buf = kmem_alloc(UATH_MAX_TXBUFSZ, KM_NOSLEEP); |
| if (buf == NULL) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_dataflush(): " |
| "no bufs\n"); |
| return (ENOBUFS); |
| } |
| |
| chunk = (struct uath_chunk *)buf; |
| desc = (struct uath_tx_desc *)(chunk + 1); |
| |
| /* one chunk only */ |
| chunk->seqnum = 0; |
| chunk->flags = UATH_CFLAGS_FINAL; |
| chunk->length = BE_16(sizeof (struct uath_tx_desc)); |
| |
| bzero(desc, sizeof (struct uath_tx_desc)); |
| desc->msglen = BE_32(sizeof (struct uath_tx_desc)); |
| desc->msgid = sc->sc_msgid; /* don't care about endianness */ |
| desc->type = BE_32(WDCMSG_FLUSH); |
| desc->txqid = BE_32(0); |
| desc->connid = BE_32(0); |
| desc->flags = BE_32(0); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_dataflush(): " |
| "send flush ix %d\n", desc->msgid); |
| |
| err = uath_fw_send(sc, sc->tx_data_pipe, buf, |
| sizeof (struct uath_chunk) + sizeof (struct uath_tx_desc)); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_dataflush(): " |
| "data flush error"); |
| return (UATH_FAILURE); |
| } |
| |
| kmem_free(buf, UATH_MAX_TXBUFSZ); |
| sc->sc_msgid = (sc->sc_msgid + 1) % UATH_TX_DATA_LIST_COUNT; |
| |
| return (UATH_SUCCESS); |
| } |
| |
| static int |
| uath_cmdflush(struct uath_softc *sc) |
| { |
| return (uath_cmd_write(sc, WDCMSG_FLUSH, NULL, 0, 0)); |
| } |
| |
| static int |
| uath_flush(struct uath_softc *sc) |
| { |
| int err; |
| |
| err = uath_dataflush(sc); |
| if (err != UATH_SUCCESS) |
| goto failed; |
| |
| err = uath_cmdflush(sc); |
| if (err != UATH_SUCCESS) |
| goto failed; |
| |
| return (UATH_SUCCESS); |
| failed: |
| return (err); |
| } |
| |
| static int |
| uath_set_ledstate(struct uath_softc *sc, int connected) |
| { |
| int err; |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_ledstate(): " |
| "set led state %sconnected\n", connected ? "" : "!"); |
| |
| connected = BE_32(connected); |
| err = uath_cmd_write(sc, WDCMSG_SET_LED_STATE, |
| &connected, sizeof (connected), 0); |
| return (err); |
| } |
| |
| static int |
| uath_config_multi(struct uath_softc *sc, uint32_t reg, const void *data, |
| int len) |
| { |
| struct uath_write_mac write; |
| int err; |
| |
| write.reg = BE_32(reg); |
| write.len = BE_32(len); |
| bcopy(data, write.data, len); |
| |
| /* properly handle the case where len is zero (reset) */ |
| err = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, |
| (len == 0) ? sizeof (uint32_t) : 2 * sizeof (uint32_t) + len, 0); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_config_multi(): " |
| "could not write %d bytes to register 0x%02x\n", len, reg); |
| } |
| return (err); |
| } |
| |
| static void |
| uath_config(struct uath_softc *sc, uint32_t reg, uint32_t val) |
| { |
| struct uath_write_mac write; |
| int err; |
| |
| write.reg = BE_32(reg); |
| write.len = BE_32(0); /* 0 = single write */ |
| *(uint32_t *)write.data = BE_32(val); |
| |
| err = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, |
| 3 * sizeof (uint32_t), 0); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_config(): " |
| "could not write register 0x%02x\n", |
| reg); |
| } |
| } |
| |
| static int |
| uath_switch_channel(struct uath_softc *sc, struct ieee80211_channel *c) |
| { |
| int err; |
| |
| /* set radio frequency */ |
| err = uath_set_chan(sc, c); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_switch_channel(): " |
| "could not set channel\n"); |
| goto failed; |
| } |
| |
| /* reset Tx rings */ |
| err = uath_reset_tx_queues(sc); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_switch_channel(): " |
| "could not reset Tx queues\n"); |
| goto failed; |
| } |
| |
| /* set Tx rings WME properties */ |
| err = uath_wme_init(sc); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_switch_channel(): " |
| "could not init Tx queues\n"); |
| goto failed; |
| } |
| |
| err = uath_set_ledstate(sc, 0); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_switch_channel(): " |
| "could not set led state\n"); |
| goto failed; |
| } |
| |
| err = uath_flush(sc); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_switch_channel(): " |
| "could not flush pipes\n"); |
| goto failed; |
| } |
| |
| failed: |
| return (err); |
| } |
| |
| static int |
| uath_set_rxfilter(struct uath_softc *sc, uint32_t bits, uint32_t op) |
| { |
| struct uath_cmd_rx_filter rxfilter; |
| |
| rxfilter.bits = BE_32(bits); |
| rxfilter.op = BE_32(op); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_rxfilter(): " |
| "setting Rx filter=0x%x flags=0x%x\n", bits, op); |
| |
| return ((uath_cmd_write(sc, WDCMSG_RX_FILTER, &rxfilter, |
| sizeof (rxfilter), 0))); |
| } |
| |
| static int |
| uath_set_chan(struct uath_softc *sc, struct ieee80211_channel *c) |
| { |
| struct ieee80211com *ic = &sc->sc_ic; |
| struct uath_cmd_reset reset; |
| |
| bzero(&reset, sizeof (reset)); |
| if (IEEE80211_IS_CHAN_2GHZ(c)) |
| reset.flags |= BE_32(UATH_CHAN_2GHZ); |
| if (IEEE80211_IS_CHAN_5GHZ(c)) |
| reset.flags |= BE_32(UATH_CHAN_5GHZ); |
| /* NB: 11g =>'s 11b so don't specify both OFDM and CCK */ |
| if (UATH_IS_CHAN_OFDM(c)) |
| reset.flags |= BE_32(UATH_CHAN_OFDM); |
| else if (UATH_IS_CHAN_CCK(c)) |
| reset.flags |= BE_32(UATH_CHAN_CCK); |
| /* turbo can be used in either 2GHz or 5GHz */ |
| if (c->ich_flags & IEEE80211_CHAN_TURBO) |
| reset.flags |= BE_32(UATH_CHAN_TURBO); |
| |
| reset.freq = BE_32(c->ich_freq); |
| reset.maxrdpower = BE_32(50); /* XXX */ |
| reset.channelchange = BE_32(1); |
| reset.keeprccontent = BE_32(0); |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_set_chan(): " |
| "set channel %d, flags 0x%x freq %u\n", |
| ieee80211_chan2ieee(ic, c), |
| BE_32(reset.flags), BE_32(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, err; |
| |
| for (ac = 0; ac < 4; ac++) { |
| const uint32_t qid = BE_32(ac); |
| err = uath_cmd_write(sc, WDCMSG_RELEASE_TX_QUEUE, &qid, |
| sizeof (qid), 0); |
| if (err != UATH_SUCCESS) |
| break; |
| } |
| return (err); |
| } |
| |
| 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, err; |
| |
| for (ac = 0; ac < 4; ac++) { |
| qinfo.qid = BE_32(ac); |
| qinfo.len = BE_32(sizeof (qinfo.attr)); |
| qinfo.attr.priority = BE_32(ac); /* XXX */ |
| qinfo.attr.aifs = BE_32(uath_wme_11g[ac].aifsn); |
| qinfo.attr.logcwmin = BE_32(uath_wme_11g[ac].logcwmin); |
| qinfo.attr.logcwmax = BE_32(uath_wme_11g[ac].logcwmax); |
| qinfo.attr.mode = BE_32(uath_wme_11g[ac].acm); |
| qinfo.attr.qflags = BE_32(1); |
| qinfo.attr.bursttime = |
| BE_32(UATH_TXOP_TO_US(uath_wme_11g[ac].txop)); |
| |
| err = uath_cmd_write(sc, WDCMSG_SETUP_TX_QUEUE, &qinfo, |
| sizeof (qinfo), 0); |
| if (err != UATH_SUCCESS) |
| break; |
| } |
| return (err); |
| } |
| |
| static void |
| uath_stop_locked(void *arg) |
| { |
| struct uath_softc *sc = (struct uath_softc *)arg; |
| |
| /* flush data & control requests into the target */ |
| (void) uath_flush(sc); |
| |
| /* set a LED status to the disconnected. */ |
| (void) uath_set_ledstate(sc, 0); |
| |
| /* stop the target */ |
| (void) uath_cmd_write(sc, WDCMSG_TARGET_STOP, NULL, 0, 0); |
| |
| /* abort any pending transfers */ |
| usb_pipe_reset(sc->sc_dev, sc->rx_data_pipe, USB_FLAGS_SLEEP, NULL, 0); |
| usb_pipe_reset(sc->sc_dev, sc->tx_data_pipe, USB_FLAGS_SLEEP, NULL, 0); |
| usb_pipe_reset(sc->sc_dev, sc->tx_cmd_pipe, USB_FLAGS_SLEEP, NULL, 0); |
| } |
| |
| static int |
| uath_init_locked(void *arg) |
| { |
| struct uath_softc *sc = arg; |
| struct ieee80211com *ic = &sc->sc_ic; |
| uint32_t val; |
| int i, err; |
| |
| if (UATH_IS_RUNNING(sc)) |
| uath_stop_locked(sc); |
| |
| uath_init_data_queue(sc); |
| |
| /* reset variables */ |
| sc->sc_intrx_nextnum = sc->sc_msgid = 0; |
| |
| val = BE_32(0); |
| (void) uath_cmd_write(sc, WDCMSG_BIND, &val, sizeof (val), 0); |
| |
| /* set MAC address */ |
| (void) uath_config_multi(sc, CFG_MAC_ADDR, |
| ic->ic_macaddr, 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); |
| |
| err = uath_cmd_read(sc, WDCMSG_TARGET_START, NULL, 0, |
| &val, sizeof (val), UATH_CMD_FLAG_MAGIC); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_init_locked(): " |
| "could not start target\n"); |
| goto fail; |
| } |
| |
| UATH_DEBUG(UATH_DBG_MSG, "uath: uath_init_locked(): " |
| "%s returns handle: 0x%x\n", |
| uath_codename(WDCMSG_TARGET_START), BE_32(val)); |
| |
| /* set default channel */ |
| err = uath_switch_channel(sc, ic->ic_curchan); |
| if (err) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_init_locked(): " |
| "could not switch channel, error %d\n", err); |
| goto fail; |
| } |
| |
| val = BE_32(TARGET_DEVICE_AWAKE); |
| (void) uath_cmd_write(sc, WDCMSG_SET_PWR_MODE, &val, sizeof (val), 0); |
| /* XXX? check */ |
| (void) uath_cmd_write(sc, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0); |
| |
| for (i = 0; i < UATH_RX_DATA_LIST_COUNT; i++) { |
| err = uath_rx_data_xfer(sc); |
| if (err != UATH_SUCCESS) { |
| UATH_DEBUG(UATH_DBG_ERR, "uath: uath_init_locked(): " |
| "could not alloc rx xfer %x\n", i); |
| goto fail; |
| } |
| } |
| |
| /* enable Rx */ |
| (void) uath_set_rxfilter(sc, 0x0, UATH_FILTER_OP_INIT); |
| (void) uath_set_rxfilter(sc, |
| UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | |
| UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON, |
| UATH_FILTER_OP_SET); |
| |
| return (UATH_SUCCESS); |
|