diff options
Diffstat (limited to 'drivers/bluetooth/bpa10x.c')
-rw-r--r-- | drivers/bluetooth/bpa10x.c | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/drivers/bluetooth/bpa10x.c b/drivers/bluetooth/bpa10x.c new file mode 100644 index 0000000..2771c86 --- /dev/null +++ b/drivers/bluetooth/bpa10x.c @@ -0,0 +1,657 @@ +/* + * + * Digianswer Bluetooth USB driver + * + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/errno.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#ifndef CONFIG_BT_HCIBPA10X_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "0.8" + +static int ignore = 0; + +static struct usb_device_id bpa10x_table[] = { + /* Tektronix BPA 100/105 (Digianswer) */ + { USB_DEVICE(0x08fd, 0x0002) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bpa10x_table); + +#define BPA10X_CMD_EP 0x00 +#define BPA10X_EVT_EP 0x81 +#define BPA10X_TX_EP 0x02 +#define BPA10X_RX_EP 0x82 + +#define BPA10X_CMD_BUF_SIZE 252 +#define BPA10X_EVT_BUF_SIZE 16 +#define BPA10X_TX_BUF_SIZE 384 +#define BPA10X_RX_BUF_SIZE 384 + +struct bpa10x_data { + struct hci_dev *hdev; + struct usb_device *udev; + + rwlock_t lock; + + struct sk_buff_head cmd_queue; + struct urb *cmd_urb; + struct urb *evt_urb; + struct sk_buff *evt_skb; + unsigned int evt_len; + + struct sk_buff_head tx_queue; + struct urb *tx_urb; + struct urb *rx_urb; +}; + +#define HCI_VENDOR_HDR_SIZE 5 + +struct hci_vendor_hdr { + __u8 type; + __u16 snum; + __u16 dlen; +} __attribute__ ((packed)); + +static void bpa10x_recv_bulk(struct bpa10x_data *data, unsigned char *buf, int count) +{ + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + struct hci_vendor_hdr *vh; + struct sk_buff *skb; + int len; + + while (count) { + switch (*buf++) { + case HCI_ACLDATA_PKT: + ah = (struct hci_acl_hdr *) buf; + len = HCI_ACL_HDR_SIZE + __le16_to_cpu(ah->dlen); + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_ACLDATA_PKT; + hci_recv_frame(skb); + } + break; + + case HCI_SCODATA_PKT: + sh = (struct hci_sco_hdr *) buf; + len = HCI_SCO_HDR_SIZE + sh->dlen; + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_SCODATA_PKT; + hci_recv_frame(skb); + } + break; + + case HCI_VENDOR_PKT: + vh = (struct hci_vendor_hdr *) buf; + len = HCI_VENDOR_HDR_SIZE + __le16_to_cpu(vh->dlen); + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_VENDOR_PKT; + hci_recv_frame(skb); + } + break; + + default: + len = count - 1; + break; + } + + buf += len; + count -= (len + 1); + } +} + +static int bpa10x_recv_event(struct bpa10x_data *data, unsigned char *buf, int size) +{ + BT_DBG("data %p buf %p size %d", data, buf, size); + + if (data->evt_skb) { + struct sk_buff *skb = data->evt_skb; + + memcpy(skb_put(skb, size), buf, size); + + if (skb->len == data->evt_len) { + data->evt_skb = NULL; + data->evt_len = 0; + hci_recv_frame(skb); + } + } else { + struct sk_buff *skb; + struct hci_event_hdr *hdr; + unsigned char pkt_type; + int pkt_len = 0; + + if (size < HCI_EVENT_HDR_SIZE + 1) { + BT_ERR("%s event packet block with size %d is too short", + data->hdev->name, size); + return -EILSEQ; + } + + pkt_type = *buf++; + size--; + + if (pkt_type != HCI_EVENT_PKT) { + BT_ERR("%s unexpected event packet start byte 0x%02x", + data->hdev->name, pkt_type); + return -EPROTO; + } + + hdr = (struct hci_event_hdr *) buf; + pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen; + + skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for new event packet", + data->hdev->name); + return -ENOMEM; + } + + skb->dev = (void *) data->hdev; + skb->pkt_type = pkt_type; + + memcpy(skb_put(skb, size), buf, size); + + if (pkt_len == size) { + hci_recv_frame(skb); + } else { + data->evt_skb = skb; + data->evt_len = pkt_len; + } + } + + return 0; +} + +static void bpa10x_wakeup(struct bpa10x_data *data) +{ + struct urb *urb; + struct sk_buff *skb; + int err; + + BT_DBG("data %p", data); + + urb = data->cmd_urb; + if (urb->status == -EINPROGRESS) + skb = NULL; + else + skb = skb_dequeue(&data->cmd_queue); + + if (skb) { + struct usb_ctrlrequest *cr; + + if (skb->len > BPA10X_CMD_BUF_SIZE) { + BT_ERR("%s command packet with size %d is too big", + data->hdev->name, skb->len); + kfree_skb(skb); + return; + } + + cr = (struct usb_ctrlrequest *) urb->setup_packet; + cr->wLength = __cpu_to_le16(skb->len); + + memcpy(urb->transfer_buffer, skb->data, skb->len); + urb->transfer_buffer_length = skb->len; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s submit failed for command urb %p with error %d", + data->hdev->name, urb, err); + skb_queue_head(&data->cmd_queue, skb); + } else + kfree_skb(skb); + } + + urb = data->tx_urb; + if (urb->status == -EINPROGRESS) + skb = NULL; + else + skb = skb_dequeue(&data->tx_queue); + + if (skb) { + memcpy(urb->transfer_buffer, skb->data, skb->len); + urb->transfer_buffer_length = skb->len; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s submit failed for command urb %p with error %d", + data->hdev->name, urb, err); + skb_queue_head(&data->tx_queue, skb); + } else + kfree_skb(skb); + } +} + +static void bpa10x_complete(struct urb *urb, struct pt_regs *regs) +{ + struct bpa10x_data *data = urb->context; + unsigned char *buf = urb->transfer_buffer; + int err, count = urb->actual_length; + + BT_DBG("data %p urb %p buf %p count %d", data, urb, buf, count); + + read_lock(&data->lock); + + if (!test_bit(HCI_RUNNING, &data->hdev->flags)) + goto unlock; + + if (urb->status < 0 || !count) + goto resubmit; + + if (usb_pipein(urb->pipe)) { + data->hdev->stat.byte_rx += count; + + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) + bpa10x_recv_event(data, buf, count); + + if (usb_pipetype(urb->pipe) == PIPE_BULK) + bpa10x_recv_bulk(data, buf, count); + } else { + data->hdev->stat.byte_tx += count; + + bpa10x_wakeup(data); + } + +resubmit: + if (usb_pipein(urb->pipe)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s urb %p type %d resubmit status %d", + data->hdev->name, urb, usb_pipetype(urb->pipe), err); + } + } + +unlock: + read_unlock(&data->lock); +} + +static inline struct urb *bpa10x_alloc_urb(struct usb_device *udev, unsigned int pipe, size_t size, int flags, void *data) +{ + struct urb *urb; + struct usb_ctrlrequest *cr; + unsigned char *buf; + + BT_DBG("udev %p data %p", udev, data); + + urb = usb_alloc_urb(0, flags); + if (!urb) + return NULL; + + buf = kmalloc(size, flags); + if (!buf) { + usb_free_urb(urb); + return NULL; + } + + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + cr = kmalloc(sizeof(*cr), flags); + if (!cr) { + kfree(buf); + usb_free_urb(urb); + return NULL; + } + + cr->bRequestType = USB_TYPE_VENDOR; + cr->bRequest = 0; + cr->wIndex = 0; + cr->wValue = 0; + cr->wLength = __cpu_to_le16(0); + + usb_fill_control_urb(urb, udev, pipe, (void *) cr, buf, 0, bpa10x_complete, data); + break; + + case PIPE_INTERRUPT: + usb_fill_int_urb(urb, udev, pipe, buf, size, bpa10x_complete, data, 1); + break; + + case PIPE_BULK: + usb_fill_bulk_urb(urb, udev, pipe, buf, size, bpa10x_complete, data); + break; + + default: + kfree(buf); + usb_free_urb(urb); + return NULL; + } + + return urb; +} + +static inline void bpa10x_free_urb(struct urb *urb) +{ + BT_DBG("urb %p", urb); + + if (!urb) + return; + + if (urb->setup_packet) + kfree(urb->setup_packet); + + if (urb->transfer_buffer) + kfree(urb->transfer_buffer); + + usb_free_urb(urb); +} + +static int bpa10x_open(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + struct usb_device *udev = data->udev; + unsigned long flags; + int err; + + BT_DBG("hdev %p data %p", hdev, data); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + data->cmd_urb = bpa10x_alloc_urb(udev, usb_sndctrlpipe(udev, BPA10X_CMD_EP), + BPA10X_CMD_BUF_SIZE, GFP_KERNEL, data); + if (!data->cmd_urb) { + err = -ENOMEM; + goto done; + } + + data->evt_urb = bpa10x_alloc_urb(udev, usb_rcvintpipe(udev, BPA10X_EVT_EP), + BPA10X_EVT_BUF_SIZE, GFP_KERNEL, data); + if (!data->evt_urb) { + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + data->rx_urb = bpa10x_alloc_urb(udev, usb_rcvbulkpipe(udev, BPA10X_RX_EP), + BPA10X_RX_BUF_SIZE, GFP_KERNEL, data); + if (!data->rx_urb) { + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + data->tx_urb = bpa10x_alloc_urb(udev, usb_sndbulkpipe(udev, BPA10X_TX_EP), + BPA10X_TX_BUF_SIZE, GFP_KERNEL, data); + if (!data->rx_urb) { + bpa10x_free_urb(data->rx_urb); + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + write_lock_irqsave(&data->lock, flags); + + err = usb_submit_urb(data->evt_urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s submit failed for event urb %p with error %d", + data->hdev->name, data->evt_urb, err); + } else { + err = usb_submit_urb(data->rx_urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s submit failed for rx urb %p with error %d", + data->hdev->name, data->evt_urb, err); + usb_kill_urb(data->evt_urb); + } + } + + write_unlock_irqrestore(&data->lock, flags); + +done: + if (err < 0) + clear_bit(HCI_RUNNING, &hdev->flags); + + return err; +} + +static int bpa10x_close(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + unsigned long flags; + + BT_DBG("hdev %p data %p", hdev, data); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&data->lock, flags); + + skb_queue_purge(&data->cmd_queue); + usb_kill_urb(data->cmd_urb); + usb_kill_urb(data->evt_urb); + usb_kill_urb(data->rx_urb); + usb_kill_urb(data->tx_urb); + + write_unlock_irqrestore(&data->lock, flags); + + bpa10x_free_urb(data->cmd_urb); + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->rx_urb); + bpa10x_free_urb(data->tx_urb); + + return 0; +} + +static int bpa10x_flush(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("hdev %p data %p", hdev, data); + + skb_queue_purge(&data->cmd_queue); + + return 0; +} + +static int bpa10x_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct bpa10x_data *data; + + BT_DBG("hdev %p skb %p type %d len %d", hdev, skb, skb->pkt_type, skb->len); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + data = hdev->driver_data; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &(skb->pkt_type), 1); + + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + skb_queue_tail(&data->cmd_queue, skb); + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + skb_queue_tail(&data->tx_queue, skb); + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + skb_queue_tail(&data->tx_queue, skb); + break; + }; + + read_lock(&data->lock); + + bpa10x_wakeup(data); + + read_unlock(&data->lock); + + return 0; +} + +static void bpa10x_destruct(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("hdev %p data %p", hdev, data); + + kfree(data); +} + +static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct hci_dev *hdev; + struct bpa10x_data *data; + int err; + + BT_DBG("intf %p id %p", intf, id); + + if (ignore) + return -ENODEV; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + BT_ERR("Can't allocate data structure"); + return -ENOMEM; + } + + memset(data, 0, sizeof(*data)); + + data->udev = udev; + + rwlock_init(&data->lock); + + skb_queue_head_init(&data->cmd_queue); + skb_queue_head_init(&data->tx_queue); + + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + kfree(data); + return -ENOMEM; + } + + data->hdev = hdev; + + hdev->type = HCI_USB; + hdev->driver_data = data; + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = bpa10x_open; + hdev->close = bpa10x_close; + hdev->flush = bpa10x_flush; + hdev->send = bpa10x_send_frame; + hdev->destruct = bpa10x_destruct; + + hdev->owner = THIS_MODULE; + + err = hci_register_dev(hdev); + if (err < 0) { + BT_ERR("Can't register HCI device"); + kfree(data); + hci_free_dev(hdev); + return err; + } + + usb_set_intfdata(intf, data); + + return 0; +} + +static void bpa10x_disconnect(struct usb_interface *intf) +{ + struct bpa10x_data *data = usb_get_intfdata(intf); + struct hci_dev *hdev = data->hdev; + + BT_DBG("intf %p", intf); + + if (!hdev) + return; + + usb_set_intfdata(intf, NULL); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); +} + +static struct usb_driver bpa10x_driver = { + .owner = THIS_MODULE, + .name = "bpa10x", + .probe = bpa10x_probe, + .disconnect = bpa10x_disconnect, + .id_table = bpa10x_table, +}; + +static int __init bpa10x_init(void) +{ + int err; + + BT_INFO("Digianswer Bluetooth USB driver ver %s", VERSION); + + err = usb_register(&bpa10x_driver); + if (err < 0) + BT_ERR("Failed to register USB driver"); + + return err; +} + +static void __exit bpa10x_exit(void) +{ + usb_deregister(&bpa10x_driver); +} + +module_init(bpa10x_init); +module_exit(bpa10x_exit); + +module_param(ignore, bool, 0644); +MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Digianswer Bluetooth USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); |