/* * ng_ubt.c * * Copyright (c) 2001-2002 Maksim Yevmenkin * 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 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 THE AUTHOR OR CONTRIBUTORS 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. * * $Id: ng_ubt.c,v 1.16 2003/10/10 19:15:06 max Exp $ * $FreeBSD$ */ #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 /* * USB methods */ USB_DECLARE_DRIVER(ubt); Static int ubt_modevent (module_t, int, void *); Static usbd_status ubt_request_start (ubt_softc_p); Static void ubt_request_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_request_complete2 (node_p, hook_p, void *, int); Static usbd_status ubt_intr_start (ubt_softc_p); Static void ubt_intr_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_intr_complete2 (node_p, hook_p, void *, int); Static usbd_status ubt_bulk_in_start (ubt_softc_p); Static void ubt_bulk_in_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_bulk_in_complete2 (node_p, hook_p, void *, int); Static usbd_status ubt_bulk_out_start (ubt_softc_p); Static void ubt_bulk_out_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_bulk_out_complete2 (node_p, hook_p, void *, int); Static usbd_status ubt_isoc_in_start (ubt_softc_p); Static void ubt_isoc_in_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_isoc_in_complete2 (node_p, hook_p, void *, int); Static usbd_status ubt_isoc_out_start (ubt_softc_p); Static void ubt_isoc_out_complete (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void ubt_isoc_out_complete2 (node_p, hook_p, void *, int); Static void ubt_reset (ubt_softc_p); /* * Netgraph methods */ Static ng_constructor_t ng_ubt_constructor; Static ng_shutdown_t ng_ubt_shutdown; Static ng_newhook_t ng_ubt_newhook; Static ng_connect_t ng_ubt_connect; Static ng_disconnect_t ng_ubt_disconnect; Static ng_rcvmsg_t ng_ubt_rcvmsg; Static ng_rcvdata_t ng_ubt_rcvdata; /* Queue length */ Static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] = { { "queue", &ng_parse_int32_type, }, { "qlen", &ng_parse_int32_type, }, { NULL, } }; Static const struct ng_parse_type ng_ubt_node_qlen_type = { &ng_parse_struct_type, &ng_ubt_node_qlen_type_fields }; /* Stat info */ Static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] = { { "pckts_recv", &ng_parse_uint32_type, }, { "bytes_recv", &ng_parse_uint32_type, }, { "pckts_sent", &ng_parse_uint32_type, }, { "bytes_sent", &ng_parse_uint32_type, }, { "oerrors", &ng_parse_uint32_type, }, { "ierrors", &ng_parse_uint32_type, }, { NULL, } }; Static const struct ng_parse_type ng_ubt_node_stat_type = { &ng_parse_struct_type, &ng_ubt_node_stat_type_fields }; /* Netgraph node command list */ Static const struct ng_cmdlist ng_ubt_cmdlist[] = { { NGM_UBT_COOKIE, NGM_UBT_NODE_SET_DEBUG, "set_debug", &ng_parse_uint16_type, NULL }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_DEBUG, "get_debug", NULL, &ng_parse_uint16_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_SET_QLEN, "set_qlen", &ng_ubt_node_qlen_type, NULL }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_QLEN, "get_qlen", &ng_ubt_node_qlen_type, &ng_ubt_node_qlen_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_GET_STAT, "get_stat", NULL, &ng_ubt_node_stat_type }, { NGM_UBT_COOKIE, NGM_UBT_NODE_RESET_STAT, "reset_stat", NULL, NULL }, { NGM_UBT_COOKIE, NGM_UBT_NODE_DEV_NODES, "dev_nodes", &ng_parse_uint16_type, NULL }, { 0, } }; /* Netgraph node type */ Static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_UBT_NODE_TYPE, .constructor = ng_ubt_constructor, .rcvmsg = ng_ubt_rcvmsg, .shutdown = ng_ubt_shutdown, .newhook = ng_ubt_newhook, .connect = ng_ubt_connect, .rcvdata = ng_ubt_rcvdata, .disconnect = ng_ubt_disconnect, .cmdlist = ng_ubt_cmdlist }; /* * Device methods */ #define UBT_UNIT(n) ((minor(n) >> 4) & 0xf) #define UBT_ENDPOINT(n) (minor(n) & 0xf) #define UBT_MINOR(u, e) (((u) << 4) | (e)) #define UBT_BSIZE 1024 Static d_open_t ubt_open; Static d_close_t ubt_close; Static d_read_t ubt_read; Static d_write_t ubt_write; Static d_ioctl_t ubt_ioctl; Static d_poll_t ubt_poll; Static void ubt_create_device_nodes (ubt_softc_p); Static void ubt_destroy_device_nodes (ubt_softc_p); Static struct cdevsw ubt_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = ubt_open, .d_close = ubt_close, .d_read = ubt_read, .d_write = ubt_write, .d_ioctl = ubt_ioctl, .d_poll = ubt_poll, .d_name = "ubt", }; /* * Module */ DRIVER_MODULE(ubt, uhub, ubt_driver, ubt_devclass, ubt_modevent, 0); MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION); MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); /**************************************************************************** **************************************************************************** ** USB specific **************************************************************************** ****************************************************************************/ /* * Load/Unload the driver module */ Static int ubt_modevent(module_t mod, int event, void *data) { int error; switch (event) { case MOD_LOAD: error = ng_newtype(&typestruct); if (error != 0) printf( "%s: Could not register Netgraph node type, error=%d\n", NG_UBT_NODE_TYPE, error); else error = usbd_driver_load(mod, event, data); break; case MOD_UNLOAD: error = ng_rmtype(&typestruct); if (error == 0) error = usbd_driver_load(mod, event, data); break; default: error = EOPNOTSUPP; break; } return (error); } /* ubt_modevent */ /* * Probe for a USB Bluetooth device */ USB_MATCH(ubt) { /* * If for some reason device should not be attached then put * VendorID/ProductID pair into the list below. Currently I * do not know of any such devices. The format is as follows: * * { VENDOR_ID, PRODUCT_ID }, * * where VENDOR_ID and PRODUCT_ID are hex numbers. */ Static struct usb_devno const ubt_ignored_devices[] = { { 0, 0 } /* This should be the last item in the list */ }; /* * If device violates Bluetooth specification and has bDeviceClass, * bDeviceSubClass and bDeviceProtocol set to wrong values then you * could try to put VendorID/ProductID pair into the list below. * Currently I do not know of any such devices. */ Static struct usb_devno const ubt_broken_devices[] = { { 0, 0 } /* This should be the last item in the list */ }; USB_MATCH_START(ubt, uaa); usb_device_descriptor_t *dd = usbd_get_device_descriptor(uaa->device); if (uaa->iface == NULL || usb_lookup(ubt_ignored_devices, uaa->vendor, uaa->product)) return (UMATCH_NONE); if (dd->bDeviceClass == UDCLASS_WIRELESS && dd->bDeviceSubClass == UDSUBCLASS_RF && dd->bDeviceProtocol == UDPROTO_BLUETOOTH) return (UMATCH_DEVCLASS_DEVSUBCLASS); if (usb_lookup(ubt_broken_devices, uaa->vendor, uaa->product)) return (UMATCH_VENDOR_PRODUCT); return (UMATCH_NONE); } /* USB_MATCH(ubt) */ /* * Attach the device */ USB_ATTACH(ubt) { USB_ATTACH_START(ubt, sc, uaa); usb_config_descriptor_t *cd = NULL; usb_interface_descriptor_t *id = NULL; usb_endpoint_descriptor_t *ed = NULL; char devinfo[UBT_BSIZE]; usbd_status error; int i, ai, alt_no, isoc_in, isoc_out, isoc_isize, isoc_osize; /* Get USB device info */ sc->sc_udev = uaa->device; usbd_devinfo(sc->sc_udev, 0, devinfo); USB_ATTACH_SETUP; printf("%s: %s\n", USBDEVNAME(sc->sc_dev), devinfo); /* * Initialize device softc structure */ /* State */ sc->sc_debug = NG_UBT_WARN_LEVEL; sc->sc_flags = 0; NG_UBT_STAT_RESET(sc->sc_stat); /* Interfaces */ sc->sc_iface0 = sc->sc_iface1 = NULL; /* Interrupt pipe */ sc->sc_intr_ep = -1; sc->sc_intr_pipe = NULL; sc->sc_intr_xfer = NULL; sc->sc_intr_buffer = NULL; /* Control pipe */ sc->sc_ctrl_xfer = NULL; sc->sc_ctrl_buffer = NULL; NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN); /* Bulk-in pipe */ sc->sc_bulk_in_ep = -1; sc->sc_bulk_in_pipe = NULL; sc->sc_bulk_in_xfer = NULL; sc->sc_bulk_in_buffer = NULL; /* Bulk-out pipe */ sc->sc_bulk_out_ep = -1; sc->sc_bulk_out_pipe = NULL; sc->sc_bulk_out_xfer = NULL; sc->sc_bulk_out_buffer = NULL; NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN); /* Isoc-in pipe */ sc->sc_isoc_in_ep = -1; sc->sc_isoc_in_pipe = NULL; sc->sc_isoc_in_xfer = NULL; /* Isoc-out pipe */ sc->sc_isoc_out_ep = -1; sc->sc_isoc_out_pipe = NULL; sc->sc_isoc_out_xfer = NULL; sc->sc_isoc_size = -1; NG_BT_MBUFQ_INIT(&sc->sc_scoq, UBT_DEFAULT_QLEN); /* Netgraph part */ sc->sc_node = NULL; sc->sc_hook = NULL; /* Device part */ sc->sc_ctrl_dev = sc->sc_intr_dev = sc->sc_bulk_dev = NULL; sc->sc_refcnt = sc->sc_dying = 0; /* * XXX set configuration? * * Configure Bluetooth USB device. Discover all required USB interfaces * and endpoints. * * USB device must present two interfaces: * 1) Interface 0 that has 3 endpoints * 1) Interrupt endpoint to receive HCI events * 2) Bulk IN endpoint to receive ACL data * 3) Bulk OUT endpoint to send ACL data * * 2) Interface 1 then has 2 endpoints * 1) Isochronous IN endpoint to receive SCO data * 2) Isochronous OUT endpoint to send SCO data * * Interface 1 (with isochronous endpoints) has several alternate * configurations with different packet size. */ /* * Interface 0 */ error = usbd_device2interface_handle(sc->sc_udev, 0, &sc->sc_iface0); if (error || sc->sc_iface0 == NULL) { printf("%s: Could not get interface 0 handle. %s (%d), " \ "handle=%p\n", USBDEVNAME(sc->sc_dev), usbd_errstr(error), error, sc->sc_iface0); goto bad; } id = usbd_get_interface_descriptor(sc->sc_iface0); if (id == NULL) { printf("%s: Could not get interface 0 descriptor\n", USBDEVNAME(sc->sc_dev)); goto bad; } for (i = 0; i < id->bNumEndpoints; i ++) { ed = usbd_interface2endpoint_descriptor(sc->sc_iface0, i); if (ed == NULL) { printf("%s: Could not read endpoint descriptor for " \ "interface 0, i=%d\n", USBDEVNAME(sc->sc_dev), i); goto bad; } switch (UE_GET_XFERTYPE(ed->bmAttributes)) { case UE_BULK: if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) sc->sc_bulk_in_ep = ed->bEndpointAddress; else sc->sc_bulk_out_ep = ed->bEndpointAddress; break; case UE_INTERRUPT: sc->sc_intr_ep = ed->bEndpointAddress; break; } } /* Check if we got everything we wanted on Interface 0 */ if (sc->sc_intr_ep == -1) { printf("%s: Could not detect interrupt endpoint\n", USBDEVNAME(sc->sc_dev)); goto bad; } if (sc->sc_bulk_in_ep == -1) { printf("%s: Could not detect bulk-in endpoint\n", USBDEVNAME(sc->sc_dev)); goto bad; } if (sc->sc_bulk_out_ep == -1) { printf("%s: Could not detect bulk-out endpoint\n", USBDEVNAME(sc->sc_dev)); goto bad; } printf("%s: Interface 0 endpoints: interrupt=%#x, bulk-in=%#x, " \ "bulk-out=%#x\n", USBDEVNAME(sc->sc_dev), sc->sc_intr_ep, sc->sc_bulk_in_ep, sc->sc_bulk_out_ep); /* * Interface 1 */ cd = usbd_get_config_descriptor(sc->sc_udev); if (cd == NULL) { printf("%s: Could not get device configuration descriptor\n", USBDEVNAME(sc->sc_dev)); goto bad; } error = usbd_device2interface_handle(sc->sc_udev, 1, &sc->sc_iface1); if (error || sc->sc_iface1 == NULL) { printf("%s: Could not get interface 1 handle. %s (%d), " \ "handle=%p\n", USBDEVNAME(sc->sc_dev), usbd_errstr(error), error, sc->sc_iface1); goto bad; } id = usbd_get_interface_descriptor(sc->sc_iface1); if (id == NULL) { printf("%s: Could not get interface 1 descriptor\n", USBDEVNAME(sc->sc_dev)); goto bad; } /* * Scan all alternate configurations for interface 1 */ alt_no = -1; for (ai = 0; ai < usbd_get_no_alts(cd, 1); ai++) { error = usbd_set_interface(sc->sc_iface1, ai); if (error) { printf("%s: [SCAN] Could not set alternate " \ "configuration %d for interface 1. %s (%d)\n", USBDEVNAME(sc->sc_dev), ai, usbd_errstr(error), error); goto bad; } id = usbd_get_interface_descriptor(sc->sc_iface1); if (id == NULL) { printf("%s: Could not get interface 1 descriptor for " \ "alternate configuration %d\n", USBDEVNAME(sc->sc_dev), ai); goto bad; } isoc_in = isoc_out = -1; isoc_isize = isoc_osize = 0; for (i = 0; i < id->bNumEndpoints; i ++) { ed = usbd_interface2endpoint_descriptor(sc->sc_iface1, i); if (ed == NULL) { printf("%s: Could not read endpoint " \ "descriptor for interface 1, " \ "alternate configuration %d, i=%d\n", USBDEVNAME(sc->sc_dev), ai, i); goto bad; } if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) continue; if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) { isoc_in = ed->bEndpointAddress; isoc_isize = UGETW(ed->wMaxPacketSize); } else { isoc_out = ed->bEndpointAddress; isoc_osize = UGETW(ed->wMaxPacketSize); } } /* * Make sure that configuration looks sane and if so * update current settings */ if (isoc_in != -1 && isoc_out != -1 && isoc_isize > 0 && isoc_osize > 0 && isoc_isize == isoc_osize && isoc_isize > sc->sc_isoc_size) { sc->sc_isoc_in_ep = isoc_in; sc->sc_isoc_out_ep = isoc_out; sc->sc_isoc_size = isoc_isize; alt_no = ai; } } /* Check if we got everything we wanted on Interface 0 */ if (sc->sc_isoc_in_ep == -1) { printf("%s: Could not detect isoc-in endpoint\n", USBDEVNAME(sc->sc_dev)); goto bad; } if (sc->sc_isoc_out_ep == -1) { printf("%s: Could not detect isoc-out endpoint\n", USBDEVNAME(sc->sc_dev)); goto bad; } if (sc->sc_isoc_size <= 0) { printf("%s: Invalid isoc. packet size=%d\n", USBDEVNAME(sc->sc_dev), sc->sc_isoc_size); goto bad; } error = usbd_set_interface(sc->sc_iface1, alt_no); if (error) { printf("%s: Could not set alternate configuration " \ "%d for interface 1. %s (%d)\n", USBDEVNAME(sc->sc_dev), alt_no, usbd_errstr(error), error); goto bad; } /* Allocate USB transfer handles and buffers */ sc->sc_ctrl_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_ctrl_xfer == NULL) { printf("%s: Could not allocate control xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_ctrl_buffer = usbd_alloc_buffer(sc->sc_ctrl_xfer, UBT_CTRL_BUFFER_SIZE); if (sc->sc_ctrl_buffer == NULL) { printf("%s: Could not allocate control buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_intr_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_intr_xfer == NULL) { printf("%s: Could not allocate interrupt xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_bulk_in_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_bulk_in_xfer == NULL) { printf("%s: Could not allocate bulk-in xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_bulk_out_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_bulk_out_xfer == NULL) { printf("%s: Could not allocate bulk-out xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_bulk_out_buffer = usbd_alloc_buffer(sc->sc_bulk_out_xfer, UBT_BULK_BUFFER_SIZE); if (sc->sc_bulk_out_buffer == NULL) { printf("%s: Could not allocate bulk-out buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } /* * Allocate buffers for isoc. transfers */ sc->sc_isoc_nframes = (UBT_ISOC_BUFFER_SIZE / sc->sc_isoc_size) + 1; sc->sc_isoc_in_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_isoc_in_xfer == NULL) { printf("%s: Could not allocate isoc-in xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_isoc_in_buffer = usbd_alloc_buffer(sc->sc_isoc_in_xfer, sc->sc_isoc_nframes * sc->sc_isoc_size); if (sc->sc_isoc_in_buffer == NULL) { printf("%s: Could not allocate isoc-in buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_isoc_in_frlen = malloc(sizeof(u_int16_t) * sc->sc_isoc_nframes, M_USBDEV, M_NOWAIT); if (sc->sc_isoc_in_frlen == NULL) { printf("%s: Could not allocate isoc-in frame sizes buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_isoc_out_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_isoc_out_xfer == NULL) { printf("%s: Could not allocate isoc-out xfer handle\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_isoc_out_buffer = usbd_alloc_buffer(sc->sc_isoc_out_xfer, sc->sc_isoc_nframes * sc->sc_isoc_size); if (sc->sc_isoc_out_buffer == NULL) { printf("%s: Could not allocate isoc-out buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } sc->sc_isoc_out_frlen = malloc(sizeof(u_int16_t) * sc->sc_isoc_nframes, M_USBDEV, M_NOWAIT); if (sc->sc_isoc_out_frlen == NULL) { printf("%s: Could not allocate isoc-out frame sizes buffer\n", USBDEVNAME(sc->sc_dev)); goto bad; } printf("%s: Interface 1 (alt.config %d) endpoints: isoc-in=%#x, " \ "isoc-out=%#x; wMaxPacketSize=%d; nframes=%d, buffer size=%d\n", USBDEVNAME(sc->sc_dev), alt_no, sc->sc_isoc_in_ep, sc->sc_isoc_out_ep, sc->sc_isoc_size, sc->sc_isoc_nframes, (sc->sc_isoc_nframes * sc->sc_isoc_size)); /* * Open pipes */ /* Interrupt */ error = usbd_open_pipe(sc->sc_iface0, sc->sc_intr_ep, USBD_EXCLUSIVE_USE, &sc->sc_intr_pipe); if (error != USBD_NORMAL_COMPLETION) { printf("%s: %s - Could not open interrupt pipe. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(error), error); goto bad; } /* Bulk-in */ error = usbd_open_pipe(sc->sc_iface0, sc->sc_bulk_in_ep, USBD_EXCLUSIVE_USE, &sc->sc_bulk_in_pipe); if (error != USBD_NORMAL_COMPLETION) { printf("%s: %s - Could not open bulk-in pipe. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(error), error); goto bad; } /* Bulk-out */ error = usbd_open_pipe(sc->sc_iface0, sc->sc_bulk_out_ep, USBD_EXCLUSIVE_USE, &sc->sc_bulk_out_pipe); if (error != USBD_NORMAL_COMPLETION) { printf("%s: %s - Could not open bulk-out pipe. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(error), error); goto bad; } #if __broken__ /* XXX FIXME */ /* Isoc-in */ error = usbd_open_pipe(sc->sc_iface1, sc->sc_isoc_in_ep, USBD_EXCLUSIVE_USE, &sc->sc_isoc_in_pipe); if (error != USBD_NORMAL_COMPLETION) { printf("%s: %s - Could not open isoc-in pipe. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(error), error); goto bad; } /* Isoc-out */ error = usbd_open_pipe(sc->sc_iface1, sc->sc_isoc_out_ep, USBD_EXCLUSIVE_USE, &sc->sc_isoc_out_pipe); if (error != USBD_NORMAL_COMPLETION) { printf("%s: %s - Could not open isoc-out pipe. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(error), error); goto bad; } #endif /* __broken__ */ /* Create Netgraph node */ if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { printf("%s: Could not create Netgraph node\n", USBDEVNAME(sc->sc_dev)); sc->sc_node = NULL; goto bad; } /* Name node */ if (ng_name_node(sc->sc_node, USBDEVNAME(sc->sc_dev)) != 0) { printf("%s: Could not name Netgraph node\n", USBDEVNAME(sc->sc_dev)); NG_NODE_UNREF(sc->sc_node); sc->sc_node = NULL; goto bad; } NG_NODE_SET_PRIVATE(sc->sc_node, sc); NG_NODE_FORCE_WRITER(sc->sc_node); /* Claim all interfaces on the device */ for (i = 0; i < uaa->nifaces; i++) uaa->ifaces[i] = NULL; usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, USBDEV(sc->sc_dev)); USB_ATTACH_SUCCESS_RETURN; bad: ubt_detach(self); USB_ATTACH_ERROR_RETURN; } /* USB_ATTACH(ubt) */ /* * Detach the device */ USB_DETACH(ubt) { USB_DETACH_START(ubt, sc); sc->sc_dying = 1; ubt_destroy_device_nodes(sc); /* XXX FIXME locking? */ /* Destroy Netgraph node */ if (sc->sc_node != NULL) { NG_NODE_SET_PRIVATE(sc->sc_node, NULL); ng_rmnode_self(sc->sc_node); sc->sc_node = NULL; } /* Close pipes */ if (sc->sc_intr_pipe != NULL) { usbd_close_pipe(sc->sc_intr_pipe); sc->sc_intr_pipe = NULL; } if (sc->sc_bulk_in_pipe != NULL) { usbd_close_pipe(sc->sc_bulk_in_pipe); sc->sc_bulk_in_pipe = NULL; } if (sc->sc_bulk_out_pipe != NULL) { usbd_close_pipe(sc->sc_bulk_out_pipe); sc->sc_bulk_out_pipe = NULL; } if (sc->sc_isoc_in_pipe != NULL) { usbd_close_pipe(sc->sc_isoc_in_pipe); sc->sc_isoc_in_pipe = NULL; } if (sc->sc_isoc_out_pipe != NULL) { usbd_close_pipe(sc->sc_isoc_out_pipe); sc->sc_isoc_out_pipe = NULL; } /* Destroy USB transfer handles */ if (sc->sc_ctrl_xfer != NULL) { usbd_free_xfer(sc->sc_ctrl_xfer); sc->sc_ctrl_xfer = NULL; } if (sc->sc_intr_xfer != NULL) { usbd_free_xfer(sc->sc_intr_xfer); sc->sc_intr_xfer = NULL; } if (sc->sc_bulk_in_xfer != NULL) { usbd_free_xfer(sc->sc_bulk_in_xfer); sc->sc_bulk_in_xfer = NULL; } if (sc->sc_bulk_out_xfer != NULL) { usbd_free_xfer(sc->sc_bulk_out_xfer); sc->sc_bulk_out_xfer = NULL; } if (sc->sc_isoc_in_xfer != NULL) { usbd_free_xfer(sc->sc_isoc_in_xfer); sc->sc_isoc_in_xfer = NULL; } if (sc->sc_isoc_out_xfer != NULL) { usbd_free_xfer(sc->sc_isoc_out_xfer); sc->sc_isoc_out_xfer = NULL; } /* Destroy isoc. frame size buffers */ if (sc->sc_isoc_in_frlen != NULL) { free(sc->sc_isoc_in_frlen, M_USBDEV); sc->sc_isoc_in_frlen = NULL; } if (sc->sc_isoc_out_frlen != NULL) { free(sc->sc_isoc_out_frlen, M_USBDEV); sc->sc_isoc_out_frlen = NULL; } /* Destroy queues */ NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); return (0); } /* USB_DETACH(ubt) */ /* * Start USB control request (HCI command). Must be called with node locked */ Static usbd_status ubt_request_start(ubt_softc_p sc) { usb_device_request_t req; struct mbuf *m = NULL; usbd_status status; KASSERT(!(sc->sc_flags & UBT_CMD_XMIT), ( "%s: %s - Another control request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m); if (m == NULL) { NG_UBT_INFO( "%s: %s - HCI command queue is empty\n", __func__, USBDEVNAME(sc->sc_dev)); return (USBD_NORMAL_COMPLETION); } /* * Check HCI command frame size and copy it back to * linear USB transfer buffer. */ if (m->m_pkthdr.len > UBT_CTRL_BUFFER_SIZE) panic( "%s: %s - HCI command frame too big, size=%zd, len=%d\n", __func__, USBDEVNAME(sc->sc_dev), UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len); m_copydata(m, 0, m->m_pkthdr.len, sc->sc_ctrl_buffer); /* Initialize a USB control request and then schedule it */ bzero(&req, sizeof(req)); req.bmRequestType = UBT_HCI_REQUEST; USETW(req.wLength, m->m_pkthdr.len); NG_UBT_INFO( "%s: %s - Sending control request, bmRequestType=%#x, wLength=%d\n", __func__, USBDEVNAME(sc->sc_dev), req.bmRequestType, UGETW(req.wLength)); usbd_setup_default_xfer( sc->sc_ctrl_xfer, sc->sc_udev, (usbd_private_handle) sc->sc_node, USBD_DEFAULT_TIMEOUT, /* XXX */ &req, sc->sc_ctrl_buffer, m->m_pkthdr.len, USBD_NO_COPY, ubt_request_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_ctrl_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Could not start control request. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); NG_BT_MBUFQ_DROP(&sc->sc_cmdq); NG_UBT_STAT_OERROR(sc->sc_stat); /* XXX FIXME should we try to resubmit another request? */ } else { NG_UBT_INFO( "%s: %s - Control request has been started\n", __func__, USBDEVNAME(sc->sc_dev)); sc->sc_flags |= UBT_CMD_XMIT; status = USBD_NORMAL_COMPLETION; } NG_FREE_M(m); return (status); } /* ubt_request_start */ /* * USB control request callback */ Static void ubt_request_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_request_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_request_complete */ Static void ubt_request_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_CMD_XMIT), ( "%s: %s - No control request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_CMD_XMIT; if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Control request cancelled\n", __func__, USBDEVNAME(sc->sc_dev)); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_ERR( "%s: %s - Control request failed. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(h->pipe); NG_UBT_STAT_OERROR(sc->sc_stat); } else { NG_UBT_INFO( "%s: %s - Sent %d bytes to control pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); } if (NG_BT_MBUFQ_LEN(&sc->sc_cmdq) > 0) ubt_request_start(sc); } /* ubt_request_complete2 */ /* * Start interrupt transfer. Must be called when node is locked */ Static usbd_status ubt_intr_start(ubt_softc_p sc) { struct mbuf *m = NULL; usbd_status status; KASSERT(!(sc->sc_flags & UBT_EVT_RECV), ( "%s: %s - Another interrupt request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); /* Allocate new mbuf cluster */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (USBD_NOMEM); MCLGET(m, M_DONTWAIT); if (!(m->m_flags & M_EXT)) { NG_FREE_M(m); return (USBD_NOMEM); } if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { *mtod(m, u_int8_t *) = NG_HCI_EVENT_PKT; m->m_pkthdr.len = m->m_len = 1; } else m->m_pkthdr.len = m->m_len = 0; /* Initialize a USB transfer and then schedule it */ usbd_setup_xfer( sc->sc_intr_xfer, sc->sc_intr_pipe, (usbd_private_handle) sc->sc_node, (void *)(mtod(m, u_int8_t *) + m->m_len), MCLBYTES - m->m_len, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ubt_intr_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_intr_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Failed to start intrerrupt transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); NG_FREE_M(m); return (status); } sc->sc_flags |= UBT_EVT_RECV; sc->sc_intr_buffer = m; return (USBD_NORMAL_COMPLETION); } /* ubt_intr_start */ /* * Process interrupt from USB device (We got data from interrupt pipe) */ Static void ubt_intr_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_intr_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_intr_complete */ Static void ubt_intr_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; struct mbuf *m = NULL; ng_hci_event_pkt_t *hdr = NULL; int error; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_EVT_RECV), ( "%s: %s - No interrupt request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_EVT_RECV; m = sc->sc_intr_buffer; sc->sc_intr_buffer = NULL; hdr = mtod(m, ng_hci_event_pkt_t *); if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { NG_UBT_INFO( "%s: %s - No upstream hook\n", __func__, USBDEVNAME(sc->sc_dev)); NG_FREE_M(m); return; } if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Interrupt xfer cancelled\n", __func__, USBDEVNAME(sc->sc_dev)); NG_FREE_M(m); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_WARN( "%s: %s - Interrupt xfer failed, %s (%d). No new xfer will be submitted!\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); NG_UBT_STAT_IERROR(sc->sc_stat); NG_FREE_M(m); return; /* XXX FIXME we should restart after some delay */ } NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); m->m_pkthdr.len += h->actlen; m->m_len += h->actlen; NG_UBT_INFO( "%s: %s - Got %d bytes from interrupt pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); if (m->m_pkthdr.len < sizeof(*hdr)) { NG_FREE_M(m); goto done; } if (hdr->length == m->m_pkthdr.len - sizeof(*hdr)) { NG_UBT_INFO( "%s: %s - Got complete HCI event frame, pktlen=%d, length=%d\n", __func__, USBDEVNAME(sc->sc_dev), m->m_pkthdr.len, hdr->length); NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); NG_SEND_DATA_ONLY(error, sc->sc_hook, m); if (error != 0) NG_UBT_STAT_IERROR(sc->sc_stat); } else { NG_UBT_ERR( "%s: %s - Invalid HCI event frame size, length=%d, pktlen=%d\n", __func__, USBDEVNAME(sc->sc_dev), hdr->length, m->m_pkthdr.len); NG_UBT_STAT_IERROR(sc->sc_stat); NG_FREE_M(m); } done: ubt_intr_start(sc); } /* ubt_intr_complete2 */ /* * Start bulk-in USB transfer (ACL data). Must be called when node is locked */ Static usbd_status ubt_bulk_in_start(ubt_softc_p sc) { struct mbuf *m = NULL; usbd_status status; KASSERT(!(sc->sc_flags & UBT_ACL_RECV), ( "%s: %s - Another bulk-in request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); /* Allocate new mbuf cluster */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (USBD_NOMEM); MCLGET(m, M_DONTWAIT); if (!(m->m_flags & M_EXT)) { NG_FREE_M(m); return (USBD_NOMEM); } if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { *mtod(m, u_int8_t *) = NG_HCI_ACL_DATA_PKT; m->m_pkthdr.len = m->m_len = 1; } else m->m_pkthdr.len = m->m_len = 0; /* Initialize a bulk-in USB transfer and then schedule it */ usbd_setup_xfer( sc->sc_bulk_in_xfer, sc->sc_bulk_in_pipe, (usbd_private_handle) sc->sc_node, (void *)(mtod(m, u_int8_t *) + m->m_len), MCLBYTES - m->m_len, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ubt_bulk_in_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_bulk_in_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Failed to start bulk-in transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); NG_FREE_M(m); return (status); } sc->sc_flags |= UBT_ACL_RECV; sc->sc_bulk_in_buffer = m; return (USBD_NORMAL_COMPLETION); } /* ubt_bulk_in_start */ /* * USB bulk-in transfer callback */ Static void ubt_bulk_in_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_bulk_in_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_bulk_in_complete */ Static void ubt_bulk_in_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; struct mbuf *m = NULL; ng_hci_acldata_pkt_t *hdr = NULL; int len; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_ACL_RECV), ( "%s: %s - No bulk-in request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_ACL_RECV; m = sc->sc_bulk_in_buffer; sc->sc_bulk_in_buffer = NULL; hdr = mtod(m, ng_hci_acldata_pkt_t *); if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { NG_UBT_INFO( "%s: %s - No upstream hook\n", __func__, USBDEVNAME(sc->sc_dev)); NG_FREE_M(m); return; } if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Bulk-in xfer cancelled, pipe=%p\n", __func__, USBDEVNAME(sc->sc_dev), sc->sc_bulk_in_pipe); NG_FREE_M(m); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_WARN( "%s: %s - Bulk-in xfer failed, %s (%d). No new xfer will be submitted!\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_bulk_in_pipe); NG_UBT_STAT_IERROR(sc->sc_stat); NG_FREE_M(m); return; /* XXX FIXME we should restart after some delay */ } NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); m->m_pkthdr.len += h->actlen; m->m_len += h->actlen; NG_UBT_INFO( "%s: %s - Got %d bytes from bulk-in pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); if (m->m_pkthdr.len < sizeof(*hdr)) { NG_FREE_M(m); goto done; } len = le16toh(hdr->length); if (len == m->m_pkthdr.len - sizeof(*hdr)) { NG_UBT_INFO( "%s: %s - Got complete ACL data frame, pktlen=%d, length=%d\n", __func__, USBDEVNAME(sc->sc_dev), m->m_pkthdr.len, len); NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); NG_SEND_DATA_ONLY(len, sc->sc_hook, m); if (len != 0) NG_UBT_STAT_IERROR(sc->sc_stat); } else { NG_UBT_ERR( "%s: %s - Invalid ACL frame size, length=%d, pktlen=%d\n", __func__, USBDEVNAME(sc->sc_dev), len, m->m_pkthdr.len); NG_UBT_STAT_IERROR(sc->sc_stat); NG_FREE_M(m); } done: ubt_bulk_in_start(sc); } /* ubt_bulk_in_complete2 */ /* * Start bulk-out USB transfer. Must be called with node locked */ Static usbd_status ubt_bulk_out_start(ubt_softc_p sc) { struct mbuf *m = NULL; usbd_status status; KASSERT(!(sc->sc_flags & UBT_ACL_XMIT), ( "%s: %s - Another bulk-out request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m); if (m == NULL) { NG_UBT_INFO( "%s: %s - ACL data queue is empty\n", __func__, USBDEVNAME(sc->sc_dev)); return (USBD_NORMAL_COMPLETION); } /* * Check ACL data frame size and copy it back to linear USB * transfer buffer. */ if (m->m_pkthdr.len > UBT_BULK_BUFFER_SIZE) panic( "%s: %s - ACL data frame too big, size=%d, len=%d\n", __func__, USBDEVNAME(sc->sc_dev), UBT_BULK_BUFFER_SIZE, m->m_pkthdr.len); m_copydata(m, 0, m->m_pkthdr.len, sc->sc_bulk_out_buffer); /* Initialize a bulk-out USB transfer and then schedule it */ usbd_setup_xfer( sc->sc_bulk_out_xfer, sc->sc_bulk_out_pipe, (usbd_private_handle) sc->sc_node, sc->sc_bulk_out_buffer, m->m_pkthdr.len, USBD_NO_COPY, USBD_DEFAULT_TIMEOUT, /* XXX */ ubt_bulk_out_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_bulk_out_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Could not start bulk-out transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); NG_BT_MBUFQ_DROP(&sc->sc_aclq); NG_UBT_STAT_OERROR(sc->sc_stat); /* XXX FIXME should we try to start another transfer? */ } else { NG_UBT_INFO( "%s: %s - Bulk-out transfer has been started, len=%d\n", __func__, USBDEVNAME(sc->sc_dev), m->m_pkthdr.len); sc->sc_flags |= UBT_ACL_XMIT; status = USBD_NORMAL_COMPLETION; } NG_FREE_M(m); return (status); } /* ubt_bulk_out_start */ /* * USB bulk-out transfer callback */ Static void ubt_bulk_out_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_bulk_out_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_bulk_out_complete */ Static void ubt_bulk_out_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_ACL_XMIT), ( "%s: %s - No bulk-out request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_ACL_XMIT; if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Bulk-out xfer cancelled, pipe=%p\n", __func__, USBDEVNAME(sc->sc_dev), sc->sc_bulk_out_pipe); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_WARN( "%s: %s - Bulk-out xfer failed. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_bulk_out_pipe); NG_UBT_STAT_OERROR(sc->sc_stat); } else { NG_UBT_INFO( "%s: %s - Sent %d bytes to bulk-out pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); } if (NG_BT_MBUFQ_LEN(&sc->sc_aclq) > 0) ubt_bulk_out_start(sc); } /* ubt_bulk_out_complete2 */ /* * Start Isochronous-in USB transfer. Must be called with node locked */ Static usbd_status ubt_isoc_in_start(ubt_softc_p sc) { usbd_status status; int i; KASSERT(!(sc->sc_flags & UBT_SCO_RECV), ( "%s: %s - Another isoc-in request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); /* Initialize a isoc-in USB transfer and then schedule it */ for (i = 0; i < sc->sc_isoc_nframes; i++) sc->sc_isoc_in_frlen[i] = sc->sc_isoc_size; usbd_setup_isoc_xfer( sc->sc_isoc_in_xfer, sc->sc_isoc_in_pipe, (usbd_private_handle) sc->sc_node, sc->sc_isoc_in_frlen, sc->sc_isoc_nframes, USBD_NO_COPY, /* XXX flags */ ubt_isoc_in_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_isoc_in_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Failed to start isoc-in transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); return (status); } sc->sc_flags |= UBT_SCO_RECV; return (USBD_NORMAL_COMPLETION); } /* ubt_isoc_in_start */ /* * USB isochronous transfer callback */ Static void ubt_isoc_in_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_isoc_in_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_isoc_in_complete */ Static void ubt_isoc_in_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; struct mbuf *m = NULL; ng_hci_scodata_pkt_t *hdr = NULL; u_int8_t *b = NULL; int i; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_SCO_RECV), ( "%s: %s - No isoc-in request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_SCO_RECV; if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) { NG_UBT_INFO( "%s: %s - No upstream hook\n", __func__, USBDEVNAME(sc->sc_dev)); return; } if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Isoc-in xfer cancelled, pipe=%p\n", __func__, USBDEVNAME(sc->sc_dev), sc->sc_isoc_in_pipe); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_WARN( "%s: %s - Isoc-in xfer failed, %s (%d). No new xfer will be submitted!\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_isoc_in_pipe); NG_UBT_STAT_IERROR(sc->sc_stat); return; /* XXX FIXME we should restart after some delay */ } NG_UBT_STAT_BYTES_RECV(sc->sc_stat, h->actlen); NG_UBT_INFO( "%s: %s - Got %d bytes from isoc-in pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); /* Copy SCO data frame to mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { NG_UBT_ALERT( "%s: %s - Could not allocate mbuf\n", __func__, USBDEVNAME(sc->sc_dev)); NG_UBT_STAT_IERROR(sc->sc_stat); goto done; } /* Fix SCO data frame header if required */ if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) { *mtod(m, u_int8_t *) = NG_HCI_SCO_DATA_PKT; m->m_pkthdr.len = 1; m->m_len = min(MHLEN, h->actlen + 1); /* XXX m_copyback */ } else { m->m_pkthdr.len = 0; m->m_len = min(MHLEN, h->actlen); /* XXX m_copyback */ } /* * XXX FIXME how do we know how many frames we have received? * XXX use frlen for now. is that correct? */ b = (u_int8_t *) sc->sc_isoc_in_buffer; for (i = 0; i < sc->sc_isoc_nframes; i++) { b += (i * sc->sc_isoc_size); if (sc->sc_isoc_in_frlen[i] > 0) m_copyback(m, m->m_pkthdr.len, sc->sc_isoc_in_frlen[i], b); } if (m->m_pkthdr.len < sizeof(*hdr)) goto done; hdr = mtod(m, ng_hci_scodata_pkt_t *); if (hdr->length == m->m_pkthdr.len - sizeof(*hdr)) { NG_UBT_INFO( "%s: %s - Got complete SCO data frame, pktlen=%d, length=%d\n", __func__, USBDEVNAME(sc->sc_dev), m->m_pkthdr.len, hdr->length); NG_UBT_STAT_PCKTS_RECV(sc->sc_stat); NG_SEND_DATA_ONLY(i, sc->sc_hook, m); if (i != 0) NG_UBT_STAT_IERROR(sc->sc_stat); } else { NG_UBT_ERR( "%s: %s - Invalid SCO frame size, length=%d, pktlen=%d\n", __func__, USBDEVNAME(sc->sc_dev), hdr->length, m->m_pkthdr.len); NG_UBT_STAT_IERROR(sc->sc_stat); NG_FREE_M(m); } done: ubt_isoc_in_start(sc); } /* ubt_isoc_in_complete2 */ /* * Start isochronous-out USB transfer. Must be called with node locked */ Static usbd_status ubt_isoc_out_start(ubt_softc_p sc) { struct mbuf *m = NULL; u_int8_t *b = NULL; int i, len, nframes; usbd_status status; KASSERT(!(sc->sc_flags & UBT_SCO_XMIT), ( "%s: %s - Another isoc-out request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m); if (m == NULL) { NG_UBT_INFO( "%s: %s - SCO data queue is empty\n", __func__, USBDEVNAME(sc->sc_dev)); return (USBD_NORMAL_COMPLETION); } /* Copy entire SCO frame into USB transfer buffer and start transfer */ b = (u_int8_t *) sc->sc_isoc_out_buffer; nframes = 0; for (i = 0; i < sc->sc_isoc_nframes; i++) { b += (i * sc->sc_isoc_size); len = min(m->m_pkthdr.len, sc->sc_isoc_size); if (len > 0) { m_copydata(m, 0, len, b); m_adj(m, len); nframes ++; } sc->sc_isoc_out_frlen[i] = len; } if (m->m_pkthdr.len > 0) panic( "%s: %s - SCO data frame is too big, nframes=%d, size=%d, len=%d\n", __func__, USBDEVNAME(sc->sc_dev), sc->sc_isoc_nframes, sc->sc_isoc_size, m->m_pkthdr.len); NG_FREE_M(m); /* Initialize a isoc-out USB transfer and then schedule it */ usbd_setup_isoc_xfer( sc->sc_isoc_out_xfer, sc->sc_isoc_out_pipe, (usbd_private_handle) sc->sc_node, sc->sc_isoc_out_frlen, nframes, USBD_NO_COPY, ubt_isoc_out_complete); NG_NODE_REF(sc->sc_node); status = usbd_transfer(sc->sc_isoc_out_xfer); if (status != USBD_NORMAL_COMPLETION && status != USBD_IN_PROGRESS) { NG_UBT_ERR( "%s: %s - Could not start isoc-out transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); NG_NODE_UNREF(sc->sc_node); NG_BT_MBUFQ_DROP(&sc->sc_scoq); NG_UBT_STAT_OERROR(sc->sc_stat); } else { NG_UBT_INFO( "%s: %s - Isoc-out transfer has been started, nframes=%d, size=%d\n", __func__, USBDEVNAME(sc->sc_dev), nframes, sc->sc_isoc_size); sc->sc_flags |= UBT_SCO_XMIT; status = USBD_NORMAL_COMPLETION; } return (status); } /* ubt_isoc_out_start */ /* * USB isoc-out. transfer callback */ Static void ubt_isoc_out_complete(usbd_xfer_handle h, usbd_private_handle p, usbd_status s) { ng_send_fn((node_p) p, NULL, ubt_isoc_out_complete2, (void *) h, s); NG_NODE_UNREF((node_p) p); } /* ubt_isoc_out_complete */ Static void ubt_isoc_out_complete2(node_p node, hook_p hook, void *arg1, int arg2) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); usbd_xfer_handle h = (usbd_xfer_handle) arg1; usbd_status s = (usbd_status) arg2; if (sc == NULL) return; KASSERT((sc->sc_flags & UBT_SCO_XMIT), ( "%s: %s - No isoc-out request is pending\n", __func__, USBDEVNAME(sc->sc_dev))); sc->sc_flags &= ~UBT_SCO_XMIT; if (s == USBD_CANCELLED) { NG_UBT_INFO( "%s: %s - Isoc-out xfer cancelled, pipe=%p\n", __func__, USBDEVNAME(sc->sc_dev), sc->sc_isoc_out_pipe); return; } if (s != USBD_NORMAL_COMPLETION) { NG_UBT_WARN( "%s: %s - Isoc-out xfer failed. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(s), s); if (s == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_isoc_out_pipe); NG_UBT_STAT_OERROR(sc->sc_stat); } else { NG_UBT_INFO( "%s: %s - Sent %d bytes to isoc-out pipe\n", __func__, USBDEVNAME(sc->sc_dev), h->actlen); NG_UBT_STAT_BYTES_SENT(sc->sc_stat, h->actlen); NG_UBT_STAT_PCKTS_SENT(sc->sc_stat); } if (NG_BT_MBUFQ_LEN(&sc->sc_scoq) > 0) ubt_isoc_out_start(sc); } /* ubt_isoc_out_complete2 */ /* * Abort transfers on all USB pipes */ Static void ubt_reset(ubt_softc_p sc) { /* Interrupt */ if (sc->sc_intr_pipe != NULL) usbd_abort_pipe(sc->sc_intr_pipe); /* Bulk-in/out */ if (sc->sc_bulk_in_pipe != NULL) usbd_abort_pipe(sc->sc_bulk_in_pipe); if (sc->sc_bulk_out_pipe != NULL) usbd_abort_pipe(sc->sc_bulk_out_pipe); /* Isoc-in/out */ if (sc->sc_isoc_in_pipe != NULL) usbd_abort_pipe(sc->sc_isoc_in_pipe); if (sc->sc_isoc_out_pipe != NULL) usbd_abort_pipe(sc->sc_isoc_out_pipe); /* Cleanup queues */ NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq); NG_BT_MBUFQ_DRAIN(&sc->sc_aclq); NG_BT_MBUFQ_DRAIN(&sc->sc_scoq); } /* ubt_reset */ /**************************************************************************** **************************************************************************** ** Netgraph specific **************************************************************************** ****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ Static int ng_ubt_constructor(node_p node) { return (EINVAL); } /* ng_ubt_constructor */ /* * Netgraph node destructor. Destroy node only when device has been detached */ Static int ng_ubt_shutdown(node_p node) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); /* Let old node go */ NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); if (sc == NULL) goto done; /* Create Netgraph node */ if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) { printf("%s: Could not create Netgraph node\n", USBDEVNAME(sc->sc_dev)); sc->sc_node = NULL; goto done; } /* Name node */ if (ng_name_node(sc->sc_node, USBDEVNAME(sc->sc_dev)) != 0) { printf("%s: Could not name Netgraph node\n", USBDEVNAME(sc->sc_dev)); NG_NODE_UNREF(sc->sc_node); sc->sc_node = NULL; goto done; } NG_NODE_SET_PRIVATE(sc->sc_node, sc); NG_NODE_FORCE_WRITER(sc->sc_node); done: return (0); } /* ng_ubt_shutdown */ /* * Create new hook. There can only be one. */ Static int ng_ubt_newhook(node_p node, hook_p hook, char const *name) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); /* Refuse to create new hook if device interface is active */ if (sc->sc_ctrl_dev != NULL || sc->sc_intr_dev != NULL || sc->sc_bulk_dev != NULL) return (EBUSY); if (strcmp(name, NG_UBT_HOOK) != 0) return (EINVAL); if (sc->sc_hook != NULL) return (EISCONN); sc->sc_hook = hook; return (0); } /* ng_ubt_newhook */ /* * Connect hook. Start incoming USB transfers */ Static int ng_ubt_connect(hook_p hook) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); usbd_status status; /* Refuse to connect hook if device interface is active */ if (sc->sc_ctrl_dev != NULL || sc->sc_intr_dev != NULL || sc->sc_bulk_dev != NULL) return (EBUSY); NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); /* Start intr transfer */ status = ubt_intr_start(sc); if (status != USBD_NORMAL_COMPLETION) { NG_UBT_ALERT( "%s: %s - Could not start interrupt transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); goto fail; } /* Start bulk-in transfer */ status = ubt_bulk_in_start(sc); if (status != USBD_NORMAL_COMPLETION) { NG_UBT_ALERT( "%s: %s - Could not start bulk-in transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); goto fail; } #if __broken__ /* XXX FIXME */ /* Start isoc-in transfer */ status = ubt_isoc_in_start(sc); if (status != USBD_NORMAL_COMPLETION) { NG_UBT_ALERT( "%s: %s - Could not start isoc-in transfer. %s (%d)\n", __func__, USBDEVNAME(sc->sc_dev), usbd_errstr(status), status); goto fail; } #endif /* __broken__ */ return (0); fail: ubt_reset(sc); sc->sc_hook = NULL; return (ENXIO); } /* ng_ubt_connect */ /* * Disconnect hook */ Static int ng_ubt_disconnect(hook_p hook) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (sc != NULL) { if (hook != sc->sc_hook) return (EINVAL); ubt_reset(sc); sc->sc_hook = NULL; } return (0); } /* ng_ubt_disconnect */ /* * Process control message */ Static int ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(node); struct ng_mesg *msg = NULL, *rsp = NULL; struct ng_bt_mbufq *q = NULL; int error = 0, queue, qlen; if (sc == NULL) { NG_FREE_ITEM(item); return (EHOSTDOWN); } NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_GENERIC_COOKIE: switch (msg->header.cmd) { case NGM_TEXT_STATUS: NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT); if (rsp == NULL) error = ENOMEM; else snprintf(rsp->data, NG_TEXTRESPONSE, "Hook: %s\n" \ "Flags: %#x\n" \ "Debug: %d\n" \ "CMD queue: [have:%d,max:%d]\n" \ "ACL queue: [have:%d,max:%d]\n" \ "SCO queue: [have:%d,max:%d]", (sc->sc_hook != NULL)? NG_UBT_HOOK : "", sc->sc_flags, sc->sc_debug, NG_BT_MBUFQ_LEN(&sc->sc_cmdq), sc->sc_cmdq.maxlen, NG_BT_MBUFQ_LEN(&sc->sc_aclq), sc->sc_aclq.maxlen, NG_BT_MBUFQ_LEN(&sc->sc_scoq), sc->sc_scoq.maxlen); break; default: error = EINVAL; break; } break; case NGM_UBT_COOKIE: switch (msg->header.cmd) { case NGM_UBT_NODE_SET_DEBUG: if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)) error = EMSGSIZE; else sc->sc_debug = *((ng_ubt_node_debug_ep *)(msg->data)); break; case NGM_UBT_NODE_GET_DEBUG: NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep), M_NOWAIT); if (rsp == NULL) error = ENOMEM; else *((ng_ubt_node_debug_ep *)(rsp->data)) = sc->sc_debug; break; case NGM_UBT_NODE_SET_QLEN: if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) error = EMSGSIZE; else { queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue; qlen = ((ng_ubt_node_qlen_ep *) (msg->data))->qlen; if (qlen <= 0) { error = EINVAL; break; } switch (queue) { case NGM_UBT_NODE_QUEUE_CMD: q = &sc->sc_cmdq; break; case NGM_UBT_NODE_QUEUE_ACL: q = &sc->sc_aclq; break; case NGM_UBT_NODE_QUEUE_SCO: q = &sc->sc_scoq; break; default: q = NULL; error = EINVAL; break; } if (q != NULL) q->maxlen = qlen; } break; case NGM_UBT_NODE_GET_QLEN: if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) { error = EMSGSIZE; break; } queue = ((ng_ubt_node_qlen_ep *)(msg->data))->queue; switch (queue) { case NGM_UBT_NODE_QUEUE_CMD: q = &sc->sc_cmdq; break; case NGM_UBT_NODE_QUEUE_ACL: q = &sc->sc_aclq; break; case NGM_UBT_NODE_QUEUE_SCO: q = &sc->sc_scoq; break; default: q = NULL; error = EINVAL; break; } if (q != NULL) { NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_qlen_ep), M_NOWAIT); if (rsp == NULL) { error = ENOMEM; break; } ((ng_ubt_node_qlen_ep *)(rsp->data))->queue = queue; ((ng_ubt_node_qlen_ep *)(rsp->data))->qlen = q->maxlen; } break; case NGM_UBT_NODE_GET_STAT: NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep), M_NOWAIT); if (rsp == NULL) error = ENOMEM; else bcopy(&sc->sc_stat, rsp->data, sizeof(ng_ubt_node_stat_ep)); break; case NGM_UBT_NODE_RESET_STAT: NG_UBT_STAT_RESET(sc->sc_stat); break; case NGM_UBT_NODE_DEV_NODES: if (msg->header.arglen != sizeof(ng_ubt_node_dev_nodes_ep)) { error = EMSGSIZE; break; } if ((sc->sc_flags & UBT_ANY_DEV) || sc->sc_hook != NULL) { error = EBUSY; break; } if (*((ng_ubt_node_dev_nodes_ep *)(msg->data))) ubt_create_device_nodes(sc); else ubt_destroy_device_nodes(sc); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, rsp); NG_FREE_MSG(msg); return (error); } /* ng_ubt_rcvmsg */ /* * Process data */ Static int ng_ubt_rcvdata(hook_p hook, item_p item) { ubt_softc_p sc = (ubt_softc_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct mbuf *m = NULL; usbd_status (*f)(ubt_softc_p) = NULL; struct ng_bt_mbufq *q = NULL; int b, error = 0; if (sc == NULL) { error = EHOSTDOWN; goto done; } if (hook != sc->sc_hook) { error = EINVAL; goto done; } /* Deatch mbuf and get HCI frame type */ NGI_GET_M(item, m); /* Process HCI frame */ switch (*mtod(m, u_int8_t *)) { /* XXX call m_pullup ? */ case NG_HCI_CMD_PKT: f = ubt_request_start; q = &sc->sc_cmdq; b = UBT_CMD_XMIT; break; case NG_HCI_ACL_DATA_PKT: f = ubt_bulk_out_start; q = &sc->sc_aclq; b = UBT_ACL_XMIT; break; #if __broken__ /* XXX FIXME */ case NG_HCI_SCO_DATA_PKT: f = ubt_isoc_out_start; q = &sc->sc_scoq; b = UBT_SCO_XMIT; break; #endif /* __broken__ */ default: NG_UBT_ERR( "%s: %s - Dropping unknown/unsupported HCI frame, type=%d, pktlen=%d\n", __func__, USBDEVNAME(sc->sc_dev), *mtod(m, u_int8_t *), m->m_pkthdr.len); NG_FREE_M(m); error = EINVAL; goto done; /* NOT REACHED */ } /* Loose frame type, if required */ if (!(sc->sc_flags & UBT_NEED_FRAME_TYPE)) m_adj(m, sizeof(u_int8_t)); if (NG_BT_MBUFQ_FULL(q)) { NG_UBT_ERR( "%s: %s - Dropping HCI frame %#x, len=%d. Queue full\n", __func__, USBDEVNAME(sc->sc_dev), *mtod(m, u_int8_t *), m->m_pkthdr.len); NG_FREE_M(m); } else NG_BT_MBUFQ_ENQUEUE(q, m); if (!(sc->sc_flags & b)) if ((*f)(sc) != USBD_NORMAL_COMPLETION) error = EIO; done: NG_FREE_ITEM(item); return (error); } /* ng_ubt_rcvdata */ /**************************************************************************** **************************************************************************** ** Device specific **************************************************************************** ****************************************************************************/ /* * Open endpoint device * XXX FIXME softc locking */ Static int ubt_open(struct cdev *dev, int flag, int mode, usb_proc_ptr p) { ubt_softc_p sc = NULL; int ep = UBT_ENDPOINT(dev); USB_GET_SC_OPEN(ubt, UBT_UNIT(dev), sc); /* check for sc != NULL */ if (sc->sc_dying) return (ENXIO); if (ep == USB_CONTROL_ENDPOINT) { if (sc->sc_flags & UBT_CTRL_DEV) return (EBUSY); sc->sc_flags |= UBT_CTRL_DEV; } else if (ep == UE_GET_ADDR(sc->sc_intr_ep)) { if (sc->sc_flags & UBT_INTR_DEV) return (EBUSY); if (sc->sc_intr_pipe == NULL) return (ENXIO); sc->sc_flags |= UBT_INTR_DEV; } else if (ep == UE_GET_ADDR(sc->sc_bulk_in_ep)) { if (sc->sc_flags & UBT_BULK_DEV) return (EBUSY); if (sc->sc_bulk_in_pipe == NULL || sc->sc_bulk_out_pipe == NULL) return (ENXIO); sc->sc_flags |= UBT_BULK_DEV; } else return (EINVAL); return (0); } /* ubt_open */ /* * Close endpoint device * XXX FIXME softc locking */ Static int ubt_close(struct cdev *dev, int flag, int mode, usb_proc_ptr p) { ubt_softc_p sc = NULL; int ep = UBT_ENDPOINT(dev); USB_GET_SC(ubt, UBT_UNIT(dev), sc); if (sc == NULL) return (ENXIO); if (ep == USB_CONTROL_ENDPOINT) sc->sc_flags &= ~UBT_CTRL_DEV; else if (ep == UE_GET_ADDR(sc->sc_intr_ep)) { if (sc->sc_intr_pipe != NULL) usbd_abort_pipe(sc->sc_intr_pipe); sc->sc_flags &= ~UBT_INTR_DEV; } else if (ep == UE_GET_ADDR(sc->sc_bulk_in_ep)) { /* Close both in and out bulk pipes */ if (sc->sc_bulk_in_pipe != NULL) usbd_abort_pipe(sc->sc_bulk_in_pipe); if (sc->sc_bulk_out_pipe != NULL) usbd_abort_pipe(sc->sc_bulk_out_pipe); sc->sc_flags &= ~UBT_BULK_DEV; } else return (EINVAL); return (0); } /* ubt_close */ /* * Read from the endpoint device * XXX FIXME softc locking */ Static int ubt_read(struct cdev *dev, struct uio *uio, int flag) { ubt_softc_p sc = NULL; int error = 0, n, tn, ep = UBT_ENDPOINT(dev); usbd_status status; usbd_pipe_handle pipe = NULL; usbd_xfer_handle xfer = NULL; u_int8_t buf[UBT_BSIZE]; USB_GET_SC(ubt, UBT_UNIT(dev), sc); if (sc == NULL || sc->sc_dying) return (ENXIO); if (ep == USB_CONTROL_ENDPOINT) return (EOPNOTSUPP); if (ep == UE_GET_ADDR(sc->sc_intr_ep)) { pipe = sc->sc_intr_pipe; xfer = sc->sc_intr_xfer; } else if (ep == UE_GET_ADDR(sc->sc_bulk_in_ep)) { pipe = sc->sc_bulk_in_pipe; xfer = sc->sc_bulk_in_xfer; } else return (EINVAL); if (pipe == NULL || xfer == NULL) return (ENXIO); sc->sc_refcnt ++; while ((n = min(sizeof(buf), uio->uio_resid)) != 0) { tn = n; status = usbd_bulk_transfer(xfer, pipe, USBD_SHORT_XFER_OK, USBD_DEFAULT_TIMEOUT, buf, &tn, "ubtrd"); switch (status) { case USBD_NORMAL_COMPLETION: error = uiomove(buf, tn, uio); break; case USBD_INTERRUPTED: error = EINTR; break; case USBD_TIMEOUT: error = ETIMEDOUT; break; default: error = EIO; break; } if (error != 0 || tn < n) break; } if (-- sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } /* ubt_read */ /* * Write into the endpoint device * XXX FIXME softc locking */ Static int ubt_write(struct cdev *dev, struct uio *uio, int flag) { ubt_softc_p sc = NULL; int error = 0, n, ep = UBT_ENDPOINT(dev); usbd_status status; u_int8_t buf[UBT_BSIZE]; USB_GET_SC(ubt, UBT_UNIT(dev), sc); if (sc == NULL || sc->sc_dying) return (ENXIO); if (ep == USB_CONTROL_ENDPOINT || ep == UE_GET_ADDR(sc->sc_intr_ep)) return (EOPNOTSUPP); if (ep != UE_GET_ADDR(sc->sc_bulk_in_ep)) return (EINVAL); sc->sc_refcnt ++; while ((n = min(sizeof(buf), uio->uio_resid)) != 0) { error = uiomove(buf, n, uio); if (error != 0) break; status = usbd_bulk_transfer(sc->sc_bulk_out_xfer, sc->sc_bulk_out_pipe, 0, USBD_DEFAULT_TIMEOUT, buf, &n,"ubtwr"); switch (status) { case USBD_NORMAL_COMPLETION: break; case USBD_INTERRUPTED: error = EINTR; break; case USBD_TIMEOUT: error = ETIMEDOUT; break; default: error = EIO; break; } if (error != 0) break; } if (-- sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } /* ubt_write */ /* * Process ioctl on the endpoint device. Mostly stolen from ugen(4) * XXX FIXME softc locking */ Static int ubt_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, usb_proc_ptr p) { ubt_softc_p sc = NULL; int len, error = 0, ep = UBT_ENDPOINT(dev); usbd_status status; struct usb_string_desc *si = NULL; struct usb_ctl_request *ur = NULL; void *ptr = NULL; struct iovec iov; struct uio uio; USB_GET_SC(ubt, UBT_UNIT(dev), sc); if (sc == NULL || sc->sc_dying) return (ENXIO); if (ep != USB_CONTROL_ENDPOINT) return (EOPNOTSUPP); sc->sc_refcnt ++; switch (cmd) { case USB_GET_DEVICE_DESC: *(usb_device_descriptor_t *) data = *usbd_get_device_descriptor(sc->sc_udev); break; case USB_GET_STRING_DESC: si = (struct usb_string_desc *) data; status = usbd_get_string_desc(sc->sc_udev, si->usd_string_index, si->usd_language_id, &si->usd_desc); if (status != USBD_NORMAL_COMPLETION) error = EINVAL; break; case USB_DO_REQUEST: ur = (void *) data; len = UGETW(ur->ucr_request.wLength); if (!(flag & FWRITE)) { error = EPERM; break; } /* Avoid requests that would damage the bus integrity. */ if ((ur->ucr_request.bmRequestType == UT_WRITE_DEVICE && ur->ucr_request.bRequest == UR_SET_ADDRESS) || (ur->ucr_request.bmRequestType == UT_WRITE_DEVICE && ur->ucr_request.bRequest == UR_SET_CONFIG) || (ur->ucr_request.bmRequestType == UT_WRITE_INTERFACE && ur->ucr_request.bRequest == UR_SET_INTERFACE) || len < 0 || len > 32767) { error = EINVAL; break; } if (len != 0) { iov.iov_base = (caddr_t) ur->ucr_data; iov.iov_len = len; uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_resid = len; uio.uio_offset = 0; uio.uio_segflg = UIO_USERSPACE; uio.uio_rw = ur->ucr_request.bmRequestType & UT_READ ? UIO_READ : UIO_WRITE; uio.uio_procp = p; ptr = malloc(len, M_TEMP, M_WAITOK); if (uio.uio_rw == UIO_WRITE) { error = uiomove(ptr, len, &uio); if (error != 0) goto ret; } } status = usbd_do_request_flags(sc->sc_udev, &ur->ucr_request, ptr, ur->ucr_flags, &ur->ucr_actlen, USBD_DEFAULT_TIMEOUT); if (status != USBD_NORMAL_COMPLETION) { error = EIO; goto ret; } if (len != 0) { if (uio.uio_rw == UIO_READ) { error = uiomove(ptr, len, &uio); if (error != 0) goto ret; } } ret: if (ptr != NULL) free(ptr, M_TEMP); break; case USB_GET_DEVICEINFO: usbd_fill_deviceinfo(sc->sc_udev, (struct usb_device_info *) data, 1); break; default: error = EINVAL; break; } if (-- sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } /* ubt_ioctl */ /* * Poll the endpoint device * XXX FIXME softc locking */ Static int ubt_poll(struct cdev *dev, int events, usb_proc_ptr p) { ubt_softc_p sc = NULL; int revents = 0, ep = UBT_ENDPOINT(dev); USB_GET_SC(ubt, UBT_UNIT(dev), sc); if (sc == NULL || sc->sc_dying) return (ENXIO); if (ep == USB_CONTROL_ENDPOINT) return (EOPNOTSUPP); if (ep == UE_GET_ADDR(sc->sc_intr_ep)) { if (sc->sc_intr_pipe != NULL) revents |= events & (POLLIN | POLLRDNORM); else revents = EIO; } else if (ep == UE_GET_ADDR(sc->sc_bulk_in_ep)) { if (sc->sc_bulk_in_pipe != NULL) revents |= events & (POLLIN | POLLRDNORM); if (sc->sc_bulk_out_pipe != NULL) revents |= events & (POLLOUT | POLLWRNORM); if (revents == 0) revents = EIO; /* both pipes closed */ } else revents = EINVAL; return (revents); } /* ubt_poll */ /* * Create device nodes for all endpoints. Must be called with node locked. */ Static void ubt_create_device_nodes(ubt_softc_p sc) { int ep; KASSERT((sc->sc_hook == NULL), ( "%s: %s - hook != NULL!\n", __func__, USBDEVNAME(sc->sc_dev))); /* Control device */ if (sc->sc_ctrl_dev == NULL) sc->sc_ctrl_dev = make_dev(&ubt_cdevsw, UBT_MINOR(USBDEVUNIT(sc->sc_dev), 0), UID_ROOT, GID_OPERATOR, 0644, "%s", USBDEVNAME(sc->sc_dev)); /* Interrupt device */ if (sc->sc_intr_dev == NULL && sc->sc_intr_ep != -1) { ep = UE_GET_ADDR(sc->sc_intr_ep); sc->sc_intr_dev = make_dev(&ubt_cdevsw, UBT_MINOR(USBDEVUNIT(sc->sc_dev), ep), UID_ROOT, GID_OPERATOR, 0644, "%s.%d", USBDEVNAME(sc->sc_dev), ep); } /* * Bulk-in and bulk-out device * XXX will create one device for both in and out endpoints. * XXX note that address of the in and out endpoint should be the same */ if (sc->sc_bulk_dev == NULL && sc->sc_bulk_in_ep != -1 && sc->sc_bulk_out_ep != -1 && UE_GET_ADDR(sc->sc_bulk_in_ep) == UE_GET_ADDR(sc->sc_bulk_out_ep)) { ep = UE_GET_ADDR(sc->sc_bulk_in_ep); sc->sc_bulk_dev = make_dev(&ubt_cdevsw, UBT_MINOR(USBDEVUNIT(sc->sc_dev), ep), UID_ROOT, GID_OPERATOR, 0644, "%s.%d", USBDEVNAME(sc->sc_dev), ep); } } /* ubt_create_device_nodes */ /* * Destroy device nodes for all endpoints * XXX FIXME locking */ Static void ubt_destroy_device_nodes(ubt_softc_p sc) { /* * Wait for processes to go away. This should be safe as we will not * call ubt_destroy_device_nodes() from Netgraph unless all devices * were closed (and thus no active processes). */ if (-- sc->sc_refcnt >= 0) { ubt_reset(sc); usb_detach_wait(USBDEV(sc->sc_dev)); } sc->sc_refcnt = 0; /* Destroy device nodes */ if (sc->sc_bulk_dev != NULL) { destroy_dev(sc->sc_bulk_dev); sc->sc_bulk_dev = NULL; } if (sc->sc_intr_dev != NULL) { destroy_dev(sc->sc_intr_dev); sc->sc_intr_dev = NULL; } if (sc->sc_ctrl_dev != NULL) { destroy_dev(sc->sc_ctrl_dev); sc->sc_ctrl_dev = NULL; } } /* ubt_destroy_device_nodes */