/*- * Copyright (c) 2005 * Bill Paul . 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. * * $FreeBSD: src/sys/compat/ndis/subr_usbd.c,v 1.6 2009/02/24 18:09:31 rdivacky Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static driver_object usbd_driver; 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 int32_t usbd_func_getdesc(irp *); static usbd_status usbd_get_desc_ndis(usbd_device_handle, int, int, int, void *, int *); static union usbd_urb *usbd_geturb(irp *); static usbd_status usbd_init_ndispipe(irp *, usb_endpoint_descriptor_t *); static usbd_xfer_handle usbd_init_ndisxfer(irp *, usb_endpoint_descriptor_t *, void *, uint32_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 void usbd_irpcancel_cb(void *); static int32_t usbd_submit_urb(irp *); static int32_t usbd_urb2nt(int32_t); static void usbd_xfereof(usbd_xfer_handle, usbd_private_handle, usbd_status); static void usbd_xferadd(usbd_xfer_handle, usbd_private_handle, usbd_status); 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_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_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_xfertask_wrap); kfree(usbd_driver.dro_drivername.us_buf, M_DEVBUF); return (0); } static int32_t usbd_iodispatch(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(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(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(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(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 usbd_status to USBD_STATUS */ static int32_t usbd_usb2urb(int status) { switch (status) { case USBD_NORMAL_COMPLETION: return (USBD_STATUS_SUCCESS); case USBD_IN_PROGRESS: return (USBD_STATUS_PENDING); case USBD_TIMEOUT: return (USBD_STATUS_TIMEOUT); case USBD_SHORT_XFER: return (USBD_STATUS_ERROR_SHORT_TRANSFER); case USBD_IOERROR: return (USBD_STATUS_XACT_ERROR); case USBD_NOMEM: return (USBD_STATUS_NO_MEMORY); case USBD_INVAL: return (USBD_STATUS_REQUEST_FAILED); case USBD_NOT_STARTED: case USBD_TOO_DEEP: case USBD_NO_POWER: return (USBD_STATUS_DEVICE_GONE); case USBD_CANCELLED: return (USBD_STATUS_CANCELED); default: break; } return (USBD_STATUS_NOT_SUPPORTED); } static union usbd_urb * usbd_geturb(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(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(irp *ip) { device_t dev = IRP_NDIS_DEV(ip); int actlen, i; struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbd_urb_control_descriptor_request *ctldesc; uint32_t len; union usbd_urb *urb; usb_config_descriptor_t cd, *cdp; usbd_status status; get_mplock(); urb = usbd_geturb(ip); ctldesc = &urb->uu_ctldesc; if (ctldesc->ucd_desctype == UDESC_CONFIG) { /* Get the short config descriptor. */ status = usbd_get_config_desc(uaa->device, ctldesc->ucd_idx, &cd); if (status != USBD_NORMAL_COMPLETION) { ctldesc->ucd_trans_buflen = 0; rel_mplock(); return usbd_usb2urb(status); } /* Get the full descriptor. Try a few times for slow devices. */ len = MIN(ctldesc->ucd_trans_buflen, UGETW(cd.wTotalLength)); for (i = 0; i < 3; i++) { status = usbd_get_desc_ndis(uaa->device, ctldesc->ucd_desctype, ctldesc->ucd_idx, len, ctldesc->ucd_trans_buf, &actlen); if (status == USBD_NORMAL_COMPLETION) break; usbd_delay_ms(uaa->device, 200); } if (status != USBD_NORMAL_COMPLETION) { ctldesc->ucd_trans_buflen = 0; rel_mplock(); return usbd_usb2urb(status); } cdp = (usb_config_descriptor_t *)ctldesc->ucd_trans_buf; if (cdp->bDescriptorType != UDESC_CONFIG) { device_printf(dev, "bad desc %d\n", cdp->bDescriptorType); status = USBD_INVAL; } } else if (ctldesc->ucd_desctype == UDESC_STRING) { /* Try a few times for slow devices. */ for (i = 0; i < 3; i++) { status = usbd_get_string_desc(uaa->device, (UDESC_STRING << 8) + ctldesc->ucd_idx, ctldesc->ucd_langid, ctldesc->ucd_trans_buf, &actlen); if (actlen > ctldesc->ucd_trans_buflen) panic("small string buffer for UDESC_STRING"); if (status == USBD_NORMAL_COMPLETION) break; usbd_delay_ms(uaa->device, 200); } } else status = usbd_get_desc_ndis(uaa->device, ctldesc->ucd_desctype, ctldesc->ucd_idx, ctldesc->ucd_trans_buflen, ctldesc->ucd_trans_buf, &actlen); if (status != USBD_NORMAL_COMPLETION) { ctldesc->ucd_trans_buflen = 0; rel_mplock(); return usbd_usb2urb(status); } ctldesc->ucd_trans_buflen = actlen; ip->irp_iostat.isb_info = actlen; rel_mplock(); return (USBD_STATUS_SUCCESS); } /* * FIXME: at USB1, not USB2, framework, there's no a interface to get `actlen'. * However, we need it!!! */ static usbd_status usbd_get_desc_ndis(usbd_device_handle dev, int type, int index, int len, void *desc, int *actlen) { usb_device_request_t req; req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_DESCRIPTOR; USETW2(req.wValue, type, index); USETW(req.wIndex, 0); USETW(req.wLength, len); return usbd_do_request_flags_pipe(dev, dev->default_pipe, &req, desc, 0, actlen, USBD_DEFAULT_TIMEOUT); } static int32_t usbd_func_selconf(irp *ip) { device_t dev = IRP_NDIS_DEV(ip); int i, j; struct usb_attach_arg *uaa = device_get_ivars(dev); 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; usbd_device_handle udev = uaa->device; usbd_interface_handle iface; usbd_status 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(USBD_NORMAL_COMPLETION); } if (conf->bConfigurationValue > NDISUSB_CONFIG_NO) device_printf(dev, "warning: config_no is larger than default"); intf = &selconf->usc_intf; for (i = 0; i < conf->bNumInterface && intf->uii_len > 0; i++) { ret = usbd_device2interface_handle(uaa->device, intf->uii_intfnum, &iface); if (ret != USBD_NORMAL_COMPLETION) { device_printf(dev, "getting interface handle failed: %s\n", usbd_errstr(ret)); return usbd_usb2urb(ret); } ret = usbd_set_interface(iface, intf->uii_altset); if (ret != USBD_NORMAL_COMPLETION && ret != USBD_IN_USE) { device_printf(dev, "setting alternate interface failed: %s\n", usbd_errstr(ret)); return usbd_usb2urb(ret); } for (j = 0; j < iface->idesc->bNumEndpoints; j++) { if (j >= intf->uii_numeps) { device_printf(dev, "endpoint %d and above are ignored", intf->uii_numeps); break; } edesc = iface->endpoints[j].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); 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 int32_t usbd_func_abort_pipe(irp *ip) { device_t dev = IRP_NDIS_DEV(ip); struct ndis_softc *sc = device_get_softc(dev); union usbd_urb *urb; struct usbd_urb_bulk_or_intr_transfer *ubi; usb_endpoint_descriptor_t *ep; uint8_t irql; usbd_status status; urb = usbd_geturb(ip); ubi = &urb->uu_bulkintr; ep = ubi->ubi_epdesc; if (ep == NULL) return (USBD_STATUS_INVALID_PIPE_HANDLE); KeRaiseIrql(DISPATCH_LEVEL, &irql); status = usbd_abort_pipe(sc->ndisusb_ep[NDISUSB_ENDPT_BIN]); if (status != USBD_NORMAL_COMPLETION) device_printf(dev, "can't be canceld"); KeLowerIrql(irql); return (USBD_STATUS_SUCCESS); } static int32_t usbd_func_vendorclass(irp *ip) { device_t dev = IRP_NDIS_DEV(ip); struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbd_urb_vendor_or_class_request *vcreq; uint8_t type = 0; union usbd_urb *urb; usb_device_request_t req; usbd_status status; 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 reach. */ 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); if (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) { get_mplock(); status = usbd_do_request(uaa->device, &req, vcreq->uvc_trans_buf); rel_mplock(); } else status = usbd_do_request_async(uaa->device, &req, vcreq->uvc_trans_buf); return usbd_usb2urb(status); } static usbd_status usbd_init_ndispipe(irp *ip, usb_endpoint_descriptor_t *ep) { device_t dev = IRP_NDIS_DEV(ip); struct ndis_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); usbd_interface_handle iface; usbd_status status; status = usbd_device2interface_handle(uaa->device, NDISUSB_IFACE_INDEX, &iface); if (status != USBD_NORMAL_COMPLETION) { device_printf(dev, "could not get interface handle\n"); return (status); } switch (UE_GET_XFERTYPE(ep->bmAttributes)) { case UE_BULK: if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN) { /* RX (bulk IN) */ if (sc->ndisusb_ep[NDISUSB_ENDPT_BIN] != NULL) return (USBD_NORMAL_COMPLETION); status = usbd_open_pipe(iface, ep->bEndpointAddress, USBD_EXCLUSIVE_USE, &sc->ndisusb_ep[NDISUSB_ENDPT_BIN]); break; } /* TX (bulk OUT) */ if (sc->ndisusb_ep[NDISUSB_ENDPT_BOUT] != NULL) return (USBD_NORMAL_COMPLETION); status = usbd_open_pipe(iface, ep->bEndpointAddress, USBD_EXCLUSIVE_USE, &sc->ndisusb_ep[NDISUSB_ENDPT_BOUT]); break; case UE_INTERRUPT: if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN) { /* Interrupt IN. */ if (sc->ndisusb_ep[NDISUSB_ENDPT_IIN] != NULL) return (USBD_NORMAL_COMPLETION); status = usbd_open_pipe(iface, ep->bEndpointAddress, USBD_EXCLUSIVE_USE, &sc->ndisusb_ep[NDISUSB_ENDPT_IIN]); break; } /* Interrupt OUT. */ if (sc->ndisusb_ep[NDISUSB_ENDPT_IOUT] != NULL) return (USBD_NORMAL_COMPLETION); status = usbd_open_pipe(iface, ep->bEndpointAddress, USBD_EXCLUSIVE_USE, &sc->ndisusb_ep[NDISUSB_ENDPT_IOUT]); break; default: device_printf(dev, "can't handle xfertype 0x%x\n", UE_GET_XFERTYPE(ep->bmAttributes)); return (USBD_INVAL); } if (status != USBD_NORMAL_COMPLETION) device_printf(dev, "open pipe failed: (0x%x) %s\n", ep->bEndpointAddress, usbd_errstr(status)); return (status); } static void usbd_irpcancel_cb(void *priv) { struct ndisusb_cancel *nc = priv; struct ndis_softc *sc = device_get_softc(nc->dev); usbd_status status; usbd_xfer_handle xfer = nc->xfer; if (sc->ndisusb_status & NDISUSB_STATUS_DETACH) goto exit; status = usbd_abort_pipe(xfer->pipe); if (status != USBD_NORMAL_COMPLETION) device_printf(nc->dev, "can't be canceld"); exit: kfree(nc, M_USBDEV); } static void usbd_irpcancel(device_object *dobj, irp *ip) { device_t dev = IRP_NDIS_DEV(ip); struct ndisusb_cancel *nc; struct usb_attach_arg *uaa = device_get_ivars(dev); if (IRP_NDISUSB_XFER(ip) == NULL) { ip->irp_cancel = TRUE; IoReleaseCancelSpinLock(ip->irp_cancelirql); return; } /* * XXX Since we're under DISPATCH_LEVEL during calling usbd_irpcancel(), * we can't sleep at all. However, currently FreeBSD's USB stack * requires a sleep to abort a transfer. It's inevitable! so it causes * serveral fatal problems (e.g. kernel hangups or crashes). I think * that there are no ways to make this reliable. In this implementation, * I used usb_add_task() but it's not a perfect method to solve this * because of as follows: NDIS drivers would expect that IRP's * completely canceld when usbd_irpcancel() is returned but we need * a sleep to do it. During canceling XFERs, usbd_intr() would be * called with a status, USBD_CANCELLED. */ nc = kmalloc(sizeof(struct ndisusb_cancel), M_USBDEV, M_NOWAIT | M_ZERO); if (nc == NULL) { ip->irp_cancel = FALSE; IoReleaseCancelSpinLock(ip->irp_cancelirql); return; } nc->dev = dev; nc->xfer = IRP_NDISUSB_XFER(ip); usb_init_task(&nc->task, usbd_irpcancel_cb, nc); IRP_NDISUSB_XFER(ip) = NULL; usb_add_task(uaa->device, &nc->task, USB_TASKQ_DRIVER); ip->irp_cancel = TRUE; IoReleaseCancelSpinLock(ip->irp_cancelirql); } static usbd_xfer_handle usbd_init_ndisxfer(irp *ip, usb_endpoint_descriptor_t *ep, void *buf, uint32_t buflen) { device_t dev = IRP_NDIS_DEV(ip); struct usb_attach_arg *uaa = device_get_ivars(dev); usbd_xfer_handle xfer; xfer = usbd_alloc_xfer(uaa->device); if (xfer == NULL) return (NULL); if (buf != NULL && MmIsAddressValid(buf) == FALSE && buflen > 0) { xfer->buffer = usbd_alloc_buffer(xfer, buflen); if (xfer->buffer == NULL) return (NULL); if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_OUT) memcpy(xfer->buffer, buf, buflen); } else xfer->buffer = buf; xfer->length = buflen; IoAcquireCancelSpinLock(&ip->irp_cancelirql); IRP_NDISUSB_XFER(ip) = xfer; ip->irp_cancelfunc = (cancel_func)usbd_irpcancel_wrap; IoReleaseCancelSpinLock(ip->irp_cancelirql); return (xfer); } static void usbd_xferadd(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { irp *ip = priv; device_t dev = IRP_NDIS_DEV(ip); struct ndis_softc *sc = device_get_softc(dev); struct ndisusb_xfer *nx; uint8_t irql; nx = kmalloc(sizeof(struct ndisusb_xfer), M_USBDEV, M_NOWAIT | M_ZERO); if (nx == NULL) { device_printf(dev, "out of memory"); return; } nx->nx_xfer = xfer; nx->nx_priv = priv; nx->nx_status = status; KeAcquireSpinLock(&sc->ndisusb_xferlock, &irql); InsertTailList((&sc->ndisusb_xferlist), (&nx->nx_xferlist)); KeReleaseSpinLock(&sc->ndisusb_xferlock, irql); IoQueueWorkItem(sc->ndisusb_xferitem, (io_workitem_func)usbd_xfertask_wrap, WORKQUEUE_CRITICAL, sc); } static void usbd_xfereof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { usbd_xferadd(xfer, priv, status); } static void usbd_xfertask(device_object *dobj, void *arg) { int error; irp *ip; device_t dev; list_entry *l; struct ndis_softc *sc = arg; struct ndisusb_xfer *nx; struct usbd_urb_bulk_or_intr_transfer *ubi; uint8_t irql; union usbd_urb *urb; usbd_private_handle priv; usbd_status status; usbd_xfer_handle xfer; dev = sc->ndis_dev; if (IsListEmpty(&sc->ndisusb_xferlist)) return; KeAcquireSpinLock(&sc->ndisusb_xferlock, &irql); l = sc->ndisusb_xferlist.nle_flink; while (l != &sc->ndisusb_xferlist) { nx = CONTAINING_RECORD(l, struct ndisusb_xfer, nx_xferlist); xfer = nx->nx_xfer; priv = nx->nx_priv; status = nx->nx_status; error = 0; ip = priv; if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED) { error = 1; goto next; } if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(xfer->pipe); /* * NB: just for notice. We must handle error cases also * because if we just return without notifying to the * NDIS driver the driver never knows about that there * was a error. This can cause a lot of problems like * system hangs. */ device_printf(dev, "usb xfer warning (%s)\n", usbd_errstr(status)); } urb = usbd_geturb(ip); KASSERT(urb->uu_hdr.uuh_func == URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER, ("function(%d) isn't for bulk or interrupt", urb->uu_hdr.uuh_func)); IoAcquireCancelSpinLock(&ip->irp_cancelirql); ip->irp_cancelfunc = NULL; IRP_NDISUSB_XFER(ip) = NULL; switch (status) { case USBD_NORMAL_COMPLETION: ubi = &urb->uu_bulkintr; ubi->ubi_trans_buflen = xfer->actlen; if (ubi->ubi_trans_flags & USBD_TRANSFER_DIRECTION_IN) memcpy(ubi->ubi_trans_buf, xfer->buffer, xfer->actlen); ip->irp_iostat.isb_info = xfer->actlen; ip->irp_iostat.isb_status = STATUS_SUCCESS; USBD_URB_STATUS(urb) = USBD_STATUS_SUCCESS; break; case USBD_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; } IoReleaseCancelSpinLock(ip->irp_cancelirql); next: l = l->nle_flink; RemoveEntryList(&nx->nx_xferlist); usbd_free_xfer(nx->nx_xfer); kfree(nx, M_USBDEV); if (error) continue; /* NB: call after cleaning */ IoCompleteRequest(ip, IO_NO_INCREMENT); } KeReleaseSpinLock(&sc->ndisusb_xferlock, irql); } static int32_t usbd_func_bulkintr(irp *ip) { device_t dev = IRP_NDIS_DEV(ip); struct ndis_softc *sc = device_get_softc(dev); struct usbd_urb_bulk_or_intr_transfer *ubi; union usbd_urb *urb; usb_endpoint_descriptor_t *ep; usbd_status status; usbd_xfer_handle xfer; urb = usbd_geturb(ip); ubi = &urb->uu_bulkintr; ep = ubi->ubi_epdesc; if (ep == NULL) return (USBD_STATUS_INVALID_PIPE_HANDLE); status = usbd_init_ndispipe(ip, ep); if (status != USBD_NORMAL_COMPLETION) return usbd_usb2urb(status); xfer = usbd_init_ndisxfer(ip, ep, ubi->ubi_trans_buf, ubi->ubi_trans_buflen); if (xfer == NULL) { device_printf(IRP_NDIS_DEV(ip), "can't allocate xfer\n"); return (USBD_STATUS_NO_MEMORY); } if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN) { xfer->flags |= USBD_SHORT_XFER_OK; if (!(ubi->ubi_trans_flags & USBD_SHORT_TRANSFER_OK)) xfer->flags &= ~USBD_SHORT_XFER_OK; } if (UE_GET_XFERTYPE(ep->bmAttributes) == UE_BULK) { if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN) /* RX (bulk IN) */ usbd_setup_xfer(xfer, sc->ndisusb_ep[NDISUSB_ENDPT_BIN], ip, xfer->buffer, xfer->length, xfer->flags, USBD_NO_TIMEOUT, usbd_xfereof); else { /* TX (bulk OUT) */ xfer->flags |= USBD_NO_COPY; usbd_setup_xfer(xfer, sc->ndisusb_ep[NDISUSB_ENDPT_BOUT], ip, xfer->buffer, xfer->length, xfer->flags, NDISUSB_TX_TIMEOUT, usbd_xfereof); } } else { if (UE_GET_DIR(ep->bEndpointAddress) == UE_DIR_IN) /* Interrupt IN */ usbd_setup_xfer(xfer, sc->ndisusb_ep[NDISUSB_ENDPT_IIN], ip, xfer->buffer, xfer->length, xfer->flags, USBD_NO_TIMEOUT, usbd_xfereof); else /* Interrupt OUT */ usbd_setup_xfer(xfer, sc->ndisusb_ep[NDISUSB_ENDPT_IOUT], ip, xfer->buffer, xfer->length, xfer->flags, NDISUSB_INTR_TIMEOUT, usbd_xfereof); } /* 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); status = usbd_transfer(xfer); if (status == USBD_IN_PROGRESS) return (USBD_STATUS_PENDING); usbd_free_xfer(xfer); IRP_NDISUSB_XFER(ip) = NULL; IoUnmarkIrpPending(ip); USBD_URB_STATUS(urb) = usbd_usb2urb(status); return USBD_URB_STATUS(urb); } static union usbd_urb * USBD_CreateConfigurationRequest(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(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(usbd_version_info *ui) { /* Pretend to be Windows XP. */ ui->uvi_usbdi_vers = USBDI_VERSION; ui->uvi_supported_vers = USB_VER_2_0; } 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(usb_config_descriptor_t *conf, void *start, int32_t intfnum, int32_t altset, int32_t intfclass, int32_t intfsubclass, int32_t intfproto) { char *pos; usb_interface_descriptor_t *desc; for (pos = start; pos < ((char *)conf + UGETW(conf->wTotalLength)); pos += desc->bLength) { desc = (usb_interface_descriptor_t *)pos; 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) { kprintf("USBD dummy called\n"); } 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);