/*	$OpenBSD: if_wi_usb.c,v 1.42 2007/06/14 10:11:15 mbalmer Exp $ */

/*
 * Copyright (c) 2003 Dale Rahn. 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.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, 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 DAMAGE.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 */
#include "bpfilter.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/device.h>
#include <sys/kthread.h>
#include <sys/tree.h>

#include <net/if.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/ip.h>
#include <netinet/if_ether.h>
#endif

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbdevs.h>

#define ROUNDUP64(x) (((x)+63) & ~63)

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_ioctl.h>

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#include <machine/bus.h>

#include <dev/rndvar.h>

#include <dev/ic/if_wireg.h>
#include <dev/ic/if_wi_ieee.h>
#include <dev/ic/if_wivar.h>

#include <dev/usb/if_wi_usb.h>

int wi_usb_do_transmit_sync(struct wi_usb_softc *wsc, struct wi_usb_chain *c, 
    void *ident);
void wi_usb_txeof(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status);
void wi_usb_txeof_frm(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status);
void wi_usb_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status);
void wi_usb_intr(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status);
void wi_usb_stop(struct wi_usb_softc *usc);
int wi_usb_tx_list_init(struct wi_usb_softc *usc);
int wi_usb_rx_list_init(struct wi_usb_softc *usc);
int wi_usb_open_pipes(struct wi_usb_softc *usc);
void wi_usb_cmdresp(struct wi_usb_chain *c);
void wi_usb_rridresp(struct wi_usb_chain *c);
void wi_usb_wridresp(struct wi_usb_chain *c);
void wi_usb_infofrm(struct wi_usb_chain *c, int len);
int wi_send_packet(struct wi_usb_softc *sc, int id);
void wi_usb_rxfrm(struct wi_usb_softc *usc, wi_usb_usbin *uin, int total_len);
void wi_usb_txfrm(struct wi_usb_softc *usc, wi_usb_usbin *uin, int total_len);
void wi_usb_start_thread(void *);

int wi_usb_tx_lock_try(struct wi_usb_softc *sc);
void wi_usb_tx_lock(struct wi_usb_softc *usc);
void wi_usb_tx_unlock(struct wi_usb_softc *usc);
void wi_usb_ctl_lock(struct wi_usb_softc *usc);
void wi_usb_ctl_unlock(struct wi_usb_softc *usc);

void wi_dump_data(void *buffer, int len);

void wi_usb_thread(void *arg);

#ifdef WI_USB_DEBUG
#define DPRINTF(x)      do { if (wi_usbdebug) printf x; } while (0)
#define DPRINTFN(n,x)   do { if (wi_usbdebug >= (n)) printf x; } while (0)
int     wi_usbdebug = 1;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

struct wi_usb_thread_info {
	int status;
	int dying;
	int idle;
};

/* thread status flags */
#define WI_START	0x01
#define WI_DYING	0x02
#define WI_INQUIRE	0x04
#define WI_WATCHDOG	0x08


struct wi_usb_softc {
	struct wi_softc		sc_wi;
#define wi_usb_dev sc_wi.sc_dev

	struct timeout		wi_usb_stat_ch;

	usbd_device_handle	wi_usb_udev;
	usbd_interface_handle	wi_usb_iface;
	u_int16_t		wi_usb_vendor;
	u_int16_t		wi_usb_product;
	int			wi_usb_ed[WI_USB_ENDPT_MAX];
	usbd_pipe_handle	wi_usb_ep[WI_USB_ENDPT_MAX];

	struct wi_usb_chain	wi_usb_tx_chain[WI_USB_TX_LIST_CNT];
	struct wi_usb_chain	wi_usb_rx_chain[WI_USB_RX_LIST_CNT];

	int			wi_usb_refcnt;
	char			wi_usb_dying;
	char			wi_usb_attached;
	int			wi_usb_intr_errs;
	struct timeval		wi_usb_rx_notice;

	int			wi_usb_pollpending;

	wi_usb_usbin		wi_usb_ibuf;
	int			wi_usb_tx_prod;
	int			wi_usb_tx_cons;
	int			wi_usb_tx_cnt;
	int			wi_usb_rx_prod;

	struct wi_ltv_gen	*ridltv;
	int			ridresperr;

	int			cmdresp;
	int			cmdresperr;
	int			txresp;
	int			txresperr;

	/* nummem (tx/mgmt) */
	int			wi_usb_nummem;
#define MAX_WI_NMEM 3
	void			*wi_usb_txmem[MAX_WI_NMEM];
	int			wi_usb_txmemsize[MAX_WI_NMEM];
	void			*wi_usb_rxmem;
	int			wi_usb_rxmemsize;

	void			*wi_info;
	void			*wi_rxframe;

	/* prevent multiple outstanding USB requests */
	int			wi_lock;
	int			wi_lockwait;

	/* prevent multiple command requests */
	int			wi_ctllock;
	int			wi_ctllockwait;
	struct proc		*wi_curproc;

	/* kthread */
	struct wi_usb_thread_info	*wi_thread_info;
	int			wi_resetonce;
};

struct wi_funcs wi_func_usb = {
        wi_cmd_usb,
        wi_read_record_usb,
        wi_write_record_usb,
        wi_alloc_nicmem_usb,
        wi_read_data_usb,
        wi_write_data_usb,
        wi_get_fid_usb,
        wi_init_usb,

        wi_start_usb,
        wi_ioctl_usb,
        wi_watchdog_usb,
        wi_inquire_usb,
};

/*
 * Various supported device vendors/products.
 */
const struct wi_usb_type {
	struct usb_devno	wi_usb_device;
	u_int16_t	wi_usb_flags;
	/* XXX */
} wi_usb_devs[] = {
	{{ USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_111 }, 0 },
	{{ USB_VENDOR_ACERW, USB_PRODUCT_ACERW_WARPLINK }, 0 },
	{{ USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_FREELAN }, 0 },
	{{ USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_PRISM_25 }, 0 },
	{{ USB_VENDOR_ACTIONTEC, USB_PRODUCT_ACTIONTEC_PRISM_25A }, 0 },
	{{ USB_VENDOR_ADAPTEC, USB_PRODUCT_ADAPTEC_AWN8020 }, 0 },
	{{ USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_WLAN }, 0 },
	{{ USB_VENDOR_ASUSTEK, USB_PRODUCT_ASUSTEK_WL140 }, 0 },
	{{ USB_VENDOR_AVERATEC, USB_PRODUCT_AVERATEC_USBWLAN }, 0 },
	{{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_W100 }, 0 },
	{{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_W200 }, 0 },
	{{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLANUSB }, 0 },
	{{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_WLUSB_11_KEY }, 0 },
	{{ USB_VENDOR_DELL, USB_PRODUCT_DELL_TM1180 }, 0 },
	{{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWL120F }, 0 },
	{{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWL122 }, 0 },
	{{ USB_VENDOR_INTEL, USB_PRODUCT_INTEL_I2011B }, 0 },
	{{ USB_VENDOR_INTERSIL, USB_PRODUCT_INTERSIL_PRISM_2X }, 0 },
	{{ USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBWNB11 }, 0 },
	{{ USB_VENDOR_JVC, USB_PRODUCT_JVC_MP_XP7250_WL }, 0 },
	{{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_WUSB11_25 }, 0 },
	{{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_WUSB12_11 }, 0 },
	{{ USB_VENDOR_LINKSYS3, USB_PRODUCT_LINKSYS3_WUSB11V30 }, 0 },
	{{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KB11 }, 0 },
	{{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KS11G }, 0 },
	{{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_S11 }, 0 },
	{{ USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_MN510 }, 0 },
	{{ USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_MA111NA }, 0 },
	{{ USB_VENDOR_PHEENET, USB_PRODUCT_PHEENET_WL503IA }, 0 },
	{{ USB_VENDOR_PHEENET, USB_PRODUCT_PHEENET_WM168B }, 0 },
	{{ USB_VENDOR_PLANEX, USB_PRODUCT_PLANEX_GW_US11H }, 0 },
	{{ USB_VENDOR_SIEMENS, USB_PRODUCT_SIEMENS_SPEEDSTREAM22 }, 0 },
	{{ USB_VENDOR_SITECOM2, USB_PRODUCT_SITECOM2_WL022 }, 0 },
	{{ USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_0193 }, 0 },
	{{ USB_VENDOR_TEKRAM, USB_PRODUCT_TEKRAM_ZYAIR_B200 }, 0 },
	{{ USB_VENDOR_USR, USB_PRODUCT_USR_USR1120 }, 0 },
	{{ USB_VENDOR_VIEWSONIC, USB_PRODUCT_VIEWSONIC_AIRSYNC }, 0 },
	{{ USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XI725 }, 0 },
	{{ USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XI735 }, 0 }
};
#define wi_usb_lookup(v, p) ((struct wi_usb_type *)usb_lookup(wi_usb_devs, v, p))

int wi_usb_match(struct device *, void *, void *); 
void wi_usb_attach(struct device *, struct device *, void *); 
int wi_usb_detach(struct device *, int); 
int wi_usb_activate(struct device *, enum devact); 

struct cfdriver wi_usb_cd = { 
	NULL, "wi_usb", DV_IFNET 
}; 

const struct cfattach wi_usb_ca = { 
	sizeof(struct wi_usb_softc), 
	wi_usb_match, 
	wi_usb_attach, 
	wi_usb_detach, 
	wi_usb_activate, 
};

int
wi_usb_match(struct device *parent, void *match, void *aux)
{
	struct usb_attach_arg	*uaa = aux;

	if (uaa->iface != NULL)
		return (UMATCH_NONE);

	return (wi_usb_lookup(uaa->vendor, uaa->product) != NULL ?
		UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
}


/*
 * Attach the interface. Allocate softc structures, do ifmedia
 * setup and ethernet/BPF attach.
 */
void
wi_usb_attach(struct device *parent, struct device *self, void *aux)
{
	struct wi_usb_softc	*sc = (struct wi_usb_softc *)self;
	struct usb_attach_arg	*uaa = aux;
	char			*devinfop;
/*	int			s; */
	usbd_device_handle	dev = uaa->device;
	usbd_interface_handle	iface;
	usbd_status		err;
	usb_interface_descriptor_t	*id;
	usb_endpoint_descriptor_t	*ed;
	int			 i;

	DPRINTFN(5,(" : wi_usb_attach: sc=%p", sc));

	err = usbd_set_config_no(dev, WI_USB_CONFIG_NO, 1);
	if (err) {
		printf("%s: setting config no failed\n",
		    sc->wi_usb_dev.dv_xname);
		return;
	}

	devinfop = usbd_devinfo_alloc(dev, 0);
	printf("\n%s: %s\n", sc->wi_usb_dev.dv_xname, devinfop);
	usbd_devinfo_free(devinfop);

	/* XXX - any tasks? */

	err = usbd_device2interface_handle(dev, WI_USB_IFACE_IDX, &iface);
	if (err) {
		printf("%s: getting interface handle failed\n",
		    sc->wi_usb_dev.dv_xname);
		return;
	}

	/* XXX - flags? */

	sc->wi_usb_udev = dev;
	sc->wi_usb_iface = iface;
	sc->wi_usb_product = uaa->product;
	sc->wi_usb_vendor = uaa->vendor;

	sc->sc_wi.wi_usb_cdata = sc;
	sc->sc_wi.wi_flags |= WI_FLAGS_BUS_USB;

	sc->wi_lock = 0;
	sc->wi_lockwait = 0;
	sc->wi_resetonce = 0;

	id = usbd_get_interface_descriptor(iface);

	/* Find endpoints. */
	for (i = 0; i < id->bNumEndpoints; i++) {
		ed = usbd_interface2endpoint_descriptor(iface, i);
		if (ed == NULL) {
			printf("%s: couldn't get endpoint descriptor %d\n",
			    sc->wi_usb_dev.dv_xname, i);
			return;
		}
		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
			sc->wi_usb_ed[WI_USB_ENDPT_RX] = ed->bEndpointAddress;
		} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
			   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
			sc->wi_usb_ed[WI_USB_ENDPT_TX] = ed->bEndpointAddress;
		} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
			   UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
			sc->wi_usb_ed[WI_USB_ENDPT_INTR] = ed->bEndpointAddress;
		}
	}

	sc->wi_usb_nummem = 0;

	/* attach wi device */

	if (wi_usb_rx_list_init(sc)) {
		printf("%s: rx list init failed\n",
		    sc->wi_usb_dev.dv_xname);
		return;
	}
	if (wi_usb_tx_list_init(sc)) {
		printf("%s: tx list init failed\n",
		    sc->wi_usb_dev.dv_xname);
		return;
	}

	if (wi_usb_open_pipes(sc)){
		printf("%s: open pipes failed\n",
		    sc->wi_usb_dev.dv_xname);
		return;
	}

	sc->wi_usb_attached = 1;

	kthread_create_deferred(wi_usb_start_thread, sc);

	usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->wi_usb_udev,
			   &sc->wi_usb_dev);
}

int
wi_usb_detach(struct device *self, int flags)
{
	struct wi_usb_softc	*sc = (struct wi_usb_softc *)self;
	struct ifnet		*ifp = WI_GET_IFP(sc);
	struct wi_softc		*wsc = &sc->sc_wi;
	int s;
	int err;

	sc->wi_usb_dying = 1;
	if (sc->wi_thread_info != NULL) {
		sc->wi_thread_info->dying = 1;

		sc->wi_thread_info->status |= WI_DYING;
		if (sc->wi_thread_info->idle)
			wakeup(sc->wi_thread_info);
	}

	if (!sc->wi_usb_attached) {
		/* Detached before attach finished, so just bail out. */
		return (0);
	}
	/* tasks? */

	s = splusb();
	/* detatch wi */

	if (!(wsc->wi_flags & WI_FLAGS_ATTACHED)) {
		printf("%s: already detached\n", sc->wi_usb_dev.dv_xname);
		splx(s);
		return (0);
	}

	wi_detach(&sc->sc_wi);

	wsc->wi_flags = 0;

	ether_ifdetach(ifp);
	if_detach(ifp);

	sc->wi_usb_attached = 0;

	if (--sc->wi_usb_refcnt >= 0) {
		/* Wait for processes to go away. */
		usb_detach_wait(&sc->wi_usb_dev);
	}

	while (sc->wi_usb_nummem) {
		sc->wi_usb_nummem--;
		if (sc->wi_usb_txmem[sc->wi_usb_nummem] != NULL)
			free(sc->wi_usb_txmem[sc->wi_usb_nummem], M_DEVBUF);
		sc->wi_usb_txmem[sc->wi_usb_nummem] = NULL;
	}

	if (sc->wi_usb_ep[WI_USB_ENDPT_INTR] != NULL) {
		err = usbd_abort_pipe(sc->wi_usb_ep[WI_USB_ENDPT_INTR]);
		if (err) {
			printf("%s: abort intr pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		err = usbd_close_pipe(sc->wi_usb_ep[WI_USB_ENDPT_INTR]);
		if (err) {
			printf("%s: close intr pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		sc->wi_usb_ep[WI_USB_ENDPT_INTR] = NULL;
	}
	if (sc->wi_usb_ep[WI_USB_ENDPT_TX] != NULL) {
		usbd_abort_pipe(sc->wi_usb_ep[WI_USB_ENDPT_TX]);
		if (err) {
			printf("%s: abort tx pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		err = usbd_close_pipe(sc->wi_usb_ep[WI_USB_ENDPT_TX]);
		if (err) {
			printf("%s: close tx pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		sc->wi_usb_ep[WI_USB_ENDPT_TX] = NULL;
	}
	if (sc->wi_usb_ep[WI_USB_ENDPT_RX] != NULL) {
		usbd_abort_pipe(sc->wi_usb_ep[WI_USB_ENDPT_RX]);
		if (err) {
			printf("%s: abort rx pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		err = usbd_close_pipe(sc->wi_usb_ep[WI_USB_ENDPT_RX]);
		if (err) {
			printf("%s: close rx pipe failed: %s\n",
			    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		}
		sc->wi_usb_ep[WI_USB_ENDPT_RX] = NULL;
	}

	splx(s);

	usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->wi_usb_udev,
	    &sc->wi_usb_dev);
	return (0);
}

int
wi_send_packet(struct wi_usb_softc *sc, int id)
{
	struct wi_usb_chain	*c;
	struct wi_frame		*wibuf;
	int			total_len, rnd_len;
	int			err;

	c = &sc->wi_usb_tx_chain[0];

	DPRINTFN(10,("%s: %s: id=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, id));

	/* assemble packet from write_data buffer */
	if (id == 0 || id == 1) {
		/* tx_lock acquired before wi_start() */
		wibuf = sc->wi_usb_txmem[id];

		total_len = sizeof (struct wi_frame) +
		    letoh16(wibuf->wi_dat_len);
		rnd_len = ROUNDUP64(total_len);
		if ((total_len > sc->wi_usb_txmemsize[id]) ||
		   (rnd_len > WI_USB_BUFSZ )){
			printf("invalid packet len: %x memsz %x max %x\n",
			    total_len, sc->wi_usb_txmemsize[id], WI_USB_BUFSZ);
			total_len = sc->wi_usb_txmemsize[id];

			err = EIO;
			goto err_ret;
		}

		sc->txresp = WI_CMD_TX;
		sc->txresperr = 0;

		bcopy(wibuf, c->wi_usb_buf, total_len);

		bzero(((char *)c->wi_usb_buf)+total_len,
		    rnd_len - total_len);

		/* zero old packet for next TX */
		bzero(wibuf, total_len);

		total_len = rnd_len;

		DPRINTFN(5,("%s: %s: id=%x len=%x\n",
		    sc->wi_usb_dev.dv_xname, __func__, id, total_len));

		usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_TX],
		    c, c->wi_usb_buf, rnd_len,
		    USBD_FORCE_SHORT_XFER | USBD_NO_COPY,
		    WI_USB_TX_TIMEOUT, wi_usb_txeof_frm);

		err = usbd_transfer(c->wi_usb_xfer);
		if (err != USBD_IN_PROGRESS && err != USBD_NORMAL_COMPLETION) {
			printf("%s: %s: error=%s\n",
			    sc->wi_usb_dev.dv_xname, __func__,
			    usbd_errstr(err));
			/* Stop the interface from process context. */
			wi_usb_stop(sc);
			err = EIO;
		} else {
			err = 0;
		}

		DPRINTFN(5,("%s: %s: exit err=%x\n",
		    sc->wi_usb_dev.dv_xname, __func__, err));
err_ret:
		return err;
	}
	printf("%s:%s: invalid packet id sent %x\n",
	    sc->wi_usb_dev.dv_xname, __func__, id);
	return 0;
}

int
wi_cmd_usb(struct wi_softc *wsc, int cmd, int val0, int val1, int val2)
{
	struct wi_usb_chain	*c;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;
	struct wi_cmdreq	*pcmd;
	int			total_len, rnd_len;
	int			err;

	DPRINTFN(5,("%s: %s: enter cmd=%x %x %x %x\n",
	    sc->wi_usb_dev.dv_xname, __func__, cmd, val0, val1, val2));

	if ((cmd & WI_CMD_CODE_MASK) == WI_CMD_TX) {
		return wi_send_packet(sc, val0);
	}


	if ((cmd & WI_CMD_CODE_MASK) == WI_CMD_INI) {
		/* free alloc_nicmem regions */
		while (sc->wi_usb_nummem) {
			sc->wi_usb_nummem--;
			free(sc->wi_usb_txmem[sc->wi_usb_nummem], M_DEVBUF);
			sc->wi_usb_txmem[sc->wi_usb_nummem] = NULL;
		}

#if 0
		/* if this is the first time, init, otherwise do not?? */
		if (sc->wi_resetonce) {
			return 0;
		} else
			sc->wi_resetonce = 1;
#endif
	}

	wi_usb_ctl_lock(sc);

	wi_usb_tx_lock(sc);

	c = &sc->wi_usb_tx_chain[0];
	pcmd = c->wi_usb_buf;


	total_len = sizeof (struct wi_cmdreq);
	rnd_len = ROUNDUP64(total_len);
	if (rnd_len > WI_USB_BUFSZ) {
		printf("read_record buf size err %x %x\n", 
		    rnd_len, WI_USB_BUFSZ);
		err = EIO;
		goto err_ret;
	}

	sc->cmdresp = cmd;
	sc->cmdresperr = 0;

	pcmd->type = htole16(WI_USB_CMDREQ);
	pcmd->cmd  = htole16(cmd);
	pcmd->param0  = htole16(val0);
	pcmd->param1  = htole16(val1);
	pcmd->param2  = htole16(val2);

	bzero(((char*)pcmd)+total_len, rnd_len - total_len);

	total_len = rnd_len;

	usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_TX],
	    c, c->wi_usb_buf, rnd_len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY,
	    WI_USB_TX_TIMEOUT, wi_usb_txeof);

	err = wi_usb_do_transmit_sync(sc, c, &sc->cmdresperr);

	if (err == 0)
		err = sc->cmdresperr;

	sc->cmdresperr = 0;

err_ret:
	wi_usb_tx_unlock(sc);

	wi_usb_ctl_unlock(sc);

	DPRINTFN(5,("%s: %s: exit err=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, err));
	return err;
}


int
wi_read_record_usb(struct wi_softc *wsc, struct wi_ltv_gen *ltv)
{
	struct wi_usb_chain	*c;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;
	struct wi_rridreq	*prid;
	int			total_len, rnd_len;
	int			err;
	struct wi_ltv_gen	*oltv, p2ltv;

	DPRINTFN(5,("%s: %s: enter rid=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, ltv->wi_type));

	/* Do we need to deal with these here, as in _io version?
	 * WI_RID_ENCRYPTION -> WI_RID_P2_ENCRYPTION
	 * WI_RID_TX_CRYPT_KEY -> WI_RID_P2_TX_CRYPT_KEY
	 */
	if (wsc->sc_firmware_type != WI_LUCENT) {
		oltv = ltv;
		switch (ltv->wi_type) {
		case WI_RID_ENCRYPTION:
			p2ltv.wi_type = WI_RID_P2_ENCRYPTION;
			p2ltv.wi_len = 2;
			ltv = &p2ltv;
			break;
		case WI_RID_TX_CRYPT_KEY:
			if (ltv->wi_val > WI_NLTV_KEYS)
				return (EINVAL);
			p2ltv.wi_type = WI_RID_P2_TX_CRYPT_KEY;
			p2ltv.wi_len = 2;
			ltv = &p2ltv;
			break;
		}
	}

	wi_usb_tx_lock(sc);

	c = &sc->wi_usb_tx_chain[0];
	prid = c->wi_usb_buf;

	total_len = sizeof(struct wi_rridreq);
	rnd_len = ROUNDUP64(total_len);

	if (rnd_len > WI_USB_BUFSZ) {
		printf("read_record buf size err %x %x\n", 
		    rnd_len, WI_USB_BUFSZ);
		wi_usb_tx_unlock(sc);
		return EIO;
	}

	sc->ridltv = ltv;
	sc->ridresperr = 0;

	prid->type = htole16(WI_USB_RRIDREQ);
	prid->frmlen = htole16(2);	/* variable size? */
	prid->rid  = htole16(ltv->wi_type);

	bzero(((char*)prid)+total_len, rnd_len - total_len);

	usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_TX],
	    c, c->wi_usb_buf, rnd_len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY,
	    WI_USB_TX_TIMEOUT, wi_usb_txeof);

	DPRINTFN(10,("%s: %s: total_len=%x, wilen %d\n",
	    sc->wi_usb_dev.dv_xname, __func__, total_len, ltv->wi_len));

	err = wi_usb_do_transmit_sync(sc, c, &sc->ridresperr);

	/* Do we need to deal with these here, as in _io version?
	 *
	 * WI_RID_TX_RATE
	 * WI_RID_CUR_TX_RATE
	 * WI_RID_ENCRYPTION
	 * WI_RID_TX_CRYPT_KEY
	 * WI_RID_CNFAUTHMODE
	 */
	if (ltv->wi_type == WI_RID_PORTTYPE && wsc->wi_ptype == WI_PORTTYPE_IBSS
	    && ltv->wi_val == wsc->wi_ibss_port) {
		/*
		 * Convert vendor IBSS port type to WI_PORTTYPE_IBSS.
		 * Since Lucent uses port type 1 for BSS *and* IBSS we
		 * have to rely on wi_ptype to distinguish this for us.
		 */
		ltv->wi_val = htole16(WI_PORTTYPE_IBSS);
	} else if (wsc->sc_firmware_type != WI_LUCENT) {
		int v;

		switch (oltv->wi_type) {
		case WI_RID_TX_RATE:
		case WI_RID_CUR_TX_RATE:
			switch (letoh16(ltv->wi_val)) {
			case 1: v = 1; break;
			case 2: v = 2; break;
			case 3:	v = 6; break;
			case 4: v = 5; break;
			case 7: v = 7; break;
			case 8: v = 11; break;
			case 15: v = 3; break;
			default: v = 0x100 + letoh16(ltv->wi_val); break;
			}
			oltv->wi_val = htole16(v);
			break;
		case WI_RID_ENCRYPTION:
			oltv->wi_len = 2;
			if (ltv->wi_val & htole16(0x01))
				oltv->wi_val = htole16(1);
			else
				oltv->wi_val = htole16(0);
			break;
		case WI_RID_TX_CRYPT_KEY:
		case WI_RID_CNFAUTHMODE:
			oltv->wi_len = 2;
			oltv->wi_val = ltv->wi_val;
			break;
		}
	}

	if (err == 0)
		err = sc->ridresperr;

	sc->ridresperr = 0;

	wi_usb_tx_unlock(sc);

	DPRINTFN(5,("%s: %s: exit err=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, err));
	return err;
}

int
wi_write_record_usb(struct wi_softc *wsc, struct wi_ltv_gen *ltv)
{
	struct wi_usb_chain	*c;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;
	struct wi_wridreq	*prid;
	int			total_len, rnd_len;
	int			err;
	struct wi_ltv_gen	p2ltv;
	u_int16_t		val = 0;
	int			i;

	DPRINTFN(5,("%s: %s: enter rid=%x wi_len %d copying %x\n",
	    sc->wi_usb_dev.dv_xname, __func__, ltv->wi_type, ltv->wi_len,
	    (ltv->wi_len-1)*2 ));

	/* Do we need to deal with these here, as in _io version?
	 * WI_PORTTYPE_IBSS -> WI_RID_PORTTYPE
	 * RID_TX_RATE munging
	 * RID_ENCRYPTION
	 * WI_RID_TX_CRYPT_KEY
	 * WI_RID_DEFLT_CRYPT_KEYS
	 */
	if (ltv->wi_type == WI_RID_PORTTYPE &&
	    letoh16(ltv->wi_val) == WI_PORTTYPE_IBSS) {
		/* Convert WI_PORTTYPE_IBSS to vendor IBSS port type. */
		p2ltv.wi_type = WI_RID_PORTTYPE;
		p2ltv.wi_len = 2;
		p2ltv.wi_val = wsc->wi_ibss_port;
		ltv = &p2ltv;
	} else if (wsc->sc_firmware_type != WI_LUCENT) {
		int v;

		switch (ltv->wi_type) {
		case WI_RID_TX_RATE:
			p2ltv.wi_type = WI_RID_TX_RATE;
			p2ltv.wi_len = 2;
			switch (letoh16(ltv->wi_val)) {
			case 1: v = 1; break;
			case 2: v = 2; break;
			case 3:	v = 15; break;
			case 5: v = 4; break;
			case 6: v = 3; break;
			case 7: v = 7; break;
			case 11: v = 8; break;
			default: return EINVAL;
			}
			p2ltv.wi_val = htole16(v);
			ltv = &p2ltv;
			break;
		case WI_RID_ENCRYPTION:
			p2ltv.wi_type = WI_RID_P2_ENCRYPTION;
			p2ltv.wi_len = 2;
			if (ltv->wi_val & htole16(0x01)) {
				val = PRIVACY_INVOKED;
				/*
				 * If using shared key WEP we must set the
				 * EXCLUDE_UNENCRYPTED bit.  Symbol cards
				 * need this bit set even when not using
				 * shared key. We can't just test for
				 * IEEE80211_AUTH_SHARED since Symbol cards
				 * have 2 shared key modes.
				 */
				if (wsc->wi_authtype != IEEE80211_AUTH_OPEN ||
				    wsc->sc_firmware_type == WI_SYMBOL)
					val |= EXCLUDE_UNENCRYPTED;

				switch (wsc->wi_crypto_algorithm) {
				case WI_CRYPTO_FIRMWARE_WEP:
					/*
					 * TX encryption is broken in
					 * Host AP mode.
					 */
					if (wsc->wi_ptype == WI_PORTTYPE_HOSTAP)
						val |= HOST_ENCRYPT;
					break;
				case WI_CRYPTO_SOFTWARE_WEP:
					val |= HOST_ENCRYPT|HOST_DECRYPT;
					break;
				}
				p2ltv.wi_val = htole16(val);
			} else
				p2ltv.wi_val = htole16(HOST_ENCRYPT | HOST_DECRYPT);
			ltv = &p2ltv;
			break;
		case WI_RID_TX_CRYPT_KEY:
			if (ltv->wi_val > WI_NLTV_KEYS)
				return (EINVAL);
			p2ltv.wi_type = WI_RID_P2_TX_CRYPT_KEY;
			p2ltv.wi_len = 2;
			p2ltv.wi_val = ltv->wi_val;
			ltv = &p2ltv;
			break;
		case WI_RID_DEFLT_CRYPT_KEYS: {
				int error;
				int keylen;
				struct wi_ltv_str ws;
				struct wi_ltv_keys *wk;

				wk = (struct wi_ltv_keys *)ltv;
				keylen = wk->wi_keys[wsc->wi_tx_key].wi_keylen;
				keylen = letoh16(keylen);

				for (i = 0; i < 4; i++) {
					bzero(&ws, sizeof(ws));
					ws.wi_len = (keylen > 5) ? 8 : 4;
					ws.wi_type = WI_RID_P2_CRYPT_KEY0 + i;
					bcopy(&wk->wi_keys[i].wi_keydat,
					    ws.wi_str, keylen);
					error = wi_write_record_usb(wsc,
					    (struct wi_ltv_gen *)&ws);
					if (error)
						return (error);
				}
			}
			return (0);
		}
	}

	wi_usb_tx_lock(sc);

	c = &sc->wi_usb_tx_chain[0];

	prid = c->wi_usb_buf;

	total_len = sizeof(prid->type) + sizeof(prid->frmlen) +
	    sizeof(prid->rid) + (ltv->wi_len-1)*2;
	rnd_len = ROUNDUP64(total_len);
	if (rnd_len > WI_USB_BUFSZ) {
		printf("write_record buf size err %x %x\n", 
		    rnd_len, WI_USB_BUFSZ);
		wi_usb_tx_unlock(sc);
		return EIO;
	}

	prid->type = htole16(WI_USB_WRIDREQ);
	prid->frmlen = htole16(ltv->wi_len);
	prid->rid  = htole16(ltv->wi_type);
	if (ltv->wi_len > 1)
		bcopy((u_int8_t *)&ltv->wi_val, (u_int8_t *)&prid->data[0],
		    (ltv->wi_len-1)*2);

	bzero(((char*)prid)+total_len, rnd_len - total_len);

	usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_TX],
	    c, c->wi_usb_buf, rnd_len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY,
	    WI_USB_TX_TIMEOUT, wi_usb_txeof);

	err = wi_usb_do_transmit_sync(sc, c, &sc->ridresperr);

	if (err == 0)
		err = sc->ridresperr;

	sc->ridresperr = 0;

	wi_usb_tx_unlock(sc);

	DPRINTFN(5,("%s: %s: exit err=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, err));
	return err;
}

/*
 * This is an ugly compat portion to emulate the I/O which writes
 * a packet or management information
 * The data is copied into local memory for the requested
 * 'id' then on the wi_cmd WI_CMD_TX, the id argument
 * will identify which buffer to use
 */
int
wi_alloc_nicmem_usb(struct wi_softc *wsc, int len, int *id)
{
	int nmem;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;

	DPRINTFN(10,("%s: %s: enter len=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, len));

	/*
	 * NOTE THIS IS A USB DEVICE WHICH WILL LIKELY HAVE MANY
	 * CONNECTS/DISCONNECTS, FREE THIS MEMORY XXX XXX XXX !!! !!!
	 */
	nmem = sc->wi_usb_nummem++;

	if (nmem >= MAX_WI_NMEM) {
		sc->wi_usb_nummem--;
		return ENOMEM;
	}

	sc->wi_usb_txmem[nmem] = malloc(len, M_DEVBUF, M_WAITOK);
	if (sc->wi_usb_txmem[nmem] == NULL) {
		sc->wi_usb_nummem--;
		return ENOMEM;
	}
	sc->wi_usb_txmemsize[nmem] = len;

	*id = nmem;
	return 0;
}

/*
 * this is crazy, we skip the first 16 bits of the buf so that it
 * can be used as the 'type' of the usb transfer.
 */


int
wi_write_data_usb(struct wi_softc *wsc, int id, int off, caddr_t buf, int len)
{
	u_int8_t	*ptr;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;

	DPRINTFN(10,("%s: %s: id %x off %x len %d\n",
	    sc->wi_usb_dev.dv_xname, __func__, id, off, len));

	if (id < 0 && id >= sc->wi_usb_nummem)
		return EIO;

	ptr = (u_int8_t *)(sc->wi_usb_txmem[id]) + off;

	if (len + off > sc->wi_usb_txmemsize[id])
		return EIO;
	DPRINTFN(10,("%s: %s: completed \n",
	    sc->wi_usb_dev.dv_xname, __func__));

	bcopy(buf, ptr, len);
	return 0;
}

/*
 * On the prism I/O, this read_data points to the hardware buffer
 * which contains the
 */
int
wi_read_data_usb(struct wi_softc *wsc, int id, int off, caddr_t buf, int len)
{
	u_int8_t	*ptr;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;

	DPRINTFN(10,("%s: %s: id %x off %x len %d\n",
	    sc->wi_usb_dev.dv_xname, __func__, id, off, len));

	if (id == 0x1001 && sc->wi_info != NULL)
		ptr = (u_int8_t *)sc->wi_info + off;
	else if (id == 0x1000 && sc->wi_rxframe != NULL)
		ptr = (u_int8_t *)sc->wi_rxframe + off;
	else if (id >= 0 && id < sc->wi_usb_nummem) {

		if (sc->wi_usb_txmem[id] == NULL)
			return EIO;
		if (len + off > sc->wi_usb_txmemsize[id])
			return EIO;

		ptr = (u_int8_t *)(sc->wi_usb_txmem[id]) + off;
	} else
		return EIO;

	if (id < sc->wi_usb_nummem) {
		ptr = (u_int8_t *)(sc->wi_usb_txmem[id]) + off;

		if (len + off > sc->wi_usb_txmemsize[id])
			return EIO;
	}

	bcopy(ptr, buf, len);
	return 0;
}

void
wi_usb_stop(struct wi_usb_softc *sc)
{
	DPRINTFN(1,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname,__func__));
	/* XXX */

	/* Stop transfers */
}

int
wi_usb_do_transmit_sync(struct wi_usb_softc *sc, struct wi_usb_chain *c,
    void *ident)
{
	usbd_status		err;

	DPRINTFN(10,("%s: %s:\n",
	    sc->wi_usb_dev.dv_xname, __func__));

	sc->wi_usb_refcnt++;
	err = usbd_transfer(c->wi_usb_xfer);
	if (err != USBD_IN_PROGRESS && err != USBD_NORMAL_COMPLETION) {
		printf("%s: %s error=%s\n",
		    sc->wi_usb_dev.dv_xname, __func__,
		    usbd_errstr(err));
		/* Stop the interface from process context. */
		wi_usb_stop(sc);
		err = EIO;
		goto done;
	}
	err = tsleep(ident, PRIBIO, "wiTXsync", hz*1);
	if (err) {
		DPRINTFN(1,("%s: %s: err %x\n",
		    sc->wi_usb_dev.dv_xname, __func__, err));
		err = ETIMEDOUT;
	}
done:
	if (--sc->wi_usb_refcnt < 0)
		usb_detach_wakeup(&sc->wi_usb_dev);
	return err;
}


/*
 * A command/rrid/wrid  was sent to the chip. It's safe for us to clean up
 * the list buffers.
 */

void
wi_usb_txeof(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status)
{
	struct wi_usb_chain	*c = priv;
	struct wi_usb_softc	*sc = c->wi_usb_sc;

	int			s;

	if (sc->wi_usb_dying)
		return;

	s = splnet();

	DPRINTFN(10,("%s: %s: enter status=%d\n", sc->wi_usb_dev.dv_xname,
		    __func__, status));

	if (status != USBD_NORMAL_COMPLETION) {
		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) {
			splx(s);
			return;
		}
		printf("%s: usb error on tx: %s\n", sc->wi_usb_dev.dv_xname,
		    usbd_errstr(status));
		if (status == USBD_STALLED) {
			sc->wi_usb_refcnt++;
			usbd_clear_endpoint_stall_async(
			    sc->wi_usb_ep[WI_USB_ENDPT_TX]);
			if (--sc->wi_usb_refcnt < 0)
				usb_detach_wakeup(&sc->wi_usb_dev);
		}
		splx(s);
		return;
	}

	splx(s);
}

/*
 * A packet was sent to the chip. It's safe for us to clean up
 * the list buffers.
 */

void
wi_usb_txeof_frm(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status)
{
	struct wi_usb_chain	*c = priv;
	struct wi_usb_softc	*sc = c->wi_usb_sc;
	struct wi_softc		*wsc = &sc->sc_wi;
	struct ifnet		*ifp = &wsc->sc_ic.ic_if;

	int			s;
	int			err = 0;

	if (sc->wi_usb_dying)
		return;

	s = splnet();

	DPRINTFN(10,("%s: %s: enter status=%d\n", sc->wi_usb_dev.dv_xname,
		    __func__, status));

	if (status != USBD_NORMAL_COMPLETION) {
		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) {
			splx(s);
			return;
		}
		printf("%s: usb error on tx: %s\n", sc->wi_usb_dev.dv_xname,
		    usbd_errstr(status));
		if (status == USBD_STALLED) {
			sc->wi_usb_refcnt++;
			usbd_clear_endpoint_stall_async(
			    sc->wi_usb_ep[WI_USB_ENDPT_TX]);
			if (--sc->wi_usb_refcnt < 0)
				usb_detach_wakeup(&sc->wi_usb_dev);
		}
		splx(s);
		return;
	}

	if (status)
		err = WI_EV_TX_EXC;

	wi_txeof(wsc, err);

	wi_usb_tx_unlock(sc);

	if (!IFQ_IS_EMPTY(&ifp->if_snd))
		wi_start_usb(ifp);

	splx(s);
}

int
wi_usb_rx_list_init(struct wi_usb_softc *sc)
{
	struct wi_usb_chain	*c;
	int			i;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	for (i = 0; i < WI_USB_RX_LIST_CNT; i++) {
		c = &sc->wi_usb_rx_chain[i];
		c->wi_usb_sc = sc;
		c->wi_usb_idx = i;
		if (c->wi_usb_xfer != NULL) {
			printf("UGH RX\n");
		}
		if (c->wi_usb_xfer == NULL) {
			c->wi_usb_xfer = usbd_alloc_xfer(sc->wi_usb_udev);
			if (c->wi_usb_xfer == NULL)
				return (ENOBUFS);
			c->wi_usb_buf = usbd_alloc_buffer(c->wi_usb_xfer,
			    WI_USB_BUFSZ);
			if (c->wi_usb_buf == NULL)
				return (ENOBUFS); /* XXX free xfer */
		}
	}

	return (0);
}

int
wi_usb_tx_list_init(struct wi_usb_softc *sc)
{
	struct wi_usb_chain	*c;
	int			i;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	for (i = 0; i < WI_USB_TX_LIST_CNT; i++) {
		c = &sc->wi_usb_tx_chain[i];
		c->wi_usb_sc = sc;
		c->wi_usb_idx = i;
		c->wi_usb_mbuf = NULL;
		if (c->wi_usb_xfer != NULL) {
			printf("UGH TX\n");
		}
		if (c->wi_usb_xfer == NULL) {
			c->wi_usb_xfer = usbd_alloc_xfer(sc->wi_usb_udev);
			if (c->wi_usb_xfer == NULL)
				return (ENOBUFS);
			c->wi_usb_buf = usbd_alloc_buffer(c->wi_usb_xfer,
			    WI_USB_BUFSZ);
			if (c->wi_usb_buf == NULL)
				return (ENOBUFS);
		}
	}

	return (0);
}

int
wi_usb_open_pipes(struct wi_usb_softc *sc)
{
	usbd_status		err;
	int			error = 0;
	struct wi_usb_chain	*c;
	int			i;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname,__func__));

	sc->wi_usb_refcnt++;

	/* Open RX and TX pipes. */
	err = usbd_open_pipe(sc->wi_usb_iface, sc->wi_usb_ed[WI_USB_ENDPT_RX],
	    USBD_EXCLUSIVE_USE, &sc->wi_usb_ep[WI_USB_ENDPT_RX]);
	if (err) {
		printf("%s: open rx pipe failed: %s\n",
		    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		error = EIO;
		goto done;
	}

	err = usbd_open_pipe(sc->wi_usb_iface, sc->wi_usb_ed[WI_USB_ENDPT_TX],
	    USBD_EXCLUSIVE_USE, &sc->wi_usb_ep[WI_USB_ENDPT_TX]);
	if (err) {
		printf("%s: open tx pipe failed: %s\n",
		    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		error = EIO;
		goto done;
	}

	/* is this used? */
	err = usbd_open_pipe_intr(sc->wi_usb_iface,
	    sc->wi_usb_ed[WI_USB_ENDPT_INTR], USBD_EXCLUSIVE_USE,
	    &sc->wi_usb_ep[WI_USB_ENDPT_INTR], sc, &sc->wi_usb_ibuf,
	    WI_USB_INTR_PKTLEN, wi_usb_intr, WI_USB_INTR_INTERVAL);
	if (err) {
		printf("%s: open intr pipe failed: %s\n",
		    sc->wi_usb_dev.dv_xname, usbd_errstr(err));
		error = EIO;
		goto done;
	}

	/* Start up the receive pipe. */
	for (i = 0; i < WI_USB_RX_LIST_CNT; i++) {
		c = &sc->wi_usb_rx_chain[i];
		usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_RX],
		    c, c->wi_usb_buf, WI_USB_BUFSZ,
		    USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT,
		    wi_usb_rxeof);
		DPRINTFN(10,("%s: %s: start read\n", sc->wi_usb_dev.dv_xname,
			    __func__));
		usbd_transfer(c->wi_usb_xfer);
	}

done:
	if (--sc->wi_usb_refcnt < 0)
		usb_detach_wakeup(&sc->wi_usb_dev);

	return (error);
}

/*
 * This is a bit of a kludge, however wi_rxeof and wi_update_stats
 * call wi_get_fid to determine where the data associated with
 * the transaction is located, the returned id is then used to
 * wi_read_data the information out.
 *
 * This code returns which 'fid' should be used. The results are only valid
 * during a wi_usb_rxeof because the data is received packet is 'held'
 * an a variable for reading by wi_read_data_usb for that period.
 *
 * for magic numbers this uses  0x1000, 0x1001 for rx/info
 */

int
wi_get_fid_usb(struct wi_softc *sc, int fid)
{
	switch (fid) {
	case WI_RX_FID:
		return 0x1000;
	case WI_INFO_FID:
		return 0x1001;
	default:
		return 0x1111;
	}

}

int
wi_usb_activate(struct device *self, enum devact act)
{
	struct wi_usb_softc *sc = (struct wi_usb_softc *)self;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	switch (act) {
	case DVACT_ACTIVATE:
		break;

	case DVACT_DEACTIVATE:
		sc->wi_usb_dying = 1;
		sc->wi_thread_info->dying = 1;
		break;
	}
	return (0);
}

#if 0
void
wi_dump_data(void *buffer, int len)
{
	int i;
	for (i = 0; i < len; i++) {
		if (((i) % 16) == 0)
			printf("\n %02x:", i);
		printf(" %02x",
		    ((uint8_t *)(buffer))[i]);

	}
	printf("\n");

}
#endif

/*
 * A frame has been received.
 */
void
wi_usb_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
{
	struct wi_usb_chain	*c = priv;
	struct wi_usb_softc	*sc = c->wi_usb_sc;
	wi_usb_usbin		*uin;
	int			total_len = 0;
	u_int16_t		rtype;

	if (sc->wi_usb_dying)
		return;

	DPRINTFN(10,("%s: %s: enter status=%d\n", sc->wi_usb_dev.dv_xname,
		    __func__, status));


	if (status != USBD_NORMAL_COMPLETION) {
		if (status == USBD_NOT_STARTED || status == USBD_IOERROR
		    || status == USBD_CANCELLED) {
			printf("%s: %u usb errors on rx: %s\n",
			    sc->wi_usb_dev.dv_xname, 1,
			    /* sc->wi_usb_rx_errs, */
			    usbd_errstr(status));
			return;
		}
#if 0
		sc->wi_usb_rx_errs++;
		if (usbd_ratecheck(&sc->wi_usb_rx_notice)) {
			printf("%s: %u usb errors on rx: %s\n",
			    sc->wi_usb_dev.dv_xname, sc->wi_usb_rx_errs,
			    usbd_errstr(status));
			sc->wi_usb_rx_errs = 0;
		}
#endif
		if (status == USBD_STALLED) {
			sc->wi_usb_refcnt++;
			usbd_clear_endpoint_stall_async(
			    sc->wi_usb_ep[WI_USB_ENDPT_RX]);
			if (--sc->wi_usb_refcnt < 0)
				usb_detach_wakeup(&sc->wi_usb_dev);
		}
		goto done;
	}

	usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL);

	if (total_len < 6) /* short XXX */
		goto done;

	uin = (wi_usb_usbin *)(c->wi_usb_buf);

	rtype = letoh16(uin->type);


#if 0
	wi_dump_data(c->wi_usb_buf, total_len);
#endif

	if (WI_USB_ISRXFRM(rtype)) {
		wi_usb_rxfrm(sc, uin, total_len);
		goto done;
	}
	if (WI_USB_ISTXFRM(rtype)) {
		DPRINTFN(2,("%s: %s: txfrm type %x\n",
		    sc->wi_usb_dev.dv_xname, __func__, rtype));
		wi_usb_txfrm(sc, uin, total_len);
		goto done;
	}

	switch (rtype) {
	case WI_USB_INFOFRM:
		/* info packet, INFO_FID hmm */
		DPRINTFN(10,("%s: %s: infofrm type %x\n",
		    sc->wi_usb_dev.dv_xname, __func__, rtype));
		wi_usb_infofrm(c, total_len);
		break;
	case WI_USB_CMDRESP:
		wi_usb_cmdresp(c);
		break;
	case WI_USB_WRIDRESP:
		wi_usb_wridresp(c);
		break;
	case WI_USB_RRIDRESP:
		wi_usb_rridresp(c);
		break;
	case WI_USB_WMEMRESP:
		/* Not currently used */
		DPRINTFN(2,("%s: %s: wmemresp type %x\n",
		    sc->wi_usb_dev.dv_xname, __func__, rtype));
		break;
	case WI_USB_RMEMRESP:
		/* Not currently used */
		DPRINTFN(2,("%s: %s: rmemresp type %x\n",
		    sc->wi_usb_dev.dv_xname, __func__, rtype));
		break;
	case WI_USB_BUFAVAIL:
		printf("wi_usb: received USB_BUFAVAIL packet\n"); /* XXX */
		break;
	case WI_USB_ERROR:
		printf("wi_usb: received USB_ERROR packet\n"); /* XXX */
		break;
#if 0
	default:
		printf("wi_usb: received Unknown packet 0x%x len %x\n",
		    rtype, total_len);
		wi_dump_data(c->wi_usb_buf, total_len);
#endif
	}

 done:
	/* Setup new transfer. */
	usbd_setup_xfer(c->wi_usb_xfer, sc->wi_usb_ep[WI_USB_ENDPT_RX],
	    c, c->wi_usb_buf, WI_USB_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY,
	    USBD_NO_TIMEOUT, wi_usb_rxeof);
	sc->wi_usb_refcnt++;
	usbd_transfer(c->wi_usb_xfer);
	if (--sc->wi_usb_refcnt < 0)
		usb_detach_wakeup(&sc->wi_usb_dev);

	DPRINTFN(10,("%s: %s: start rx\n", sc->wi_usb_dev.dv_xname,
		    __func__));
}

void
wi_usb_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
{
	struct wi_usb_softc	*sc = priv;

	DPRINTFN(2,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	if (sc->wi_usb_dying)
		return;

	if (status != USBD_NORMAL_COMPLETION) {
		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
			return;

		if (status == USBD_STALLED) {
			sc->wi_usb_refcnt++;
			usbd_clear_endpoint_stall_async(
			    sc->wi_usb_ep[WI_USB_ENDPT_RX]);
			if (--sc->wi_usb_refcnt < 0)
				usb_detach_wakeup(&sc->wi_usb_dev);
		}
		return;
	}
	/* XXX oerrors or collisions? */
}
void
wi_usb_cmdresp(struct wi_usb_chain *c)
{
	struct wi_cmdresp *presp = (struct wi_cmdresp *)(c->wi_usb_buf);
	u_int16_t status = letoh16(presp->status);
	struct wi_usb_softc	*sc = c->wi_usb_sc;
	uint16_t type;
	uint16_t cmdresperr;

	type = htole16(presp->type);
	cmdresperr = letoh16(presp->resp0);
	DPRINTFN(10,("%s: %s: enter type=%x, status=%x, cmdresp=%x, "
	    "resp=%x,%x,%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, type, status, sc->cmdresp,
	    cmdresperr, letoh16(presp->resp1),
	    letoh16(presp->resp2)));

	/* XXX */
	if (sc->cmdresp != (status & WI_STAT_CMD_CODE)) {
		DPRINTFN(1,("%s: cmd ty %x st %x cmd %x failed %x\n",
		    sc->wi_usb_dev.dv_xname,
			type, status, sc->cmdresp, cmdresperr));
		return;
	}

	sc->cmdresperr = (status & WI_STAT_CMD_RESULT) >> 8;

	sc->cmdresp = 0; /* good value for idle == INI ?? XXX  */

	wakeup(&sc->cmdresperr);
}
void
wi_usb_rridresp(struct wi_usb_chain *c)
{
	struct wi_rridresp *presp = (struct wi_rridresp *)(c->wi_usb_buf);
	u_int16_t frmlen = letoh16(presp->frmlen);
	struct wi_usb_softc	*sc = c->wi_usb_sc;
	struct wi_ltv_gen *ltv;
	uint16_t rid;

	rid = letoh16(presp->rid);
	ltv =  sc->ridltv;

	if (ltv == 0) {
		DPRINTFN(5,("%s: %s: enter ltv = 0 rid=%x len %d\n",
		    sc->wi_usb_dev.dv_xname, __func__, rid,
		    frmlen));
		return;
	}

	DPRINTFN(5,("%s: %s: enter rid=%x expecting %x len %d exptlen %d\n",
	    sc->wi_usb_dev.dv_xname, __func__, rid, ltv->wi_type,
	    frmlen, ltv->wi_len));

	rid = letoh16(presp->rid);

	if (rid != ltv->wi_type) {
		sc->ridresperr = EIO;
		return;
	}

	if (frmlen > ltv->wi_len) {
		sc->ridresperr = ENOSPC;
		sc->ridltv = 0;
		wakeup(&sc->ridresperr);
		return;
	}

	ltv->wi_len = frmlen;

	DPRINTFN(10,("%s: %s: copying %d frmlen %d\n",
	    sc->wi_usb_dev.dv_xname, __func__, (ltv->wi_len-1)*2,
	    frmlen));

	if (ltv->wi_len > 1)
		bcopy(&presp->data[0], (u_int8_t *)&ltv->wi_val,
		    (ltv->wi_len-1)*2);

	sc->ridresperr = 0;
	sc->ridltv = 0;
	wakeup(&sc->ridresperr);

}

void
wi_usb_wridresp(struct wi_usb_chain *c)
{
	struct wi_wridresp *presp = (struct wi_wridresp *)(c->wi_usb_buf);
	struct wi_usb_softc	*sc = c->wi_usb_sc;
	uint16_t status;

	status = letoh16(presp->status);

	DPRINTFN(10,("%s: %s: enter status=%x\n",
	    sc->wi_usb_dev.dv_xname, __func__, status));

	sc->ridresperr = (status & WI_STAT_CMD_RESULT) >> 8;
	sc->ridltv = 0;
	wakeup(&sc->ridresperr);
}

void
wi_usb_infofrm(struct wi_usb_chain *c, int len)
{
	struct wi_usb_softc	*sc = c->wi_usb_sc;

	DPRINTFN(10,("%s: %s: enter\n",
	    sc->wi_usb_dev.dv_xname, __func__));

	sc->wi_info = ((char *)c->wi_usb_buf) + 2;
	wi_update_stats(&sc->sc_wi);
	sc->wi_info = NULL;
}

void
wi_usb_txfrm(struct wi_usb_softc *sc, wi_usb_usbin *uin, int total_len)
{
	u_int16_t		status;
	int 			s;
	struct wi_softc		*wsc = &sc->sc_wi;
	struct ifnet		*ifp = &wsc->sc_ic.ic_if;

	s = splnet();
	status = letoh16(uin->type); /* XXX -- type == status */


	DPRINTFN(2,("%s: %s: enter status=%d\n",
	    sc->wi_usb_dev.dv_xname, __func__, status));

	if (sc->txresp == WI_CMD_TX) {
		sc->txresperr=status;
		sc->txresp = 0;
		wakeup(&sc->txresperr);
	} else {
		if (status != 0) /* XXX */
			wi_watchdog_usb(ifp);
	DPRINTFN(1,("%s: %s: txresp not expected status=%d \n",
	    sc->wi_usb_dev.dv_xname, __func__, status));
	}

	splx(s);
}
void
wi_usb_rxfrm(struct wi_usb_softc *sc, wi_usb_usbin *uin, int total_len)
{
	int s;

	DPRINTFN(5,("%s: %s: enter len=%d\n",
	    sc->wi_usb_dev.dv_xname, __func__, total_len));

	s = splnet();

	sc->wi_rxframe = (void *)uin;

	wi_rxeof(&sc->sc_wi);

	sc->wi_rxframe = NULL;

	splx(s);

}


void
wi_usb_start_thread(void *arg)
{
	struct wi_usb_softc	*sc = arg;
	kthread_create (wi_usb_thread, arg, NULL, sc->wi_usb_dev.dv_xname);
}

void
wi_start_usb(struct ifnet *ifp)
{
	struct wi_softc		*wsc;
	struct wi_usb_softc	*sc;
	int s;

	wsc = ifp->if_softc;
	sc  = wsc->wi_usb_cdata;

	s = splnet();

	DPRINTFN(5,("%s: %s:\n",
	    sc->wi_usb_dev.dv_xname, __func__));

	if (wi_usb_tx_lock_try(sc)) {
		/* lock acquired do start now */
		wi_func_io.f_start(ifp);
	} else {
		sc->wi_thread_info->status |= WI_START;
		if (sc->wi_thread_info->idle)
			wakeup(sc->wi_thread_info);
	}

	splx(s);
}

/* 
 * inquire is called from interrupt context (timeout)
 * It is not possible to sleep in interrupt context so it is necessary
 * to signal the kernel thread to perform the action.
 */
void
wi_init_usb(struct wi_softc *wsc)
{
	DPRINTFN(5,("%s: %s:\n", WI_PRT_ARG(wsc), __func__));

	wi_usb_ctl_lock(wsc->wi_usb_cdata);
	wi_func_io.f_init(wsc);
	wi_usb_ctl_unlock(wsc->wi_usb_cdata);
}


/* 
 * inquire is called from interrupt context (timeout)
 * It is not possible to sleep in interrupt context so it is necessary
 * to signal the kernel thread to perform the action.
 */
void
wi_inquire_usb(void *xsc)
{
	struct wi_softc		*wsc = xsc;
	struct wi_usb_softc	*sc = wsc->wi_usb_cdata;
	int s;


	s = splnet();

	DPRINTFN(2,("%s: %s:\n",
	    sc->wi_usb_dev.dv_xname, __func__));

	sc->wi_thread_info->status |= WI_INQUIRE;

	if (sc->wi_thread_info->idle)
		wakeup(sc->wi_thread_info);
	splx(s);
}

/* 
 * Watchdog is normally called from interrupt context (timeout)
 * It is not possible to sleep in interrupt context so it is necessary
 * to signal the kernel thread to perform the action.
 */
void
wi_watchdog_usb(struct ifnet *ifp)
{
	struct wi_softc		*wsc;
	struct wi_usb_softc	*sc;
	int s;

	wsc = ifp->if_softc;
	sc = wsc->wi_usb_cdata;

	s = splnet();

	DPRINTFN(5,("%s: %s: ifp %x\n",
	    sc->wi_usb_dev.dv_xname, __func__, ifp));

	sc->wi_thread_info->status |= WI_WATCHDOG;

	if (sc->wi_thread_info->idle)
		wakeup(sc->wi_thread_info);
	splx(s);
}

/*
 * ioctl will always be called from a user context, 
 * therefore it is possible to sleep in the calling context
 * acquire the lock and call the real ioctl fucntion directly 
 */
int
wi_ioctl_usb(struct ifnet *ifp, u_long command, caddr_t data)
{
	struct wi_softc		*wsc;
	int err;

	wsc = ifp->if_softc;

	wi_usb_ctl_lock(wsc->wi_usb_cdata);
	err = wi_func_io.f_ioctl(ifp, command, data);
	wi_usb_ctl_unlock(wsc->wi_usb_cdata);
	return err;
}

void
wi_usb_thread(void *arg)
{
	struct wi_usb_softc *sc = arg;
	struct wi_usb_thread_info *wi_thread_info;
	int s;

	wi_thread_info = malloc(sizeof(*wi_thread_info), M_DEVBUF, M_WAITOK);

	/*
	 * is there a remote possibility that the device could
	 * be removed before the kernel thread starts up?
	 */

	sc->wi_usb_refcnt++;

	sc->wi_thread_info = wi_thread_info;
	wi_thread_info->dying = 0;
	wi_thread_info->status = 0;

	wi_usb_ctl_lock(sc);

	wi_attach(&sc->sc_wi, &wi_func_usb);

	wi_usb_ctl_unlock(sc);

	for(;;) {
		if (wi_thread_info->dying) { 
			if (--sc->wi_usb_refcnt < 0)
				usb_detach_wakeup(&sc->wi_usb_dev);
			kthread_exit(0);
		}

		DPRINTFN(5,("%s: %s: dying %x status %x\n",
		    sc->wi_usb_dev.dv_xname, __func__,
			wi_thread_info->dying, wi_thread_info->status));

		wi_usb_ctl_lock(sc);

		DPRINTFN(5,("%s: %s: starting %x\n",
		    sc->wi_usb_dev.dv_xname, __func__,
		    wi_thread_info->status));

		s = splusb();
		if (wi_thread_info->status & WI_START) {
			wi_thread_info->status &= ~WI_START;
			wi_usb_tx_lock(sc);
			wi_func_io.f_start(&sc->sc_wi.sc_ic.ic_if);
			/*
			 * tx_unlock is explicitly missing here
			 * it is done in txeof_frm
			 */
		} else if (wi_thread_info->status & WI_INQUIRE) {
			wi_thread_info->status &= ~WI_INQUIRE;
			wi_func_io.f_inquire(&sc->sc_wi);
		} else if (wi_thread_info->status & WI_WATCHDOG) {
			wi_thread_info->status &= ~WI_WATCHDOG;
			wi_func_io.f_watchdog( &sc->sc_wi.sc_ic.ic_if);
		}
		splx(s);

		DPRINTFN(5,("%s: %s: ending %x\n",
		    sc->wi_usb_dev.dv_xname, __func__,
		    wi_thread_info->status));
		wi_usb_ctl_unlock(sc);

		if (wi_thread_info->status == 0) {
			s = splnet();
			wi_thread_info->idle = 1;
			tsleep(wi_thread_info, PRIBIO, "wiIDL", 0);
			wi_thread_info->idle = 0;
			splx(s);
		}
	}
}

int
wi_usb_tx_lock_try(struct wi_usb_softc *sc)
{
	int s;

	s = splnet();

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	if (sc->wi_lock != 0) {
		return 0; /* failed to aquire lock */
	}

	sc->wi_lock = 1;

	splx(s);

	return 1;
}
void
wi_usb_tx_lock(struct wi_usb_softc *sc)
{
	int s;

	s = splnet();

	again:
	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	if (sc->wi_lock != 0) {
		sc->wi_lockwait++;
		DPRINTFN(10,("%s: %s: busy %d\n", sc->wi_usb_dev.dv_xname,
		__func__, sc->wi_lockwait ));
		tsleep(&sc->wi_lock, PRIBIO, "witxl", 0);
	}

	if (sc->wi_lock != 0)
		goto again;
	sc->wi_lock = 1;

	splx(s);

	return;

}

void
wi_usb_tx_unlock(struct wi_usb_softc *sc)
{
	int s;
	s = splnet();

	sc->wi_lock = 0;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	if (sc->wi_lockwait) {
		DPRINTFN(10,("%s: %s: waking\n",
		    sc->wi_usb_dev.dv_xname, __func__));
		sc->wi_lockwait = 0;
		wakeup(&sc->wi_lock);
	}

	splx(s);
}

void
wi_usb_ctl_lock(struct wi_usb_softc *sc)
{
	int s;

	s = splnet();

	again:
	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname,
	    __func__));

	if (sc->wi_ctllock != 0) {
		if (curproc == sc->wi_curproc) {
			/* allow recursion */
			sc->wi_ctllock++;
			splx(s);
			return;
		}
		sc->wi_ctllockwait++;
		DPRINTFN(10,("%s: %s: busy %d\n", sc->wi_usb_dev.dv_xname,
		__func__, sc->wi_ctllockwait ));
		tsleep(&sc->wi_ctllock, PRIBIO, "wiusbthr", 0);
	}

	if (sc->wi_ctllock != 0)
		goto again;
	sc->wi_ctllock++;
	sc->wi_curproc = curproc;

	splx(s);

	return;

}

void
wi_usb_ctl_unlock(struct wi_usb_softc *sc)
{
	int s;

	s = splnet();

	sc->wi_ctllock--;

	DPRINTFN(10,("%s: %s: enter\n", sc->wi_usb_dev.dv_xname, __func__));

	if (sc->wi_ctllock == 0 && sc->wi_ctllockwait) {
		DPRINTFN(10,("%s: %s: waking\n",
		    sc->wi_usb_dev.dv_xname, __func__));
		sc->wi_ctllockwait = 0;
		sc->wi_curproc = 0;
		wakeup(&sc->wi_ctllock);
	}

	splx(s);
}