Logo Search packages:      
Sourcecode: linux version File versions  Download package

btusb.c

/*
 *
 *  Generic Bluetooth USB driver
 *
 *  Copyright (C) 2005-2007  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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/skbuff.h>

#include <linux/usb.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

//#define CONFIG_BT_HCIBTUSB_DEBUG
#ifndef CONFIG_BT_HCIBTUSB_DEBUG
#undef  BT_DBG
#define BT_DBG(D...)
#endif

#define VERSION "0.1"

static struct usb_device_id btusb_table[] = {
      /* Generic Bluetooth USB device */
      { USB_DEVICE_INFO(0xe0, 0x01, 0x01) },

      { }   /* Terminating entry */
};

MODULE_DEVICE_TABLE(usb, btusb_table);

static struct usb_device_id blacklist_table[] = {
      { }   /* Terminating entry */
};

#define BTUSB_INTR_RUNNING    0
#define BTUSB_BULK_RUNNING    1

struct btusb_data {
      struct hci_dev       *hdev;
      struct usb_device    *udev;

      spinlock_t lock;

      unsigned long flags;

      struct work_struct work;

      struct usb_anchor tx_anchor;
      struct usb_anchor intr_anchor;
      struct usb_anchor bulk_anchor;

      struct usb_endpoint_descriptor *intr_ep;
      struct usb_endpoint_descriptor *bulk_tx_ep;
      struct usb_endpoint_descriptor *bulk_rx_ep;
};

static void btusb_intr_complete(struct urb *urb)
{
      struct hci_dev *hdev = urb->context;
      struct btusb_data *data = hdev->driver_data;
      int err;

      BT_DBG("%s urb %p status %d count %d", hdev->name,
                              urb, urb->status, urb->actual_length);

      if (!test_bit(HCI_RUNNING, &hdev->flags))
            return;

      if (urb->status == 0) {
            if (hci_recv_fragment(hdev, HCI_EVENT_PKT,
                                    urb->transfer_buffer,
                                    urb->actual_length) < 0) {
                  BT_ERR("%s corrupted event packet", hdev->name);
                  hdev->stat.err_rx++;
            }
      }

      if (!test_bit(BTUSB_INTR_RUNNING, &data->flags))
            return;

      usb_anchor_urb(urb, &data->intr_anchor);

      err = usb_submit_urb(urb, GFP_ATOMIC);
      if (err < 0) {
            BT_ERR("%s urb %p failed to resubmit (%d)",
                                    hdev->name, urb, -err);
            usb_unanchor_urb(urb);
      }
}

static inline int btusb_submit_intr_urb(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;
      struct urb *urb;
      unsigned char *buf;
      unsigned int pipe;
      int err, size;

      BT_DBG("%s", hdev->name);

      urb = usb_alloc_urb(0, GFP_ATOMIC);
      if (!urb)
            return -ENOMEM;

      size = le16_to_cpu(data->intr_ep->wMaxPacketSize);

      buf = kmalloc(size, GFP_ATOMIC);
      if (!buf) {
            usb_free_urb(urb);
            return -ENOMEM;
      }

      pipe = usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress);

      usb_fill_int_urb(urb, data->udev, pipe, buf, size,
                                    btusb_intr_complete, hdev,
                                    data->intr_ep->bInterval);

      urb->transfer_flags |= URB_FREE_BUFFER;

      usb_anchor_urb(urb, &data->intr_anchor);

      err = usb_submit_urb(urb, GFP_ATOMIC);
      if (err < 0) {
            BT_ERR("%s urb %p submission failed (%d)",
                                    hdev->name, urb, -err);
            usb_unanchor_urb(urb);
            kfree(buf);
      }

      usb_free_urb(urb);

      return err;
}

static void btusb_bulk_complete(struct urb *urb)
{
      struct hci_dev *hdev = urb->context;
      struct btusb_data *data = hdev->driver_data;
      int err;

      BT_DBG("%s urb %p status %d count %d", hdev->name,
                              urb, urb->status, urb->actual_length);

      if (!test_bit(HCI_RUNNING, &hdev->flags))
            return;

      if (urb->status == 0) {
            if (hci_recv_fragment(hdev, HCI_ACLDATA_PKT,
                                    urb->transfer_buffer,
                                    urb->actual_length) < 0) {
                  BT_ERR("%s corrupted ACL packet", hdev->name);
                  hdev->stat.err_rx++;
            }
      }

      if (!test_bit(BTUSB_BULK_RUNNING, &data->flags))
            return;

      usb_anchor_urb(urb, &data->bulk_anchor);

      err = usb_submit_urb(urb, GFP_ATOMIC);
      if (err < 0) {
            BT_ERR("%s urb %p failed to resubmit (%d)",
                                    hdev->name, urb, -err);
            usb_unanchor_urb(urb);
      }
}

static inline int btusb_submit_bulk_urb(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;
      struct urb *urb;
      unsigned char *buf;
      unsigned int pipe;
      int err, size;

      BT_DBG("%s", hdev->name);

      urb = usb_alloc_urb(0, GFP_KERNEL);
      if (!urb)
            return -ENOMEM;

      size = le16_to_cpu(data->bulk_rx_ep->wMaxPacketSize);

      buf = kmalloc(size, GFP_KERNEL);
      if (!buf) {
            usb_free_urb(urb);
            return -ENOMEM;
      }

      pipe = usb_rcvbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress);

      usb_fill_bulk_urb(urb, data->udev, pipe,
                              buf, size, btusb_bulk_complete, hdev);

      urb->transfer_flags |= URB_FREE_BUFFER;

      usb_anchor_urb(urb, &data->bulk_anchor);

      err = usb_submit_urb(urb, GFP_KERNEL);
      if (err < 0) {
            BT_ERR("%s urb %p submission failed (%d)",
                                    hdev->name, urb, -err);
            usb_unanchor_urb(urb);
            kfree(buf);
      }

      usb_free_urb(urb);

      return err;
}

static void btusb_tx_complete(struct urb *urb)
{
      struct sk_buff *skb = urb->context;
      struct hci_dev *hdev = (struct hci_dev *) skb->dev;

      BT_DBG("%s urb %p status %d count %d", hdev->name,
                              urb, urb->status, urb->actual_length);

      if (!test_bit(HCI_RUNNING, &hdev->flags))
            goto done;

      if (!urb->status)
            hdev->stat.byte_tx += urb->transfer_buffer_length;
      else
            hdev->stat.err_tx++;

done:
      kfree(urb->setup_packet);

      kfree_skb(skb);
}

static int btusb_open(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;
      int err;

      BT_DBG("%s", hdev->name);

      if (test_and_set_bit(HCI_RUNNING, &hdev->flags))
            return 0;

      if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
            return 0;

      err = btusb_submit_intr_urb(hdev);
      if (err < 0) {
            clear_bit(BTUSB_INTR_RUNNING, &hdev->flags);
            clear_bit(HCI_RUNNING, &hdev->flags);
      }

      return err;
}

static int btusb_close(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;

      BT_DBG("%s", hdev->name);

      if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
            return 0;

      clear_bit(BTUSB_BULK_RUNNING, &data->flags);
      usb_kill_anchored_urbs(&data->bulk_anchor);

      clear_bit(BTUSB_INTR_RUNNING, &data->flags);
      usb_kill_anchored_urbs(&data->intr_anchor);

      return 0;
}

static int btusb_flush(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;

      BT_DBG("%s", hdev->name);

      usb_kill_anchored_urbs(&data->tx_anchor);

      return 0;
}

static int btusb_send_frame(struct sk_buff *skb)
{
      struct hci_dev *hdev = (struct hci_dev *) skb->dev;
      struct btusb_data *data = hdev->driver_data;
      struct usb_ctrlrequest *dr;
      struct urb *urb;
      unsigned int pipe;
      int err;

      BT_DBG("%s", hdev->name);

      if (!test_bit(HCI_RUNNING, &hdev->flags))
            return -EBUSY;

      switch (bt_cb(skb)->pkt_type) {
      case HCI_COMMAND_PKT:
            urb = usb_alloc_urb(0, GFP_ATOMIC);
            if (!urb)
                  return -ENOMEM;

            dr = kmalloc(sizeof(*dr), GFP_ATOMIC);
            if (!dr) {
                  usb_free_urb(urb);
                  return -ENOMEM;
            }

            dr->bRequestType = USB_TYPE_CLASS;
            dr->bRequest     = 0;
            dr->wIndex       = 0;
            dr->wValue       = 0;
            dr->wLength      = __cpu_to_le16(skb->len);

            pipe = usb_sndctrlpipe(data->udev, 0x00);

            usb_fill_control_urb(urb, data->udev, pipe, (void *) dr,
                        skb->data, skb->len, btusb_tx_complete, skb);

            hdev->stat.cmd_tx++;
            break;

      case HCI_ACLDATA_PKT:
            urb = usb_alloc_urb(0, GFP_ATOMIC);
            if (!urb)
                  return -ENOMEM;

            pipe = usb_sndbulkpipe(data->udev,
                              data->bulk_tx_ep->bEndpointAddress);

            usb_fill_bulk_urb(urb, data->udev, pipe,
                        skb->data, skb->len, btusb_tx_complete, skb);

            hdev->stat.acl_tx++;
            break;

      case HCI_SCODATA_PKT:
            hdev->stat.sco_tx++;
            kfree_skb(skb);
            return 0;

      default:
            return -EILSEQ;
      }

      usb_anchor_urb(urb, &data->tx_anchor);

      err = usb_submit_urb(urb, GFP_ATOMIC);
      if (err < 0) {
            BT_ERR("%s urb %p submission failed", hdev->name, urb);
            kfree(urb->setup_packet);
            usb_unanchor_urb(urb);
      }

      usb_free_urb(urb);

      return err;
}

static void btusb_destruct(struct hci_dev *hdev)
{
      struct btusb_data *data = hdev->driver_data;

      BT_DBG("%s", hdev->name);

      kfree(data);
}

static void btusb_notify(struct hci_dev *hdev, unsigned int evt)
{
      struct btusb_data *data = hdev->driver_data;

      BT_DBG("%s evt %d", hdev->name, evt);

      if (evt == HCI_NOTIFY_CONN_ADD || evt == HCI_NOTIFY_CONN_DEL)
            schedule_work(&data->work);
}

static void btusb_work(struct work_struct *work)
{
      struct btusb_data *data = container_of(work, struct btusb_data, work);
      struct hci_dev *hdev = data->hdev;

      if (hdev->conn_hash.acl_num == 0) {
            clear_bit(BTUSB_BULK_RUNNING, &data->flags);
            usb_kill_anchored_urbs(&data->bulk_anchor);
            return;
      }

      if (!test_and_set_bit(BTUSB_BULK_RUNNING, &data->flags)) {
            if (btusb_submit_bulk_urb(hdev) < 0)
                  clear_bit(BTUSB_BULK_RUNNING, &data->flags);
            else
                  btusb_submit_bulk_urb(hdev);
      }
}

static int btusb_probe(struct usb_interface *intf,
                        const struct usb_device_id *id)
{
      struct usb_endpoint_descriptor *ep_desc;
      struct btusb_data *data;
      struct hci_dev *hdev;
      int i, err;

      BT_DBG("intf %p id %p", intf, id);

      if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
            return -ENODEV;

      if (!id->driver_info) {
            const struct usb_device_id *match;
            match = usb_match_id(intf, blacklist_table);
            if (match)
                  id = match;
      }

      data = kzalloc(sizeof(*data), GFP_KERNEL);
      if (!data)
            return -ENOMEM;

      for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
            ep_desc = &intf->cur_altsetting->endpoint[i].desc;

            if (!data->intr_ep && usb_endpoint_is_int_in(ep_desc)) {
                  data->intr_ep = ep_desc;
                  continue;
            }

            if (!data->bulk_tx_ep && usb_endpoint_is_bulk_out(ep_desc)) {
                  data->bulk_tx_ep = ep_desc;
                  continue;
            }

            if (!data->bulk_rx_ep && usb_endpoint_is_bulk_in(ep_desc)) {
                  data->bulk_rx_ep = ep_desc;
                  continue;
            }
      }

      if (!data->intr_ep || !data->bulk_tx_ep || !data->bulk_rx_ep) {
            kfree(data);
            return -ENODEV;
      }

      data->udev = interface_to_usbdev(intf);

      spin_lock_init(&data->lock);

      INIT_WORK(&data->work, btusb_work);

      init_usb_anchor(&data->tx_anchor);
      init_usb_anchor(&data->intr_anchor);
      init_usb_anchor(&data->bulk_anchor);

      hdev = hci_alloc_dev();
      if (!hdev) {
            kfree(data);
            return -ENOMEM;
      }

      hdev->type = HCI_USB;
      hdev->driver_data = data;

      data->hdev = hdev;

      SET_HCIDEV_DEV(hdev, &intf->dev);

      hdev->open     = btusb_open;
      hdev->close    = btusb_close;
      hdev->flush    = btusb_flush;
      hdev->send     = btusb_send_frame;
      hdev->destruct = btusb_destruct;
      hdev->notify   = btusb_notify;

      hdev->owner = THIS_MODULE;

      set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks);

      err = hci_register_dev(hdev);
      if (err < 0) {
            hci_free_dev(hdev);
            kfree(data);
            return err;
      }

      usb_set_intfdata(intf, data);

      return 0;
}

static void btusb_disconnect(struct usb_interface *intf)
{
      struct btusb_data *data = usb_get_intfdata(intf);
      struct hci_dev *hdev;

      BT_DBG("intf %p", intf);

      if (!data)
            return;

      hdev = data->hdev;

      usb_set_intfdata(intf, NULL);

      hci_unregister_dev(hdev);

      hci_free_dev(hdev);
}

static struct usb_driver btusb_driver = {
      .name       = "btusb",
      .probe            = btusb_probe,
      .disconnect = btusb_disconnect,
      .id_table   = btusb_table,
};

static int __init btusb_init(void)
{
      BT_INFO("Generic Bluetooth USB driver ver %s", VERSION);

      return usb_register(&btusb_driver);
}

static void __exit btusb_exit(void)
{
      usb_deregister(&btusb_driver);
}

module_init(btusb_init);
module_exit(btusb_exit);

MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Generic Bluetooth USB driver ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");

Generated by  Doxygen 1.6.0   Back to index