mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 22:51:42 +00:00
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/holtmann/bluetooth-2.6
This commit is contained in:
commit
d2805395aa
91
MAINTAINERS
91
MAINTAINERS
@ -936,96 +936,21 @@ M: joern@lazybastard.org
|
||||
L: linux-mtd@lists.infradead.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH DRIVERS
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
L: linux-bluetooth@vger.kernel.org
|
||||
W: http://www.bluez.org/
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH SUBSYSTEM
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
L: linux-bluetooth@vger.kernel.org
|
||||
W: http://bluez.sf.net
|
||||
W: http://www.bluez.org
|
||||
W: http://www.holtmann.org/linux/bluetooth/
|
||||
W: http://www.bluez.org/
|
||||
T: git kernel.org:/pub/scm/linux/kernel/git/holtmann/bluetooth-2.6.git
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH RFCOMM LAYER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH BNEP LAYER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH CMTP LAYER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HIDP LAYER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI UART DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI USB DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BCM203X DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BPA10X DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BFUSB DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI DTL1 DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BLUECARD DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BT3C DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI BTUART DRIVER
|
||||
P: Marcel Holtmann
|
||||
M: marcel@holtmann.org
|
||||
S: Maintained
|
||||
|
||||
BLUETOOTH HCI VHCI DRIVER
|
||||
P: Maxim Krasnyansky
|
||||
M: maxk@qualcomm.com
|
||||
S: Maintained
|
||||
|
||||
BONDING DRIVER
|
||||
P: Jay Vosburgh
|
||||
M: fubar@us.ibm.com
|
||||
|
@ -3,8 +3,8 @@ menu "Bluetooth device drivers"
|
||||
depends on BT
|
||||
|
||||
config BT_HCIUSB
|
||||
tristate "HCI USB driver"
|
||||
depends on USB
|
||||
tristate "HCI USB driver (old version)"
|
||||
depends on USB && BT_HCIBTUSB=n
|
||||
help
|
||||
Bluetooth HCI USB driver.
|
||||
This driver is required if you want to use Bluetooth devices with
|
||||
@ -23,15 +23,13 @@ config BT_HCIUSB_SCO
|
||||
Say Y here to compile support for SCO over HCI USB.
|
||||
|
||||
config BT_HCIBTUSB
|
||||
tristate "HCI USB driver (alternate version)"
|
||||
depends on USB && EXPERIMENTAL && BT_HCIUSB=n
|
||||
tristate "HCI USB driver"
|
||||
depends on USB
|
||||
help
|
||||
Bluetooth HCI USB driver.
|
||||
This driver is required if you want to use Bluetooth devices with
|
||||
USB interface.
|
||||
|
||||
This driver is still experimental and has no SCO support.
|
||||
|
||||
Say Y here to compile support for Bluetooth USB devices into the
|
||||
kernel or say M to compile it as module (btusb).
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
/* ======================== Module parameters ======================== */
|
||||
|
||||
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>, Jose Orlando Pereira <jop@di.uminho.pt>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth driver for the 3Com Bluetooth PCMCIA card");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_FIRMWARE("BT3CPCC.bin");
|
||||
|
@ -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
|
||||
@ -41,7 +41,7 @@
|
||||
#define BT_DBG(D...)
|
||||
#endif
|
||||
|
||||
#define VERSION "0.2"
|
||||
#define VERSION "0.3"
|
||||
|
||||
static int ignore_dga;
|
||||
static int ignore_csr;
|
||||
@ -160,12 +160,16 @@ static struct usb_device_id blacklist_table[] = {
|
||||
{ } /* 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 *isoc;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
@ -176,10 +180,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)
|
||||
@ -195,6 +204,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) {
|
||||
@ -216,7 +227,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;
|
||||
@ -226,6 +237,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;
|
||||
@ -274,6 +288,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) {
|
||||
@ -295,7 +311,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;
|
||||
@ -305,6 +321,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;
|
||||
@ -339,6 +358,127 @@ static inline int btusb_submit_bulk_urb(struct hci_dev *hdev)
|
||||
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);
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
usb_free_urb(urb);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void btusb_tx_complete(struct urb *urb)
|
||||
{
|
||||
struct sk_buff *skb = urb->context;
|
||||
@ -392,6 +532,9 @@ static int btusb_close(struct hci_dev *hdev)
|
||||
if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
|
||||
return 0;
|
||||
|
||||
clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
|
||||
usb_kill_anchored_urbs(&data->intr_anchor);
|
||||
|
||||
clear_bit(BTUSB_BULK_RUNNING, &data->flags);
|
||||
usb_kill_anchored_urbs(&data->bulk_anchor);
|
||||
|
||||
@ -453,6 +596,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;
|
||||
@ -467,9 +613,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;
|
||||
@ -508,22 +676,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,6 +829,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) {
|
||||
@ -620,6 +853,9 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
|
||||
hdev->owner = THIS_MODULE;
|
||||
|
||||
/* 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);
|
||||
|
||||
@ -628,11 +864,16 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
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 = interface_to_usbdev(intf);
|
||||
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) {
|
||||
@ -646,6 +887,16 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
}
|
||||
}
|
||||
|
||||
if (data->isoc) {
|
||||
err = usb_driver_claim_interface(&btusb_driver,
|
||||
data->isoc, NULL);
|
||||
if (err < 0) {
|
||||
hci_free_dev(hdev);
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = hci_register_dev(hdev);
|
||||
if (err < 0) {
|
||||
hci_free_dev(hdev);
|
||||
@ -670,6 +921,9 @@ static void btusb_disconnect(struct usb_interface *intf)
|
||||
|
||||
hdev = data->hdev;
|
||||
|
||||
if (data->isoc)
|
||||
usb_driver_release_interface(&btusb_driver, data->isoc);
|
||||
|
||||
usb_set_intfdata(intf, NULL);
|
||||
|
||||
hci_unregister_dev(hdev);
|
||||
|
@ -577,7 +577,7 @@ module_exit(hci_uart_exit);
|
||||
module_param(reset, bool, 0644);
|
||||
MODULE_PARM_DESC(reset, "Send HCI reset command on initialization");
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -1130,7 +1130,7 @@ module_param(isoc, int, 0644);
|
||||
MODULE_PARM_DESC(isoc, "Set isochronous transfers for SCO over HCI support");
|
||||
#endif
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth HCI USB driver ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -377,7 +377,7 @@ module_exit(vhci_exit);
|
||||
module_param(minor, int, 0444);
|
||||
MODULE_PARM_DESC(minor, "Miscellaneous minor device number");
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -456,7 +456,7 @@ static void __exit bt_exit(void)
|
||||
subsys_initcall(bt_init);
|
||||
module_exit(bt_exit);
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth Core ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -736,7 +736,7 @@ MODULE_PARM_DESC(compress_src, "Compress sources headers");
|
||||
module_param(compress_dst, bool, 0644);
|
||||
MODULE_PARM_DESC(compress_dst, "Compress destination headers");
|
||||
|
||||
MODULE_AUTHOR("David Libault <david.libault@inventel.fr>, Maxim Krasnyansky <maxk@qualcomm.com>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth BNEP ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -3,8 +3,6 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
@ -12,10 +10,164 @@
|
||||
#undef BT_DBG
|
||||
#define BT_DBG(D...)
|
||||
#endif
|
||||
|
||||
struct class *bt_class = NULL;
|
||||
EXPORT_SYMBOL_GPL(bt_class);
|
||||
|
||||
static struct workqueue_struct *btaddconn;
|
||||
static struct workqueue_struct *btdelconn;
|
||||
|
||||
static inline char *typetostr(int type)
|
||||
static inline char *link_typetostr(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case ACL_LINK:
|
||||
return "ACL";
|
||||
case SCO_LINK:
|
||||
return "SCO";
|
||||
case ESCO_LINK:
|
||||
return "eSCO";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t show_link_type(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", link_typetostr(conn->type));
|
||||
}
|
||||
|
||||
static ssize_t show_link_address(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
bdaddr_t bdaddr;
|
||||
baswap(&bdaddr, &conn->dst);
|
||||
return sprintf(buf, "%s\n", batostr(&bdaddr));
|
||||
}
|
||||
|
||||
static ssize_t show_link_features(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
|
||||
conn->features[0], conn->features[1],
|
||||
conn->features[2], conn->features[3],
|
||||
conn->features[4], conn->features[5],
|
||||
conn->features[6], conn->features[7]);
|
||||
}
|
||||
|
||||
#define LINK_ATTR(_name,_mode,_show,_store) \
|
||||
struct device_attribute link_attr_##_name = __ATTR(_name,_mode,_show,_store)
|
||||
|
||||
static LINK_ATTR(type, S_IRUGO, show_link_type, NULL);
|
||||
static LINK_ATTR(address, S_IRUGO, show_link_address, NULL);
|
||||
static LINK_ATTR(features, S_IRUGO, show_link_features, NULL);
|
||||
|
||||
static struct attribute *bt_link_attrs[] = {
|
||||
&link_attr_type.attr,
|
||||
&link_attr_address.attr,
|
||||
&link_attr_features.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group bt_link_group = {
|
||||
.attrs = bt_link_attrs,
|
||||
};
|
||||
|
||||
static struct attribute_group *bt_link_groups[] = {
|
||||
&bt_link_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static void bt_link_release(struct device *dev)
|
||||
{
|
||||
void *data = dev_get_drvdata(dev);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
static struct device_type bt_link = {
|
||||
.name = "link",
|
||||
.groups = bt_link_groups,
|
||||
.release = bt_link_release,
|
||||
};
|
||||
|
||||
static void add_conn(struct work_struct *work)
|
||||
{
|
||||
struct hci_conn *conn = container_of(work, struct hci_conn, work);
|
||||
|
||||
flush_workqueue(btdelconn);
|
||||
|
||||
if (device_add(&conn->dev) < 0) {
|
||||
BT_ERR("Failed to register connection device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void hci_conn_add_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
conn->dev.type = &bt_link;
|
||||
conn->dev.class = bt_class;
|
||||
conn->dev.parent = &hdev->dev;
|
||||
|
||||
snprintf(conn->dev.bus_id, BUS_ID_SIZE, "%s:%d",
|
||||
hdev->name, conn->handle);
|
||||
|
||||
dev_set_drvdata(&conn->dev, conn);
|
||||
|
||||
device_initialize(&conn->dev);
|
||||
|
||||
INIT_WORK(&conn->work, add_conn);
|
||||
|
||||
queue_work(btaddconn, &conn->work);
|
||||
}
|
||||
|
||||
/*
|
||||
* The rfcomm tty device will possibly retain even when conn
|
||||
* is down, and sysfs doesn't support move zombie device,
|
||||
* so we should move the device before conn device is destroyed.
|
||||
*/
|
||||
static int __match_tty(struct device *dev, void *data)
|
||||
{
|
||||
return !strncmp(dev->bus_id, "rfcomm", 6);
|
||||
}
|
||||
|
||||
static void del_conn(struct work_struct *work)
|
||||
{
|
||||
struct hci_conn *conn = container_of(work, struct hci_conn, work);
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
while (1) {
|
||||
struct device *dev;
|
||||
|
||||
dev = device_find_child(&conn->dev, NULL, __match_tty);
|
||||
if (!dev)
|
||||
break;
|
||||
device_move(dev, NULL);
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
device_del(&conn->dev);
|
||||
put_device(&conn->dev);
|
||||
hci_dev_put(hdev);
|
||||
}
|
||||
|
||||
void hci_conn_del_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
if (!device_is_registered(&conn->dev))
|
||||
return;
|
||||
|
||||
INIT_WORK(&conn->work, del_conn);
|
||||
|
||||
queue_work(btdelconn, &conn->work);
|
||||
}
|
||||
|
||||
static inline char *host_typetostr(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case HCI_VIRTUAL:
|
||||
@ -40,7 +192,7 @@ static inline char *typetostr(int type)
|
||||
static ssize_t show_type(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_dev *hdev = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", typetostr(hdev->type));
|
||||
return sprintf(buf, "%s\n", host_typetostr(hdev->type));
|
||||
}
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
@ -221,183 +373,62 @@ static DEVICE_ATTR(sniff_max_interval, S_IRUGO | S_IWUSR,
|
||||
static DEVICE_ATTR(sniff_min_interval, S_IRUGO | S_IWUSR,
|
||||
show_sniff_min_interval, store_sniff_min_interval);
|
||||
|
||||
static struct device_attribute *bt_attrs[] = {
|
||||
&dev_attr_type,
|
||||
&dev_attr_name,
|
||||
&dev_attr_class,
|
||||
&dev_attr_address,
|
||||
&dev_attr_features,
|
||||
&dev_attr_manufacturer,
|
||||
&dev_attr_hci_version,
|
||||
&dev_attr_hci_revision,
|
||||
&dev_attr_inquiry_cache,
|
||||
&dev_attr_idle_timeout,
|
||||
&dev_attr_sniff_max_interval,
|
||||
&dev_attr_sniff_min_interval,
|
||||
static struct attribute *bt_host_attrs[] = {
|
||||
&dev_attr_type.attr,
|
||||
&dev_attr_name.attr,
|
||||
&dev_attr_class.attr,
|
||||
&dev_attr_address.attr,
|
||||
&dev_attr_features.attr,
|
||||
&dev_attr_manufacturer.attr,
|
||||
&dev_attr_hci_version.attr,
|
||||
&dev_attr_hci_revision.attr,
|
||||
&dev_attr_inquiry_cache.attr,
|
||||
&dev_attr_idle_timeout.attr,
|
||||
&dev_attr_sniff_max_interval.attr,
|
||||
&dev_attr_sniff_min_interval.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static ssize_t show_conn_type(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%s\n", conn->type == ACL_LINK ? "ACL" : "SCO");
|
||||
}
|
||||
static struct attribute_group bt_host_group = {
|
||||
.attrs = bt_host_attrs,
|
||||
};
|
||||
|
||||
static ssize_t show_conn_address(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
bdaddr_t bdaddr;
|
||||
baswap(&bdaddr, &conn->dst);
|
||||
return sprintf(buf, "%s\n", batostr(&bdaddr));
|
||||
}
|
||||
|
||||
static ssize_t show_conn_features(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct hci_conn *conn = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
|
||||
conn->features[0], conn->features[1],
|
||||
conn->features[2], conn->features[3],
|
||||
conn->features[4], conn->features[5],
|
||||
conn->features[6], conn->features[7]);
|
||||
}
|
||||
|
||||
#define CONN_ATTR(_name,_mode,_show,_store) \
|
||||
struct device_attribute conn_attr_##_name = __ATTR(_name,_mode,_show,_store)
|
||||
|
||||
static CONN_ATTR(type, S_IRUGO, show_conn_type, NULL);
|
||||
static CONN_ATTR(address, S_IRUGO, show_conn_address, NULL);
|
||||
static CONN_ATTR(features, S_IRUGO, show_conn_features, NULL);
|
||||
|
||||
static struct device_attribute *conn_attrs[] = {
|
||||
&conn_attr_type,
|
||||
&conn_attr_address,
|
||||
&conn_attr_features,
|
||||
static struct attribute_group *bt_host_groups[] = {
|
||||
&bt_host_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
struct class *bt_class = NULL;
|
||||
EXPORT_SYMBOL_GPL(bt_class);
|
||||
|
||||
static struct bus_type bt_bus = {
|
||||
.name = "bluetooth",
|
||||
};
|
||||
|
||||
static struct platform_device *bt_platform;
|
||||
|
||||
static void bt_release(struct device *dev)
|
||||
static void bt_host_release(struct device *dev)
|
||||
{
|
||||
void *data = dev_get_drvdata(dev);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
static void add_conn(struct work_struct *work)
|
||||
{
|
||||
struct hci_conn *conn = container_of(work, struct hci_conn, work);
|
||||
int i;
|
||||
|
||||
flush_workqueue(btdelconn);
|
||||
|
||||
if (device_add(&conn->dev) < 0) {
|
||||
BT_ERR("Failed to register connection device");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; conn_attrs[i]; i++)
|
||||
if (device_create_file(&conn->dev, conn_attrs[i]) < 0)
|
||||
BT_ERR("Failed to create connection attribute");
|
||||
}
|
||||
|
||||
void hci_conn_add_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
conn->dev.bus = &bt_bus;
|
||||
conn->dev.parent = &hdev->dev;
|
||||
|
||||
conn->dev.release = bt_release;
|
||||
|
||||
snprintf(conn->dev.bus_id, BUS_ID_SIZE, "%s:%d",
|
||||
hdev->name, conn->handle);
|
||||
|
||||
dev_set_drvdata(&conn->dev, conn);
|
||||
|
||||
device_initialize(&conn->dev);
|
||||
|
||||
INIT_WORK(&conn->work, add_conn);
|
||||
|
||||
queue_work(btaddconn, &conn->work);
|
||||
}
|
||||
|
||||
/*
|
||||
* The rfcomm tty device will possibly retain even when conn
|
||||
* is down, and sysfs doesn't support move zombie device,
|
||||
* so we should move the device before conn device is destroyed.
|
||||
*/
|
||||
static int __match_tty(struct device *dev, void *data)
|
||||
{
|
||||
return !strncmp(dev->bus_id, "rfcomm", 6);
|
||||
}
|
||||
|
||||
static void del_conn(struct work_struct *work)
|
||||
{
|
||||
struct hci_conn *conn = container_of(work, struct hci_conn, work);
|
||||
struct hci_dev *hdev = conn->hdev;
|
||||
|
||||
while (1) {
|
||||
struct device *dev;
|
||||
|
||||
dev = device_find_child(&conn->dev, NULL, __match_tty);
|
||||
if (!dev)
|
||||
break;
|
||||
device_move(dev, NULL);
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
device_del(&conn->dev);
|
||||
put_device(&conn->dev);
|
||||
hci_dev_put(hdev);
|
||||
}
|
||||
|
||||
void hci_conn_del_sysfs(struct hci_conn *conn)
|
||||
{
|
||||
BT_DBG("conn %p", conn);
|
||||
|
||||
if (!device_is_registered(&conn->dev))
|
||||
return;
|
||||
|
||||
INIT_WORK(&conn->work, del_conn);
|
||||
|
||||
queue_work(btdelconn, &conn->work);
|
||||
}
|
||||
static struct device_type bt_host = {
|
||||
.name = "host",
|
||||
.groups = bt_host_groups,
|
||||
.release = bt_host_release,
|
||||
};
|
||||
|
||||
int hci_register_sysfs(struct hci_dev *hdev)
|
||||
{
|
||||
struct device *dev = &hdev->dev;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
|
||||
|
||||
dev->bus = &bt_bus;
|
||||
dev->type = &bt_host;
|
||||
dev->class = bt_class;
|
||||
dev->parent = hdev->parent;
|
||||
|
||||
strlcpy(dev->bus_id, hdev->name, BUS_ID_SIZE);
|
||||
|
||||
dev->release = bt_release;
|
||||
|
||||
dev_set_drvdata(dev, hdev);
|
||||
|
||||
err = device_register(dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; bt_attrs[i]; i++)
|
||||
if (device_create_file(dev, bt_attrs[i]) < 0)
|
||||
BT_ERR("Failed to create device attribute");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -410,59 +441,30 @@ void hci_unregister_sysfs(struct hci_dev *hdev)
|
||||
|
||||
int __init bt_sysfs_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
btaddconn = create_singlethread_workqueue("btaddconn");
|
||||
if (!btaddconn) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (!btaddconn)
|
||||
return -ENOMEM;
|
||||
|
||||
btdelconn = create_singlethread_workqueue("btdelconn");
|
||||
if (!btdelconn) {
|
||||
err = -ENOMEM;
|
||||
goto out_del;
|
||||
destroy_workqueue(btaddconn);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
bt_platform = platform_device_register_simple("bluetooth", -1, NULL, 0);
|
||||
if (IS_ERR(bt_platform)) {
|
||||
err = PTR_ERR(bt_platform);
|
||||
goto out_platform;
|
||||
}
|
||||
|
||||
err = bus_register(&bt_bus);
|
||||
if (err < 0)
|
||||
goto out_bus;
|
||||
|
||||
bt_class = class_create(THIS_MODULE, "bluetooth");
|
||||
if (IS_ERR(bt_class)) {
|
||||
err = PTR_ERR(bt_class);
|
||||
goto out_class;
|
||||
destroy_workqueue(btdelconn);
|
||||
destroy_workqueue(btaddconn);
|
||||
return PTR_ERR(bt_class);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_class:
|
||||
bus_unregister(&bt_bus);
|
||||
out_bus:
|
||||
platform_device_unregister(bt_platform);
|
||||
out_platform:
|
||||
destroy_workqueue(btdelconn);
|
||||
out_del:
|
||||
destroy_workqueue(btaddconn);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
void bt_sysfs_cleanup(void)
|
||||
{
|
||||
destroy_workqueue(btaddconn);
|
||||
|
||||
destroy_workqueue(btdelconn);
|
||||
|
||||
class_destroy(bt_class);
|
||||
|
||||
bus_unregister(&bt_bus);
|
||||
|
||||
platform_device_unregister(bt_platform);
|
||||
}
|
||||
|
@ -2516,7 +2516,7 @@ EXPORT_SYMBOL(l2cap_load);
|
||||
module_init(l2cap_init);
|
||||
module_exit(l2cap_exit);
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth L2CAP ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -2115,7 +2115,7 @@ MODULE_PARM_DESC(channel_mtu, "Default MTU for the RFCOMM channel");
|
||||
module_param(l2cap_mtu, uint, 0644);
|
||||
MODULE_PARM_DESC(l2cap_mtu, "Default MTU for the L2CAP connection");
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth RFCOMM ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -1002,7 +1002,7 @@ module_exit(sco_exit);
|
||||
module_param(disable_esco, bool, 0644);
|
||||
MODULE_PARM_DESC(disable_esco, "Disable eSCO connection creation");
|
||||
|
||||
MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||
MODULE_DESCRIPTION("Bluetooth SCO ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
Loading…
Reference in New Issue
Block a user