diff options
| author | dsainty <dsainty@NetBSD.org> | 2003-01-05 05:20:20 +0000 |
|---|---|---|
| committer | dsainty <dsainty@NetBSD.org> | 2003-01-05 05:20:20 +0000 |
| commit | 97f7cada0bc6fc9873b7c904c5b3efcbdf47a4fc (patch) | |
| tree | d1af130f9c56e8dbf6e081bd524d2f579f61b9e3 /sys/dev | |
| parent | 466f76bf7b86916c6e848e97bdf3240a697a034e (diff) | |
Make the ubt driver functional. This is an experimental, incomplete, but
actually functional driver. It provides a local HCI interface to the
HCI-over-USB interface on standards-compliant Bluetooth USB devices.
Currently this device can be attached to the bthci driver for direct user-land
access to the device.
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/usb/ubt.c | 480 | ||||
| -rw-r--r-- | sys/dev/usb/ubtreg.h | 40 |
2 files changed, 485 insertions, 35 deletions
diff --git a/sys/dev/usb/ubt.c b/sys/dev/usb/ubt.c index 09e58e2d047..1dbdb3e6eb9 100644 --- a/sys/dev/usb/ubt.c +++ b/sys/dev/usb/ubt.c @@ -1,11 +1,12 @@ -/* $NetBSD: ubt.c,v 1.4 2003/01/05 03:48:18 dsainty Exp $ */ +/* $NetBSD: ubt.c,v 1.5 2003/01/05 05:20:20 dsainty Exp $ */ /* - * Copyright (c) 2002 The NetBSD Foundation, Inc. + * 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). + * 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 @@ -37,11 +38,12 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ubt.c,v 1.4 2003/01/05 03:48:18 dsainty Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ubt.c,v 1.5 2003/01/05 05:20:20 dsainty Exp $"); #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> +#include <sys/malloc.h> #include <sys/device.h> #include <sys/lock.h> #include <sys/ioctl.h> @@ -51,6 +53,7 @@ __KERNEL_RCSID(0, "$NetBSD: ubt.c,v 1.4 2003/01/05 03:48:18 dsainty Exp $"); #include <sys/select.h> #include <sys/proc.h> +#include <dev/usb/ubtreg.h> #include <dev/usb/usb.h> #include <dev/usb/usbdi.h> #include <dev/usb/usbdi_util.h> @@ -61,7 +64,7 @@ __KERNEL_RCSID(0, "$NetBSD: ubt.c,v 1.4 2003/01/05 03:48:18 dsainty Exp $"); #ifdef UBT_DEBUG #define DPRINTF(x) if (ubtdebug) logprintf x #define DPRINTFN(n,x) if (ubtdebug>(n)) logprintf x -int ubtdebug = 1; +int ubtdebug = 99; #else #define DPRINTF(x) #define DPRINTFN(n,x) @@ -77,17 +80,59 @@ struct ubt_softc { usbd_interface_handle sc_ctl_iface; usbd_interface_handle sc_isoc_iface; - int sc_rd_addr; - usbd_pipe_handle sc_rd_pipe; - - int sc_wr_addr; - usbd_pipe_handle sc_wr_pipe; + /* Control */ + usbd_pipe_handle sc_ctl_pipe; + usbd_xfer_handle sc_ctl_xfer; + u_int8_t *sc_ctl_buf; + + /* Events */ + int sc_evt_addr; + usbd_pipe_handle sc_evt_pipe; + usbd_xfer_handle sc_evt_xfer; + u_int8_t *sc_evt_buf; + + /* ACL data (in) */ + int sc_aclrd_addr; + usbd_pipe_handle sc_aclrd_pipe; + usbd_xfer_handle sc_aclrd_xfer; + u_int8_t *sc_aclrd_buf; + int sc_aclrd_running; + + /* ACL data (out) */ + int sc_aclwr_addr; + usbd_pipe_handle sc_aclwr_pipe; + usbd_xfer_handle sc_aclwr_xfer; + u_int8_t *sc_aclwr_buf; struct device *sc_child; + struct btframe_callback_methods const *sc_cb; + + unsigned int sc_blocked; + int sc_refcnt; char sc_dying; }; +static int ubt_open(void *h, int flag, int mode, usb_proc_ptr p); +static int ubt_close(void *h, int flag, int mode, usb_proc_ptr p); + +static int ubt_control(void *h, u_int8_t *data, size_t len); +static int ubt_sendacldata(void *h, u_int8_t *data, size_t len); +static int ubt_sendscodata(void *h, u_int8_t *data, size_t len); + +static int ubt_splraise(void); +static unsigned int ubt_blockcb(void *h, unsigned int cbblocks); +static unsigned int ubt_unblockcb(void *h, unsigned int cbblocks); + +static void ubt_event_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ubt_aclrd_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ubt_aclrd_request(struct ubt_softc *); + +static struct btframe_methods const ubt_methods = { + ubt_open, ubt_close, ubt_control, ubt_sendacldata, ubt_sendscodata, + ubt_splraise, ubt_blockcb, ubt_unblockcb +}; + USB_DECLARE_DRIVER(ubt); USB_MATCH(ubt) @@ -115,9 +160,9 @@ USB_ATTACH(ubt) usbd_device_handle dev = uaa->device; usbd_interface_handle iface = uaa->iface; struct bt_attach_args bt; - usb_interface_descriptor_t *id; + usb_interface_descriptor_t const *id; char devinfo[1024]; - usb_endpoint_descriptor_t *ed; + usb_endpoint_descriptor_t const *ed; u_int8_t epcount; int i; @@ -130,14 +175,17 @@ USB_ATTACH(ubt) sc->sc_udev = dev; sc->sc_ctl_iface = iface; - /* + /* * The control interface comes before the isoc interface * according to the spec, so we find it first. */ epcount = 0; (void)usbd_endpoint_count(iface, &epcount); - sc->sc_rd_addr = -1; - sc->sc_wr_addr = -1; + + sc->sc_evt_addr = -1; + sc->sc_aclrd_addr = -1; + sc->sc_aclwr_addr = -1; + for (i = 0; i < epcount; i++) { ed = usbd_interface2endpoint_descriptor(iface, i); if (ed == NULL) { @@ -145,20 +193,28 @@ USB_ATTACH(ubt) USBDEVNAME(sc->sc_dev), i); USB_ATTACH_ERROR_RETURN; } + + DPRINTFN(10, ("%s: addr=%d attr=%d\n", __func__, + (int)ed->bEndpointAddress, + (int)ed->bmAttributes)); + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && - UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { - sc->sc_rd_addr = ed->bEndpointAddress; + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_evt_addr = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_aclrd_addr = ed->bEndpointAddress; } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { - sc->sc_wr_addr = ed->bEndpointAddress; + sc->sc_aclwr_addr = ed->bEndpointAddress; } } -#if 0 - if (sc->sc_rd_addr == -1 || sc->sc_wr_addr == -1) { + + if (sc->sc_evt_addr == -1 || + sc->sc_aclrd_addr == -1 || sc->sc_aclwr_addr == -1) { printf("%s: missing endpoint\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } -#endif /* XXX works because isoc comes after ctl */ /* Grab isoc interface as well. */ @@ -181,50 +237,84 @@ USB_ATTACH(ubt) usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, USBDEV(sc->sc_dev)); + bt.bt_methods = &ubt_methods; + bt.bt_cb = &sc->sc_cb; + sc->sc_child = config_found(self, &bt, bt_print); USB_ATTACH_SUCCESS_RETURN; } +static void +ubt_abortdealloc(struct ubt_softc *sc) +{ + DPRINTFN(0, ("%s: sc=%p\n", __func__, sc)); + + 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_evt_buf != NULL) { + free(sc->sc_evt_buf, M_USBDEV); + sc->sc_evt_buf = 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_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; + } +} + USB_DETACH(ubt) { USB_DETACH_START(ubt, sc); int s; int rv = 0; - DPRINTF(("ubt_detach: sc=%p flags=%d\n", sc, flags)); + DPRINTF(("%s: sc=%p flags=%d\n", __func__, sc, flags)); sc->sc_dying = 1; + /* Abort all pipes. Causes processes waiting for transfer to wake. */ - if (sc->sc_rd_pipe != NULL) { - usbd_abort_pipe(sc->sc_rd_pipe); - usbd_close_pipe(sc->sc_rd_pipe); - sc->sc_rd_pipe = NULL; - } - if (sc->sc_wr_pipe != NULL) { - usbd_abort_pipe(sc->sc_wr_pipe); - usbd_close_pipe(sc->sc_wr_pipe); - sc->sc_wr_pipe = NULL; - } -#if 0 - wakeup(&sc->sc_rd_count); -#endif + ubt_abortdealloc(sc); + DPRINTFN(1, ("%s: waiting for USB detach\n", __func__)); s = splusb(); if (--sc->sc_refcnt >= 0) { /* Wait for processes to go away. */ usb_detach_wait(USBDEV(sc->sc_dev)); } splx(s); + DPRINTFN(1, ("%s: USB detach complete\n", __func__)); if (sc->sc_child != NULL) { + DPRINTFN(1, ("%s: waiting for child detach\n", __func__)); rv = config_detach(sc->sc_child, flags); sc->sc_child = NULL; + DPRINTFN(1, ("%s: child detach complete\n", __func__)); } usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); + DPRINTFN(1, ("%s: driver detached\n", __func__)); + return (rv); } @@ -247,3 +337,323 @@ ubt_activate(device_ptr_t self, enum devact act) } return (error); } + +static int +ubt_open(void *h, int flag, int mode, usb_proc_ptr p) +{ + struct ubt_softc *sc = h; + int error; + usbd_status err; + + DPRINTFN(0, ("%s: sc=%p\n", __func__, sc)); + + sc->sc_evt_buf = malloc(BTHCI_EVENT_MAX_LEN, M_USBDEV, M_NOWAIT); + if (sc->sc_evt_buf == NULL) { + error = ENOMEM; + goto bad0; + } + + err = usbd_open_pipe_intr(sc->sc_ctl_iface, sc->sc_evt_addr, + USBD_SHORT_XFER_OK, &sc->sc_evt_pipe, + sc, sc->sc_evt_buf, BTHCI_EVENT_MAX_LEN, + ubt_event_cb, UBT_EVENT_EP_INTERVAL); + if (err != USBD_NORMAL_COMPLETION) { + error = EIO; + goto bad1; + } + + err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_aclrd_addr, + 0, &sc->sc_aclrd_pipe); + if (err != USBD_NORMAL_COMPLETION) { + error = EIO; + goto bad2; + } + + err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_aclwr_addr, + 0, &sc->sc_aclwr_pipe); + if (err != USBD_NORMAL_COMPLETION) { + error = EIO; + goto bad3; + } + + sc->sc_ctl_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_ctl_xfer == NULL) { + error = ENOMEM; + goto bad4; + } + sc->sc_aclrd_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_aclrd_xfer == NULL) { + error = ENOMEM; + goto bad5; + } + sc->sc_aclwr_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_aclwr_xfer == NULL) { + error = ENOMEM; + goto bad6; + } + + /* Buffers */ + sc->sc_ctl_buf = usbd_alloc_buffer(sc->sc_ctl_xfer, + BTHCI_COMMAND_MAX_LEN); + if (sc->sc_ctl_buf == NULL) { + error = ENOMEM; + goto bad7; + } + sc->sc_aclrd_buf = usbd_alloc_buffer(sc->sc_aclrd_xfer, + BTHCI_ACL_DATA_MAX_LEN); + if (sc->sc_aclrd_buf == NULL) { + error = ENOMEM; + goto bad7; + } + sc->sc_aclwr_buf = usbd_alloc_buffer(sc->sc_aclwr_xfer, + BTHCI_ACL_DATA_MAX_LEN); + if (sc->sc_aclwr_buf == NULL) { + error = ENOMEM; + goto bad7; + } + + /* Start reading */ + ubt_aclrd_request(sc); + + return 0; + + bad7: + usbd_free_xfer(sc->sc_aclwr_xfer); + sc->sc_aclwr_xfer = NULL; + bad6: + usbd_free_xfer(sc->sc_aclrd_xfer); + sc->sc_aclrd_xfer = NULL; + bad5: + usbd_free_xfer(sc->sc_ctl_xfer); + sc->sc_ctl_xfer = NULL; + bad4: + usbd_close_pipe(sc->sc_aclwr_pipe); + sc->sc_aclwr_pipe = NULL; + bad3: + usbd_close_pipe(sc->sc_aclrd_pipe); + sc->sc_aclrd_pipe = NULL; + bad2: + usbd_close_pipe(sc->sc_evt_pipe); + sc->sc_evt_pipe = NULL; + bad1: + free(sc->sc_evt_buf, M_USBDEV); + sc->sc_evt_buf = NULL; + bad0: + return error; +} + +static int +ubt_close(void *h, int flag, int mode, usb_proc_ptr p) +{ + struct ubt_softc *sc = h; + + DPRINTFN(0, ("%s: sc=%p\n", __func__, sc)); + + ubt_abortdealloc(sc); + + return 0; +} + +static int +ubt_control(void *h, u_int8_t *data, size_t len) +{ + struct ubt_softc *sc = h; + usb_device_request_t req; + usbd_status status; + + DPRINTFN(1,("%s: sc=%p\n", __func__, sc)); + + if (sc->sc_dying) + return EIO; + + if (len < BTHCI_COMMAND_MIN_LEN || len > BTHCI_COMMAND_MAX_LEN) + return EINVAL; + + sc->sc_refcnt++; + + memset(&req, 0, sizeof(req)); + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + USETW(req.wLength, len); + + memcpy(sc->sc_ctl_buf, data, len); + + usbd_setup_default_xfer(sc->sc_ctl_xfer, + sc->sc_udev, + sc, + USBD_DEFAULT_TIMEOUT, + &req, sc->sc_ctl_buf, len, + USBD_SYNCHRONOUS | USBD_NO_COPY, NULL); + + status = usbd_transfer(sc->sc_ctl_xfer); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + if (status != USBD_NORMAL_COMPLETION) + return EIO; + + return 0; +} + +static int +ubt_sendacldata(void *h, u_int8_t *data, size_t len) +{ + struct ubt_softc *sc = h; + usbd_status status; + + DPRINTFN(1,("%s: sc=%p\n", __func__, sc)); + + if (sc->sc_dying) + return EIO; + + if (len < BTHCI_ACL_DATA_MIN_LEN || len > BTHCI_ACL_DATA_MAX_LEN) + return EINVAL; + + sc->sc_refcnt++; + + memcpy(sc->sc_aclwr_buf, data, len); + + usbd_setup_xfer(sc->sc_aclwr_xfer, + sc->sc_aclwr_pipe, + (usbd_private_handle)sc, + sc->sc_aclwr_buf, len, + USBD_SYNCHRONOUS | USBD_NO_COPY, + USBD_DEFAULT_TIMEOUT, + NULL); + + status = usbd_transfer(sc->sc_aclwr_xfer); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + if (status != USBD_NORMAL_COMPLETION) + return EIO; + + return 0; +} + +static int +ubt_sendscodata(void *h, u_int8_t *data, size_t len) +{ + struct ubt_softc *sc = h; + + DPRINTFN(1,("%s: sc=%p\n", __func__, sc)); + + if (sc->sc_dying) + return EIO; + + return ENXIO; +} + +static void +ubt_event_cb(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) +{ + struct ubt_softc *sc = h; + void *buf; + u_int32_t size; + + DPRINTFN(1,("%s: sc=%p status=%s\n", __func__, sc, + usbd_errstr(status))); + + if (status != USBD_NORMAL_COMPLETION || sc->sc_dying || + sc->sc_child == NULL) + return; + + usbd_get_xfer_status(xfer, NULL, &buf, &size, NULL); + + sc->sc_cb->bt_recveventdata(sc->sc_child, buf, (size_t)size); +} + +static void +ubt_aclrd_request(struct ubt_softc *sc) +{ + usbd_status status; + int s; + + DPRINTFN(1,("%s: sc=%p\n", __func__, sc)); + + if (sc->sc_dying) + return; + + s = splusb(); + if (sc->sc_aclrd_running) { + splx(s); + return; + } + sc->sc_aclrd_running = 1; + splx(s); + + usbd_setup_xfer(sc->sc_aclrd_xfer, sc->sc_aclrd_pipe, + sc, sc->sc_aclrd_buf, BTHCI_ACL_DATA_MAX_LEN, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, ubt_aclrd_cb); + + status = usbd_transfer(sc->sc_aclrd_xfer); + if (status == USBD_IN_PROGRESS || USBD_CANCELLED) + return; + + DPRINTFN(1,("%s: read request failed: %s\n", __func__, + usbd_errstr(status))); + + sc->sc_aclrd_running = 0; + sc->sc_blocked |= BT_CBBLOCK_ACL_DATA; +} + +static void +ubt_aclrd_cb(usbd_xfer_handle xfer, usbd_private_handle h, usbd_status status) +{ + struct ubt_softc *sc = h; + void *buf; + u_int32_t size; + + DPRINTFN(1,("%s: sc=%p status=%s\n", __func__, sc, + usbd_errstr(status))); + + sc->sc_aclrd_running = 0; + + if (status != USBD_NORMAL_COMPLETION || sc->sc_dying || + sc->sc_child == NULL) + return; + + usbd_get_xfer_status(xfer, NULL, &buf, &size, NULL); + + sc->sc_cb->bt_recvacldata(sc->sc_child, buf, (size_t)size); + + /* Re-issue the request if not blocked */ + if (!sc->sc_dying && !(sc->sc_blocked & BT_CBBLOCK_ACL_DATA)) + ubt_aclrd_request(sc); +} + +static unsigned int +ubt_blockcb(void *h, unsigned int cbblocks) +{ + struct ubt_softc *sc = h; + + sc->sc_blocked |= (cbblocks & BT_CBBLOCK_ACL_DATA); + + return sc->sc_blocked; +} + +static unsigned int +ubt_unblockcb(void *h, unsigned int cbblocks) +{ + struct ubt_softc *sc = h; + unsigned int oblocks, changes; + + oblocks = sc->sc_blocked; + sc->sc_blocked = oblocks & ~cbblocks; + + changes = oblocks & cbblocks; + + if (changes & BT_CBBLOCK_ACL_DATA) + /* Re-issue the request if action un-blocked reads */ + ubt_aclrd_request(sc); + + return sc->sc_blocked; +} + +static int +ubt_splraise(void) +{ + return splusb(); +} diff --git a/sys/dev/usb/ubtreg.h b/sys/dev/usb/ubtreg.h new file mode 100644 index 00000000000..1c604d5e501 --- /dev/null +++ b/sys/dev/usb/ubtreg.h @@ -0,0 +1,40 @@ +/* $NetBSD: ubtreg.h,v 1.1 2003/01/05 05:20:20 dsainty Exp $ */ + +/* + * Copyright (c) 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by 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. + */ + +/* Event endpoint interval */ +#define UBT_EVENT_EP_INTERVAL 1 |
