58f30773d8
request whose body has some datas through the default pipe. Tested by: Nikos Vassiliadis <nvass9573 at gmx.com>
1505 lines
40 KiB
C
1505 lines
40 KiB
C
/*-
|
|
* Copyright (c) 2005
|
|
* Bill Paul <wpaul@windriver.com>. 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.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Bill Paul.
|
|
* 4. Neither the name of the author nor the names of any co-contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``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 Bill Paul OR THE VOICES IN HIS HEAD
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/condvar.h>
|
|
#include <sys/module.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <machine/bus.h>
|
|
#include <sys/bus.h>
|
|
|
|
#include <sys/queue.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_media.h>
|
|
#include <net80211/ieee80211_var.h>
|
|
#include <net80211/ieee80211_ioctl.h>
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usbdi.h>
|
|
#include <dev/usb/usbdi_util.h>
|
|
#include <dev/usb/usb_busdma.h>
|
|
#include <dev/usb/usb_device.h>
|
|
#include <dev/usb/usb_request.h>
|
|
|
|
#include <compat/ndis/pe_var.h>
|
|
#include <compat/ndis/cfg_var.h>
|
|
#include <compat/ndis/resource_var.h>
|
|
#include <compat/ndis/ntoskrnl_var.h>
|
|
#include <compat/ndis/ndis_var.h>
|
|
#include <compat/ndis/hal_var.h>
|
|
#include <compat/ndis/usbd_var.h>
|
|
#include <dev/if_ndis/if_ndisvar.h>
|
|
|
|
static driver_object usbd_driver;
|
|
static usb_callback_t usbd_non_isoc_callback;
|
|
static usb_callback_t usbd_ctrl_callback;
|
|
|
|
#define USBD_CTRL_READ_PIPE 0
|
|
#define USBD_CTRL_WRITE_PIPE 1
|
|
#define USBD_CTRL_MAX_PIPE 2
|
|
#define USBD_CTRL_READ_BUFFER_SP 256
|
|
#define USBD_CTRL_WRITE_BUFFER_SP 256
|
|
#define USBD_CTRL_READ_BUFFER_SIZE \
|
|
(sizeof(struct usb_device_request) + USBD_CTRL_READ_BUFFER_SP)
|
|
#define USBD_CTRL_WRITE_BUFFER_SIZE \
|
|
(sizeof(struct usb_device_request) + USBD_CTRL_WRITE_BUFFER_SP)
|
|
static struct usb_config usbd_default_epconfig[USBD_CTRL_MAX_PIPE] = {
|
|
[USBD_CTRL_READ_PIPE] = {
|
|
.type = UE_CONTROL,
|
|
.endpoint = 0x00, /* control pipe */
|
|
.direction = UE_DIR_ANY,
|
|
.if_index = 0,
|
|
.bufsize = USBD_CTRL_READ_BUFFER_SIZE,
|
|
.flags = { .short_xfer_ok = 1, },
|
|
.callback = &usbd_ctrl_callback,
|
|
.timeout = 5000, /* 5 seconds */
|
|
},
|
|
[USBD_CTRL_WRITE_PIPE] = {
|
|
.type = UE_CONTROL,
|
|
.endpoint = 0x00, /* control pipe */
|
|
.direction = UE_DIR_ANY,
|
|
.if_index = 0,
|
|
.bufsize = USBD_CTRL_WRITE_BUFFER_SIZE,
|
|
.flags = { .proxy_buffer = 1, },
|
|
.callback = &usbd_ctrl_callback,
|
|
.timeout = 5000, /* 5 seconds */
|
|
}
|
|
};
|
|
|
|
static int32_t usbd_func_bulkintr(irp *);
|
|
static int32_t usbd_func_vendorclass(irp *);
|
|
static int32_t usbd_func_selconf(irp *);
|
|
static int32_t usbd_func_abort_pipe(irp *);
|
|
static usb_error_t usbd_setup_endpoint(irp *, uint8_t,
|
|
struct usb_endpoint_descriptor *);
|
|
static usb_error_t usbd_setup_endpoint_default(irp *, uint8_t);
|
|
static usb_error_t usbd_setup_endpoint_one(irp *, uint8_t,
|
|
struct ndisusb_ep *, struct usb_config *);
|
|
static int32_t usbd_func_getdesc(irp *);
|
|
static union usbd_urb *usbd_geturb(irp *);
|
|
static struct ndisusb_ep*usbd_get_ndisep(irp *, usb_endpoint_descriptor_t *);
|
|
static int32_t usbd_iodispatch(device_object *, irp *);
|
|
static int32_t usbd_ioinvalid(device_object *, irp *);
|
|
static int32_t usbd_pnp(device_object *, irp *);
|
|
static int32_t usbd_power(device_object *, irp *);
|
|
static void usbd_irpcancel(device_object *, irp *);
|
|
static int32_t usbd_submit_urb(irp *);
|
|
static int32_t usbd_urb2nt(int32_t);
|
|
static void usbd_task(device_object *, void *);
|
|
static int32_t usbd_taskadd(irp *, unsigned);
|
|
static void usbd_xfertask(device_object *, void *);
|
|
static void dummy(void);
|
|
|
|
static union usbd_urb *USBD_CreateConfigurationRequestEx(
|
|
usb_config_descriptor_t *,
|
|
struct usbd_interface_list_entry *);
|
|
static union usbd_urb *USBD_CreateConfigurationRequest(
|
|
usb_config_descriptor_t *,
|
|
uint16_t *);
|
|
static void USBD_GetUSBDIVersion(usbd_version_info *);
|
|
static usb_interface_descriptor_t *USBD_ParseConfigurationDescriptorEx(
|
|
usb_config_descriptor_t *, void *, int32_t, int32_t,
|
|
int32_t, int32_t, int32_t);
|
|
static usb_interface_descriptor_t *USBD_ParseConfigurationDescriptor(
|
|
usb_config_descriptor_t *, uint8_t, uint8_t);
|
|
|
|
/*
|
|
* We need to wrap these functions because these need `context switch' from
|
|
* Windows to UNIX before it's called.
|
|
*/
|
|
static funcptr usbd_iodispatch_wrap;
|
|
static funcptr usbd_ioinvalid_wrap;
|
|
static funcptr usbd_pnp_wrap;
|
|
static funcptr usbd_power_wrap;
|
|
static funcptr usbd_irpcancel_wrap;
|
|
static funcptr usbd_task_wrap;
|
|
static funcptr usbd_xfertask_wrap;
|
|
|
|
int
|
|
usbd_libinit(void)
|
|
{
|
|
image_patch_table *patch;
|
|
int i;
|
|
|
|
patch = usbd_functbl;
|
|
while (patch->ipt_func != NULL) {
|
|
windrv_wrap((funcptr)patch->ipt_func,
|
|
(funcptr *)&patch->ipt_wrap,
|
|
patch->ipt_argcnt, patch->ipt_ftype);
|
|
patch++;
|
|
}
|
|
|
|
windrv_wrap((funcptr)usbd_ioinvalid,
|
|
(funcptr *)&usbd_ioinvalid_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_iodispatch,
|
|
(funcptr *)&usbd_iodispatch_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_pnp,
|
|
(funcptr *)&usbd_pnp_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_power,
|
|
(funcptr *)&usbd_power_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_irpcancel,
|
|
(funcptr *)&usbd_irpcancel_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_task,
|
|
(funcptr *)&usbd_task_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
windrv_wrap((funcptr)usbd_xfertask,
|
|
(funcptr *)&usbd_xfertask_wrap, 2, WINDRV_WRAP_STDCALL);
|
|
|
|
/* Create a fake USB driver instance. */
|
|
|
|
windrv_bus_attach(&usbd_driver, "USB Bus");
|
|
|
|
/* Set up our dipatch routine. */
|
|
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
|
|
usbd_driver.dro_dispatch[i] =
|
|
(driver_dispatch)usbd_ioinvalid_wrap;
|
|
|
|
usbd_driver.dro_dispatch[IRP_MJ_INTERNAL_DEVICE_CONTROL] =
|
|
(driver_dispatch)usbd_iodispatch_wrap;
|
|
usbd_driver.dro_dispatch[IRP_MJ_DEVICE_CONTROL] =
|
|
(driver_dispatch)usbd_iodispatch_wrap;
|
|
usbd_driver.dro_dispatch[IRP_MJ_POWER] =
|
|
(driver_dispatch)usbd_power_wrap;
|
|
usbd_driver.dro_dispatch[IRP_MJ_PNP] =
|
|
(driver_dispatch)usbd_pnp_wrap;
|
|
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
usbd_libfini(void)
|
|
{
|
|
image_patch_table *patch;
|
|
|
|
patch = usbd_functbl;
|
|
while (patch->ipt_func != NULL) {
|
|
windrv_unwrap(patch->ipt_wrap);
|
|
patch++;
|
|
}
|
|
|
|
windrv_unwrap(usbd_ioinvalid_wrap);
|
|
windrv_unwrap(usbd_iodispatch_wrap);
|
|
windrv_unwrap(usbd_pnp_wrap);
|
|
windrv_unwrap(usbd_power_wrap);
|
|
windrv_unwrap(usbd_irpcancel_wrap);
|
|
windrv_unwrap(usbd_task_wrap);
|
|
windrv_unwrap(usbd_xfertask_wrap);
|
|
|
|
free(usbd_driver.dro_drivername.us_buf, M_DEVBUF);
|
|
|
|
return(0);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_iodispatch(dobj, ip)
|
|
device_object *dobj;
|
|
irp *ip;
|
|
{
|
|
device_t dev = dobj->do_devext;
|
|
int32_t status;
|
|
struct io_stack_location *irp_sl;
|
|
|
|
irp_sl = IoGetCurrentIrpStackLocation(ip);
|
|
switch (irp_sl->isl_parameters.isl_ioctl.isl_iocode) {
|
|
case IOCTL_INTERNAL_USB_SUBMIT_URB:
|
|
IRP_NDIS_DEV(ip) = dev;
|
|
|
|
status = usbd_submit_urb(ip);
|
|
break;
|
|
default:
|
|
device_printf(dev, "ioctl 0x%x isn't supported\n",
|
|
irp_sl->isl_parameters.isl_ioctl.isl_iocode);
|
|
status = USBD_STATUS_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
if (status == USBD_STATUS_PENDING)
|
|
return (STATUS_PENDING);
|
|
|
|
ip->irp_iostat.isb_status = usbd_urb2nt(status);
|
|
if (status != USBD_STATUS_SUCCESS)
|
|
ip->irp_iostat.isb_info = 0;
|
|
return (ip->irp_iostat.isb_status);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_ioinvalid(dobj, ip)
|
|
device_object *dobj;
|
|
irp *ip;
|
|
{
|
|
device_t dev = dobj->do_devext;
|
|
struct io_stack_location *irp_sl;
|
|
|
|
irp_sl = IoGetCurrentIrpStackLocation(ip);
|
|
device_printf(dev, "invalid I/O dispatch %d:%d\n", irp_sl->isl_major,
|
|
irp_sl->isl_minor);
|
|
|
|
ip->irp_iostat.isb_status = STATUS_FAILURE;
|
|
ip->irp_iostat.isb_info = 0;
|
|
|
|
IoCompleteRequest(ip, IO_NO_INCREMENT);
|
|
|
|
return (STATUS_FAILURE);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_pnp(dobj, ip)
|
|
device_object *dobj;
|
|
irp *ip;
|
|
{
|
|
device_t dev = dobj->do_devext;
|
|
struct io_stack_location *irp_sl;
|
|
|
|
irp_sl = IoGetCurrentIrpStackLocation(ip);
|
|
device_printf(dev, "%s: unsupported I/O dispatch %d:%d\n",
|
|
__func__, irp_sl->isl_major, irp_sl->isl_minor);
|
|
|
|
ip->irp_iostat.isb_status = STATUS_FAILURE;
|
|
ip->irp_iostat.isb_info = 0;
|
|
|
|
IoCompleteRequest(ip, IO_NO_INCREMENT);
|
|
|
|
return (STATUS_FAILURE);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_power(dobj, ip)
|
|
device_object *dobj;
|
|
irp *ip;
|
|
{
|
|
device_t dev = dobj->do_devext;
|
|
struct io_stack_location *irp_sl;
|
|
|
|
irp_sl = IoGetCurrentIrpStackLocation(ip);
|
|
device_printf(dev, "%s: unsupported I/O dispatch %d:%d\n",
|
|
__func__, irp_sl->isl_major, irp_sl->isl_minor);
|
|
|
|
ip->irp_iostat.isb_status = STATUS_FAILURE;
|
|
ip->irp_iostat.isb_info = 0;
|
|
|
|
IoCompleteRequest(ip, IO_NO_INCREMENT);
|
|
|
|
return (STATUS_FAILURE);
|
|
}
|
|
|
|
/* Convert USBD_STATUS to NTSTATUS */
|
|
static int32_t
|
|
usbd_urb2nt(status)
|
|
int32_t status;
|
|
{
|
|
|
|
switch (status) {
|
|
case USBD_STATUS_SUCCESS:
|
|
return (STATUS_SUCCESS);
|
|
case USBD_STATUS_DEVICE_GONE:
|
|
return (STATUS_DEVICE_NOT_CONNECTED);
|
|
case USBD_STATUS_PENDING:
|
|
return (STATUS_PENDING);
|
|
case USBD_STATUS_NOT_SUPPORTED:
|
|
return (STATUS_NOT_IMPLEMENTED);
|
|
case USBD_STATUS_NO_MEMORY:
|
|
return (STATUS_NO_MEMORY);
|
|
case USBD_STATUS_REQUEST_FAILED:
|
|
return (STATUS_NOT_SUPPORTED);
|
|
case USBD_STATUS_CANCELED:
|
|
return (STATUS_CANCELLED);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (STATUS_FAILURE);
|
|
}
|
|
|
|
/* Convert FreeBSD's usb_error_t to USBD_STATUS */
|
|
static int32_t
|
|
usbd_usb2urb(int status)
|
|
{
|
|
|
|
switch (status) {
|
|
case USB_ERR_NORMAL_COMPLETION:
|
|
return (USBD_STATUS_SUCCESS);
|
|
case USB_ERR_PENDING_REQUESTS:
|
|
return (USBD_STATUS_PENDING);
|
|
case USB_ERR_TIMEOUT:
|
|
return (USBD_STATUS_TIMEOUT);
|
|
case USB_ERR_SHORT_XFER:
|
|
return (USBD_STATUS_ERROR_SHORT_TRANSFER);
|
|
case USB_ERR_IOERROR:
|
|
return (USBD_STATUS_XACT_ERROR);
|
|
case USB_ERR_NOMEM:
|
|
return (USBD_STATUS_NO_MEMORY);
|
|
case USB_ERR_INVAL:
|
|
return (USBD_STATUS_REQUEST_FAILED);
|
|
case USB_ERR_NOT_STARTED:
|
|
case USB_ERR_TOO_DEEP:
|
|
case USB_ERR_NO_POWER:
|
|
return (USBD_STATUS_DEVICE_GONE);
|
|
case USB_ERR_CANCELLED:
|
|
return (USBD_STATUS_CANCELED);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (USBD_STATUS_NOT_SUPPORTED);
|
|
}
|
|
|
|
static union usbd_urb *
|
|
usbd_geturb(ip)
|
|
irp *ip;
|
|
{
|
|
struct io_stack_location *irp_sl;
|
|
|
|
irp_sl = IoGetCurrentIrpStackLocation(ip);
|
|
|
|
return (irp_sl->isl_parameters.isl_others.isl_arg1);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_submit_urb(ip)
|
|
irp *ip;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
int32_t status;
|
|
union usbd_urb *urb;
|
|
|
|
urb = usbd_geturb(ip);
|
|
/*
|
|
* In a case of URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER,
|
|
* USBD_URB_STATUS(urb) would be set at callback functions like
|
|
* usbd_intr() or usbd_xfereof().
|
|
*/
|
|
switch (urb->uu_hdr.uuh_func) {
|
|
case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
|
|
status = usbd_func_bulkintr(ip);
|
|
if (status != USBD_STATUS_SUCCESS &&
|
|
status != USBD_STATUS_PENDING)
|
|
USBD_URB_STATUS(urb) = status;
|
|
break;
|
|
case URB_FUNCTION_VENDOR_DEVICE:
|
|
case URB_FUNCTION_VENDOR_INTERFACE:
|
|
case URB_FUNCTION_VENDOR_ENDPOINT:
|
|
case URB_FUNCTION_VENDOR_OTHER:
|
|
case URB_FUNCTION_CLASS_DEVICE:
|
|
case URB_FUNCTION_CLASS_INTERFACE:
|
|
case URB_FUNCTION_CLASS_ENDPOINT:
|
|
case URB_FUNCTION_CLASS_OTHER:
|
|
status = usbd_func_vendorclass(ip);
|
|
USBD_URB_STATUS(urb) = status;
|
|
break;
|
|
case URB_FUNCTION_SELECT_CONFIGURATION:
|
|
status = usbd_func_selconf(ip);
|
|
USBD_URB_STATUS(urb) = status;
|
|
break;
|
|
case URB_FUNCTION_ABORT_PIPE:
|
|
status = usbd_func_abort_pipe(ip);
|
|
USBD_URB_STATUS(urb) = status;
|
|
break;
|
|
case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE:
|
|
status = usbd_func_getdesc(ip);
|
|
USBD_URB_STATUS(urb) = status;
|
|
break;
|
|
default:
|
|
device_printf(dev, "func 0x%x isn't supported\n",
|
|
urb->uu_hdr.uuh_func);
|
|
USBD_URB_STATUS(urb) = status = USBD_STATUS_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_func_getdesc(ip)
|
|
irp *ip;
|
|
{
|
|
#define NDISUSB_GETDESC_MAXRETRIES 3
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct usbd_urb_control_descriptor_request *ctldesc;
|
|
uint16_t actlen;
|
|
uint32_t len;
|
|
union usbd_urb *urb;
|
|
usb_config_descriptor_t *cdp;
|
|
usb_error_t status;
|
|
|
|
urb = usbd_geturb(ip);
|
|
ctldesc = &urb->uu_ctldesc;
|
|
if (ctldesc->ucd_desctype == UDESC_CONFIG) {
|
|
/*
|
|
* The NDIS driver is not allowed to change the
|
|
* config! There is only one choice!
|
|
*/
|
|
cdp = usbd_get_config_descriptor(sc->ndisusb_dev);
|
|
if (cdp == NULL) {
|
|
status = USB_ERR_INVAL;
|
|
goto exit;
|
|
}
|
|
if (cdp->bDescriptorType != UDESC_CONFIG) {
|
|
device_printf(dev, "bad desc %d\n",
|
|
cdp->bDescriptorType);
|
|
status = USB_ERR_INVAL;
|
|
goto exit;
|
|
}
|
|
/* get minimum length */
|
|
len = MIN(UGETW(cdp->wTotalLength), ctldesc->ucd_trans_buflen);
|
|
/* copy out config descriptor */
|
|
memcpy(ctldesc->ucd_trans_buf, cdp, len);
|
|
/* set actual length */
|
|
actlen = len;
|
|
status = USB_ERR_NORMAL_COMPLETION;
|
|
} else {
|
|
NDISUSB_LOCK(sc);
|
|
status = usbd_req_get_desc(sc->ndisusb_dev, &sc->ndisusb_mtx,
|
|
&actlen, ctldesc->ucd_trans_buf, 2,
|
|
ctldesc->ucd_trans_buflen, ctldesc->ucd_langid,
|
|
ctldesc->ucd_desctype, ctldesc->ucd_idx,
|
|
NDISUSB_GETDESC_MAXRETRIES);
|
|
NDISUSB_UNLOCK(sc);
|
|
}
|
|
exit:
|
|
if (status != USB_ERR_NORMAL_COMPLETION) {
|
|
ctldesc->ucd_trans_buflen = 0;
|
|
return usbd_usb2urb(status);
|
|
}
|
|
|
|
ctldesc->ucd_trans_buflen = actlen;
|
|
ip->irp_iostat.isb_info = actlen;
|
|
|
|
return (USBD_STATUS_SUCCESS);
|
|
#undef NDISUSB_GETDESC_MAXRETRIES
|
|
}
|
|
|
|
static int32_t
|
|
usbd_func_selconf(ip)
|
|
irp *ip;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
int i, j;
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct usb_device *udev = sc->ndisusb_dev;
|
|
struct usb_endpoint *ep = NULL;
|
|
struct usbd_interface_information *intf;
|
|
struct usbd_pipe_information *pipe;
|
|
struct usbd_urb_select_configuration *selconf;
|
|
union usbd_urb *urb;
|
|
usb_config_descriptor_t *conf;
|
|
usb_endpoint_descriptor_t *edesc;
|
|
usb_error_t ret;
|
|
|
|
urb = usbd_geturb(ip);
|
|
|
|
selconf = &urb->uu_selconf;
|
|
conf = selconf->usc_conf;
|
|
if (conf == NULL) {
|
|
device_printf(dev, "select configuration is NULL\n");
|
|
return usbd_usb2urb(USB_ERR_NORMAL_COMPLETION);
|
|
}
|
|
|
|
intf = &selconf->usc_intf;
|
|
for (i = 0; i < conf->bNumInterface && intf->uii_len > 0; i++) {
|
|
ret = usbd_set_alt_interface_index(udev,
|
|
intf->uii_intfnum, intf->uii_altset);
|
|
if (ret != USB_ERR_NORMAL_COMPLETION && ret != USB_ERR_IN_USE) {
|
|
device_printf(dev,
|
|
"setting alternate interface failed: %s\n",
|
|
usbd_errstr(ret));
|
|
return usbd_usb2urb(ret);
|
|
}
|
|
|
|
for (j = 0; (ep = usb_endpoint_foreach(udev, ep)); j++) {
|
|
if (j >= intf->uii_numeps) {
|
|
device_printf(dev,
|
|
"endpoint %d and above are ignored",
|
|
intf->uii_numeps);
|
|
break;
|
|
}
|
|
edesc = ep->edesc;
|
|
pipe = &intf->uii_pipes[j];
|
|
pipe->upi_handle = edesc;
|
|
pipe->upi_epaddr = edesc->bEndpointAddress;
|
|
pipe->upi_maxpktsize = UGETW(edesc->wMaxPacketSize);
|
|
pipe->upi_type = UE_GET_XFERTYPE(edesc->bmAttributes);
|
|
|
|
ret = usbd_setup_endpoint(ip, intf->uii_intfnum, edesc);
|
|
if (ret != USB_ERR_NORMAL_COMPLETION)
|
|
return usbd_usb2urb(ret);
|
|
|
|
if (pipe->upi_type != UE_INTERRUPT)
|
|
continue;
|
|
|
|
/* XXX we're following linux USB's interval policy. */
|
|
if (udev->speed == USB_SPEED_LOW)
|
|
pipe->upi_interval = edesc->bInterval + 5;
|
|
else if (udev->speed == USB_SPEED_FULL)
|
|
pipe->upi_interval = edesc->bInterval;
|
|
else {
|
|
int k0 = 0, k1 = 1;
|
|
do {
|
|
k1 = k1 * 2;
|
|
k0 = k0 + 1;
|
|
} while (k1 < edesc->bInterval);
|
|
pipe->upi_interval = k0;
|
|
}
|
|
}
|
|
|
|
intf = (struct usbd_interface_information *)(((char *)intf) +
|
|
intf->uii_len);
|
|
}
|
|
|
|
return USBD_STATUS_SUCCESS;
|
|
}
|
|
|
|
static usb_error_t
|
|
usbd_setup_endpoint_one(ip, ifidx, ne, epconf)
|
|
irp *ip;
|
|
uint8_t ifidx;
|
|
struct ndisusb_ep *ne;
|
|
struct usb_config *epconf;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct usb_xfer *xfer;
|
|
usb_error_t status;
|
|
|
|
InitializeListHead(&ne->ne_active);
|
|
InitializeListHead(&ne->ne_pending);
|
|
KeInitializeSpinLock(&ne->ne_lock);
|
|
|
|
status = usbd_transfer_setup(sc->ndisusb_dev, &ifidx, ne->ne_xfer,
|
|
epconf, 1, sc, &sc->ndisusb_mtx);
|
|
if (status != USB_ERR_NORMAL_COMPLETION) {
|
|
device_printf(dev, "couldn't setup xfer: %s\n",
|
|
usbd_errstr(status));
|
|
return (status);
|
|
}
|
|
xfer = ne->ne_xfer[0];
|
|
usbd_xfer_set_priv(xfer, ne);
|
|
|
|
return (status);
|
|
}
|
|
|
|
static usb_error_t
|
|
usbd_setup_endpoint_default(ip, ifidx)
|
|
irp *ip;
|
|
uint8_t ifidx;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
usb_error_t status;
|
|
|
|
if (ifidx > 0)
|
|
device_printf(dev, "warning: ifidx > 0 isn't supported.\n");
|
|
|
|
status = usbd_setup_endpoint_one(ip, ifidx, &sc->ndisusb_dread_ep,
|
|
&usbd_default_epconfig[USBD_CTRL_READ_PIPE]);
|
|
if (status != USB_ERR_NORMAL_COMPLETION)
|
|
return (status);
|
|
|
|
status = usbd_setup_endpoint_one(ip, ifidx, &sc->ndisusb_dwrite_ep,
|
|
&usbd_default_epconfig[USBD_CTRL_WRITE_PIPE]);
|
|
return (status);
|
|
}
|
|
|
|
static usb_error_t
|
|
usbd_setup_endpoint(ip, ifidx, ep)
|
|
irp *ip;
|
|
uint8_t ifidx;
|
|
struct usb_endpoint_descriptor *ep;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_ep *ne;
|
|
struct usb_config cfg;
|
|
struct usb_xfer *xfer;
|
|
usb_error_t status;
|
|
|
|
/* check for non-supported transfer types */
|
|
if (UE_GET_XFERTYPE(ep->bmAttributes) == UE_CONTROL ||
|
|
UE_GET_XFERTYPE(ep->bmAttributes) == UE_ISOCHRONOUS) {
|
|
device_printf(dev, "%s: unsuppotted transfer types %#x\n",
|
|
__func__, UE_GET_XFERTYPE(ep->bmAttributes));
|
|
return (USB_ERR_INVAL);
|
|
}
|
|
|
|
ne = &sc->ndisusb_ep[NDISUSB_GET_ENDPT(ep->bEndpointAddress)];
|
|
InitializeListHead(&ne->ne_active);
|
|
InitializeListHead(&ne->ne_pending);
|
|
KeInitializeSpinLock(&ne->ne_lock);
|
|
ne->ne_dirin = UE_GET_DIR(ep->bEndpointAddress) >> 7;
|
|
|
|
memset(&cfg, 0, sizeof(struct usb_config));
|
|
cfg.type = UE_GET_XFERTYPE(ep->bmAttributes);
|
|
cfg.endpoint = UE_GET_ADDR(ep->bEndpointAddress);
|
|
cfg.direction = UE_GET_DIR(ep->bEndpointAddress);
|
|
cfg.callback = &usbd_non_isoc_callback;
|
|
cfg.bufsize = UGETW(ep->wMaxPacketSize);
|
|
cfg.flags.proxy_buffer = 1;
|
|
if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN)
|
|
cfg.flags.short_xfer_ok = 1;
|
|
|
|
status = usbd_transfer_setup(sc->ndisusb_dev, &ifidx, ne->ne_xfer,
|
|
&cfg, 1, sc, &sc->ndisusb_mtx);
|
|
if (status != USB_ERR_NORMAL_COMPLETION) {
|
|
device_printf(dev, "couldn't setup xfer: %s\n",
|
|
usbd_errstr(status));
|
|
return (status);
|
|
}
|
|
xfer = ne->ne_xfer[0];
|
|
usbd_xfer_set_priv(xfer, ne);
|
|
if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN)
|
|
usbd_xfer_set_timeout(xfer, NDISUSB_NO_TIMEOUT);
|
|
else {
|
|
if (UE_GET_XFERTYPE(ep->bmAttributes) == UE_BULK)
|
|
usbd_xfer_set_timeout(xfer, NDISUSB_TX_TIMEOUT);
|
|
else
|
|
usbd_xfer_set_timeout(xfer, NDISUSB_INTR_TIMEOUT);
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_func_abort_pipe(ip)
|
|
irp *ip;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_ep *ne;
|
|
union usbd_urb *urb;
|
|
|
|
urb = usbd_geturb(ip);
|
|
ne = usbd_get_ndisep(ip, urb->uu_pipe.upr_handle);
|
|
if (ne == NULL) {
|
|
device_printf(IRP_NDIS_DEV(ip), "get NULL endpoint info.\n");
|
|
return (USBD_STATUS_INVALID_PIPE_HANDLE);
|
|
}
|
|
|
|
NDISUSB_LOCK(sc);
|
|
usbd_transfer_stop(ne->ne_xfer[0]);
|
|
usbd_transfer_start(ne->ne_xfer[0]);
|
|
NDISUSB_UNLOCK(sc);
|
|
|
|
return (USBD_STATUS_SUCCESS);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_func_vendorclass(ip)
|
|
irp *ip;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
int32_t error;
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_ep *ne;
|
|
struct ndisusb_xfer *nx;
|
|
struct usbd_urb_vendor_or_class_request *vcreq;
|
|
union usbd_urb *urb;
|
|
|
|
if (!(sc->ndisusb_status & NDISUSB_STATUS_SETUP_EP)) {
|
|
/*
|
|
* XXX In some cases the interface number isn't 0. However
|
|
* some driver (eg. RTL8187L NDIS driver) calls this function
|
|
* before calling URB_FUNCTION_SELECT_CONFIGURATION.
|
|
*/
|
|
error = usbd_setup_endpoint_default(ip, 0);
|
|
if (error != USB_ERR_NORMAL_COMPLETION)
|
|
return usbd_usb2urb(error);
|
|
sc->ndisusb_status |= NDISUSB_STATUS_SETUP_EP;
|
|
}
|
|
|
|
urb = usbd_geturb(ip);
|
|
vcreq = &urb->uu_vcreq;
|
|
ne = (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) ?
|
|
&sc->ndisusb_dread_ep : &sc->ndisusb_dwrite_ep;
|
|
IRP_NDISUSB_EP(ip) = ne;
|
|
ip->irp_cancelfunc = (cancel_func)usbd_irpcancel_wrap;
|
|
|
|
nx = malloc(sizeof(struct ndisusb_xfer), M_USBDEV, M_NOWAIT | M_ZERO);
|
|
if (nx == NULL) {
|
|
device_printf(IRP_NDIS_DEV(ip), "out of memory\n");
|
|
return (USBD_STATUS_NO_MEMORY);
|
|
}
|
|
nx->nx_ep = ne;
|
|
nx->nx_priv = ip;
|
|
KeAcquireSpinLockAtDpcLevel(&ne->ne_lock);
|
|
InsertTailList((&ne->ne_pending), (&nx->nx_next));
|
|
KeReleaseSpinLockFromDpcLevel(&ne->ne_lock);
|
|
|
|
/* we've done to setup xfer. Let's transfer it. */
|
|
ip->irp_iostat.isb_status = STATUS_PENDING;
|
|
ip->irp_iostat.isb_info = 0;
|
|
USBD_URB_STATUS(urb) = USBD_STATUS_PENDING;
|
|
IoMarkIrpPending(ip);
|
|
|
|
error = usbd_taskadd(ip, NDISUSB_TASK_VENDOR);
|
|
if (error != USBD_STATUS_SUCCESS)
|
|
return (error);
|
|
|
|
return (USBD_STATUS_PENDING);
|
|
}
|
|
|
|
static void
|
|
usbd_irpcancel(dobj, ip)
|
|
device_object *dobj;
|
|
irp *ip;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_ep *ne = IRP_NDISUSB_EP(ip);
|
|
|
|
if (ne == NULL) {
|
|
ip->irp_cancel = TRUE;
|
|
IoReleaseCancelSpinLock(ip->irp_cancelirql);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Make sure that the current USB transfer proxy is
|
|
* cancelled and then restarted.
|
|
*/
|
|
NDISUSB_LOCK(sc);
|
|
usbd_transfer_stop(ne->ne_xfer[0]);
|
|
usbd_transfer_start(ne->ne_xfer[0]);
|
|
NDISUSB_UNLOCK(sc);
|
|
|
|
ip->irp_cancel = TRUE;
|
|
IoReleaseCancelSpinLock(ip->irp_cancelirql);
|
|
}
|
|
|
|
static void
|
|
usbd_xfer_complete(struct ndis_softc *sc, struct ndisusb_ep *ne,
|
|
struct ndisusb_xfer *nx, usb_error_t status)
|
|
{
|
|
struct ndisusb_xferdone *nd;
|
|
uint8_t irql;
|
|
|
|
nd = malloc(sizeof(struct ndisusb_xferdone), M_USBDEV,
|
|
M_NOWAIT | M_ZERO);
|
|
if (nd == NULL) {
|
|
device_printf(sc->ndis_dev, "out of memory");
|
|
return;
|
|
}
|
|
nd->nd_xfer = nx;
|
|
nd->nd_status = status;
|
|
|
|
KeAcquireSpinLock(&sc->ndisusb_xferdonelock, &irql);
|
|
InsertTailList((&sc->ndisusb_xferdonelist), (&nd->nd_donelist));
|
|
KeReleaseSpinLock(&sc->ndisusb_xferdonelock, irql);
|
|
|
|
IoQueueWorkItem(sc->ndisusb_xferdoneitem,
|
|
(io_workitem_func)usbd_xfertask_wrap, WORKQUEUE_CRITICAL, sc);
|
|
}
|
|
|
|
static struct ndisusb_xfer *
|
|
usbd_aq_getfirst(struct ndis_softc *sc, struct ndisusb_ep *ne)
|
|
{
|
|
struct ndisusb_xfer *nx;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&ne->ne_lock);
|
|
if (IsListEmpty(&ne->ne_active)) {
|
|
device_printf(sc->ndis_dev,
|
|
"%s: the active queue can't be empty.\n", __func__);
|
|
KeReleaseSpinLockFromDpcLevel(&ne->ne_lock);
|
|
return (NULL);
|
|
}
|
|
nx = CONTAINING_RECORD(ne->ne_active.nle_flink, struct ndisusb_xfer,
|
|
nx_next);
|
|
RemoveEntryList(&nx->nx_next);
|
|
KeReleaseSpinLockFromDpcLevel(&ne->ne_lock);
|
|
|
|
return (nx);
|
|
}
|
|
|
|
static void
|
|
usbd_non_isoc_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
irp *ip;
|
|
struct ndis_softc *sc = usbd_xfer_softc(xfer);
|
|
struct ndisusb_ep *ne = usbd_xfer_get_priv(xfer);
|
|
struct ndisusb_xfer *nx;
|
|
struct usbd_urb_bulk_or_intr_transfer *ubi;
|
|
struct usb_page_cache *pc;
|
|
uint8_t irql;
|
|
uint32_t len;
|
|
union usbd_urb *urb;
|
|
usb_endpoint_descriptor_t *ep;
|
|
int actlen, sumlen;
|
|
|
|
usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
nx = usbd_aq_getfirst(sc, ne);
|
|
pc = usbd_xfer_get_frame(xfer, 0);
|
|
if (nx == NULL)
|
|
return;
|
|
|
|
/* copy in data with regard to the URB */
|
|
if (ne->ne_dirin != 0)
|
|
usbd_copy_out(pc, 0, nx->nx_urbbuf, actlen);
|
|
nx->nx_urbbuf += actlen;
|
|
nx->nx_urbactlen += actlen;
|
|
nx->nx_urblen -= actlen;
|
|
|
|
/* check for short transfer */
|
|
if (actlen < sumlen)
|
|
nx->nx_urblen = 0;
|
|
else {
|
|
/* check remainder */
|
|
if (nx->nx_urblen > 0) {
|
|
KeAcquireSpinLock(&ne->ne_lock, &irql);
|
|
InsertHeadList((&ne->ne_active), (&nx->nx_next));
|
|
KeReleaseSpinLock(&ne->ne_lock, irql);
|
|
|
|
ip = nx->nx_priv;
|
|
urb = usbd_geturb(ip);
|
|
ubi = &urb->uu_bulkintr;
|
|
ep = ubi->ubi_epdesc;
|
|
goto extra;
|
|
}
|
|
}
|
|
usbd_xfer_complete(sc, ne, nx,
|
|
((actlen < sumlen) && (nx->nx_shortxfer == 0)) ?
|
|
USB_ERR_SHORT_XFER : USB_ERR_NORMAL_COMPLETION);
|
|
|
|
/* fall through */
|
|
case USB_ST_SETUP:
|
|
next:
|
|
/* get next transfer */
|
|
KeAcquireSpinLock(&ne->ne_lock, &irql);
|
|
if (IsListEmpty(&ne->ne_pending)) {
|
|
KeReleaseSpinLock(&ne->ne_lock, irql);
|
|
return;
|
|
}
|
|
nx = CONTAINING_RECORD(ne->ne_pending.nle_flink,
|
|
struct ndisusb_xfer, nx_next);
|
|
RemoveEntryList(&nx->nx_next);
|
|
/* add a entry to the active queue's tail. */
|
|
InsertTailList((&ne->ne_active), (&nx->nx_next));
|
|
KeReleaseSpinLock(&ne->ne_lock, irql);
|
|
|
|
ip = nx->nx_priv;
|
|
urb = usbd_geturb(ip);
|
|
ubi = &urb->uu_bulkintr;
|
|
ep = ubi->ubi_epdesc;
|
|
|
|
nx->nx_urbbuf = ubi->ubi_trans_buf;
|
|
nx->nx_urbactlen = 0;
|
|
nx->nx_urblen = ubi->ubi_trans_buflen;
|
|
nx->nx_shortxfer = (ubi->ubi_trans_flags &
|
|
USBD_SHORT_TRANSFER_OK) ? 1 : 0;
|
|
extra:
|
|
len = MIN(usbd_xfer_max_len(xfer), nx->nx_urblen);
|
|
pc = usbd_xfer_get_frame(xfer, 0);
|
|
if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_OUT)
|
|
usbd_copy_in(pc, 0, nx->nx_urbbuf, len);
|
|
usbd_xfer_set_frame_len(xfer, 0, len);
|
|
usbd_xfer_set_frames(xfer, 1);
|
|
usbd_transfer_submit(xfer);
|
|
break;
|
|
default:
|
|
nx = usbd_aq_getfirst(sc, ne);
|
|
if (nx == NULL)
|
|
return;
|
|
if (error != USB_ERR_CANCELLED) {
|
|
usbd_xfer_set_stall(xfer);
|
|
device_printf(sc->ndis_dev, "usb xfer warning (%s)\n",
|
|
usbd_errstr(error));
|
|
}
|
|
usbd_xfer_complete(sc, ne, nx, error);
|
|
if (error != USB_ERR_CANCELLED)
|
|
goto next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
usbd_ctrl_callback(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
irp *ip;
|
|
struct ndis_softc *sc = usbd_xfer_softc(xfer);
|
|
struct ndisusb_ep *ne = usbd_xfer_get_priv(xfer);
|
|
struct ndisusb_xfer *nx;
|
|
uint8_t irql;
|
|
union usbd_urb *urb;
|
|
struct usbd_urb_vendor_or_class_request *vcreq;
|
|
struct usb_page_cache *pc;
|
|
uint8_t type = 0;
|
|
struct usb_device_request req;
|
|
int len;
|
|
|
|
switch (USB_GET_STATE(xfer)) {
|
|
case USB_ST_TRANSFERRED:
|
|
nx = usbd_aq_getfirst(sc, ne);
|
|
if (nx == NULL)
|
|
return;
|
|
|
|
ip = nx->nx_priv;
|
|
urb = usbd_geturb(ip);
|
|
vcreq = &urb->uu_vcreq;
|
|
|
|
if (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) {
|
|
pc = usbd_xfer_get_frame(xfer, 1);
|
|
len = usbd_xfer_frame_len(xfer, 1);
|
|
usbd_copy_out(pc, 0, vcreq->uvc_trans_buf, len);
|
|
nx->nx_urbactlen += len;
|
|
}
|
|
|
|
usbd_xfer_complete(sc, ne, nx, USB_ERR_NORMAL_COMPLETION);
|
|
/* fall through */
|
|
case USB_ST_SETUP:
|
|
next:
|
|
/* get next transfer */
|
|
KeAcquireSpinLock(&ne->ne_lock, &irql);
|
|
if (IsListEmpty(&ne->ne_pending)) {
|
|
KeReleaseSpinLock(&ne->ne_lock, irql);
|
|
return;
|
|
}
|
|
nx = CONTAINING_RECORD(ne->ne_pending.nle_flink,
|
|
struct ndisusb_xfer, nx_next);
|
|
RemoveEntryList(&nx->nx_next);
|
|
/* add a entry to the active queue's tail. */
|
|
InsertTailList((&ne->ne_active), (&nx->nx_next));
|
|
KeReleaseSpinLock(&ne->ne_lock, irql);
|
|
|
|
ip = nx->nx_priv;
|
|
urb = usbd_geturb(ip);
|
|
vcreq = &urb->uu_vcreq;
|
|
|
|
switch (urb->uu_hdr.uuh_func) {
|
|
case URB_FUNCTION_CLASS_DEVICE:
|
|
type = UT_CLASS | UT_DEVICE;
|
|
break;
|
|
case URB_FUNCTION_CLASS_INTERFACE:
|
|
type = UT_CLASS | UT_INTERFACE;
|
|
break;
|
|
case URB_FUNCTION_CLASS_OTHER:
|
|
type = UT_CLASS | UT_OTHER;
|
|
break;
|
|
case URB_FUNCTION_CLASS_ENDPOINT:
|
|
type = UT_CLASS | UT_ENDPOINT;
|
|
break;
|
|
case URB_FUNCTION_VENDOR_DEVICE:
|
|
type = UT_VENDOR | UT_DEVICE;
|
|
break;
|
|
case URB_FUNCTION_VENDOR_INTERFACE:
|
|
type = UT_VENDOR | UT_INTERFACE;
|
|
break;
|
|
case URB_FUNCTION_VENDOR_OTHER:
|
|
type = UT_VENDOR | UT_OTHER;
|
|
break;
|
|
case URB_FUNCTION_VENDOR_ENDPOINT:
|
|
type = UT_VENDOR | UT_ENDPOINT;
|
|
break;
|
|
default:
|
|
/* never reached. */
|
|
break;
|
|
}
|
|
|
|
type |= (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) ?
|
|
UT_READ : UT_WRITE;
|
|
type |= vcreq->uvc_reserved1;
|
|
|
|
req.bmRequestType = type;
|
|
req.bRequest = vcreq->uvc_req;
|
|
USETW(req.wIndex, vcreq->uvc_idx);
|
|
USETW(req.wValue, vcreq->uvc_value);
|
|
USETW(req.wLength, vcreq->uvc_trans_buflen);
|
|
|
|
nx->nx_urbbuf = vcreq->uvc_trans_buf;
|
|
nx->nx_urblen = vcreq->uvc_trans_buflen;
|
|
nx->nx_urbactlen = 0;
|
|
|
|
pc = usbd_xfer_get_frame(xfer, 0);
|
|
usbd_copy_in(pc, 0, &req, sizeof(req));
|
|
usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
|
|
usbd_xfer_set_frames(xfer, 1);
|
|
if (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) {
|
|
if (vcreq->uvc_trans_buflen >= USBD_CTRL_READ_BUFFER_SP)
|
|
device_printf(sc->ndis_dev,
|
|
"warning: not enough buffer space (%d).\n",
|
|
vcreq->uvc_trans_buflen);
|
|
usbd_xfer_set_frame_len(xfer, 1,
|
|
MIN(usbd_xfer_max_len(xfer),
|
|
vcreq->uvc_trans_buflen));
|
|
usbd_xfer_set_frames(xfer, 2);
|
|
} else {
|
|
if (nx->nx_urblen > USBD_CTRL_WRITE_BUFFER_SP)
|
|
device_printf(sc->ndis_dev,
|
|
"warning: not enough write buffer space"
|
|
" (%d).\n", nx->nx_urblen);
|
|
/*
|
|
* XXX with my local tests there was no cases to require
|
|
* a extra buffer until now but it'd need to update in
|
|
* the future if it needs to be.
|
|
*/
|
|
if (nx->nx_urblen > 0) {
|
|
pc = usbd_xfer_get_frame(xfer, 1);
|
|
usbd_copy_in(pc, 0, nx->nx_urbbuf,
|
|
nx->nx_urblen);
|
|
usbd_xfer_set_frame_len(xfer, 1, nx->nx_urblen);
|
|
usbd_xfer_set_frames(xfer, 2);
|
|
}
|
|
}
|
|
usbd_transfer_submit(xfer);
|
|
break;
|
|
default:
|
|
nx = usbd_aq_getfirst(sc, ne);
|
|
if (nx == NULL)
|
|
return;
|
|
if (error != USB_ERR_CANCELLED) {
|
|
usbd_xfer_set_stall(xfer);
|
|
device_printf(sc->ndis_dev, "usb xfer warning (%s)\n",
|
|
usbd_errstr(error));
|
|
}
|
|
usbd_xfer_complete(sc, ne, nx, error);
|
|
if (error != USB_ERR_CANCELLED)
|
|
goto next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct ndisusb_ep *
|
|
usbd_get_ndisep(ip, ep)
|
|
irp *ip;
|
|
usb_endpoint_descriptor_t *ep;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_ep *ne;
|
|
|
|
ne = &sc->ndisusb_ep[NDISUSB_GET_ENDPT(ep->bEndpointAddress)];
|
|
|
|
IRP_NDISUSB_EP(ip) = ne;
|
|
ip->irp_cancelfunc = (cancel_func)usbd_irpcancel_wrap;
|
|
|
|
return (ne);
|
|
}
|
|
|
|
static void
|
|
usbd_xfertask(dobj, arg)
|
|
device_object *dobj;
|
|
void *arg;
|
|
{
|
|
int error;
|
|
irp *ip;
|
|
device_t dev;
|
|
list_entry *l;
|
|
struct ndis_softc *sc = arg;
|
|
struct ndisusb_xferdone *nd;
|
|
struct ndisusb_xfer *nq;
|
|
struct usbd_urb_bulk_or_intr_transfer *ubi;
|
|
struct usbd_urb_vendor_or_class_request *vcreq;
|
|
union usbd_urb *urb;
|
|
usb_error_t status;
|
|
void *priv;
|
|
|
|
dev = sc->ndis_dev;
|
|
|
|
if (IsListEmpty(&sc->ndisusb_xferdonelist))
|
|
return;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&sc->ndisusb_xferdonelock);
|
|
l = sc->ndisusb_xferdonelist.nle_flink;
|
|
while (l != &sc->ndisusb_xferdonelist) {
|
|
nd = CONTAINING_RECORD(l, struct ndisusb_xferdone, nd_donelist);
|
|
nq = nd->nd_xfer;
|
|
priv = nq->nx_priv;
|
|
status = nd->nd_status;
|
|
error = 0;
|
|
ip = priv;
|
|
urb = usbd_geturb(ip);
|
|
|
|
ip->irp_cancelfunc = NULL;
|
|
IRP_NDISUSB_EP(ip) = NULL;
|
|
|
|
switch (status) {
|
|
case USB_ERR_NORMAL_COMPLETION:
|
|
if (urb->uu_hdr.uuh_func ==
|
|
URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER) {
|
|
ubi = &urb->uu_bulkintr;
|
|
ubi->ubi_trans_buflen = nq->nx_urbactlen;
|
|
} else {
|
|
vcreq = &urb->uu_vcreq;
|
|
vcreq->uvc_trans_buflen = nq->nx_urbactlen;
|
|
}
|
|
ip->irp_iostat.isb_info = nq->nx_urbactlen;
|
|
ip->irp_iostat.isb_status = STATUS_SUCCESS;
|
|
USBD_URB_STATUS(urb) = USBD_STATUS_SUCCESS;
|
|
break;
|
|
case USB_ERR_CANCELLED:
|
|
ip->irp_iostat.isb_info = 0;
|
|
ip->irp_iostat.isb_status = STATUS_CANCELLED;
|
|
USBD_URB_STATUS(urb) = USBD_STATUS_CANCELED;
|
|
break;
|
|
default:
|
|
ip->irp_iostat.isb_info = 0;
|
|
USBD_URB_STATUS(urb) = usbd_usb2urb(status);
|
|
ip->irp_iostat.isb_status =
|
|
usbd_urb2nt(USBD_URB_STATUS(urb));
|
|
break;
|
|
}
|
|
|
|
l = l->nle_flink;
|
|
RemoveEntryList(&nd->nd_donelist);
|
|
free(nq, M_USBDEV);
|
|
free(nd, M_USBDEV);
|
|
if (error)
|
|
continue;
|
|
KeReleaseSpinLockFromDpcLevel(&sc->ndisusb_xferdonelock);
|
|
/* NB: call after cleaning */
|
|
IoCompleteRequest(ip, IO_NO_INCREMENT);
|
|
KeAcquireSpinLockAtDpcLevel(&sc->ndisusb_xferdonelock);
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&sc->ndisusb_xferdonelock);
|
|
}
|
|
|
|
/*
|
|
* this function is for mainly deferring a task to the another thread because
|
|
* we don't want to be in the scope of HAL lock.
|
|
*/
|
|
static int32_t
|
|
usbd_taskadd(ip, type)
|
|
irp *ip;
|
|
unsigned type;
|
|
{
|
|
device_t dev = IRP_NDIS_DEV(ip);
|
|
struct ndis_softc *sc = device_get_softc(dev);
|
|
struct ndisusb_task *nt;
|
|
|
|
nt = malloc(sizeof(struct ndisusb_task), M_USBDEV, M_NOWAIT | M_ZERO);
|
|
if (nt == NULL)
|
|
return (USBD_STATUS_NO_MEMORY);
|
|
nt->nt_type = type;
|
|
nt->nt_ctx = ip;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&sc->ndisusb_tasklock);
|
|
InsertTailList((&sc->ndisusb_tasklist), (&nt->nt_tasklist));
|
|
KeReleaseSpinLockFromDpcLevel(&sc->ndisusb_tasklock);
|
|
|
|
IoQueueWorkItem(sc->ndisusb_taskitem,
|
|
(io_workitem_func)usbd_task_wrap, WORKQUEUE_CRITICAL, sc);
|
|
|
|
return (USBD_STATUS_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
usbd_task(dobj, arg)
|
|
device_object *dobj;
|
|
void *arg;
|
|
{
|
|
irp *ip;
|
|
list_entry *l;
|
|
struct ndis_softc *sc = arg;
|
|
struct ndisusb_ep *ne;
|
|
struct ndisusb_task *nt;
|
|
union usbd_urb *urb;
|
|
|
|
if (IsListEmpty(&sc->ndisusb_tasklist))
|
|
return;
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&sc->ndisusb_tasklock);
|
|
l = sc->ndisusb_tasklist.nle_flink;
|
|
while (l != &sc->ndisusb_tasklist) {
|
|
nt = CONTAINING_RECORD(l, struct ndisusb_task, nt_tasklist);
|
|
|
|
ip = nt->nt_ctx;
|
|
urb = usbd_geturb(ip);
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&sc->ndisusb_tasklock);
|
|
NDISUSB_LOCK(sc);
|
|
switch (nt->nt_type) {
|
|
case NDISUSB_TASK_TSTART:
|
|
ne = usbd_get_ndisep(ip, urb->uu_bulkintr.ubi_epdesc);
|
|
if (ne == NULL)
|
|
goto exit;
|
|
usbd_transfer_start(ne->ne_xfer[0]);
|
|
break;
|
|
case NDISUSB_TASK_IRPCANCEL:
|
|
ne = usbd_get_ndisep(ip,
|
|
(nt->nt_type == NDISUSB_TASK_IRPCANCEL) ?
|
|
urb->uu_bulkintr.ubi_epdesc :
|
|
urb->uu_pipe.upr_handle);
|
|
if (ne == NULL)
|
|
goto exit;
|
|
|
|
usbd_transfer_stop(ne->ne_xfer[0]);
|
|
usbd_transfer_start(ne->ne_xfer[0]);
|
|
break;
|
|
case NDISUSB_TASK_VENDOR:
|
|
ne = (urb->uu_vcreq.uvc_trans_flags &
|
|
USBD_TRANSFER_DIRECTION_IN) ?
|
|
&sc->ndisusb_dread_ep : &sc->ndisusb_dwrite_ep;
|
|
usbd_transfer_start(ne->ne_xfer[0]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
exit:
|
|
NDISUSB_UNLOCK(sc);
|
|
KeAcquireSpinLockAtDpcLevel(&sc->ndisusb_tasklock);
|
|
|
|
l = l->nle_flink;
|
|
RemoveEntryList(&nt->nt_tasklist);
|
|
free(nt, M_USBDEV);
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&sc->ndisusb_tasklock);
|
|
}
|
|
|
|
static int32_t
|
|
usbd_func_bulkintr(ip)
|
|
irp *ip;
|
|
{
|
|
int32_t error;
|
|
struct ndisusb_ep *ne;
|
|
struct ndisusb_xfer *nx;
|
|
struct usbd_urb_bulk_or_intr_transfer *ubi;
|
|
union usbd_urb *urb;
|
|
usb_endpoint_descriptor_t *ep;
|
|
|
|
urb = usbd_geturb(ip);
|
|
ubi = &urb->uu_bulkintr;
|
|
ep = ubi->ubi_epdesc;
|
|
if (ep == NULL)
|
|
return (USBD_STATUS_INVALID_PIPE_HANDLE);
|
|
|
|
ne = usbd_get_ndisep(ip, ep);
|
|
if (ne == NULL) {
|
|
device_printf(IRP_NDIS_DEV(ip), "get NULL endpoint info.\n");
|
|
return (USBD_STATUS_INVALID_PIPE_HANDLE);
|
|
}
|
|
|
|
nx = malloc(sizeof(struct ndisusb_xfer), M_USBDEV, M_NOWAIT | M_ZERO);
|
|
if (nx == NULL) {
|
|
device_printf(IRP_NDIS_DEV(ip), "out of memory\n");
|
|
return (USBD_STATUS_NO_MEMORY);
|
|
}
|
|
nx->nx_ep = ne;
|
|
nx->nx_priv = ip;
|
|
KeAcquireSpinLockAtDpcLevel(&ne->ne_lock);
|
|
InsertTailList((&ne->ne_pending), (&nx->nx_next));
|
|
KeReleaseSpinLockFromDpcLevel(&ne->ne_lock);
|
|
|
|
/* we've done to setup xfer. Let's transfer it. */
|
|
ip->irp_iostat.isb_status = STATUS_PENDING;
|
|
ip->irp_iostat.isb_info = 0;
|
|
USBD_URB_STATUS(urb) = USBD_STATUS_PENDING;
|
|
IoMarkIrpPending(ip);
|
|
|
|
error = usbd_taskadd(ip, NDISUSB_TASK_TSTART);
|
|
if (error != USBD_STATUS_SUCCESS)
|
|
return (error);
|
|
|
|
return (USBD_STATUS_PENDING);
|
|
}
|
|
|
|
static union usbd_urb *
|
|
USBD_CreateConfigurationRequest(conf, len)
|
|
usb_config_descriptor_t *conf;
|
|
uint16_t *len;
|
|
{
|
|
struct usbd_interface_list_entry list[2];
|
|
union usbd_urb *urb;
|
|
|
|
bzero(list, sizeof(struct usbd_interface_list_entry) * 2);
|
|
list[0].uil_intfdesc = USBD_ParseConfigurationDescriptorEx(conf, conf,
|
|
-1, -1, -1, -1, -1);
|
|
urb = USBD_CreateConfigurationRequestEx(conf, list);
|
|
if (urb == NULL)
|
|
return NULL;
|
|
|
|
*len = urb->uu_selconf.usc_hdr.uuh_len;
|
|
return urb;
|
|
}
|
|
|
|
static union usbd_urb *
|
|
USBD_CreateConfigurationRequestEx(conf, list)
|
|
usb_config_descriptor_t *conf;
|
|
struct usbd_interface_list_entry *list;
|
|
{
|
|
int i, j, size;
|
|
struct usbd_interface_information *intf;
|
|
struct usbd_pipe_information *pipe;
|
|
struct usbd_urb_select_configuration *selconf;
|
|
usb_interface_descriptor_t *desc;
|
|
|
|
for (i = 0, size = 0; i < conf->bNumInterface; i++) {
|
|
j = list[i].uil_intfdesc->bNumEndpoints;
|
|
size = size + sizeof(struct usbd_interface_information) +
|
|
sizeof(struct usbd_pipe_information) * (j - 1);
|
|
}
|
|
size += sizeof(struct usbd_urb_select_configuration) -
|
|
sizeof(struct usbd_interface_information);
|
|
|
|
selconf = ExAllocatePoolWithTag(NonPagedPool, size, 0);
|
|
if (selconf == NULL)
|
|
return NULL;
|
|
selconf->usc_hdr.uuh_func = URB_FUNCTION_SELECT_CONFIGURATION;
|
|
selconf->usc_hdr.uuh_len = size;
|
|
selconf->usc_handle = conf;
|
|
selconf->usc_conf = conf;
|
|
|
|
intf = &selconf->usc_intf;
|
|
for (i = 0; i < conf->bNumInterface; i++) {
|
|
if (list[i].uil_intfdesc == NULL)
|
|
break;
|
|
|
|
list[i].uil_intf = intf;
|
|
desc = list[i].uil_intfdesc;
|
|
|
|
intf->uii_len = sizeof(struct usbd_interface_information) +
|
|
(desc->bNumEndpoints - 1) *
|
|
sizeof(struct usbd_pipe_information);
|
|
intf->uii_intfnum = desc->bInterfaceNumber;
|
|
intf->uii_altset = desc->bAlternateSetting;
|
|
intf->uii_intfclass = desc->bInterfaceClass;
|
|
intf->uii_intfsubclass = desc->bInterfaceSubClass;
|
|
intf->uii_intfproto = desc->bInterfaceProtocol;
|
|
intf->uii_handle = desc;
|
|
intf->uii_numeps = desc->bNumEndpoints;
|
|
|
|
pipe = &intf->uii_pipes[0];
|
|
for (j = 0; j < intf->uii_numeps; j++)
|
|
pipe[j].upi_maxtxsize =
|
|
USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE;
|
|
|
|
intf = (struct usbd_interface_information *)((char *)intf +
|
|
intf->uii_len);
|
|
}
|
|
|
|
return ((union usbd_urb *)selconf);
|
|
}
|
|
|
|
static void
|
|
USBD_GetUSBDIVersion(ui)
|
|
usbd_version_info *ui;
|
|
{
|
|
|
|
/* Pretend to be Windows XP. */
|
|
|
|
ui->uvi_usbdi_vers = USBDI_VERSION;
|
|
ui->uvi_supported_vers = USB_VER_2_0;
|
|
|
|
return;
|
|
}
|
|
|
|
static usb_interface_descriptor_t *
|
|
USBD_ParseConfigurationDescriptor(usb_config_descriptor_t *conf,
|
|
uint8_t intfnum, uint8_t altset)
|
|
{
|
|
|
|
return USBD_ParseConfigurationDescriptorEx(conf, conf, intfnum, altset,
|
|
-1, -1, -1);
|
|
}
|
|
|
|
static usb_interface_descriptor_t *
|
|
USBD_ParseConfigurationDescriptorEx(conf, start, intfnum,
|
|
altset, intfclass, intfsubclass, intfproto)
|
|
usb_config_descriptor_t *conf;
|
|
void *start;
|
|
int32_t intfnum;
|
|
int32_t altset;
|
|
int32_t intfclass;
|
|
int32_t intfsubclass;
|
|
int32_t intfproto;
|
|
{
|
|
struct usb_descriptor *next = NULL;
|
|
usb_interface_descriptor_t *desc;
|
|
|
|
while ((next = usb_desc_foreach(conf, next)) != NULL) {
|
|
desc = (usb_interface_descriptor_t *)next;
|
|
if (desc->bDescriptorType != UDESC_INTERFACE)
|
|
continue;
|
|
if (!(intfnum == -1 || desc->bInterfaceNumber == intfnum))
|
|
continue;
|
|
if (!(altset == -1 || desc->bAlternateSetting == altset))
|
|
continue;
|
|
if (!(intfclass == -1 || desc->bInterfaceClass == intfclass))
|
|
continue;
|
|
if (!(intfsubclass == -1 ||
|
|
desc->bInterfaceSubClass == intfsubclass))
|
|
continue;
|
|
if (!(intfproto == -1 || desc->bInterfaceProtocol == intfproto))
|
|
continue;
|
|
return (desc);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
dummy(void)
|
|
{
|
|
printf("USBD dummy called\n");
|
|
return;
|
|
}
|
|
|
|
image_patch_table usbd_functbl[] = {
|
|
IMPORT_SFUNC(USBD_CreateConfigurationRequest, 2),
|
|
IMPORT_SFUNC(USBD_CreateConfigurationRequestEx, 2),
|
|
IMPORT_SFUNC_MAP(_USBD_CreateConfigurationRequestEx@8,
|
|
USBD_CreateConfigurationRequestEx, 2),
|
|
IMPORT_SFUNC(USBD_GetUSBDIVersion, 1),
|
|
IMPORT_SFUNC(USBD_ParseConfigurationDescriptor, 3),
|
|
IMPORT_SFUNC(USBD_ParseConfigurationDescriptorEx, 7),
|
|
IMPORT_SFUNC_MAP(_USBD_ParseConfigurationDescriptorEx@28,
|
|
USBD_ParseConfigurationDescriptorEx, 7),
|
|
|
|
/*
|
|
* This last entry is a catch-all for any function we haven't
|
|
* implemented yet. The PE import list patching routine will
|
|
* use it for any function that doesn't have an explicit match
|
|
* in this table.
|
|
*/
|
|
|
|
{ NULL, (FUNC)dummy, NULL, 0, WINDRV_WRAP_STDCALL },
|
|
|
|
/* End of list. */
|
|
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
MODULE_DEPEND(ndis, usb, 1, 1, 1);
|