/* $OpenBSD: ubt.c,v 1.4 2007/06/06 19:25:49 mk Exp $ */ /*- * Copyright (c) 2006 Itronix Inc. * All rights reserved. * * Written by Iain Hibbert for Itronix Inc. * * 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. The name of Itronix Inc. may not be used to endorse * or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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. */ /* * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) and * David Sainty (David.Sainty@dtsp.co.nz). * * 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 the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * This driver originally written by Lennart Augustsson and David Sainty, * but was mostly rewritten for the NetBSD Bluetooth protocol stack by * Iain Hibbert for Itronix, Inc using the FreeBSD ng_ubt.c driver as a * reference. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /******************************************************************************* * * debugging stuff */ #undef DPRINTF #undef DPRINTFN #ifdef UBT_DEBUG int ubt_debug = UBT_DEBUG; #define DPRINTF(fmt, args...) do { \ if (ubt_debug) \ printf("%s: "fmt, __func__ , ##args); \ } while (/* CONSTCOND */0) #define DPRINTFN(n, fmt, args...) do { \ if (ubt_debug > (n)) \ printf("%s: "fmt, __func__ , ##args); \ } while (/* CONSTCOND */0) #else #define DPRINTF(...) #define DPRINTFN(...) #endif /******************************************************************************* * * ubt softc structure * */ /* buffer sizes */ /* * NB: although ACL packets can extend to 65535 bytes, most devices * have max_acl_size at much less (largest I have seen is 384) */ #define UBT_BUFSIZ_CMD (HCI_CMD_PKT_SIZE - 1) #define UBT_BUFSIZ_ACL (2048 - 1) #define UBT_BUFSIZ_EVENT (HCI_EVENT_PKT_SIZE - 1) /* Transmit timeouts */ #define UBT_CMD_TIMEOUT USBD_DEFAULT_TIMEOUT #define UBT_ACL_TIMEOUT USBD_DEFAULT_TIMEOUT /* * ISOC transfers * * xfer buffer size depends on the frame size, and the number * of frames per transfer is fixed, as each frame should be * 1ms worth of data. This keeps the rate that xfers complete * fairly constant. We use multiple xfers to keep the hardware * busy */ #define UBT_NXFERS 3 /* max xfers to queue */ #define UBT_NFRAMES 10 /* frames per xfer */ struct ubt_isoc_xfer { struct ubt_softc *softc; usbd_xfer_handle xfer; uint8_t *buf; uint16_t size[UBT_NFRAMES]; int busy; }; struct ubt_softc { struct device sc_dev; usbd_device_handle sc_udev; int sc_refcnt; int sc_dying; /* Control Interface */ usbd_interface_handle sc_iface0; /* Commands (control) */ usbd_xfer_handle sc_cmd_xfer; uint8_t *sc_cmd_buf; /* Events (interrupt) */ int sc_evt_addr; /* endpoint address */ usbd_pipe_handle sc_evt_pipe; uint8_t *sc_evt_buf; /* ACL data (in) */ int sc_aclrd_addr; /* endpoint address */ usbd_pipe_handle sc_aclrd_pipe; /* read pipe */ usbd_xfer_handle sc_aclrd_xfer; /* read xfer */ uint8_t *sc_aclrd_buf; /* read buffer */ int sc_aclrd_busy; /* reading */ /* ACL data (out) */ int sc_aclwr_addr; /* endpoint address */ usbd_pipe_handle sc_aclwr_pipe; /* write pipe */ usbd_xfer_handle sc_aclwr_xfer; /* write xfer */ uint8_t *sc_aclwr_buf; /* write buffer */ /* ISOC interface */ usbd_interface_handle sc_iface1; /* ISOC interface */ struct sysctllog *sc_log; /* sysctl log */ int sc_config; /* current config no */ int sc_alt_config; /* no of alternates */ /* SCO data (in) */ int sc_scord_addr; /* endpoint address */ usbd_pipe_handle sc_scord_pipe; /* read pipe */ int sc_scord_size; /* frame length */ struct ubt_isoc_xfer sc_scord[UBT_NXFERS]; struct mbuf *sc_scord_mbuf; /* current packet */ /* SCO data (out) */ int sc_scowr_addr; /* endpoint address */ usbd_pipe_handle sc_scowr_pipe; /* write pipe */ int sc_scowr_size; /* frame length */ struct ubt_isoc_xfer sc_scowr[UBT_NXFERS]; struct mbuf *sc_scowr_mbuf; /* current packet */ /* Protocol structure */ struct hci_unit sc_unit; /* Successfully attached */ int sc_ok; }; /* * Bluetooth unit/USB callback routines */ int ubt_enable(struct hci_unit *); void ubt_disable(struct hci_unit *); void ubt_xmit_cmd_start(struct hci_unit *); void ubt_xmit_cmd_complete(usbd_xfer_handle, usbd_private_handle, usbd_status); void ubt_xmit_acl_start(struct hci_unit *); void ubt_xmit_acl_complete(usbd_xfer_handle, usbd_private_handle, usbd_status); void ubt_xmit_sco_start(struct hci_unit *); void ubt_xmit_sco_start1(struct ubt_softc *, struct ubt_isoc_xfer *); void ubt_xmit_sco_complete(usbd_xfer_handle, usbd_private_handle, usbd_status); void ubt_recv_event(usbd_xfer_handle, usbd_private_handle, usbd_status); void ubt_recv_acl_start(struct ubt_softc *); void ubt_recv_acl_complete(usbd_xfer_handle, usbd_private_handle, usbd_status); void ubt_recv_sco_start1(struct ubt_softc *, struct ubt_isoc_xfer *); void ubt_recv_sco_complete(usbd_xfer_handle, usbd_private_handle, usbd_status); USB_DECLARE_DRIVER(ubt); static int ubt_set_isoc_config(struct ubt_softc *); static void ubt_abortdealloc(struct ubt_softc *); /* * Match against the whole device, since we want to take * both interfaces. If a device should be ignored then add * * { VendorID, ProductID } * * to the ubt_ignore list. */ static const struct usb_devno ubt_ignore[] = { { USB_VENDOR_BROADCOM, USB_PRODUCT_BROADCOM_BCM2033 }, { USB_VENDOR_BROADCOM, USB_PRODUCT_BROADCOM_BCM2033NF }, { 0, 0 } /* end of list */ }; int ubt_match(struct device *parent, void *match, void *aux) { struct usb_attach_arg *uaa = aux; usb_device_descriptor_t *dd = usbd_get_device_descriptor(uaa->device); DPRINTFN(50, "ubt_match\n"); if (usb_lookup(ubt_ignore, uaa->vendor, uaa->product)) return UMATCH_NONE; if (dd->bDeviceClass == UDCLASS_WIRELESS && dd->bDeviceSubClass == UDSUBCLASS_RF && dd->bDeviceProtocol == UDPROTO_BLUETOOTH) return UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO; return UMATCH_NONE; } void ubt_attach(struct device *parent, struct device *self, void *aux) { struct ubt_softc *sc = (struct ubt_softc *)self; struct usb_attach_arg *uaa = aux; usb_config_descriptor_t *cd; usb_endpoint_descriptor_t *ed; char *devinfop; int err; uint8_t count, i; DPRINTFN(50, "ubt_attach: sc=%p\n", sc); sc->sc_udev = uaa->device; devinfop = usbd_devinfo_alloc(sc->sc_udev, 0); printf("\n%s: %s\n", USBDEVNAME(sc->sc_dev), devinfop); usbd_devinfo_free(devinfop); /* * Move the device into the configured state */ err = usbd_set_config_index(sc->sc_udev, 0, 1); if (err) { printf("%s: failed to set configuration idx 0: %s\n", USBDEVNAME(sc->sc_dev), usbd_errstr(err)); return; } /* * Interface 0 must have 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 */ err = usbd_device2interface_handle(sc->sc_udev, 0, &sc->sc_iface0); if (err) { printf("%s: Could not get interface 0 handle %s (%d)\n", USBDEVNAME(sc->sc_dev), usbd_errstr(err), err); return; } sc->sc_evt_addr = -1; sc->sc_aclrd_addr = -1; sc->sc_aclwr_addr = -1; count = 0; (void)usbd_endpoint_count(sc->sc_iface0, &count); for (i = 0 ; i < count ; i++) { int dir, type; ed = usbd_interface2endpoint_descriptor(sc->sc_iface0, i); if (ed == NULL) { printf("%s: could not read endpoint descriptor %d\n", USBDEVNAME(sc->sc_dev), i); return; } dir = UE_GET_DIR(ed->bEndpointAddress); type = UE_GET_XFERTYPE(ed->bmAttributes); if (dir == UE_DIR_IN && type == UE_INTERRUPT) sc->sc_evt_addr = ed->bEndpointAddress; else if (dir == UE_DIR_IN && type == UE_BULK) sc->sc_aclrd_addr = ed->bEndpointAddress; else if (dir == UE_DIR_OUT && type == UE_BULK) sc->sc_aclwr_addr = ed->bEndpointAddress; } if (sc->sc_evt_addr == -1) { printf("%s: missing INTERRUPT endpoint on interface 0\n", USBDEVNAME(sc->sc_dev)); return; } if (sc->sc_aclrd_addr == -1) { printf("%s: missing BULK IN endpoint on interface 0\n", USBDEVNAME(sc->sc_dev)); return; } if (sc->sc_aclwr_addr == -1) { printf("%s: missing BULK OUT endpoint on interface 0\n", USBDEVNAME(sc->sc_dev)); return; } /* * Interface 1 must have 2 endpoints * 1) Isochronous IN endpoint to receive SCO data * 2) Isochronous OUT endpoint to send SCO data * * and will have several configurations, which can be selected * via a sysctl variable. We select config 0 to start, which * means that no SCO data will be available. */ err = usbd_device2interface_handle(sc->sc_udev, 1, &sc->sc_iface1); if (err) { printf("%s: Could not get interface 1 handle %s (%d)\n", USBDEVNAME(sc->sc_dev), usbd_errstr(err), err); return; } cd = usbd_get_config_descriptor(sc->sc_udev); if (cd == NULL) { printf("%s: could not get config descriptor\n", USBDEVNAME(sc->sc_dev)); return; } sc->sc_alt_config = usbd_get_no_alts(cd, 1); /* set initial config */ err = ubt_set_isoc_config(sc); if (err) { printf("%s: ISOC config failed\n", USBDEVNAME(sc->sc_dev)); return; } /* Attach HCI */ sc->sc_unit.hci_softc = self; sc->sc_unit.hci_devname = USBDEVNAME(sc->sc_dev); sc->sc_unit.hci_enable = ubt_enable; sc->sc_unit.hci_disable = ubt_disable; sc->sc_unit.hci_start_cmd = ubt_xmit_cmd_start; sc->sc_unit.hci_start_acl = ubt_xmit_acl_start; sc->sc_unit.hci_start_sco = ubt_xmit_sco_start; sc->sc_unit.hci_ipl = IPL_USB; /* XXX: IPL_SOFTUSB ?? */ hci_attach(&sc->sc_unit); usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, USBDEV(sc->sc_dev)); sc->sc_ok = 1; return; } int ubt_detach(struct device *self, int flags) { struct ubt_softc *sc = (struct ubt_softc *)self; int s; DPRINTF("sc=%p flags=%d\n", sc, flags); sc->sc_dying = 1; if (!sc->sc_ok) return 0; /* Detach HCI interface */ hci_detach(&sc->sc_unit); /* * Abort all pipes. Causes processes waiting for transfer to wake. * * Actually, hci_detach() above will call ubt_disable() which may * call ubt_abortdealloc(), but lets be sure since doing it twice * wont cause an error. */ ubt_abortdealloc(sc); /* wait for all processes to finish */ s = splusb(); if (sc->sc_refcnt-- > 0) usb_detach_wait(USBDEV(sc->sc_dev)); splx(s); usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); DPRINTFN(1, "driver detached\n"); return 0; } int ubt_activate(device_ptr_t self, enum devact act) { struct ubt_softc *sc = (struct ubt_softc *)self; int error = 0; DPRINTFN(1, "ubt_activate: sc=%p, act=%d\n", sc, act); switch (act) { case DVACT_ACTIVATE: return EOPNOTSUPP; break; case DVACT_DEACTIVATE: sc->sc_dying = 1; break; } return error; } /* set ISOC configuration */ int ubt_set_isoc_config(struct ubt_softc *sc) { usb_endpoint_descriptor_t *ed; int rd_addr, wr_addr, rd_size, wr_size; uint8_t count, i; int err; err = usbd_set_interface(sc->sc_iface1, sc->sc_config); if (err != USBD_NORMAL_COMPLETION) { printf( "%s: Could not set config %d on ISOC interface. %s (%d)\n", USBDEVNAME(sc->sc_dev), sc->sc_config, usbd_errstr(err), err); return err == USBD_IN_USE ? EBUSY : EIO; } /* * We wont get past the above if there are any pipes open, so no * need to worry about buf/xfer/pipe deallocation. If we get an * error after this, the frame quantities will be 0 and no SCO * data will be possible. */ sc->sc_scord_size = rd_size = 0; sc->sc_scord_addr = rd_addr = -1; sc->sc_scowr_size = wr_size = 0; sc->sc_scowr_addr = wr_addr = -1; count = 0; (void)usbd_endpoint_count(sc->sc_iface1, &count); for (i = 0 ; i < count ; i++) { ed = usbd_interface2endpoint_descriptor(sc->sc_iface1, i); if (ed == NULL) { printf("%s: could not read endpoint descriptor %d\n", USBDEVNAME(sc->sc_dev), i); return EIO; } DPRINTFN(5, "%s: endpoint type %02x (%02x) addr %02x (%s)\n", USBDEVNAME(sc->sc_dev), UE_GET_XFERTYPE(ed->bmAttributes), UE_GET_ISO_TYPE(ed->bmAttributes), ed->bEndpointAddress, UE_GET_DIR(ed->bEndpointAddress) ? "in" : "out"); if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) continue; if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) { rd_addr = ed->bEndpointAddress; rd_size = UGETW(ed->wMaxPacketSize); } else { wr_addr = ed->bEndpointAddress; wr_size = UGETW(ed->wMaxPacketSize); } } if (rd_addr == -1) { printf( "%s: missing ISOC IN endpoint on interface config %d\n", USBDEVNAME(sc->sc_dev), sc->sc_config); return ENOENT; } if (wr_addr == -1) { printf( "%s: missing ISOC OUT endpoint on interface config %d\n", USBDEVNAME(sc->sc_dev), sc->sc_config); return ENOENT; } #ifdef DIAGNOSTIC if (rd_size > MLEN) { printf("%s: rd_size=%d exceeds MLEN\n", USBDEVNAME(sc->sc_dev), rd_size); return EOVERFLOW; } if (wr_size > MLEN) { printf("%s: wr_size=%d exceeds MLEN\n", USBDEVNAME(sc->sc_dev), wr_size); return EOVERFLOW; } #endif sc->sc_scord_size = rd_size; sc->sc_scord_addr = rd_addr; sc->sc_scowr_size = wr_size; sc->sc_scowr_addr = wr_addr; return 0; } void ubt_abortdealloc(struct ubt_softc *sc) { int i; DPRINTFN(1, "sc=%p\n", sc); /* Abort all pipes */ if (sc->sc_evt_pipe != NULL) { usbd_abort_pipe(sc->sc_evt_pipe); usbd_close_pipe(sc->sc_evt_pipe); sc->sc_evt_pipe = NULL; } if (sc->sc_aclrd_pipe != NULL) { usbd_abort_pipe(sc->sc_aclrd_pipe); usbd_close_pipe(sc->sc_aclrd_pipe); sc->sc_aclrd_pipe = NULL; } if (sc->sc_aclwr_pipe != NULL) { usbd_abort_pipe(sc->sc_aclwr_pipe); usbd_close_pipe(sc->sc_aclwr_pipe); sc->sc_aclwr_pipe = NULL; } if (sc->sc_scord_pipe != NULL) { usbd_abort_pipe(sc->sc_scord_pipe); usbd_close_pipe(sc->sc_scord_pipe); sc->sc_scord_pipe = NULL; } if (sc->sc_scowr_pipe != NULL) { usbd_abort_pipe(sc->sc_scowr_pipe); usbd_close_pipe(sc->sc_scowr_pipe); sc->sc_scowr_pipe = NULL; } /* Free event buffer */ if (sc->sc_evt_buf != NULL) { free(sc->sc_evt_buf, M_USBDEV); sc->sc_evt_buf = NULL; } /* Free all xfers and xfer buffers (implicit) */ if (sc->sc_cmd_xfer != NULL) { usbd_free_xfer(sc->sc_cmd_xfer); sc->sc_cmd_xfer = NULL; sc->sc_cmd_buf = NULL; } if (sc->sc_aclrd_xfer != NULL) { usbd_free_xfer(sc->sc_aclrd_xfer); sc->sc_aclrd_xfer = NULL; sc->sc_aclrd_buf = NULL; } if (sc->sc_aclwr_xfer != NULL) { usbd_free_xfer(sc->sc_aclwr_xfer); sc->sc_aclwr_xfer = NULL; sc->sc_aclwr_buf = NULL; } for (i = 0 ; i < UBT_NXFERS ; i++) { if (sc->sc_scord[i].xfer != NULL) { usbd_free_xfer(sc->sc_scord[i].xfer); sc->sc_scord[i].xfer = NULL; sc->sc_scord[i].buf = NULL; } if (sc->sc_scowr[i].xfer != NULL) { usbd_free_xfer(sc->sc_scowr[i].xfer); sc->sc_scowr[i].xfer = NULL; sc->sc_scowr[i].buf = NULL; } } /* Free partial SCO packets */ if (sc->sc_scord_mbuf != NULL) { m_freem(sc->sc_scord_mbuf); sc->sc_scord_mbuf = NULL; } if (sc->sc_scowr_mbuf != NULL) { m_freem(sc->sc_scowr_mbuf); sc->sc_scowr_mbuf = NULL; } } /******************************************************************************* * * Bluetooth Unit/USB callbacks * * All of this will be called at the IPL_ we specified above */ int ubt_enable(struct hci_unit *unit) { struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; usbd_status err; int i, error; DPRINTFN(1, "sc=%p\n", sc); if (unit->hci_flags & BTF_RUNNING) return 0; /* Events */ sc->sc_evt_buf = malloc(UBT_BUFSIZ_EVENT, M_USBDEV, M_NOWAIT); if (sc->sc_evt_buf == NULL) { error = ENOMEM; goto bad; } err = usbd_open_pipe_intr(sc->sc_iface0, sc->sc_evt_addr, USBD_SHORT_XFER_OK, &sc->sc_evt_pipe, sc, sc->sc_evt_buf, UBT_BUFSIZ_EVENT, ubt_recv_event, USBD_DEFAULT_INTERVAL); if (err != USBD_NORMAL_COMPLETION) { error = EIO; goto bad; } /* Commands */ sc->sc_cmd_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_cmd_xfer == NULL) { error = ENOMEM; goto bad; } sc->sc_cmd_buf = usbd_alloc_buffer(sc->sc_cmd_xfer, UBT_BUFSIZ_CMD); if (sc->sc_cmd_buf == NULL) { error = ENOMEM; goto bad; } /* ACL read */ err = usbd_open_pipe(sc->sc_iface0, sc->sc_aclrd_addr, USBD_EXCLUSIVE_USE, &sc->sc_aclrd_pipe); if (err != USBD_NORMAL_COMPLETION) { error = EIO; goto bad; } sc->sc_aclrd_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_aclrd_xfer == NULL) { error = ENOMEM; goto bad; } sc->sc_aclrd_buf = usbd_alloc_buffer(sc->sc_aclrd_xfer, UBT_BUFSIZ_ACL); if (sc->sc_aclrd_buf == NULL) { error = ENOMEM; goto bad; } sc->sc_aclrd_busy = 0; ubt_recv_acl_start(sc); /* ACL write */ err = usbd_open_pipe(sc->sc_iface0, sc->sc_aclwr_addr, USBD_EXCLUSIVE_USE, &sc->sc_aclwr_pipe); if (err != USBD_NORMAL_COMPLETION) { error = EIO; goto bad; } sc->sc_aclwr_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_aclwr_xfer == NULL) { error = ENOMEM; goto bad; } sc->sc_aclwr_buf = usbd_alloc_buffer(sc->sc_aclwr_xfer, UBT_BUFSIZ_ACL); if (sc->sc_aclwr_buf == NULL) { error = ENOMEM; goto bad; } /* SCO read */ if (sc->sc_scord_size > 0) { err = usbd_open_pipe(sc->sc_iface1, sc->sc_scord_addr, USBD_EXCLUSIVE_USE, &sc->sc_scord_pipe); if (err != USBD_NORMAL_COMPLETION) { error = EIO; goto bad; } for (i = 0 ; i < UBT_NXFERS ; i++) { sc->sc_scord[i].xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_scord[i].xfer == NULL) { error = ENOMEM; goto bad; } sc->sc_scord[i].buf = usbd_alloc_buffer(sc->sc_scord[i].xfer, sc->sc_scord_size * UBT_NFRAMES); if (sc->sc_scord[i].buf == NULL) { error = ENOMEM; goto bad; } sc->sc_scord[i].softc = sc; sc->sc_scord[i].busy = 0; ubt_recv_sco_start1(sc, &sc->sc_scord[i]); } } /* SCO write */ if (sc->sc_scowr_size > 0) { err = usbd_open_pipe(sc->sc_iface1, sc->sc_scowr_addr, USBD_EXCLUSIVE_USE, &sc->sc_scowr_pipe); if (err != USBD_NORMAL_COMPLETION) { error = EIO; goto bad; } for (i = 0 ; i < UBT_NXFERS ; i++) { sc->sc_scowr[i].xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->sc_scowr[i].xfer == NULL) { error = ENOMEM; goto bad; } sc->sc_scowr[i].buf = usbd_alloc_buffer(sc->sc_scowr[i].xfer, sc->sc_scowr_size * UBT_NFRAMES); if (sc->sc_scowr[i].buf == NULL) { error = ENOMEM; goto bad; } sc->sc_scowr[i].softc = sc; sc->sc_scowr[i].busy = 0; } } unit->hci_flags &= ~BTF_XMIT; unit->hci_flags |= BTF_RUNNING; return 0; bad: ubt_abortdealloc(sc); return error; } void ubt_disable(struct hci_unit *unit) { struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; DPRINTFN(1, "sc=%p\n", sc); if ((unit->hci_flags & BTF_RUNNING) == 0) return; ubt_abortdealloc(sc); unit->hci_flags &= ~BTF_RUNNING; } void ubt_xmit_cmd_start(struct hci_unit *unit) { struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; usb_device_request_t req; usbd_status status; struct mbuf *m; int len; if (sc->sc_dying) return; if (IF_IS_EMPTY(&unit->hci_cmdq)) return; IF_DEQUEUE(&unit->hci_cmdq, m); DPRINTFN(15, "%s: xmit CMD packet (%d bytes)\n", unit->hci_devname, m->m_pkthdr.len); sc->sc_refcnt++; unit->hci_flags |= BTF_XMIT_CMD; len = m->m_pkthdr.len - 1; m_copydata(m, 1, len, sc->sc_cmd_buf); m_freem(m); memset(&req, 0, sizeof(req)); req.bmRequestType = UT_WRITE_CLASS_DEVICE; USETW(req.wLength, len); usbd_setup_default_xfer(sc->sc_cmd_xfer, sc->sc_udev, unit, UBT_CMD_TIMEOUT, &req, sc->sc_cmd_buf, len, USBD_NO_COPY | USBD_FORCE_SHORT_XFER, ubt_xmit_cmd_complete); status = usbd_transfer(sc->sc_cmd_xfer); KASSERT(status != USBD_NORMAL_COMPLETION); if (status != USBD_IN_PROGRESS) { DPRINTF("usbd_transfer status=%s (%d)\n", usbd_errstr(status), status); sc->sc_refcnt--; unit->hci_flags &= ~BTF_XMIT_CMD; } } void ubt_xmit_cmd_complete(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct hci_unit *unit = h; struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; uint32_t count; DPRINTFN(15, "%s: CMD complete status=%s (%d)\n", unit->hci_devname, usbd_errstr(status), status); unit->hci_flags &= ~BTF_XMIT_CMD; if (--sc->sc_refcnt < 0) { DPRINTF("sc_refcnt=%d\n", sc->sc_refcnt); usb_detach_wakeup(USBDEV(sc->sc_dev)); return; } if (sc->sc_dying) { DPRINTF("sc_dying\n"); return; } if (status != USBD_NORMAL_COMPLETION) { DPRINTF("status=%s (%d)\n", usbd_errstr(status), status); unit->hci_stats.err_tx++; return; } usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); unit->hci_stats.cmd_tx++; unit->hci_stats.byte_tx += count; ubt_xmit_cmd_start(unit); } void ubt_xmit_acl_start(struct hci_unit *unit) { struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; struct mbuf *m; usbd_status status; int len; if (sc->sc_dying) return; if (IF_IS_EMPTY(&unit->hci_acltxq) == NULL) return; sc->sc_refcnt++; unit->hci_flags |= BTF_XMIT_ACL; IF_DEQUEUE(&unit->hci_acltxq, m); DPRINTFN(15, "%s: xmit ACL packet (%d bytes)\n", unit->hci_devname, m->m_pkthdr.len); len = m->m_pkthdr.len - 1; if (len > UBT_BUFSIZ_ACL) { DPRINTF("%s: truncating ACL packet (%d => %d)!\n", unit->hci_devname, len, UBT_BUFSIZ_ACL); len = UBT_BUFSIZ_ACL; } m_copydata(m, 1, len, sc->sc_aclwr_buf); m_freem(m); unit->hci_stats.acl_tx++; unit->hci_stats.byte_tx += len; usbd_setup_xfer(sc->sc_aclwr_xfer, sc->sc_aclwr_pipe, unit, sc->sc_aclwr_buf, len, USBD_NO_COPY | USBD_FORCE_SHORT_XFER, UBT_ACL_TIMEOUT, ubt_xmit_acl_complete); status = usbd_transfer(sc->sc_aclwr_xfer); KASSERT(status != USBD_NORMAL_COMPLETION); if (status != USBD_IN_PROGRESS) { DPRINTF("usbd_transfer status=%s (%d)\n", usbd_errstr(status), status); sc->sc_refcnt--; unit->hci_flags &= ~BTF_XMIT_ACL; } } void ubt_xmit_acl_complete(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct hci_unit *unit = h; struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; DPRINTFN(15, "%s: ACL complete status=%s (%d)\n", unit->hci_devname, usbd_errstr(status), status); unit->hci_flags &= ~BTF_XMIT_ACL; if (--sc->sc_refcnt < 0) { usb_detach_wakeup(USBDEV(sc->sc_dev)); return; } if (sc->sc_dying) return; if (status != USBD_NORMAL_COMPLETION) { DPRINTF("status=%s (%d)\n", usbd_errstr(status), status); unit->hci_stats.err_tx++; if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_aclwr_pipe); else return; } ubt_xmit_acl_start(unit); } void ubt_xmit_sco_start(struct hci_unit *unit) { struct ubt_softc *sc = (struct ubt_softc *)unit->hci_softc; int i; if (sc->sc_dying || sc->sc_scowr_size == 0) return; for (i = 0 ; i < UBT_NXFERS ; i++) { if (sc->sc_scowr[i].busy) continue; ubt_xmit_sco_start1(sc, &sc->sc_scowr[i]); } } void ubt_xmit_sco_start1(struct ubt_softc *sc, struct ubt_isoc_xfer *isoc) { struct mbuf *m; uint8_t *buf; int num, len, size, space; space = sc->sc_scowr_size * UBT_NFRAMES; buf = isoc->buf; len = 0; /* * Fill the request buffer with data from the queue, * keeping any leftover packet on our private hook. * * Complete packets are passed back up to the stack * for disposal, since we can't rely on the controller * to tell us when it has finished with them. */ m = sc->sc_scowr_mbuf; while (space > 0) { if (m == NULL) { IF_DEQUEUE(&sc->sc_unit.hci_scotxq, m); if (m == NULL) break; m_adj(m, 1); /* packet type */ } if (m->m_pkthdr.len > 0) { size = MIN(m->m_pkthdr.len, space); m_copydata(m, 0, size, buf); m_adj(m, size); buf += size; len += size; space -= size; } if (m->m_pkthdr.len == 0) { sc->sc_unit.hci_stats.sco_tx++; hci_complete_sco(&sc->sc_unit, m); m = NULL; } } sc->sc_scowr_mbuf = m; DPRINTFN(15, "isoc=%p, len=%d, space=%d\n", isoc, len, space); if (len == 0) /* nothing to send */ return; sc->sc_refcnt++; sc->sc_unit.hci_flags |= BTF_XMIT_SCO; sc->sc_unit.hci_stats.byte_tx += len; isoc->busy = 1; /* * calculate number of isoc frames and sizes */ for (num = 0 ; len > 0 ; num++) { size = MIN(sc->sc_scowr_size, len); isoc->size[num] = size; len -= size; } usbd_setup_isoc_xfer(isoc->xfer, sc->sc_scowr_pipe, isoc, isoc->size, num, USBD_NO_COPY | USBD_FORCE_SHORT_XFER, ubt_xmit_sco_complete); usbd_transfer(isoc->xfer); } void ubt_xmit_sco_complete(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct ubt_isoc_xfer *isoc = h; struct ubt_softc *sc; int i; KASSERT(xfer == isoc->xfer); sc = isoc->softc; DPRINTFN(15, "isoc=%p, status=%s (%d)\n", isoc, usbd_errstr(status), status); isoc->busy = 0; for (i = 0 ; ; i++) { if (i == UBT_NXFERS) { sc->sc_unit.hci_flags &= ~BTF_XMIT_SCO; break; } if (sc->sc_scowr[i].busy) break; } if (--sc->sc_refcnt < 0) { usb_detach_wakeup(USBDEV(sc->sc_dev)); return; } if (sc->sc_dying) return; if (status != USBD_NORMAL_COMPLETION) { DPRINTF("status=%s (%d)\n", usbd_errstr(status), status); sc->sc_unit.hci_stats.err_tx++; if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_scowr_pipe); else return; } ubt_xmit_sco_start(&sc->sc_unit); } /* * load incoming data into an mbuf with * leading type byte */ static struct mbuf * ubt_mbufload(uint8_t *buf, int count, uint8_t type) { struct mbuf *m; MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return NULL; *mtod(m, uint8_t *) = type; m->m_pkthdr.len = m->m_len = MHLEN; m_copyback(m, 1, count, buf); // (extends if necessary) if (m->m_pkthdr.len != MAX(MHLEN, count + 1)) { m_free(m); return NULL; } m->m_pkthdr.len = count + 1; m->m_len = MIN(MHLEN, m->m_pkthdr.len); return m; } void ubt_recv_event(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct ubt_softc *sc = h; struct mbuf *m; uint32_t count; void *buf; DPRINTFN(15, "sc=%p status=%s (%d)\n", sc, usbd_errstr(status), status); if (status != USBD_NORMAL_COMPLETION || sc->sc_dying) return; usbd_get_xfer_status(xfer, NULL, &buf, &count, NULL); if (count < sizeof(hci_event_hdr_t) - 1) { DPRINTF("dumped undersized event (count = %d)\n", count); sc->sc_unit.hci_stats.err_rx++; return; } sc->sc_unit.hci_stats.evt_rx++; sc->sc_unit.hci_stats.byte_rx += count; m = ubt_mbufload(buf, count, HCI_EVENT_PKT); if (m != NULL) hci_input_event(&sc->sc_unit, m); else sc->sc_unit.hci_stats.err_rx++; } void ubt_recv_acl_start(struct ubt_softc *sc) { usbd_status status; DPRINTFN(15, "sc=%p\n", sc); if (sc->sc_aclrd_busy || sc->sc_dying) { DPRINTF("sc_aclrd_busy=%d, sc_dying=%d\n", sc->sc_aclrd_busy, sc->sc_dying); return; } sc->sc_refcnt++; sc->sc_aclrd_busy = 1; usbd_setup_xfer(sc->sc_aclrd_xfer, sc->sc_aclrd_pipe, sc, sc->sc_aclrd_buf, UBT_BUFSIZ_ACL, USBD_NO_COPY | USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ubt_recv_acl_complete); status = usbd_transfer(sc->sc_aclrd_xfer); KASSERT(status != USBD_NORMAL_COMPLETION); if (status != USBD_IN_PROGRESS) { DPRINTF("usbd_transfer status=%s (%d)\n", usbd_errstr(status), status); sc->sc_refcnt--; sc->sc_aclrd_busy = 0; } } void ubt_recv_acl_complete(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct ubt_softc *sc = h; struct mbuf *m; uint32_t count; void *buf; DPRINTFN(15, "sc=%p status=%s (%d)\n", sc, usbd_errstr(status), status); sc->sc_aclrd_busy = 0; if (--sc->sc_refcnt < 0) { DPRINTF("refcnt = %d\n", sc->sc_refcnt); usb_detach_wakeup(USBDEV(sc->sc_dev)); return; } if (sc->sc_dying) { DPRINTF("sc_dying\n"); return; } if (status != USBD_NORMAL_COMPLETION) { DPRINTF("status=%s (%d)\n", usbd_errstr(status), status); sc->sc_unit.hci_stats.err_rx++; if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_aclrd_pipe); else return; } else { usbd_get_xfer_status(xfer, NULL, &buf, &count, NULL); if (count < sizeof(hci_acldata_hdr_t) - 1) { DPRINTF("dumped undersized packet (%d)\n", count); sc->sc_unit.hci_stats.err_rx++; } else { sc->sc_unit.hci_stats.acl_rx++; sc->sc_unit.hci_stats.byte_rx += count; m = ubt_mbufload(buf, count, HCI_ACL_DATA_PKT); if (m != NULL) hci_input_acl(&sc->sc_unit, m); else sc->sc_unit.hci_stats.err_rx++; } } /* and restart */ ubt_recv_acl_start(sc); } void ubt_recv_sco_start1(struct ubt_softc *sc, struct ubt_isoc_xfer *isoc) { int i; DPRINTFN(15, "sc=%p, isoc=%p\n", sc, isoc); if (isoc->busy || sc->sc_dying || sc->sc_scord_size == 0) { DPRINTF("%s%s%s\n", isoc->busy ? " busy" : "", sc->sc_dying ? " dying" : "", sc->sc_scord_size == 0 ? " size=0" : ""); return; } sc->sc_refcnt++; isoc->busy = 1; for (i = 0 ; i < UBT_NFRAMES ; i++) isoc->size[i] = sc->sc_scord_size; usbd_setup_isoc_xfer(isoc->xfer, sc->sc_scord_pipe, isoc, isoc->size, UBT_NFRAMES, USBD_NO_COPY | USBD_SHORT_XFER_OK, ubt_recv_sco_complete); usbd_transfer(isoc->xfer); } void ubt_recv_sco_complete(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) { struct ubt_isoc_xfer *isoc = h; struct ubt_softc *sc; struct mbuf *m; uint32_t count; uint8_t *ptr, *frame; int i, size, got, want; KASSERT(isoc != NULL); KASSERT(isoc->xfer == xfer); sc = isoc->softc; isoc->busy = 0; if (--sc->sc_refcnt < 0) { DPRINTF("refcnt=%d\n", sc->sc_refcnt); usb_detach_wakeup(USBDEV(sc->sc_dev)); return; } if (sc->sc_dying) { DPRINTF("sc_dying\n"); return; } if (status != USBD_NORMAL_COMPLETION) { DPRINTF("status=%s (%d)\n", usbd_errstr(status), status); sc->sc_unit.hci_stats.err_rx++; if (status == USBD_STALLED) { usbd_clear_endpoint_stall_async(sc->sc_scord_pipe); goto restart; } return; } usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); if (count == 0) goto restart; DPRINTFN(15, "sc=%p, isoc=%p, count=%u\n", sc, isoc, count); sc->sc_unit.hci_stats.byte_rx += count; /* * Extract SCO packets from ISOC frames. The way we have it, * no SCO packet can be bigger than MHLEN. This is unlikely * to actually happen, but if we ran out of mbufs and lost * sync then we may get spurious data that makes it seem that * way, so we discard data that wont fit. This doesnt really * help with the lost sync situation alas. */ m = sc->sc_scord_mbuf; if (m != NULL) { sc->sc_scord_mbuf = NULL; ptr = mtod(m, uint8_t *) + m->m_pkthdr.len; got = m->m_pkthdr.len; want = sizeof(hci_scodata_hdr_t); if (got >= want) want += mtod(m, hci_scodata_hdr_t *)->length ; } else { ptr = NULL; got = 0; want = 0; } for (i = 0 ; i < UBT_NFRAMES ; i++) { frame = isoc->buf + (i * sc->sc_scord_size); while (isoc->size[i] > 0) { size = isoc->size[i]; if (m == NULL) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { printf("%s: out of memory (xfer halted)\n", USBDEVNAME(sc->sc_dev)); sc->sc_unit.hci_stats.err_rx++; return; /* lost sync */ } ptr = mtod(m, uint8_t *); *ptr++ = HCI_SCO_DATA_PKT; got = 1; want = sizeof(hci_scodata_hdr_t); } if (got + size > want) size = want - got; if (got + size > MHLEN) memcpy(ptr, frame, MHLEN - got); else memcpy(ptr, frame, size); ptr += size; got += size; frame += size; if (got == want) { /* * If we only got a header, add the packet * length to our want count. Send complete * packets up to protocol stack. */ if (want == sizeof(hci_scodata_hdr_t)) want += mtod(m, hci_scodata_hdr_t *)->length; if (got == want) { m->m_pkthdr.len = m->m_len = got; sc->sc_unit.hci_stats.sco_rx++; hci_input_sco(&sc->sc_unit, m); m = NULL; } } isoc->size[i] -= size; } } if (m != NULL) { m->m_pkthdr.len = m->m_len = got; sc->sc_scord_mbuf = m; } restart: /* and restart */ ubt_recv_sco_start1(sc, isoc); }