[Bluetooth] Fix double frees on error paths of btusb and bpa10x drivers
[pandora-kernel.git] / drivers / bluetooth / btusb.c
index 12e1089..262e9be 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  Generic Bluetooth USB driver
  *
- *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
 #define BT_DBG(D...)
 #endif
 
-#define VERSION "0.1"
+#define VERSION "0.3"
+
+static int ignore_dga;
+static int ignore_csr;
+static int ignore_sniffer;
+static int disable_scofix;
+static int force_scofix;
+static int reset;
+
+static struct usb_driver btusb_driver;
+
+#define BTUSB_IGNORE           0x01
+#define BTUSB_RESET            0x02
+#define BTUSB_DIGIANSWER       0x04
+#define BTUSB_CSR              0x08
+#define BTUSB_SNIFFER          0x10
+#define BTUSB_BCM92035         0x20
+#define BTUSB_BROKEN_ISOC      0x40
+#define BTUSB_WRONG_SCO_MTU    0x80
 
 static struct usb_device_id btusb_table[] = {
        /* Generic Bluetooth USB device */
        { USB_DEVICE_INFO(0xe0, 0x01, 0x01) },
 
+       /* AVM BlueFRITZ! USB v2.0 */
+       { USB_DEVICE(0x057c, 0x3800) },
+
+       /* Bluetooth Ultraport Module from IBM */
+       { USB_DEVICE(0x04bf, 0x030a) },
+
+       /* ALPS Modules with non-standard id */
+       { USB_DEVICE(0x044e, 0x3001) },
+       { USB_DEVICE(0x044e, 0x3002) },
+
+       /* Ericsson with non-standard id */
+       { USB_DEVICE(0x0bdb, 0x1002) },
+
+       /* Canyon CN-BTU1 with HID interfaces */
+       { USB_DEVICE(0x0c10, 0x0000), .driver_info = BTUSB_RESET },
+
        { }     /* Terminating entry */
 };
 
 MODULE_DEVICE_TABLE(usb, btusb_table);
 
 static struct usb_device_id blacklist_table[] = {
+       /* CSR BlueCore devices */
+       { USB_DEVICE(0x0a12, 0x0001), .driver_info = BTUSB_CSR },
+
+       /* Broadcom BCM2033 without firmware */
+       { USB_DEVICE(0x0a5c, 0x2033), .driver_info = BTUSB_IGNORE },
+
+       /* Broadcom BCM2035 */
+       { USB_DEVICE(0x0a5c, 0x2035), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+       { USB_DEVICE(0x0a5c, 0x200a), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Broadcom BCM2045 */
+       { USB_DEVICE(0x0a5c, 0x2039), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+       { USB_DEVICE(0x0a5c, 0x2101), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Broadcom BCM2046 */
+       { USB_DEVICE(0x0a5c, 0x2151), .driver_info = BTUSB_RESET },
+
+       /* Apple MacBook Pro with Broadcom chip */
+       { USB_DEVICE(0x05ac, 0x820f), .driver_info = BTUSB_RESET },
+
+       /* IBM/Lenovo ThinkPad with Broadcom chip */
+       { USB_DEVICE(0x0a5c, 0x201e), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+       { USB_DEVICE(0x0a5c, 0x2110), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Targus ACB10US */
+       { USB_DEVICE(0x0a5c, 0x2100), .driver_info = BTUSB_RESET },
+
+       /* ANYCOM Bluetooth USB-200 and USB-250 */
+       { USB_DEVICE(0x0a5c, 0x2111), .driver_info = BTUSB_RESET },
+
+       /* HP laptop with Broadcom chip */
+       { USB_DEVICE(0x03f0, 0x171d), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Dell laptop with Broadcom chip */
+       { USB_DEVICE(0x413c, 0x8126), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Dell Wireless 370 */
+       { USB_DEVICE(0x413c, 0x8156), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Dell Wireless 410 */
+       { USB_DEVICE(0x413c, 0x8152), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Microsoft Wireless Transceiver for Bluetooth 2.0 */
+       { USB_DEVICE(0x045e, 0x009c), .driver_info = BTUSB_RESET },
+
+       /* Kensington Bluetooth USB adapter */
+       { USB_DEVICE(0x047d, 0x105d), .driver_info = BTUSB_RESET },
+       { USB_DEVICE(0x047d, 0x105e), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* ISSC Bluetooth Adapter v3.1 */
+       { USB_DEVICE(0x1131, 0x1001), .driver_info = BTUSB_RESET },
+
+       /* RTX Telecom based adapters with buggy SCO support */
+       { USB_DEVICE(0x0400, 0x0807), .driver_info = BTUSB_BROKEN_ISOC },
+       { USB_DEVICE(0x0400, 0x080a), .driver_info = BTUSB_BROKEN_ISOC },
+
+       /* CONWISE Technology based adapters with buggy SCO support */
+       { USB_DEVICE(0x0e5e, 0x6622), .driver_info = BTUSB_BROKEN_ISOC },
+
+       /* Belkin F8T012 and F8T013 devices */
+       { USB_DEVICE(0x050d, 0x0012), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+       { USB_DEVICE(0x050d, 0x0013), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
+
+       /* Digianswer devices */
+       { USB_DEVICE(0x08fd, 0x0001), .driver_info = BTUSB_DIGIANSWER },
+       { USB_DEVICE(0x08fd, 0x0002), .driver_info = BTUSB_IGNORE },
+
+       /* CSR BlueCore Bluetooth Sniffer */
+       { USB_DEVICE(0x0a12, 0x0002), .driver_info = BTUSB_SNIFFER },
+
+       /* Frontline ComProbe Bluetooth Sniffer */
+       { USB_DEVICE(0x16d3, 0x0002), .driver_info = BTUSB_SNIFFER },
+
        { }     /* Terminating entry */
 };
 
+#define BTUSB_MAX_ISOC_FRAMES  10
+
 #define BTUSB_INTR_RUNNING     0
 #define BTUSB_BULK_RUNNING     1
+#define BTUSB_ISOC_RUNNING     2
 
 struct btusb_data {
        struct hci_dev       *hdev;
        struct usb_device    *udev;
+       struct usb_interface *intf;
+       struct usb_interface *isoc;
 
        spinlock_t lock;
 
@@ -72,10 +184,15 @@ struct btusb_data {
        struct usb_anchor tx_anchor;
        struct usb_anchor intr_anchor;
        struct usb_anchor bulk_anchor;
+       struct usb_anchor isoc_anchor;
 
        struct usb_endpoint_descriptor *intr_ep;
        struct usb_endpoint_descriptor *bulk_tx_ep;
        struct usb_endpoint_descriptor *bulk_rx_ep;
+       struct usb_endpoint_descriptor *isoc_tx_ep;
+       struct usb_endpoint_descriptor *isoc_rx_ep;
+
+       int isoc_altsetting;
 };
 
 static void btusb_intr_complete(struct urb *urb)
@@ -91,6 +208,8 @@ static void btusb_intr_complete(struct urb *urb)
                return;
 
        if (urb->status == 0) {
+               hdev->stat.byte_rx += urb->actual_length;
+
                if (hci_recv_fragment(hdev, HCI_EVENT_PKT,
                                                urb->transfer_buffer,
                                                urb->actual_length) < 0) {
@@ -112,7 +231,7 @@ static void btusb_intr_complete(struct urb *urb)
        }
 }
 
-static inline int btusb_submit_intr_urb(struct hci_dev *hdev)
+static int btusb_submit_intr_urb(struct hci_dev *hdev)
 {
        struct btusb_data *data = hdev->driver_data;
        struct urb *urb;
@@ -122,6 +241,9 @@ static inline int btusb_submit_intr_urb(struct hci_dev *hdev)
 
        BT_DBG("%s", hdev->name);
 
+       if (!data->intr_ep)
+               return -ENODEV;
+
        urb = usb_alloc_urb(0, GFP_ATOMIC);
        if (!urb)
                return -ENOMEM;
@@ -149,7 +271,6 @@ static inline int btusb_submit_intr_urb(struct hci_dev *hdev)
                BT_ERR("%s urb %p submission failed (%d)",
                                                hdev->name, urb, -err);
                usb_unanchor_urb(urb);
-               kfree(buf);
        }
 
        usb_free_urb(urb);
@@ -170,6 +291,8 @@ static void btusb_bulk_complete(struct urb *urb)
                return;
 
        if (urb->status == 0) {
+               hdev->stat.byte_rx += urb->actual_length;
+
                if (hci_recv_fragment(hdev, HCI_ACLDATA_PKT,
                                                urb->transfer_buffer,
                                                urb->actual_length) < 0) {
@@ -191,7 +314,7 @@ static void btusb_bulk_complete(struct urb *urb)
        }
 }
 
-static inline int btusb_submit_bulk_urb(struct hci_dev *hdev)
+static int btusb_submit_bulk_urb(struct hci_dev *hdev)
 {
        struct btusb_data *data = hdev->driver_data;
        struct urb *urb;
@@ -201,6 +324,9 @@ static inline int btusb_submit_bulk_urb(struct hci_dev *hdev)
 
        BT_DBG("%s", hdev->name);
 
+       if (!data->bulk_rx_ep)
+               return -ENODEV;
+
        urb = usb_alloc_urb(0, GFP_KERNEL);
        if (!urb)
                return -ENOMEM;
@@ -227,7 +353,126 @@ static inline int btusb_submit_bulk_urb(struct hci_dev *hdev)
                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_isoc_complete(struct urb *urb)
+{
+       struct hci_dev *hdev = urb->context;
+       struct btusb_data *data = hdev->driver_data;
+       int i, 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) {
+               for (i = 0; i < urb->number_of_packets; i++) {
+                       unsigned int offset = urb->iso_frame_desc[i].offset;
+                       unsigned int length = urb->iso_frame_desc[i].actual_length;
+
+                       if (urb->iso_frame_desc[i].status)
+                               continue;
+
+                       hdev->stat.byte_rx += length;
+
+                       if (hci_recv_fragment(hdev, HCI_SCODATA_PKT,
+                                               urb->transfer_buffer + offset,
+                                                               length) < 0) {
+                               BT_ERR("%s corrupted SCO packet", hdev->name);
+                               hdev->stat.err_rx++;
+                       }
+               }
+       }
+
+       if (!test_bit(BTUSB_ISOC_RUNNING, &data->flags))
+               return;
+
+       usb_anchor_urb(urb, &data->isoc_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 void inline __fill_isoc_descriptor(struct urb *urb, int len, int mtu)
+{
+       int i, offset = 0;
+
+       BT_DBG("len %d mtu %d", len, mtu);
+
+       for (i = 0; i < BTUSB_MAX_ISOC_FRAMES && len >= mtu;
+                                       i++, offset += mtu, len -= mtu) {
+               urb->iso_frame_desc[i].offset = offset;
+               urb->iso_frame_desc[i].length = mtu;
+       }
+
+       if (len && i < BTUSB_MAX_ISOC_FRAMES) {
+               urb->iso_frame_desc[i].offset = offset;
+               urb->iso_frame_desc[i].length = len;
+               i++;
+       }
+
+       urb->number_of_packets = i;
+}
+
+static int btusb_submit_isoc_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);
+
+       if (!data->isoc_rx_ep)
+               return -ENODEV;
+
+       urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_KERNEL);
+       if (!urb)
+               return -ENOMEM;
+
+       size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) *
+                                               BTUSB_MAX_ISOC_FRAMES;
+
+       buf = kmalloc(size, GFP_KERNEL);
+       if (!buf) {
+               usb_free_urb(urb);
+               return -ENOMEM;
+       }
+
+       pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress);
+
+       urb->dev      = data->udev;
+       urb->pipe     = pipe;
+       urb->context  = hdev;
+       urb->complete = btusb_isoc_complete;
+       urb->interval = data->isoc_rx_ep->bInterval;
+
+       urb->transfer_flags  = URB_FREE_BUFFER | URB_ISO_ASAP;
+       urb->transfer_buffer = buf;
+       urb->transfer_buffer_length = size;
+
+       __fill_isoc_descriptor(urb, size,
+                       le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize));
+
+       usb_anchor_urb(urb, &data->isoc_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);
        }
 
        usb_free_urb(urb);
@@ -272,7 +517,7 @@ static int btusb_open(struct hci_dev *hdev)
 
        err = btusb_submit_intr_urb(hdev);
        if (err < 0) {
-               clear_bit(BTUSB_INTR_RUNNING, &hdev->flags);
+               clear_bit(BTUSB_INTR_RUNNING, &data->flags);
                clear_bit(HCI_RUNNING, &hdev->flags);
        }
 
@@ -288,6 +533,11 @@ static int btusb_close(struct hci_dev *hdev)
        if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
                return 0;
 
+       cancel_work_sync(&data->work);
+
+       clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
+       usb_kill_anchored_urbs(&data->isoc_anchor);
+
        clear_bit(BTUSB_BULK_RUNNING, &data->flags);
        usb_kill_anchored_urbs(&data->bulk_anchor);
 
@@ -349,6 +599,9 @@ static int btusb_send_frame(struct sk_buff *skb)
                break;
 
        case HCI_ACLDATA_PKT:
+               if (!data->bulk_tx_ep || hdev->conn_hash.acl_num < 1)
+                       return -ENODEV;
+
                urb = usb_alloc_urb(0, GFP_ATOMIC);
                if (!urb)
                        return -ENOMEM;
@@ -363,9 +616,31 @@ static int btusb_send_frame(struct sk_buff *skb)
                break;
 
        case HCI_SCODATA_PKT:
+               if (!data->isoc_tx_ep || hdev->conn_hash.sco_num < 1)
+                       return -ENODEV;
+
+               urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_ATOMIC);
+               if (!urb)
+                       return -ENOMEM;
+
+               pipe = usb_sndisocpipe(data->udev,
+                                       data->isoc_tx_ep->bEndpointAddress);
+
+               urb->dev      = data->udev;
+               urb->pipe     = pipe;
+               urb->context  = skb;
+               urb->complete = btusb_tx_complete;
+               urb->interval = data->isoc_tx_ep->bInterval;
+
+               urb->transfer_flags  = URB_ISO_ASAP;
+               urb->transfer_buffer = skb->data;
+               urb->transfer_buffer_length = skb->len;
+
+               __fill_isoc_descriptor(urb, skb->len,
+                               le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize));
+
                hdev->stat.sco_tx++;
-               kfree_skb(skb);
-               return 0;
+               break;
 
        default:
                return -EILSEQ;
@@ -404,22 +679,86 @@ static void btusb_notify(struct hci_dev *hdev, unsigned int evt)
                schedule_work(&data->work);
 }
 
+static int inline __set_isoc_interface(struct hci_dev *hdev, int altsetting)
+{
+       struct btusb_data *data = hdev->driver_data;
+       struct usb_interface *intf = data->isoc;
+       struct usb_endpoint_descriptor *ep_desc;
+       int i, err;
+
+       if (!data->isoc)
+               return -ENODEV;
+
+       err = usb_set_interface(data->udev, 1, altsetting);
+       if (err < 0) {
+               BT_ERR("%s setting interface failed (%d)", hdev->name, -err);
+               return err;
+       }
+
+       data->isoc_altsetting = altsetting;
+
+       data->isoc_tx_ep = NULL;
+       data->isoc_rx_ep = NULL;
+
+       for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
+               ep_desc = &intf->cur_altsetting->endpoint[i].desc;
+
+               if (!data->isoc_tx_ep && usb_endpoint_is_isoc_out(ep_desc)) {
+                       data->isoc_tx_ep = ep_desc;
+                       continue;
+               }
+
+               if (!data->isoc_rx_ep && usb_endpoint_is_isoc_in(ep_desc)) {
+                       data->isoc_rx_ep = ep_desc;
+                       continue;
+               }
+       }
+
+       if (!data->isoc_tx_ep || !data->isoc_rx_ep) {
+               BT_ERR("%s invalid SCO descriptors", hdev->name);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
 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) {
+       if (hdev->conn_hash.acl_num > 0) {
+               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);
+               }
+       } else {
                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);
+       if (hdev->conn_hash.sco_num > 0) {
+               if (data->isoc_altsetting != 2) {
+                       clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
+                       usb_kill_anchored_urbs(&data->isoc_anchor);
+
+                       if (__set_isoc_interface(hdev, 2) < 0)
+                               return;
+               }
+
+               if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &data->flags)) {
+                       if (btusb_submit_isoc_urb(hdev) < 0)
+                               clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
+                       else
+                               btusb_submit_isoc_urb(hdev);
+               }
+       } else {
+               clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
+               usb_kill_anchored_urbs(&data->isoc_anchor);
+
+               __set_isoc_interface(hdev, 0);
        }
 }
 
@@ -433,6 +772,7 @@ static int btusb_probe(struct usb_interface *intf,
 
        BT_DBG("intf %p id %p", intf, id);
 
+       /* interface numbers are hardcoded in the spec */
        if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
                return -ENODEV;
 
@@ -443,6 +783,18 @@ static int btusb_probe(struct usb_interface *intf,
                        id = match;
        }
 
+       if (id->driver_info == BTUSB_IGNORE)
+               return -ENODEV;
+
+       if (ignore_dga && id->driver_info & BTUSB_DIGIANSWER)
+               return -ENODEV;
+
+       if (ignore_csr && id->driver_info & BTUSB_CSR)
+               return -ENODEV;
+
+       if (ignore_sniffer && id->driver_info & BTUSB_SNIFFER)
+               return -ENODEV;
+
        data = kzalloc(sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;
@@ -472,6 +824,7 @@ static int btusb_probe(struct usb_interface *intf,
        }
 
        data->udev = interface_to_usbdev(intf);
+       data->intf = intf;
 
        spin_lock_init(&data->lock);
 
@@ -480,6 +833,7 @@ static int btusb_probe(struct usb_interface *intf,
        init_usb_anchor(&data->tx_anchor);
        init_usb_anchor(&data->intr_anchor);
        init_usb_anchor(&data->bulk_anchor);
+       init_usb_anchor(&data->isoc_anchor);
 
        hdev = hci_alloc_dev();
        if (!hdev) {
@@ -503,7 +857,49 @@ static int btusb_probe(struct usb_interface *intf,
 
        hdev->owner = THIS_MODULE;
 
-       set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks);
+       /* interface numbers are hardcoded in the spec */
+       data->isoc = usb_ifnum_to_if(data->udev, 1);
+
+       if (reset || id->driver_info & BTUSB_RESET)
+               set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks);
+
+       if (force_scofix || id->driver_info & BTUSB_WRONG_SCO_MTU) {
+               if (!disable_scofix)
+                       set_bit(HCI_QUIRK_FIXUP_BUFFER_SIZE, &hdev->quirks);
+       }
+
+       if (id->driver_info & BTUSB_BROKEN_ISOC)
+               data->isoc = NULL;
+
+       if (id->driver_info & BTUSB_SNIFFER) {
+               struct usb_device *udev = data->udev;
+
+               if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x997)
+                       set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks);
+
+               data->isoc = NULL;
+       }
+
+       if (id->driver_info & BTUSB_BCM92035) {
+               unsigned char cmd[] = { 0x3b, 0xfc, 0x01, 0x00 };
+               struct sk_buff *skb;
+
+               skb = bt_skb_alloc(sizeof(cmd), GFP_KERNEL);
+               if (skb) {
+                       memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd));
+                       skb_queue_tail(&hdev->driver_init, skb);
+               }
+       }
+
+       if (data->isoc) {
+               err = usb_driver_claim_interface(&btusb_driver,
+                                                       data->isoc, data);
+               if (err < 0) {
+                       hci_free_dev(hdev);
+                       kfree(data);
+                       return err;
+               }
+       }
 
        err = hci_register_dev(hdev);
        if (err < 0) {
@@ -529,10 +925,22 @@ static void btusb_disconnect(struct usb_interface *intf)
 
        hdev = data->hdev;
 
-       usb_set_intfdata(intf, NULL);
+       __hci_dev_hold(hdev);
+
+       usb_set_intfdata(data->intf, NULL);
+
+       if (data->isoc)
+               usb_set_intfdata(data->isoc, NULL);
 
        hci_unregister_dev(hdev);
 
+       if (intf == data->isoc)
+               usb_driver_release_interface(&btusb_driver, data->intf);
+       else if (data->isoc)
+               usb_driver_release_interface(&btusb_driver, data->isoc);
+
+       __hci_dev_put(hdev);
+
        hci_free_dev(hdev);
 }
 
@@ -558,6 +966,24 @@ static void __exit btusb_exit(void)
 module_init(btusb_init);
 module_exit(btusb_exit);
 
+module_param(ignore_dga, bool, 0644);
+MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001");
+
+module_param(ignore_csr, bool, 0644);
+MODULE_PARM_DESC(ignore_csr, "Ignore devices with id 0a12:0001");
+
+module_param(ignore_sniffer, bool, 0644);
+MODULE_PARM_DESC(ignore_sniffer, "Ignore devices with id 0a12:0002");
+
+module_param(disable_scofix, bool, 0644);
+MODULE_PARM_DESC(disable_scofix, "Disable fixup of wrong SCO buffer size");
+
+module_param(force_scofix, bool, 0644);
+MODULE_PARM_DESC(force_scofix, "Force fixup of wrong SCO buffers size");
+
+module_param(reset, bool, 0644);
+MODULE_PARM_DESC(reset, "Send HCI reset command on initialization");
+
 MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
 MODULE_DESCRIPTION("Generic Bluetooth USB driver ver " VERSION);
 MODULE_VERSION(VERSION);