diff options
| author | David S. Miller <davem@nuts.ninka.net> | 2004-01-13 21:17:19 -0800 |
|---|---|---|
| committer | David S. Miller <davem@nuts.ninka.net> | 2004-01-13 21:17:19 -0800 |
| commit | fd5ef78663e840412189b16d53ff5a3fd8bc7f71 (patch) | |
| tree | 2de76168a0534bf0e7eb12fcecf8d3b3ba91a5f9 | |
| parent | aa0d8c971aec23f84a2845a8a22b0216ece44c2c (diff) | |
| parent | 46fc74070ebdee51527a2ca20a1e3a1bf4a6bb5b (diff) | |
Merge nuts.ninka.net:/disk1/davem/BK/bluetooth-2.6
into nuts.ninka.net:/disk1/davem/BK/net-2.6
34 files changed, 2817 insertions, 99 deletions
@@ -1394,7 +1394,9 @@ S: USA N: Marcel Holtmann E: marcel@holtmann.org W: http://www.holtmann.org +D: Maintainer of the Linux Bluetooth Subsystem D: Author and maintainer of the various Bluetooth HCI drivers +D: Author and maintainer of the CAPI message transport protocol driver D: Various other Bluetooth related patches, cleanups and fixes S: Germany diff --git a/MAINTAINERS b/MAINTAINERS index 69753c92f331..8e94a100dc70 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -338,35 +338,64 @@ L: linux-kernel@vger.kernel.org S: Maintained BLUETOOTH SUBSYSTEM +P: Marcel Holtmann +M: marcel@holtmann.org P: Maxim Krasnyansky M: maxk@qualcomm.com +L: bluez-devel@lists.sf.net W: http://bluez.sf.net S: Maintained BLUETOOTH RFCOMM LAYER +P: Marcel Holtmann +M: marcel@holtmann.org P: Maxim Krasnyansky M: maxk@qualcomm.com W: http://bluez.sf.net S: Maintained BLUETOOTH BNEP LAYER +P: Marcel Holtmann +M: marcel@holtmann.org P: Maxim Krasnyansky M: maxk@qualcomm.com W: http://bluez.sf.net S: Maintained +BLUETOOTH CMTP LAYER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + BLUETOOTH HCI USB DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org P: Maxim Krasnyansky M: maxk@qualcomm.com W: http://bluez.sf.net S: Maintained BLUETOOTH HCI UART DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org P: Maxim Krasnyansky M: maxk@qualcomm.com W: http://bluez.sf.net S: Maintained +BLUETOOTH HCI BCM203X DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI BFUSB DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + BLUETOOTH HCI DTL1 DRIVER P: Marcel Holtmann M: marcel@holtmann.org diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 30d099c33aee..5ad7ea2b6bc6 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -13,22 +13,14 @@ config BT_HCIUSB Say Y here to compile support for Bluetooth USB devices into the kernel or say M to compile it as module (hci_usb). -config BT_USB_SCO - bool "SCO over HCI USB support" +config BT_HCIUSB_SCO + bool "SCO (voice) support" depends on BT_HCIUSB help This option enables the SCO support in the HCI USB driver. You need this - to transmit voice data with your Bluetooth USB device. - Say Y here to compile support for SCO over HCI USB. + to transmit voice data with your Bluetooth USB device. -config BT_USB_ZERO_PACKET - bool "USB zero packet support" - depends on BT_HCIUSB - help - This option is provided only as a work around for buggy Bluetooth USB - devices. Do _not_ enable it unless you know for sure that your device - requires zero packets. - Most people should say N here. + Say Y here to compile support for SCO over HCI USB. config BT_HCIUART tristate "HCI UART driver" @@ -65,13 +57,38 @@ config BT_HCIUART_BCSP Say Y here to compile support for HCI BCSP protocol. config BT_HCIUART_BCSP_TXCRC - bool "Transmit CRC with every BCSP packet" - depends on BT_HCIUART_BCSP - help + bool "Transmit CRC with every BCSP packet" + depends on BT_HCIUART_BCSP + help If you say Y here, a 16-bit CRC checksum will be transmitted along with every BCSP (BlueCore Serial Protocol) packet sent to the Bluetooth chip. This increases reliability, but slightly reduces efficiency. +config BT_HCIBCM203X + tristate "HCI BCM203x USB driver" + depends on USB && BT + select FW_LOADER + help + Bluetooth HCI BCM203x USB driver. + This driver provides the firmware loading mechanism for the Broadcom + Blutonium based devices. + + Say Y here to compile support for HCI BCM203x devices into the + kernel or say M to compile it as module (bcm203x). + +config BT_HCIBFUSB + tristate "HCI BlueFRITZ! USB driver" + depends on USB && BT + select FW_LOADER + help + Bluetooth HCI BlueFRITZ! USB driver. + This driver provides support for Bluetooth USB devices with AVM + interface: + AVM BlueFRITZ! USB + + Say Y here to compile support for HCI BFUSB devices into the + kernel or say M to compile it as module (bfusb). + config BT_HCIDTL1 tristate "HCI DTL1 (PC Card) driver" depends on PCMCIA && BT diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 8a6af91bb52f..ec5345fac3a3 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -5,6 +5,8 @@ obj-$(CONFIG_BT_HCIUSB) += hci_usb.o obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o obj-$(CONFIG_BT_HCIUART) += hci_uart.o +obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o +obj-$(CONFIG_BT_HCIBFUSB) += bfusb.o obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o diff --git a/drivers/bluetooth/bcm203x.c b/drivers/bluetooth/bcm203x.c new file mode 100644 index 000000000000..12e44029a38a --- /dev/null +++ b/drivers/bluetooth/bcm203x.c @@ -0,0 +1,309 @@ +/* + * + * Broadcom Blutonium firmware driver + * + * Copyright (C) 2003 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/timer.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> + +#ifndef CONFIG_BT_HCIBCM203X_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.0" + +static struct usb_device_id bcm203x_table[] = { + /* Broadcom Blutonium (BCM2033) */ + { USB_DEVICE(0x0a5c, 0x2033) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bcm203x_table); + + +#define BCM203X_ERROR 0 +#define BCM203X_RESET 1 +#define BCM203X_LOAD_MINIDRV 2 +#define BCM203X_SELECT_MEMORY 3 +#define BCM203X_CHECK_MEMORY 4 +#define BCM203X_LOAD_FIRMWARE 5 +#define BCM203X_CHECK_FIRMWARE 6 + +#define BCM203X_IN_EP 0x81 +#define BCM203X_OUT_EP 0x02 + +struct bcm203x_data { + struct usb_device *udev; + + unsigned long state; + + struct timer_list timer; + + struct urb *urb; + unsigned char buffer[4096]; + + unsigned char *fw_data; + unsigned int fw_size; + unsigned int fw_sent; +}; + +static void bcm203x_complete(struct urb *urb, struct pt_regs *regs) +{ + struct bcm203x_data *data = urb->context; + struct usb_device *udev = urb->dev; + int len; + + BT_DBG("udev %p urb %p", udev, urb); + + if (urb->status) { + BT_ERR("URB failed with status %d", urb->status); + data->state = BCM203X_ERROR; + return; + } + + switch (data->state) { + case BCM203X_LOAD_MINIDRV: + memcpy(data->buffer, "#", 1); + + usb_fill_bulk_urb(urb, udev, + usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->buffer, 1, bcm203x_complete, data); + + data->state = BCM203X_SELECT_MEMORY; + + mod_timer(&data->timer, jiffies + (HZ / 10)); + break; + + case BCM203X_SELECT_MEMORY: + usb_fill_int_urb(urb, udev, + usb_rcvintpipe(udev, BCM203X_IN_EP), + data->buffer, 32, bcm203x_complete, data, 1); + + data->state = BCM203X_CHECK_MEMORY; + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); + break; + + case BCM203X_CHECK_MEMORY: + if (data->buffer[0] != '#') { + BT_ERR("Memory select failed"); + data->state = BCM203X_ERROR; + break; + } + + data->state = BCM203X_LOAD_FIRMWARE; + + case BCM203X_LOAD_FIRMWARE: + if (data->fw_sent == data->fw_size) { + usb_fill_int_urb(urb, udev, + usb_rcvintpipe(udev, BCM203X_IN_EP), + data->buffer, 32, + bcm203x_complete, data, 1); + + data->state = BCM203X_CHECK_FIRMWARE; + } else { + len = min_t(uint, data->fw_size - data->fw_sent, + sizeof(data->buffer)); + + usb_fill_bulk_urb(urb, udev, + usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->fw_data + data->fw_sent, len, + bcm203x_complete, data); + + data->fw_sent += len; + } + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); + break; + + case BCM203X_CHECK_FIRMWARE: + if (data->buffer[0] != '.') { + BT_ERR("Firmware loading failed"); + data->state = BCM203X_ERROR; + break; + } + + data->state = BCM203X_RESET; + break; + } +} + +static void bcm203x_timer(unsigned long user_data) +{ + struct bcm203x_data *data = (struct bcm203x_data *) user_data; + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); +} + +static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + struct bcm203x_data *data; + + BT_DBG("intf %p id %p", intf, id); + + if (intf->altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + BT_ERR("Can't allocate memory for data structure"); + return -ENOMEM; + } + + memset(data, 0, sizeof(*data)); + + data->udev = udev; + data->state = BCM203X_LOAD_MINIDRV; + + data->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!data->urb) { + BT_ERR("Can't allocate URB"); + kfree(data); + return -ENOMEM; + } + + if (request_firmware(&firmware, "BCM2033-MD.hex", &udev->dev) < 0) { + BT_ERR("Mini driver request failed"); + usb_free_urb(data->urb); + kfree(data); + return -EIO; + } + + BT_DBG("minidrv data %p size %d", firmware->data, firmware->size); + + if (firmware->size > sizeof(data->buffer)) { + BT_ERR("Mini driver exceeds size of buffer"); + release_firmware(firmware); + usb_free_urb(data->urb); + kfree(data); + return -EIO; + } + + memcpy(data->buffer, firmware->data, firmware->size); + + usb_fill_bulk_urb(data->urb, udev, + usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->buffer, firmware->size, bcm203x_complete, data); + + release_firmware(firmware); + + if (request_firmware(&firmware, "BCM2033-FW.bin", &udev->dev) < 0) { + BT_ERR("Firmware request failed"); + usb_free_urb(data->urb); + kfree(data); + return -EIO; + } + + BT_DBG("firmware data %p size %d", firmware->data, firmware->size); + + data->fw_data = kmalloc(firmware->size, GFP_KERNEL); + if (!data->fw_data) { + BT_ERR("Can't allocate memory for firmware image"); + usb_free_urb(data->urb); + kfree(data); + return -ENOMEM; + } + + memcpy(data->fw_data, firmware->data, firmware->size); + data->fw_size = firmware->size; + data->fw_sent = 0; + + release_firmware(firmware); + + init_timer(&data->timer); + data->timer.function = bcm203x_timer; + data->timer.data = (unsigned long) data; + + usb_set_intfdata(intf, data); + + mod_timer(&data->timer, jiffies + HZ); + + return 0; +} + +static void bcm203x_disconnect(struct usb_interface *intf) +{ + struct bcm203x_data *data = usb_get_intfdata(intf); + + BT_DBG("intf %p", intf); + + usb_unlink_urb(data->urb); + + usb_set_intfdata(intf, NULL); + + usb_free_urb(data->urb); + kfree(data->fw_data); + kfree(data); +} + +static struct usb_driver bcm203x_driver = { + .owner = THIS_MODULE, + .name = "bcm203x", + .probe = bcm203x_probe, + .disconnect = bcm203x_disconnect, + .id_table = bcm203x_table, +}; + +static int __init bcm203x_init(void) +{ + int err; + + BT_INFO("Broadcom Blutonium firmware driver ver %s", VERSION); + + err = usb_register(&bcm203x_driver); + if (err < 0) + BT_ERR("Failed to register USB driver"); + + return err; +} + +static void __exit bcm203x_cleanup(void) +{ + usb_deregister(&bcm203x_driver); +} + +module_init(bcm203x_init); +module_exit(bcm203x_cleanup); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c new file mode 100644 index 000000000000..0ff501d08a78 --- /dev/null +++ b/drivers/bluetooth/bfusb.c @@ -0,0 +1,782 @@ +/* + * + * AVM BlueFRITZ! USB driver + * + * Copyright (C) 2003 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/sched.h> +#include <linux/errno.h> +#include <linux/skbuff.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#ifndef CONFIG_BT_HCIBFUSB_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.1" + +static struct usb_driver bfusb_driver; + +static struct usb_device_id bfusb_table[] = { + /* AVM BlueFRITZ! USB */ + { USB_DEVICE(0x057c, 0x2200) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bfusb_table); + + +#define BFUSB_MAX_BLOCK_SIZE 256 + +#define BFUSB_BLOCK_TIMEOUT (HZ * 3) + +#define BFUSB_TX_PROCESS 1 +#define BFUSB_TX_WAKEUP 2 + +#define BFUSB_MAX_BULK_TX 2 +#define BFUSB_MAX_BULK_RX 2 + +struct bfusb { + struct hci_dev hdev; + + unsigned long state; + + struct usb_device *udev; + + unsigned int bulk_in_ep; + unsigned int bulk_out_ep; + unsigned int bulk_pkt_size; + + rwlock_t lock; + + struct sk_buff_head transmit_q; + + struct sk_buff *reassembly; + + atomic_t pending_tx; + struct sk_buff_head pending_q; + struct sk_buff_head completed_q; +}; + +struct bfusb_scb { + struct urb *urb; +}; + +static void bfusb_tx_complete(struct urb *urb, struct pt_regs *regs); +static void bfusb_rx_complete(struct urb *urb, struct pt_regs *regs); + +static struct urb *bfusb_get_completed(struct bfusb *bfusb) +{ + struct sk_buff *skb; + struct urb *urb = NULL; + + BT_DBG("bfusb %p", bfusb); + + skb = skb_dequeue(&bfusb->completed_q); + if (skb) { + urb = ((struct bfusb_scb *) skb->cb)->urb; + kfree_skb(skb); + } + + return urb; +} + +static inline void bfusb_unlink_urbs(struct bfusb *bfusb) +{ + struct sk_buff *skb; + struct urb *urb; + + BT_DBG("bfusb %p", bfusb); + + while ((skb = skb_dequeue(&bfusb->pending_q))) { + urb = ((struct bfusb_scb *) skb->cb)->urb; + usb_unlink_urb(urb); + skb_queue_tail(&bfusb->completed_q, skb); + } + + while ((urb = bfusb_get_completed(bfusb))) + usb_free_urb(urb); +} + + +static int bfusb_send_bulk(struct bfusb *bfusb, struct sk_buff *skb) +{ + struct bfusb_scb *scb = (void *) skb->cb; + struct urb *urb = bfusb_get_completed(bfusb); + int err, pipe; + + BT_DBG("bfusb %p skb %p len %d", bfusb, skb, skb->len); + + if (!urb && !(urb = usb_alloc_urb(0, GFP_ATOMIC))) + return -ENOMEM; + + pipe = usb_sndbulkpipe(bfusb->udev, bfusb->bulk_out_ep); + + usb_fill_bulk_urb(urb, bfusb->udev, pipe, skb->data, skb->len, + bfusb_tx_complete, skb); + + scb->urb = urb; + + skb_queue_tail(&bfusb->pending_q, skb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk tx submit failed urb %p err %d", + bfusb->hdev.name, urb, err); + skb_unlink(skb); + usb_free_urb(urb); + } else + atomic_inc(&bfusb->pending_tx); + + return err; +} + +static void bfusb_tx_wakeup(struct bfusb *bfusb) +{ + struct sk_buff *skb; + + BT_DBG("bfusb %p", bfusb); + + if (test_and_set_bit(BFUSB_TX_PROCESS, &bfusb->state)) { + set_bit(BFUSB_TX_WAKEUP, &bfusb->state); + return; + } + + do { + clear_bit(BFUSB_TX_WAKEUP, &bfusb->state); + + while ((atomic_read(&bfusb->pending_tx) < BFUSB_MAX_BULK_TX) && + (skb = skb_dequeue(&bfusb->transmit_q))) { + if (bfusb_send_bulk(bfusb, skb) < 0) { + skb_queue_head(&bfusb->transmit_q, skb); + break; + } + } + + } while (test_bit(BFUSB_TX_WAKEUP, &bfusb->state)); + + clear_bit(BFUSB_TX_PROCESS, &bfusb->state); +} + +static void bfusb_tx_complete(struct urb *urb, struct pt_regs *regs) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct bfusb *bfusb = (struct bfusb *) skb->dev; + + BT_DBG("bfusb %p urb %p skb %p len %d", bfusb, urb, skb, skb->len); + + atomic_dec(&bfusb->pending_tx); + + if (!test_bit(HCI_RUNNING, &bfusb->hdev.flags)) + return; + + if (!urb->status) + bfusb->hdev.stat.byte_tx += skb->len; + else + bfusb->hdev.stat.err_tx++; + + read_lock(&bfusb->lock); + + skb_unlink(skb); + skb_queue_tail(&bfusb->completed_q, skb); + + bfusb_tx_wakeup(bfusb); + + read_unlock(&bfusb->lock); +} + + +static int bfusb_rx_submit(struct bfusb *bfusb, struct urb *urb) +{ + struct bfusb_scb *scb; + struct sk_buff *skb; + int err, pipe, size = HCI_MAX_FRAME_SIZE + 32; + + BT_DBG("bfusb %p urb %p", bfusb, urb); + + if (!urb && !(urb = usb_alloc_urb(0, GFP_ATOMIC))) + return -ENOMEM; + + if (!(skb = bt_skb_alloc(size, GFP_ATOMIC))) { + usb_free_urb(urb); + return -ENOMEM; + } + + skb->dev = (void *) bfusb; + + scb = (struct bfusb_scb *) skb->cb; + scb->urb = urb; + + pipe = usb_rcvbulkpipe(bfusb->udev, bfusb->bulk_in_ep); + + usb_fill_bulk_urb(urb, bfusb->udev, pipe, skb->data, size, + bfusb_rx_complete, skb); + + skb_queue_tail(&bfusb->pending_q, skb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk rx submit failed urb %p err %d", + bfusb->hdev.name, urb, err); + skb_unlink(skb); + kfree_skb(skb); + usb_free_urb(urb); + } + + return err; +} + +static inline int bfusb_recv_block(struct bfusb *bfusb, int hdr, unsigned char *data, int len) +{ + BT_DBG("bfusb %p hdr 0x%02x data %p len %d", bfusb, hdr, data, len); + + if (hdr & 0x10) { + BT_ERR("%s error in block", bfusb->hdev.name); + if (bfusb->reassembly) + kfree_skb(bfusb->reassembly); + bfusb->reassembly = NULL; + return -EIO; + } + + if (hdr & 0x04) { + struct sk_buff *skb; + unsigned char pkt_type; + int pkt_len = 0; + + if (bfusb->reassembly) { + BT_ERR("%s unexpected start block", bfusb->hdev.name); + kfree_skb(bfusb->reassembly); + bfusb->reassembly = NULL; + } + + if (len < 1) { + BT_ERR("%s no packet type found", bfusb->hdev.name); + return -EPROTO; + } + + pkt_type = *data++; len--; + + switch (pkt_type) { + case HCI_EVENT_PKT: + if (len >= HCI_EVENT_HDR_SIZE) { + struct hci_event_hdr *hdr = (struct hci_event_hdr *) data; + pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen; + } else { + BT_ERR("%s event block is too short", bfusb->hdev.name); + return -EILSEQ; + } + break; + + case HCI_ACLDATA_PKT: + if (len >= HCI_ACL_HDR_SIZE) { + struct hci_acl_hdr *hdr = (struct hci_acl_hdr *) data; + pkt_len = HCI_ACL_HDR_SIZE + __le16_to_cpu(hdr->dlen); + } else { + BT_ERR("%s data block is too short", bfusb->hdev.name); + return -EILSEQ; + } + break; + + case HCI_SCODATA_PKT: + if (len >= HCI_SCO_HDR_SIZE) { + struct hci_sco_hdr *hdr = (struct hci_sco_hdr *) data; + pkt_len = HCI_SCO_HDR_SIZE + hdr->dlen; + } else { + BT_ERR("%s audio block is too short", bfusb->hdev.name); + return -EILSEQ; + } + break; + } + + skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for the packet", bfusb->hdev.name); + return -ENOMEM; + } + + skb->dev = (void *) &bfusb->hdev; + skb->pkt_type = pkt_type; + + bfusb->reassembly = skb; + } else { + if (!bfusb->reassembly) { + BT_ERR("%s unexpected continuation block", bfusb->hdev.name); + return -EIO; + } + } + + if (len > 0) + memcpy(skb_put(bfusb->reassembly, len), data, len); + + if (hdr & 0x08) { + hci_recv_frame(bfusb->reassembly); + bfusb->reassembly = NULL; + } + + return 0; +} + +static void bfusb_rx_complete(struct urb *urb, struct pt_regs *regs) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct bfusb *bfusb = (struct bfusb *) skb->dev; + unsigned char *buf = urb->transfer_buffer; + int count = urb->actual_length; + int err, hdr, len; + + BT_DBG("bfusb %p urb %p skb %p len %d", bfusb, urb, skb, skb->len); + + if (!test_bit(HCI_RUNNING, &bfusb->hdev.flags)) + return; + + read_lock(&bfusb->lock); + + if (urb->status || !count) + goto resubmit; + + bfusb->hdev.stat.byte_rx += count; + + skb_put(skb, count); + + while (count) { + hdr = buf[0] | (buf[1] << 8); + + if (hdr & 0x4000) { + len = 0; + count -= 2; + buf += 2; + } else { + len = (buf[2] == 0) ? 256 : buf[2]; + count -= 3; + buf += 3; + } + + if (count < len) { + BT_ERR("%s block extends over URB buffer ranges", + bfusb->hdev.name); + } + + if ((hdr & 0xe1) == 0xc1) + bfusb_recv_block(bfusb, hdr, buf, len); + + count -= len; + buf += len; + } + + skb_unlink(skb); + kfree_skb(skb); + + bfusb_rx_submit(bfusb, urb); + + read_unlock(&bfusb->lock); + + return; + +resubmit: + urb->dev = bfusb->udev; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk resubmit failed urb %p err %d", + bfusb->hdev.name, urb, err); + } + + read_unlock(&bfusb->lock); +} + + +static int bfusb_open(struct hci_dev *hdev) +{ + struct bfusb *bfusb = (struct bfusb *) hdev->driver_data; + unsigned long flags; + int i, err; + + BT_DBG("hdev %p bfusb %p", hdev, bfusb); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&bfusb->lock, flags); + + err = bfusb_rx_submit(bfusb, NULL); + if (!err) { + for (i = 1; i < BFUSB_MAX_BULK_RX; i++) + bfusb_rx_submit(bfusb, NULL); + } else { + clear_bit(HCI_RUNNING, &hdev->flags); + } + + write_unlock_irqrestore(&bfusb->lock, flags); + + return err; +} + +static int bfusb_flush(struct hci_dev *hdev) +{ + struct bfusb *bfusb = (struct bfusb *) hdev->driver_data; + + BT_DBG("hdev %p bfusb %p", hdev, bfusb); + + skb_queue_purge(&bfusb->transmit_q); + + return 0; +} + +static int bfusb_close(struct hci_dev *hdev) +{ + struct bfusb *bfusb = (struct bfusb *) hdev->driver_data; + unsigned long flags; + + BT_DBG("hdev %p bfusb %p", hdev, bfusb); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&bfusb->lock, flags); + write_unlock_irqrestore(&bfusb->lock, flags); + + bfusb_unlink_urbs(bfusb); + bfusb_flush(hdev); + + return 0; +} + +static int bfusb_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct bfusb *bfusb; + struct sk_buff *nskb; + unsigned char buf[3]; + int sent = 0, size, count; + + 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 (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + bfusb = (struct bfusb *) hdev->driver_data; + + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &(skb->pkt_type), 1); + + count = skb->len; + + /* Max HCI frame size seems to be 1511 + 1 */ + if (!(nskb = bt_skb_alloc(count + 32, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for new packet"); + return -ENOMEM; + } + + nskb->dev = (void *) bfusb; + + while (count) { + size = min_t(uint, count, BFUSB_MAX_BLOCK_SIZE); + + buf[0] = 0xc1 | ((sent == 0) ? 0x04 : 0) | ((count == size) ? 0x08 : 0); + buf[1] = 0x00; + buf[2] = (size == BFUSB_MAX_BLOCK_SIZE) ? 0 : size; + + memcpy(skb_put(nskb, 3), buf, 3); + memcpy(skb_put(nskb, size), skb->data + sent, size); + + sent += size; + count -= size; + } + + /* Don't send frame with multiple size of bulk max packet */ + if ((nskb->len % bfusb->bulk_pkt_size) == 0) { + buf[0] = 0xdd; + buf[1] = 0x00; + memcpy(skb_put(nskb, 2), buf, 2); + } + + read_lock(&bfusb->lock); + + skb_queue_tail(&bfusb->transmit_q, nskb); + bfusb_tx_wakeup(bfusb); + + read_unlock(&bfusb->lock); + + kfree_skb(skb); + + return 0; +} + +static void bfusb_destruct(struct hci_dev *hdev) +{ + struct bfusb *bfusb = (struct bfusb *) hdev->driver_data; + + BT_DBG("hdev %p bfusb %p", hdev, bfusb); + + kfree(bfusb); +} + +static int bfusb_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + +static int bfusb_load_firmware(struct bfusb *bfusb, unsigned char *firmware, int count) +{ + unsigned char *buf; + int err, pipe, len, size, sent = 0; + + BT_DBG("bfusb %p udev %p", bfusb, bfusb->udev); + + BT_INFO("BlueFRITZ! USB loading firmware"); + + pipe = usb_sndctrlpipe(bfusb->udev, 0); + + if (usb_control_msg(bfusb->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 1, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT) < 0) { + BT_ERR("Can't change to loading configuration"); + return -EBUSY; + } + + buf = kmalloc(BFUSB_MAX_BLOCK_SIZE + 3, GFP_ATOMIC); + if (!buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + pipe = usb_sndbulkpipe(bfusb->udev, bfusb->bulk_out_ep); + + while (count) { + size = min_t(uint, count, BFUSB_MAX_BLOCK_SIZE + 3); + + memcpy(buf, firmware + sent, size); + + err = usb_bulk_msg(bfusb->udev, pipe, buf, size, + &len, BFUSB_BLOCK_TIMEOUT); + + if (err || (len != size)) { + BT_ERR("Error in firmware loading"); + goto error; + } + + sent += size; + count -= size; + } + + if ((err = usb_bulk_msg(bfusb->udev, pipe, NULL, 0, + &len, BFUSB_BLOCK_TIMEOUT)) < 0) { + BT_ERR("Error in null packet request"); + goto error; + } + + pipe = usb_sndctrlpipe(bfusb->udev, 0); + + if ((err = usb_control_msg(bfusb->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 2, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) { + BT_ERR("Can't change to running configuration"); + goto error; + } + + BT_INFO("BlueFRITZ! USB device ready"); + + kfree(buf); + return 0; + +error: + kfree(buf); + + pipe = usb_sndctrlpipe(bfusb->udev, 0); + + usb_control_msg(bfusb->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 0, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); + + return err; +} + +static int bfusb_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_host_endpoint *bulk_out_ep; + struct usb_host_endpoint *bulk_in_ep; + struct hci_dev *hdev; + struct bfusb *bfusb; + + BT_DBG("iface %p id %p", iface, id); + + /* Check number of endpoints */ + if (iface->altsetting[0].desc.bNumEndpoints < 2) + return -EIO; + + bulk_out_ep = &iface->altsetting[0].endpoint[0]; + bulk_in_ep = &iface->altsetting[0].endpoint[1]; + + if (!bulk_out_ep || !bulk_in_ep) { + BT_ERR("Bulk endpoints not found"); + goto done; + } + + /* Initialize control structure and load firmware */ + if (!(bfusb = kmalloc(sizeof(struct bfusb), GFP_KERNEL))) { + BT_ERR("Can't allocate memory for control structure"); + goto done; + } + + memset(bfusb, 0, sizeof(struct bfusb)); + + bfusb->udev = udev; + bfusb->bulk_in_ep = bulk_in_ep->desc.bEndpointAddress; + bfusb->bulk_out_ep = bulk_out_ep->desc.bEndpointAddress; + bfusb->bulk_pkt_size = bulk_out_ep->desc.wMaxPacketSize; + + bfusb->lock = RW_LOCK_UNLOCKED; + + bfusb->reassembly = NULL; + + skb_queue_head_init(&bfusb->transmit_q); + skb_queue_head_init(&bfusb->pending_q); + skb_queue_head_init(&bfusb->completed_q); + + if (request_firmware(&firmware, "bfubase.frm", &udev->dev) < 0) { + BT_ERR("Firmware request failed"); + goto error; + } + + BT_DBG("firmware data %p size %d", firmware->data, firmware->size); + + if (bfusb_load_firmware(bfusb, firmware->data, firmware->size) < 0) { + BT_ERR("Firmware loading failed"); + goto release; + } + + release_firmware(firmware); + + /* Initialize and register HCI device */ + hdev = &bfusb->hdev; + + hdev->type = HCI_USB; + hdev->driver_data = bfusb; + + hdev->open = bfusb_open; + hdev->close = bfusb_close; + hdev->flush = bfusb_flush; + hdev->send = bfusb_send_frame; + hdev->destruct = bfusb_destruct; + hdev->ioctl = bfusb_ioctl; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + goto error; + } + + usb_set_intfdata(iface, bfusb); + + return 0; + +release: + release_firmware(firmware); + +error: + kfree(bfusb); + +done: + return -EIO; +} + +static void bfusb_disconnect(struct usb_interface *iface) +{ + struct bfusb *bfusb = usb_get_intfdata(iface); + struct hci_dev *hdev = &bfusb->hdev; + + BT_DBG("iface %p", iface); + + if (!hdev) + return; + + usb_set_intfdata(iface, NULL); + + bfusb_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); +} + +static struct usb_driver bfusb_driver = { + .owner = THIS_MODULE, + .name = "bfusb", + .probe = bfusb_probe, + .disconnect = bfusb_disconnect, + .id_table = bfusb_table, +}; + +static int __init bfusb_init(void) +{ + int err; + + BT_INFO("BlueFRITZ! USB driver ver %s", VERSION); + + if ((err = usb_register(&bfusb_driver)) < 0) + BT_ERR("Failed to register BlueFRITZ! USB driver"); + + return err; +} + +static void __exit bfusb_cleanup(void) +{ + usb_deregister(&bfusb_driver); +} + +module_init(bfusb_init); +module_exit(bfusb_cleanup); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c index fee12e3828cf..6de94257b12a 100644 --- a/drivers/bluetooth/bluecard_cs.c +++ b/drivers/bluetooth/bluecard_cs.c @@ -499,7 +499,7 @@ static void bluecard_receive(bluecard_info_t *info, unsigned int offset) } -void bluecard_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +static irqreturn_t bluecard_interrupt(int irq, void *dev_inst, struct pt_regs *regs) { bluecard_info_t *info = dev_inst; unsigned int iobase; @@ -507,11 +507,11 @@ void bluecard_interrupt(int irq, void *dev_inst, struct pt_regs *regs) if (!info) { printk(KERN_WARNING "bluecard_cs: Call of irq %d for unknown device.\n", irq); - return; + return IRQ_NONE; } if (!test_bit(CARD_READY, &(info->hw_state))) - return; + return IRQ_NONE; iobase = info->link.io.BasePort1; @@ -556,6 +556,8 @@ void bluecard_interrupt(int irq, void *dev_inst, struct pt_regs *regs) outb(info->ctrl_reg, iobase + REG_CONTROL); spin_unlock(&(info->lock)); + + return IRQ_HANDLED; } diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c index b5a8135f9808..df16aad65de1 100644 --- a/drivers/bluetooth/bt3c_cs.c +++ b/drivers/bluetooth/bt3c_cs.c @@ -355,7 +355,7 @@ static void bt3c_receive(bt3c_info_t *info) } -void bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +static irqreturn_t bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) { bt3c_info_t *info = dev_inst; unsigned int iobase; @@ -363,7 +363,7 @@ void bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) if (!info) { printk(KERN_WARNING "bt3c_cs: Call of irq %d for unknown device.\n", irq); - return; + return IRQ_NONE; } iobase = info->link.io.BasePort1; @@ -396,6 +396,8 @@ void bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) } spin_unlock(&(info->lock)); + + return IRQ_HANDLED; } diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c index 8fb7b29fdc3d..337230b613e2 100644 --- a/drivers/bluetooth/btuart_cs.c +++ b/drivers/bluetooth/btuart_cs.c @@ -301,7 +301,7 @@ static void btuart_receive(btuart_info_t *info) } -void btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +static irqreturn_t btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs) { btuart_info_t *info = dev_inst; unsigned int iobase; @@ -310,7 +310,7 @@ void btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs) if (!info) { printk(KERN_WARNING "btuart_cs: Call of irq %d for unknown device.\n", irq); - return; + return IRQ_NONE; } iobase = info->link.io.BasePort1; @@ -351,6 +351,8 @@ void btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs) } spin_unlock(&(info->lock)); + + return IRQ_HANDLED; } diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c index 96bfe85fdfec..74d9cf9e352d 100644 --- a/drivers/bluetooth/dtl1_cs.c +++ b/drivers/bluetooth/dtl1_cs.c @@ -304,7 +304,7 @@ static void dtl1_receive(dtl1_info_t *info) } -void dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +static irqreturn_t dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) { dtl1_info_t *info = dev_inst; unsigned int iobase; @@ -314,7 +314,7 @@ void dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) if (!info) { printk(KERN_WARNING "dtl1_cs: Call of irq %d for unknown device.\n", irq); - return; + return IRQ_NONE; } iobase = info->link.io.BasePort1; @@ -363,6 +363,8 @@ void dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) } spin_unlock(&(info->lock)); + + return IRQ_HANDLED; } diff --git a/drivers/bluetooth/hci_usb.c b/drivers/bluetooth/hci_usb.c index 3bef9b14ad7d..14b8350652e4 100644 --- a/drivers/bluetooth/hci_usb.c +++ b/drivers/bluetooth/hci_usb.c @@ -62,7 +62,7 @@ #define BT_DMP( A... ) #endif -#ifndef CONFIG_BT_USB_ZERO_PACKET +#ifndef CONFIG_BT_HCIUSB_ZERO_PACKET #undef URB_ZERO_PACKET #define URB_ZERO_PACKET 0 #endif @@ -70,12 +70,21 @@ static struct usb_driver hci_usb_driver; static struct usb_device_id bluetooth_ids[] = { + /* Broadcom BCM2033 without firmware */ + { USB_DEVICE(0x0a5c, 0x2033), driver_info: HCI_IGNORE }, + + /* Digianswer device */ + { USB_DEVICE(0x08fd, 0x0001), driver_info: HCI_DIGIANSWER }, + /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(HCI_DEV_CLASS, HCI_DEV_SUBCLASS, HCI_DEV_PROTOCOL) }, /* Ericsson with non-standard id */ { USB_DEVICE(0x0bdb, 0x1002) }, + /* ALPS Module with non-standard id */ + { USB_DEVICE(0x044e, 0x3002) }, + /* Bluetooth Ultraport Module from IBM */ { USB_DEVICE(0x04bf, 0x030a) }, @@ -84,13 +93,6 @@ static struct usb_device_id bluetooth_ids[] = { MODULE_DEVICE_TABLE (usb, bluetooth_ids); -static struct usb_device_id ignore_ids[] = { - /* Broadcom BCM2033 without firmware */ - { USB_DEVICE(0x0a5c, 0x2033) }, - - { } /* Terminating entry */ -}; - struct _urb *_urb_alloc(int isoc, int gfp) { struct _urb *_urb = kmalloc(sizeof(struct _urb) + @@ -134,7 +136,7 @@ static inline struct _urb *__get_completed(struct hci_usb *husb, int type) return _urb_dequeue(__completed_q(husb, type)); } -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO static void __fill_isoc_desc(struct urb *urb, int len, int mtu) { int offset = 0, i; @@ -232,7 +234,7 @@ static int hci_usb_bulk_rx_submit(struct hci_usb *husb) return err; } -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO static int hci_usb_isoc_rx_submit(struct hci_usb *husb) { struct _urb *_urb; @@ -301,9 +303,10 @@ static int hci_usb_open(struct hci_dev *hdev) for (i = 0; i < HCI_MAX_BULK_RX; i++) hci_usb_bulk_rx_submit(husb); -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO if (husb->isoc_iface) - hci_usb_isoc_rx_submit(husb); + for (i = 0; i < HCI_MAX_ISOC_RX; i++) + hci_usb_isoc_rx_submit(husb); #endif } else { clear_bit(HCI_RUNNING, &hdev->flags); @@ -425,7 +428,7 @@ static inline int hci_usb_send_ctrl(struct hci_usb *husb, struct sk_buff *skb) } else dr = (void *) _urb->urb.setup_packet; - dr->bRequestType = HCI_CTRL_REQ; + dr->bRequestType = husb->ctrl_req; dr->bRequest = 0; dr->wIndex = 0; dr->wValue = 0; @@ -466,7 +469,7 @@ static inline int hci_usb_send_bulk(struct hci_usb *husb, struct sk_buff *skb) return __tx_submit(husb, _urb); } -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO static inline int hci_usb_send_isoc(struct hci_usb *husb, struct sk_buff *skb) { struct _urb *_urb = __get_completed(husb, skb->pkt_type); @@ -517,10 +520,10 @@ static void hci_usb_tx_process(struct hci_usb *husb) skb_queue_head(q, skb); } -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO /* Process SCO queue */ q = __transmit_q(husb, HCI_SCODATA_PKT); - if (!atomic_read(__pending_tx(husb, HCI_SCODATA_PKT)) && + if (atomic_read(__pending_tx(husb, HCI_SCODATA_PKT)) < HCI_MAX_ISOC_TX && (skb = skb_dequeue(q))) { if (hci_usb_send_isoc(husb, skb) < 0) skb_queue_head(q, skb); @@ -576,7 +579,7 @@ static int hci_usb_send_frame(struct sk_buff *skb) hdev->stat.acl_tx++; break; -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO case HCI_SCODATA_PKT: hdev->stat.sco_tx++; break; @@ -626,7 +629,7 @@ static inline int __recv_frame(struct hci_usb *husb, int type, void *data, int c } else return -EILSEQ; break; -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO case HCI_SCODATA_PKT: if (count >= HCI_SCO_HDR_SIZE) { struct hci_sco_hdr *h = data; @@ -691,7 +694,7 @@ static void hci_usb_rx_complete(struct urb *urb, struct pt_regs *regs) goto resubmit; if (_urb->type == HCI_SCODATA_PKT) { -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO int i; for (i=0; i < urb->number_of_packets; i++) { BT_DBG("desc %d status %d offset %d len %d", i, @@ -785,9 +788,11 @@ int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) iface = udev->actconfig->interface[0]; - /* Check our black list */ - if (usb_match_id(intf, ignore_ids)) - return -EIO; + if (id->driver_info & HCI_IGNORE) + return -ENODEV; + + if (intf->altsetting->desc.bInterfaceNumber > 0) + return -ENODEV; /* Check number of endpoints */ if (intf->altsetting[0].desc.bNumEndpoints < 3) @@ -826,9 +831,9 @@ int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) bulk_out_ep[i] = ep; break; -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO case USB_ENDPOINT_XFER_ISOC: - if (ep->desc.wMaxPacketSize < size) + if (ep->desc.wMaxPacketSize < size || a > 2) break; size = ep->desc.wMaxPacketSize; @@ -852,7 +857,7 @@ int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) goto done; } -#ifdef CONFIG_BT_USB_SCO +#ifdef CONFIG_BT_HCIUSB_SCO if (!isoc_in_ep[1] || !isoc_out_ep[1]) { BT_DBG("Isoc endpoints not found"); isoc_iface = NULL; @@ -871,7 +876,12 @@ int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) husb->bulk_in_ep = bulk_in_ep[0]; husb->intr_in_ep = intr_in_ep[0]; -#ifdef CONFIG_BT_USB_SCO + if (id->driver_info & HCI_DIGIANSWER) + husb->ctrl_req = HCI_DIGI_REQ; + else + husb->ctrl_req = HCI_CTRL_REQ; + +#ifdef CONFIG_BT_HCIUSB_SCO if (isoc_iface) { BT_DBG("isoc ifnum %d alts %d", isoc_ifnum, isoc_alts); if (usb_set_interface(udev, isoc_ifnum, isoc_alts)) { diff --git a/drivers/bluetooth/hci_usb.h b/drivers/bluetooth/hci_usb.h index 1e7306286e56..e2ba50da3021 100644 --- a/drivers/bluetooth/hci_usb.h +++ b/drivers/bluetooth/hci_usb.h @@ -35,12 +35,19 @@ #define HCI_DEV_PROTOCOL 0x01 /* Bluetooth programming protocol */ #define HCI_CTRL_REQ 0x20 +#define HCI_DIGI_REQ 0x40 + +#define HCI_IGNORE 0x01 +#define HCI_DIGIANSWER 0x02 #define HCI_MAX_IFACE_NUM 3 #define HCI_MAX_BULK_TX 4 #define HCI_MAX_BULK_RX 1 +#define HCI_MAX_ISOC_RX 2 +#define HCI_MAX_ISOC_TX 2 + #define HCI_MAX_ISOC_FRAMES 10 struct _urb_queue { @@ -119,6 +126,8 @@ struct hci_usb { struct usb_host_endpoint *isoc_out_ep; struct usb_host_endpoint *isoc_in_ep; + __u8 ctrl_req; + struct sk_buff_head transmit_q[4]; struct sk_buff *reassembly[4]; // Reassembly buffers diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 429847a82f5f..99301bd8096a 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -47,7 +47,8 @@ #define BTPROTO_HCI 1 #define BTPROTO_SCO 2 #define BTPROTO_RFCOMM 3 -#define BTPROTO_BNEP 4 +#define BTPROTO_BNEP 4 +#define BTPROTO_CMTP 5 #define SOL_HCI 0 #define SOL_L2CAP 6 diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 228a44322ee0..59523c39d6fb 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -408,6 +408,16 @@ struct inquiry_info { __u16 clock_offset; } __attribute__ ((packed)); +#define HCI_EV_INQUIRY_RESULT_WITH_RSSI 0x22 +struct inquiry_info_with_rssi { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 dev_class[3]; + __u16 clock_offset; + __u8 rssi; +} __attribute__ ((packed)); + #define HCI_EV_CONN_COMPLETE 0x03 struct hci_ev_conn_complete { __u8 status; diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index fd010a9dc75e..6a429873df9c 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -176,6 +176,12 @@ static inline void inquiry_cache_init(struct hci_dev *hdev) c->list = NULL; } +static inline int inquiry_cache_empty(struct hci_dev *hdev) +{ + struct inquiry_cache *c = &hdev->inq_cache; + return (c->list == NULL); +} + static inline long inquiry_cache_age(struct hci_dev *hdev) { struct inquiry_cache *c = &hdev->inq_cache; @@ -281,10 +287,12 @@ static inline void hci_conn_hold(struct hci_conn *conn) static inline void hci_conn_put(struct hci_conn *conn) { if (atomic_dec_and_test(&conn->refcnt)) { - if (conn->type == SCO_LINK) + if (conn->type == ACL_LINK) { + unsigned long timeo = (conn->out) ? + HCI_DISCONN_TIMEOUT : HCI_DISCONN_TIMEOUT * 2; + hci_conn_set_timer(conn, timeo); + } else hci_conn_set_timer(conn, HZ / 100); - else if (conn->out) - hci_conn_set_timer(conn, HCI_DISCONN_TIMEOUT); } } diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 7d410fed2520..e022bfbb5166 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -167,8 +167,8 @@ struct rfcomm_session { int initiator; /* Default DLC parameters */ + int cfc; uint mtu; - uint credits; struct list_head dlcs; }; @@ -190,7 +190,7 @@ struct rfcomm_dlc { u8 mscex; uint mtu; - uint credits; + uint cfc; uint rx_credits; uint tx_credits; @@ -219,6 +219,11 @@ struct rfcomm_dlc { #define RFCOMM_MSCEX_RX 2 #define RFCOMM_MSCEX_OK (RFCOMM_MSCEX_TX + RFCOMM_MSCEX_RX) +/* CFC states */ +#define RFCOMM_CFC_UNKNOWN -1 +#define RFCOMM_CFC_DISABLED 0 +#define RFCOMM_CFC_ENABLED RFCOMM_MAX_CREDITS + extern struct task_struct *rfcomm_thread; extern unsigned long rfcomm_event; diff --git a/mm/mremap.c b/mm/mremap.c index e308e6a58639..9c0850a56800 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -315,8 +315,12 @@ unsigned long do_mremap(unsigned long addr, old_len = PAGE_ALIGN(old_len); new_len = PAGE_ALIGN(new_len); - /* Don't allow the degenerate cases */ - if (!old_len || !new_len) + /* + * We allow a zero old-len as a special case + * for DOS-emu "duplicate shm area" thing. But + * a zero new-len is nonsensical. + */ + if (!new_len) goto out; /* new_addr is only valid if MREMAP_FIXED is specified */ diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index a152eec283cb..4ba9e9500fee 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -21,6 +21,7 @@ config BT SCO Module (SCO links) RFCOMM Module (RFCOMM protocol) BNEP Module (BNEP protocol) + CMTP Module (CMTP protocol) Say Y here to enable Linux Bluetooth support and to build Bluetooth Core layer. @@ -57,6 +58,8 @@ source "net/bluetooth/rfcomm/Kconfig" source "net/bluetooth/bnep/Kconfig" +source "net/bluetooth/cmtp/Kconfig" + source "drivers/bluetooth/Kconfig" endmenu diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index cf5916af802a..5c4d3ab34849 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -7,5 +7,6 @@ obj-$(CONFIG_BT_L2CAP) += l2cap.o obj-$(CONFIG_BT_SCO) += sco.o obj-$(CONFIG_BT_RFCOMM) += rfcomm/ obj-$(CONFIG_BT_BNEP) += bnep/ +obj-$(CONFIG_BT_CMTP) += cmtp/ bluetooth-objs := af_bluetooth.o hci_core.o hci_conn.o hci_event.o hci_sock.o hci_proc.o lib.o syms.o diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index b9b3bbf0a5e2..c17195bbcf8a 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -59,7 +59,7 @@ struct proc_dir_entry *proc_bt; /* Bluetooth sockets */ -#define BT_MAX_PROTO 5 +#define BT_MAX_PROTO 6 static struct net_proto_family *bt_proto[BT_MAX_PROTO]; static kmem_cache_t *bt_sock_cache; diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index efa44e162233..ece601f8df1d 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -707,3 +707,4 @@ module_exit(bnep_cleanup_module); MODULE_DESCRIPTION("Bluetooth BNEP ver " VERSION); MODULE_AUTHOR("David Libault <david.libault@inventel.fr>, Maxim Krasnyanskiy <maxk@qualcomm.com>"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("bt-proto-4"); diff --git a/net/bluetooth/cmtp/Kconfig b/net/bluetooth/cmtp/Kconfig new file mode 100644 index 000000000000..e832f20ceb14 --- /dev/null +++ b/net/bluetooth/cmtp/Kconfig @@ -0,0 +1,11 @@ +config BT_CMTP + tristate "CMTP protocol support" + depends on BT && BT_L2CAP && ISDN_CAPI + help + CMTP (CAPI Message Transport Protocol) is a transport layer + for CAPI messages. CMTP is required for the Bluetooth Common + ISDN Access Profile. + + Say Y here to compile CMTP support into the kernel or say M to + compile it as module (cmtp). + diff --git a/net/bluetooth/cmtp/Makefile b/net/bluetooth/cmtp/Makefile new file mode 100644 index 000000000000..890a9a5a6861 --- /dev/null +++ b/net/bluetooth/cmtp/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Linux Bluetooth CMTP layer +# + +obj-$(CONFIG_BT_CMTP) += cmtp.o + +cmtp-objs := core.o sock.o capi.o diff --git a/net/bluetooth/cmtp/capi.c b/net/bluetooth/cmtp/capi.c new file mode 100644 index 000000000000..7e03820c2bfb --- /dev/null +++ b/net/bluetooth/cmtp/capi.c @@ -0,0 +1,630 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 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 version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/fcntl.h> +#include <linux/skbuff.h> +#include <linux/socket.h> +#include <linux/ioctl.h> +#include <linux/file.h> +#include <net/sock.h> + +#include <linux/isdn/capilli.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> + +#include "cmtp.h" + +#ifndef CONFIG_BT_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define CAPI_INTEROPERABILITY 0x20 + +#define CAPI_INTEROPERABILITY_REQ CAPICMD(CAPI_INTEROPERABILITY, CAPI_REQ) +#define CAPI_INTEROPERABILITY_CONF CAPICMD(CAPI_INTEROPERABILITY, CAPI_CONF) +#define CAPI_INTEROPERABILITY_IND CAPICMD(CAPI_INTEROPERABILITY, CAPI_IND) +#define CAPI_INTEROPERABILITY_RESP CAPICMD(CAPI_INTEROPERABILITY, CAPI_RESP) + +#define CAPI_INTEROPERABILITY_REQ_LEN (CAPI_MSG_BASELEN + 2) +#define CAPI_INTEROPERABILITY_CONF_LEN (CAPI_MSG_BASELEN + 4) +#define CAPI_INTEROPERABILITY_IND_LEN (CAPI_MSG_BASELEN + 2) +#define CAPI_INTEROPERABILITY_RESP_LEN (CAPI_MSG_BASELEN + 2) + +#define CAPI_FUNCTION_REGISTER 0 +#define CAPI_FUNCTION_RELEASE 1 +#define CAPI_FUNCTION_GET_PROFILE 2 +#define CAPI_FUNCTION_GET_MANUFACTURER 3 +#define CAPI_FUNCTION_GET_VERSION 4 +#define CAPI_FUNCTION_GET_SERIAL_NUMBER 5 +#define CAPI_FUNCTION_MANUFACTURER 6 +#define CAPI_FUNCTION_LOOPBACK 7 + + +#define CMTP_MSGNUM 1 +#define CMTP_APPLID 2 +#define CMTP_MAPPING 3 + +static struct cmtp_application *cmtp_application_add(struct cmtp_session *session, __u16 appl) +{ + struct cmtp_application *app = kmalloc(sizeof(*app), GFP_KERNEL); + + BT_DBG("session %p application %p appl %d", session, app, appl); + + if (!app) + return NULL; + + memset(app, 0, sizeof(*app)); + + app->state = BT_OPEN; + app->appl = appl; + + list_add_tail(&app->list, &session->applications); + + return app; +} + +static void cmtp_application_del(struct cmtp_session *session, struct cmtp_application *app) +{ + BT_DBG("session %p application %p", session, app); + + if (app) { + list_del(&app->list); + kfree(app); + } +} + +static struct cmtp_application *cmtp_application_get(struct cmtp_session *session, int pattern, __u16 value) +{ + struct cmtp_application *app; + struct list_head *p, *n; + + list_for_each_safe(p, n, &session->applications) { + app = list_entry(p, struct cmtp_application, list); + switch (pattern) { + case CMTP_MSGNUM: + if (app->msgnum == value) + return app; + break; + case CMTP_APPLID: + if (app->appl == value) + return app; + break; + case CMTP_MAPPING: + if (app->mapping == value) + return app; + break; + } + } + + return NULL; +} + +static int cmtp_msgnum_get(struct cmtp_session *session) +{ + session->msgnum++; + + if ((session->msgnum & 0xff) > 200) + session->msgnum = CMTP_INITIAL_MSGNUM + 1; + + return session->msgnum; +} + + +static void cmtp_send_interopmsg(struct cmtp_session *session, + __u8 subcmd, __u16 appl, __u16 msgnum, + __u16 function, unsigned char *buf, int len) +{ + struct sk_buff *skb; + unsigned char *s; + + BT_DBG("session %p subcmd 0x%02x appl %d msgnum %d", session, subcmd, appl, msgnum); + + if (!(skb = alloc_skb(CAPI_MSG_BASELEN + 6 + len, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for interoperability packet"); + return; + } + + s = skb_put(skb, CAPI_MSG_BASELEN + 6 + len); + + capimsg_setu16(s, 0, CAPI_MSG_BASELEN + 6 + len); + capimsg_setu16(s, 2, appl); + capimsg_setu8 (s, 4, CAPI_INTEROPERABILITY); + capimsg_setu8 (s, 5, subcmd); + capimsg_setu16(s, 6, msgnum); + + /* Interoperability selector (Bluetooth Device Management) */ + capimsg_setu16(s, 8, 0x0001); + + capimsg_setu8 (s, 10, 3 + len); + capimsg_setu16(s, 11, function); + capimsg_setu8 (s, 13, len); + + if (len > 0) + memcpy(s + 14, buf, len); + + cmtp_send_capimsg(session, skb); +} + +static void cmtp_recv_interopmsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct capi_ctr *ctrl = &session->ctrl; + struct cmtp_application *application; + __u16 appl, msgnum, func, info; + __u32 controller; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + switch (CAPIMSG_SUBCOMMAND(skb->data)) { + case CAPI_CONF: + func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 5); + info = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 8); + + switch (func) { + case CAPI_FUNCTION_REGISTER: + msgnum = CAPIMSG_MSGID(skb->data); + + application = cmtp_application_get(session, CMTP_MSGNUM, msgnum); + if (application) { + application->state = BT_CONNECTED; + application->msgnum = 0; + application->mapping = CAPIMSG_APPID(skb->data); + wake_up_interruptible(&session->wait); + } + + break; + + case CAPI_FUNCTION_RELEASE: + appl = CAPIMSG_APPID(skb->data); + + application = cmtp_application_get(session, CMTP_MAPPING, appl); + if (application) { + application->state = BT_CLOSED; + application->msgnum = 0; + wake_up_interruptible(&session->wait); + } + + break; + + case CAPI_FUNCTION_GET_PROFILE: + controller = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 11); + msgnum = CAPIMSG_MSGID(skb->data); + + if (!info && (msgnum == CMTP_INITIAL_MSGNUM)) { + session->ncontroller = controller; + wake_up_interruptible(&session->wait); + break; + } + + if (!info && ctrl) { + memcpy(&ctrl->profile, + skb->data + CAPI_MSG_BASELEN + 11, + sizeof(capi_profile)); + session->state = BT_CONNECTED; + capi_ctr_ready(ctrl); + } + + break; + + case CAPI_FUNCTION_GET_MANUFACTURER: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 10); + + if (!info && ctrl) { + strncpy(ctrl->manu, + skb->data + CAPI_MSG_BASELEN + 15, + skb->data[CAPI_MSG_BASELEN + 14]); + } + + break; + + case CAPI_FUNCTION_GET_VERSION: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); + + if (!info && ctrl) { + ctrl->version.majorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 16); + ctrl->version.minorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 20); + ctrl->version.majormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 24); + ctrl->version.minormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 28); + } + + break; + + case CAPI_FUNCTION_GET_SERIAL_NUMBER: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); + + if (!info && ctrl) { + memset(ctrl->serial, 0, CAPI_SERIAL_LEN); + strncpy(ctrl->serial, + skb->data + CAPI_MSG_BASELEN + 17, + skb->data[CAPI_MSG_BASELEN + 16]); + } + + break; + } + + break; + + case CAPI_IND: + func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 3); + + if (func == CAPI_FUNCTION_LOOPBACK) { + appl = CAPIMSG_APPID(skb->data); + msgnum = CAPIMSG_MSGID(skb->data); + cmtp_send_interopmsg(session, CAPI_RESP, appl, msgnum, func, + skb->data + CAPI_MSG_BASELEN + 6, + skb->data[CAPI_MSG_BASELEN + 5]); + } + + break; + } + + kfree_skb(skb); +} + +void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct capi_ctr *ctrl = &session->ctrl; + struct cmtp_application *application; + __u16 cmd, appl; + __u32 contr; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + if (CAPIMSG_COMMAND(skb->data) == CAPI_INTEROPERABILITY) { + cmtp_recv_interopmsg(session, skb); + return; + } + + if (session->flags & (1 << CMTP_LOOPBACK)) { + kfree_skb(skb); + return; + } + + cmd = CAPICMD(CAPIMSG_COMMAND(skb->data), CAPIMSG_SUBCOMMAND(skb->data)); + appl = CAPIMSG_APPID(skb->data); + contr = CAPIMSG_CONTROL(skb->data); + + application = cmtp_application_get(session, CMTP_MAPPING, appl); + if (application) { + appl = application->appl; + CAPIMSG_SETAPPID(skb->data, appl); + } else { + BT_ERR("Can't find application with id %d", appl); + kfree_skb(skb); + return; + } + + if ((contr & 0x7f) == 0x01) { + contr = (contr & 0xffffff80) | session->num; + CAPIMSG_SETCONTROL(skb->data, contr); + } + + if (!ctrl) { + BT_ERR("Can't find controller %d for message", session->num); + kfree_skb(skb); + return; + } + + capi_ctr_handle_message(ctrl, appl, skb); +} + +void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct cmtp_scb *scb = (void *) skb->cb; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + scb->id = -1; + scb->data = (CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3); + + skb_queue_tail(&session->transmit, skb); + + cmtp_schedule(session); +} + + +static int cmtp_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + BT_DBG("ctrl %p data %p", ctrl, data); + + return 0; +} + +static void cmtp_reset_ctr(struct capi_ctr *ctrl) +{ + struct cmtp_session *session = ctrl->driverdata; + + BT_DBG("ctrl %p", ctrl); + + capi_ctr_reseted(ctrl); + + atomic_inc(&session->terminate); + cmtp_schedule(session); +} + +static void cmtp_register_appl(struct capi_ctr *ctrl, __u16 appl, capi_register_params *rp) +{ + DECLARE_WAITQUEUE(wait, current); + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + unsigned char buf[8]; + int err = 0, nconn, want = rp->level3cnt; + + BT_DBG("ctrl %p appl %d level3cnt %d datablkcnt %d datablklen %d", + ctrl, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen); + + application = cmtp_application_add(session, appl); + if (!application) { + BT_ERR("Can't allocate memory for new application"); + return; + } + + if (want < 0) + nconn = ctrl->profile.nbchannel * -want; + else + nconn = want; + + if (nconn == 0) + nconn = ctrl->profile.nbchannel; + + capimsg_setu16(buf, 0, nconn); + capimsg_setu16(buf, 2, rp->datablkcnt); + capimsg_setu16(buf, 4, rp->datablklen); + + application->state = BT_CONFIG; + application->msgnum = cmtp_msgnum_get(session); + + cmtp_send_interopmsg(session, CAPI_REQ, 0x0000, application->msgnum, + CAPI_FUNCTION_REGISTER, buf, 6); + + add_wait_queue(&session->wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + + if (!timeo) { + err = -EAGAIN; + break; + } + + if (application->state == BT_CLOSED) { + err = -application->err; + break; + } + + if (application->state == BT_CONNECTED) + break; + + if (signal_pending(current)) { + err = -EINTR; + break; + } + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + if (err) { + cmtp_application_del(session, application); + return; + } +} + +static void cmtp_release_appl(struct capi_ctr *ctrl, __u16 appl) +{ + DECLARE_WAITQUEUE(wait, current); + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + + BT_DBG("ctrl %p appl %d", ctrl, appl); + + application = cmtp_application_get(session, CMTP_APPLID, appl); + if (!application) { + BT_ERR("Can't find application"); + return; + } + + application->msgnum = cmtp_msgnum_get(session); + + cmtp_send_interopmsg(session, CAPI_REQ, application->mapping, application->msgnum, + CAPI_FUNCTION_RELEASE, NULL, 0); + + add_wait_queue(&session->wait, &wait); + while (timeo) { + set_current_state(TASK_INTERRUPTIBLE); + + if (application->state == BT_CLOSED) + break; + + if (signal_pending(current)) + break; + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + cmtp_application_del(session, application); +} + +static u16 cmtp_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + __u16 appl; + __u32 contr; + + BT_DBG("ctrl %p skb %p", ctrl, skb); + + appl = CAPIMSG_APPID(skb->data); + contr = CAPIMSG_CONTROL(skb->data); + + application = cmtp_application_get(session, CMTP_APPLID, appl); + if ((!application) || (application->state != BT_CONNECTED)) { + BT_ERR("Can't find application with id %d", appl); + kfree_skb(skb); + return CAPI_ILLAPPNR; + } + + CAPIMSG_SETAPPID(skb->data, application->mapping); + + if ((contr & 0x7f) == session->num) { + contr = (contr & 0xffffff80) | 0x01; + CAPIMSG_SETCONTROL(skb->data, contr); + } + + cmtp_send_capimsg(session, skb); + + return CAPI_NOERROR; +} + +static char *cmtp_procinfo(struct capi_ctr *ctrl) +{ + return "CAPI Message Transport Protocol"; +} + +static int cmtp_ctr_read_proc(char *page, char **start, off_t off, int count, int *eof, struct capi_ctr *ctrl) +{ + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *app; + struct list_head *p, *n; + int len = 0; + + len += sprintf(page + len, "%s\n\n", cmtp_procinfo(ctrl)); + len += sprintf(page + len, "addr %s\n", session->name); + len += sprintf(page + len, "ctrl %d\n", session->num); + + list_for_each_safe(p, n, &session->applications) { + app = list_entry(p, struct cmtp_application, list); + len += sprintf(page + len, "appl %d -> %d\n", app->appl, app->mapping); + } + + if (off + count >= len) + *eof = 1; + + if (len < off) + return 0; + + *start = page + off; + + return ((count < len - off) ? count : len - off); +} + + +int cmtp_attach_device(struct cmtp_session *session) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + unsigned char buf[4]; + + BT_DBG("session %p", session); + + capimsg_setu32(buf, 0, 0); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, CMTP_INITIAL_MSGNUM, + CAPI_FUNCTION_GET_PROFILE, buf, 4); + + add_wait_queue(&session->wait, &wait); + while (timeo) { + set_current_state(TASK_INTERRUPTIBLE); + + if (session->ncontroller) + break; + + if (signal_pending(current)) + break; + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + BT_INFO("Found %d CAPI controller(s) on device %s", session->ncontroller, session->name); + + if (!timeo) + return -ETIMEDOUT; + + if (!session->ncontroller) + return -ENODEV; + + + if (session->ncontroller > 1) + BT_INFO("Setting up only CAPI controller 1"); + + session->ctrl.owner = THIS_MODULE; + session->ctrl.driverdata = session; + strcpy(session->ctrl.name, session->name); + + session->ctrl.driver_name = "cmtp"; + session->ctrl.load_firmware = cmtp_load_firmware; + session->ctrl.reset_ctr = cmtp_reset_ctr; + session->ctrl.register_appl = cmtp_register_appl; + session->ctrl.release_appl = cmtp_release_appl; + session->ctrl.send_message = cmtp_send_message; + + session->ctrl.procinfo = cmtp_procinfo; + session->ctrl.ctr_read_proc = cmtp_ctr_read_proc; + + if (attach_capi_ctr(&session->ctrl) < 0) { + BT_ERR("Can't attach new controller"); + return -EBUSY; + } + + session->num = session->ctrl.cnr; + + BT_DBG("session %p num %d", session, session->num); + + capimsg_setu32(buf, 0, 1); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_MANUFACTURER, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_VERSION, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_SERIAL_NUMBER, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_PROFILE, buf, 4); + + return 0; +} + +void cmtp_detach_device(struct cmtp_session *session) +{ + BT_DBG("session %p", session); + + detach_capi_ctr(&session->ctrl); +} diff --git a/net/bluetooth/cmtp/cmtp.h b/net/bluetooth/cmtp/cmtp.h new file mode 100644 index 000000000000..0a0062607584 --- /dev/null +++ b/net/bluetooth/cmtp/cmtp.h @@ -0,0 +1,136 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 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 version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#ifndef __CMTP_H +#define __CMTP_H + +#include <linux/types.h> +#include <net/bluetooth/bluetooth.h> + +#define BTNAMSIZ 18 + +/* CMTP ioctl defines */ +#define CMTPCONNADD _IOW('C', 200, int) +#define CMTPCONNDEL _IOW('C', 201, int) +#define CMTPGETCONNLIST _IOR('C', 210, int) +#define CMTPGETCONNINFO _IOR('C', 211, int) + +#define CMTP_LOOPBACK 0 + +struct cmtp_connadd_req { + int sock; // Connected socket + __u32 flags; +}; + +struct cmtp_conndel_req { + bdaddr_t bdaddr; + __u32 flags; +}; + +struct cmtp_conninfo { + bdaddr_t bdaddr; + __u32 flags; + __u16 state; + int num; +}; + +struct cmtp_connlist_req { + __u32 cnum; + struct cmtp_conninfo *ci; +}; + +int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock); +int cmtp_del_connection(struct cmtp_conndel_req *req); +int cmtp_get_connlist(struct cmtp_connlist_req *req); +int cmtp_get_conninfo(struct cmtp_conninfo *ci); + +/* CMTP session defines */ +#define CMTP_INTEROP_TIMEOUT (HZ * 5) +#define CMTP_INITIAL_MSGNUM 0xff00 + +struct cmtp_session { + struct list_head list; + + struct socket *sock; + + bdaddr_t bdaddr; + + unsigned long state; + unsigned long flags; + + uint mtu; + + char name[BTNAMSIZ]; + + atomic_t terminate; + + wait_queue_head_t wait; + + int ncontroller; + int num; + struct capi_ctr ctrl; + + struct list_head applications; + + unsigned long blockids; + int msgnum; + + struct sk_buff_head transmit; + + struct sk_buff *reassembly[16]; +}; + +struct cmtp_application { + struct list_head list; + + unsigned long state; + int err; + + __u16 appl; + __u16 mapping; + + __u16 msgnum; +}; + +struct cmtp_scb { + int id; + int data; +}; + +int cmtp_attach_device(struct cmtp_session *session); +void cmtp_detach_device(struct cmtp_session *session); + +void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb); +void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb); + +static inline void cmtp_schedule(struct cmtp_session *session) +{ + struct sock *sk = session->sock->sk; + + wake_up_interruptible(sk->sk_sleep); +} + +/* CMTP init defines */ +int cmtp_init_sockets(void); +void cmtp_cleanup_sockets(void); + +#endif /* __CMTP_H */ diff --git a/net/bluetooth/cmtp/core.c b/net/bluetooth/cmtp/core.c new file mode 100644 index 000000000000..c5243ce93e67 --- /dev/null +++ b/net/bluetooth/cmtp/core.c @@ -0,0 +1,507 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 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 version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/fcntl.h> +#include <linux/skbuff.h> +#include <linux/socket.h> +#include <linux/ioctl.h> +#include <linux/file.h> +#include <linux/init.h> +#include <net/sock.h> + +#include <linux/isdn/capilli.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/l2cap.h> + +#include "cmtp.h" + +#ifndef CONFIG_BT_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.0" + +static DECLARE_RWSEM(cmtp_session_sem); +static LIST_HEAD(cmtp_session_list); + +static struct cmtp_session *__cmtp_get_session(bdaddr_t *bdaddr) +{ + struct cmtp_session *session; + struct list_head *p; + + BT_DBG(""); + + list_for_each(p, &cmtp_session_list) { + session = list_entry(p, struct cmtp_session, list); + if (!bacmp(bdaddr, &session->bdaddr)) + return session; + } + return NULL; +} + +static void __cmtp_link_session(struct cmtp_session *session) +{ + __module_get(THIS_MODULE); + list_add(&session->list, &cmtp_session_list); +} + +static void __cmtp_unlink_session(struct cmtp_session *session) +{ + list_del(&session->list); + module_put(THIS_MODULE); +} + +static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci) +{ + bacpy(&ci->bdaddr, &session->bdaddr); + + ci->flags = session->flags; + ci->state = session->state; + + ci->num = session->num; +} + + +static inline int cmtp_alloc_block_id(struct cmtp_session *session) +{ + int i, id = -1; + + for (i = 0; i < 16; i++) + if (!test_and_set_bit(i, &session->blockids)) { + id = i; + break; + } + + return id; +} + +static inline void cmtp_free_block_id(struct cmtp_session *session, int id) +{ + clear_bit(id, &session->blockids); +} + +static inline void cmtp_add_msgpart(struct cmtp_session *session, int id, const unsigned char *buf, int count) +{ + struct sk_buff *skb = session->reassembly[id], *nskb; + int size; + + BT_DBG("session %p buf %p count %d", session, buf, count); + + size = (skb) ? skb->len + count : count; + + if (!(nskb = alloc_skb(size, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for CAPI message"); + return; + } + + if (skb && (skb->len > 0)) + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + + memcpy(skb_put(nskb, count), buf, count); + + session->reassembly[id] = nskb; + + if (skb) + kfree_skb(skb); +} + +static inline int cmtp_recv_frame(struct cmtp_session *session, struct sk_buff *skb) +{ + __u8 hdr, hdrlen, id; + __u16 len; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + while (skb->len > 0) { + hdr = skb->data[0]; + + switch (hdr & 0xc0) { + case 0x40: + hdrlen = 2; + len = skb->data[1]; + break; + case 0x80: + hdrlen = 3; + len = skb->data[1] | (skb->data[2] << 8); + break; + default: + hdrlen = 1; + len = 0; + break; + } + + id = (hdr & 0x3c) >> 2; + + BT_DBG("hdr 0x%02x hdrlen %d len %d id %d", hdr, hdrlen, len, id); + + if (hdrlen + len > skb->len) { + BT_ERR("Wrong size or header information in CMTP frame"); + break; + } + + if (len == 0) { + skb_pull(skb, hdrlen); + continue; + } + + switch (hdr & 0x03) { + case 0x00: + cmtp_add_msgpart(session, id, skb->data + hdrlen, len); + cmtp_recv_capimsg(session, session->reassembly[id]); + session->reassembly[id] = NULL; + break; + case 0x01: + cmtp_add_msgpart(session, id, skb->data + hdrlen, len); + break; + default: + if (session->reassembly[id] != NULL) + kfree_skb(session->reassembly[id]); + session->reassembly[id] = NULL; + break; + } + + skb_pull(skb, hdrlen + len); + } + + kfree_skb(skb); + return 0; +} + +static int cmtp_send_frame(struct cmtp_session *session, unsigned char *data, int len) +{ + struct socket *sock = session->sock; + struct iovec iv = { data, len }; + struct msghdr msg; + + BT_DBG("session %p data %p len %d", session, data, len); + + if (!len) + return 0; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iovlen = 1; + msg.msg_iov = &iv; + + return sock_sendmsg(sock, &msg, len); +} + +static int cmtp_process_transmit(struct cmtp_session *session) +{ + struct sk_buff *skb, *nskb; + unsigned char *hdr; + unsigned int size, tail; + + BT_DBG("session %p", session); + + if (!(nskb = alloc_skb(session->mtu, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for new frame"); + return -ENOMEM; + } + + while ((skb = skb_dequeue(&session->transmit))) { + struct cmtp_scb *scb = (void *) skb->cb; + + if ((tail = (session->mtu - nskb->len)) < 5) { + cmtp_send_frame(session, nskb->data, nskb->len); + skb_trim(nskb, 0); + tail = session->mtu; + } + + size = min_t(uint, ((tail < 258) ? (tail - 2) : (tail - 3)), skb->len); + + if ((scb->id < 0) && ((scb->id = cmtp_alloc_block_id(session)) < 0)) { + skb_queue_head(&session->transmit, skb); + break; + } + + if (size < 256) { + hdr = skb_put(nskb, 2); + hdr[0] = 0x40 + | ((scb->id << 2) & 0x3c) + | ((skb->len == size) ? 0x00 : 0x01); + hdr[1] = size; + } else { + hdr = skb_put(nskb, 3); + hdr[0] = 0x80 + | ((scb->id << 2) & 0x3c) + | ((skb->len == size) ? 0x00 : 0x01); + hdr[1] = size & 0xff; + hdr[2] = size >> 8; + } + + memcpy(skb_put(nskb, size), skb->data, size); + skb_pull(skb, size); + + if (skb->len > 0) { + skb_queue_head(&session->transmit, skb); + } else { + cmtp_free_block_id(session, scb->id); + if (scb->data) { + cmtp_send_frame(session, nskb->data, nskb->len); + skb_trim(nskb, 0); + } + kfree_skb(skb); + } + } + + cmtp_send_frame(session, nskb->data, nskb->len); + + kfree_skb(nskb); + + return skb_queue_len(&session->transmit); +} + +static int cmtp_session(void *arg) +{ + struct cmtp_session *session = arg; + struct sock *sk = session->sock->sk; + struct sk_buff *skb; + wait_queue_t wait; + + BT_DBG("session %p", session); + + daemonize("kcmtpd_ctr_%d", session->num); + set_user_nice(current, -15); + current->flags |= PF_IOTHREAD; + + set_fs(KERNEL_DS); + + init_waitqueue_entry(&wait, current); + add_wait_queue(sk->sk_sleep, &wait); + while (!atomic_read(&session->terminate)) { + set_current_state(TASK_INTERRUPTIBLE); + + if (sk->sk_state != BT_CONNECTED) + break; + + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + skb_orphan(skb); + cmtp_recv_frame(session, skb); + } + + cmtp_process_transmit(session); + + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sk_sleep, &wait); + + down_write(&cmtp_session_sem); + + if (!(session->flags & (1 << CMTP_LOOPBACK))) + cmtp_detach_device(session); + + fput(session->sock->file); + + __cmtp_unlink_session(session); + + up_write(&cmtp_session_sem); + + kfree(session); + return 0; +} + +int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock) +{ + struct cmtp_session *session, *s; + bdaddr_t src, dst; + int i, err; + + BT_DBG(""); + + baswap(&src, &bt_sk(sock->sk)->src); + baswap(&dst, &bt_sk(sock->sk)->dst); + + session = kmalloc(sizeof(struct cmtp_session), GFP_KERNEL); + if (!session) + return -ENOMEM; + memset(session, 0, sizeof(struct cmtp_session)); + + down_write(&cmtp_session_sem); + + s = __cmtp_get_session(&bt_sk(sock->sk)->dst); + if (s && s->state == BT_CONNECTED) { + err = -EEXIST; + goto failed; + } + + bacpy(&session->bdaddr, &bt_sk(sock->sk)->dst); + + session->mtu = min_t(uint, l2cap_pi(sock->sk)->omtu, l2cap_pi(sock->sk)->imtu); + + BT_DBG("mtu %d", session->mtu); + + sprintf(session->name, "%s", batostr(&dst)); + + session->sock = sock; + session->state = BT_CONFIG; + + init_waitqueue_head(&session->wait); + + session->msgnum = CMTP_INITIAL_MSGNUM; + + INIT_LIST_HEAD(&session->applications); + + skb_queue_head_init(&session->transmit); + + for (i = 0; i < 16; i++) + session->reassembly[i] = NULL; + + session->flags = req->flags; + + __cmtp_link_session(session); + + err = kernel_thread(cmtp_session, session, CLONE_KERNEL); + if (err < 0) + goto unlink; + + if (!(session->flags & (1 << CMTP_LOOPBACK))) { + err = cmtp_attach_device(session); + if (err < 0) + goto detach; + } + + up_write(&cmtp_session_sem); + return 0; + +detach: + cmtp_detach_device(session); + +unlink: + __cmtp_unlink_session(session); + +failed: + up_write(&cmtp_session_sem); + kfree(session); + return err; +} + +int cmtp_del_connection(struct cmtp_conndel_req *req) +{ + struct cmtp_session *session; + int err = 0; + + BT_DBG(""); + + down_read(&cmtp_session_sem); + + session = __cmtp_get_session(&req->bdaddr); + if (session) { + /* Flush the transmit queue */ + skb_queue_purge(&session->transmit); + + /* Kill session thread */ + atomic_inc(&session->terminate); + cmtp_schedule(session); + } else + err = -ENOENT; + + up_read(&cmtp_session_sem); + return err; +} + +int cmtp_get_connlist(struct cmtp_connlist_req *req) +{ + struct list_head *p; + int err = 0, n = 0; + + BT_DBG(""); + + down_read(&cmtp_session_sem); + + list_for_each(p, &cmtp_session_list) { + struct cmtp_session *session; + struct cmtp_conninfo ci; + + session = list_entry(p, struct cmtp_session, list); + + __cmtp_copy_session(session, &ci); + + if (copy_to_user(req->ci, &ci, sizeof(ci))) { + err = -EFAULT; + break; + } + + if (++n >= req->cnum) + break; + + req->ci++; + } + req->cnum = n; + + up_read(&cmtp_session_sem); + return err; +} + +int cmtp_get_conninfo(struct cmtp_conninfo *ci) +{ + struct cmtp_session *session; + int err = 0; + + down_read(&cmtp_session_sem); + + session = __cmtp_get_session(&ci->bdaddr); + if (session) + __cmtp_copy_session(session, ci); + else + err = -ENOENT; + + up_read(&cmtp_session_sem); + return err; +} + + +int __init init_cmtp(void) +{ + l2cap_load(); + + BT_INFO("CMTP (CAPI Emulation) ver %s", VERSION); + + cmtp_init_sockets(); + + return 0; +} + +void __exit exit_cmtp(void) +{ + cmtp_cleanup_sockets(); +} + +module_init(init_cmtp); +module_exit(exit_cmtp); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth CMTP ver " VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bt-proto-5"); diff --git a/net/bluetooth/cmtp/sock.c b/net/bluetooth/cmtp/sock.c new file mode 100644 index 000000000000..fdb0eb6f8319 --- /dev/null +++ b/net/bluetooth/cmtp/sock.c @@ -0,0 +1,199 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 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 version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/fcntl.h> +#include <linux/skbuff.h> +#include <linux/socket.h> +#include <linux/ioctl.h> +#include <linux/file.h> +#include <net/sock.h> + +#include <linux/isdn/capilli.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +#include "cmtp.h" + +#ifndef CONFIG_BT_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +static int cmtp_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + BT_DBG("sock %p sk %p", sock, sk); + + if (!sk) + return 0; + + sock_orphan(sk); + sock_put(sk); + + return 0; +} + +static int cmtp_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct cmtp_connadd_req ca; + struct cmtp_conndel_req cd; + struct cmtp_connlist_req cl; + struct cmtp_conninfo ci; + struct socket *nsock; + int err; + + BT_DBG("cmd %x arg %lx", cmd, arg); + + switch (cmd) { + case CMTPCONNADD: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&ca, (void *) arg, sizeof(ca))) + return -EFAULT; + + nsock = sockfd_lookup(ca.sock, &err); + if (!nsock) + return err; + + if (nsock->sk->sk_state != BT_CONNECTED) + return -EBADFD; + + err = cmtp_add_connection(&ca, nsock); + if (!err) { + if (copy_to_user((void *) arg, &ca, sizeof(ca))) + err = -EFAULT; + } else + fput(nsock->file); + + return err; + + case CMTPCONNDEL: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&cd, (void *) arg, sizeof(cd))) + return -EFAULT; + + return cmtp_del_connection(&cd); + + case CMTPGETCONNLIST: + if (copy_from_user(&cl, (void *) arg, sizeof(cl))) + return -EFAULT; + + if (cl.cnum <= 0) + return -EINVAL; + + err = cmtp_get_connlist(&cl); + if (!err && copy_to_user((void *) arg, &cl, sizeof(cl))) + return -EFAULT; + + return err; + + case CMTPGETCONNINFO: + if (copy_from_user(&ci, (void *) arg, sizeof(ci))) + return -EFAULT; + + err = cmtp_get_conninfo(&ci); + if (!err && copy_to_user((void *) arg, &ci, sizeof(ci))) + return -EFAULT; + + return err; + } + + return -EINVAL; +} + +static struct proto_ops cmtp_sock_ops = { + .family = PF_BLUETOOTH, + .owner = THIS_MODULE, + .release = cmtp_sock_release, + .ioctl = cmtp_sock_ioctl, + .bind = sock_no_bind, + .getname = sock_no_getname, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .poll = sock_no_poll, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .mmap = sock_no_mmap +}; + +static int cmtp_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + if (sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + if (!(sk = bt_sock_alloc(sock, PF_BLUETOOTH, 0, GFP_KERNEL))) + return -ENOMEM; + + sk_set_owner(sk, THIS_MODULE); + + sock->ops = &cmtp_sock_ops; + + sock->state = SS_UNCONNECTED; + + sk->sk_destruct = NULL; + sk->sk_protocol = protocol; + + return 0; +} + +static struct net_proto_family cmtp_sock_family_ops = { + .family = PF_BLUETOOTH, + .owner = THIS_MODULE, + .create = cmtp_sock_create +}; + +int cmtp_init_sockets(void) +{ + bt_sock_register(BTPROTO_CMTP, &cmtp_sock_family_ops); + + return 0; +} + +void cmtp_cleanup_sockets(void) +{ + if (bt_sock_unregister(BTPROTO_CMTP)) + BT_ERR("Can't unregister CMTP socket"); +} diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index dca73e34a16f..132ce9982783 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -71,7 +71,7 @@ void hci_acl_connect(struct hci_conn *conn) memset(&cp, 0, sizeof(cp)); bacpy(&cp.bdaddr, &conn->dst); - cp.pscan_rep_mode = 0x01; + cp.pscan_rep_mode = 0x02; if ((ie = inquiry_cache_lookup(hdev, &conn->dst)) && inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 2436a71ed563..b760910ed84e 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -406,6 +406,7 @@ int hci_inquiry(unsigned long arg) hci_dev_lock_bh(hdev); if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX || + inquiry_cache_empty(hdev) || ir.flags & IREQ_CACHE_FLUSH) { inquiry_cache_flush(hdev); do_inquiry = 1; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 9d70c8c9ff35..262571e2f521 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -452,6 +452,29 @@ static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff * hci_dev_unlock(hdev); } +/* Inquiry Result With RSSI */ +static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct inquiry_info_with_rssi *info = (struct inquiry_info_with_rssi *) (skb->data + 1); + int num_rsp = *((__u8 *) skb->data); + + BT_DBG("%s num_rsp %d", hdev->name, num_rsp); + + hci_dev_lock(hdev); + for (; num_rsp; num_rsp--) { + struct inquiry_info tmp; + bacpy(&tmp.bdaddr, &info->bdaddr); + tmp.pscan_rep_mode = info->pscan_rep_mode; + tmp.pscan_period_mode = info->pscan_period_mode; + tmp.pscan_mode = 0x00; + memcpy(tmp.dev_class, &info->dev_class, 3); + tmp.clock_offset = info->clock_offset; + info++; + inquiry_cache_update(hdev, &tmp); + } + hci_dev_unlock(hdev); +} + /* Connect Request */ static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) { @@ -744,6 +767,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) hci_inquiry_result_evt(hdev, skb); break; + case HCI_EV_INQUIRY_RESULT_WITH_RSSI: + hci_inquiry_result_with_rssi_evt(hdev, skb); + break; + case HCI_EV_CONN_REQUEST: hci_conn_request_evt(hdev, skb); break; diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 349f6540f36e..886163ece9ac 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -66,20 +66,20 @@ static struct hci_sec_filter hci_sec_filter = { /* Packet types */ 0x10, /* Events */ - { 0xd9fe, 0x0 }, + { 0x1000d9fe, 0x0000300c }, /* Commands */ { { 0x0 }, /* OGF_LINK_CTL */ - { 0x2a000002, 0x0, 0x0, 0x0 }, + { 0xbe000006, 0x00000001, 0x0000, 0x00 }, /* OGF_LINK_POLICY */ - { 0x1200, 0x0, 0x0, 0x0 }, + { 0x00005200, 0x00000000, 0x0000, 0x00 }, /* OGF_HOST_CTL */ - { 0x80100000, 0x202a, 0x0, 0x0 }, + { 0xaab00200, 0x2b402aaa, 0x0154, 0x00 }, /* OGF_INFO_PARAM */ - { 0x22a, 0x0, 0x0, 0x0 }, + { 0x000002be, 0x00000000, 0x0000, 0x00 }, /* OGF_STATUS_PARAM */ - { 0x2e, 0x0, 0x0, 0x0 } + { 0x000000ea, 0x00000000, 0x0000, 0x00 } } }; diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c index 7280fac62948..a382860e3fb8 100644 --- a/net/bluetooth/l2cap.c +++ b/net/bluetooth/l2cap.c @@ -2202,3 +2202,4 @@ module_exit(l2cap_cleanup); MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); MODULE_DESCRIPTION("Bluetooth L2CAP ver " VERSION); MODULE_LICENSE("GPL"); +MODULE_ALIAS("bt-proto-0"); diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 148be892afe6..8d2608c39d29 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -52,7 +52,7 @@ #include <net/bluetooth/l2cap.h> #include <net/bluetooth/rfcomm.h> -#define VERSION "1.0" +#define VERSION "1.1" #ifndef CONFIG_BT_RFCOMM_DEBUG #undef BT_DBG @@ -207,7 +207,7 @@ static void rfcomm_dlc_clear_state(struct rfcomm_dlc *d) d->mtu = RFCOMM_DEFAULT_MTU; d->v24_sig = RFCOMM_V24_RTC | RFCOMM_V24_RTR | RFCOMM_V24_DV; - d->credits = RFCOMM_MAX_CREDITS; + d->cfc = RFCOMM_CFC_DISABLED; d->rx_credits = RFCOMM_DEFAULT_CREDITS; } @@ -314,8 +314,8 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, d->state = BT_CONFIG; rfcomm_dlc_link(s, d); - d->mtu = s->mtu; - d->credits = s->credits; + d->mtu = s->mtu; + d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; if (s->state == BT_CONNECTED) rfcomm_send_pn(s, 1, d); @@ -415,7 +415,7 @@ void __rfcomm_dlc_throttle(struct rfcomm_dlc *d) { BT_DBG("dlc %p state %ld", d, d->state); - if (!d->credits) { + if (!d->cfc) { d->v24_sig |= RFCOMM_V24_FC; set_bit(RFCOMM_MSC_PENDING, &d->flags); } @@ -426,7 +426,7 @@ void __rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) { BT_DBG("dlc %p state %ld", d, d->state); - if (!d->credits) { + if (!d->cfc) { d->v24_sig &= ~RFCOMM_V24_FC; set_bit(RFCOMM_MSC_PENDING, &d->flags); } @@ -479,8 +479,8 @@ struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state) s->state = state; s->sock = sock; - s->mtu = RFCOMM_DEFAULT_MTU; - s->credits = RFCOMM_MAX_CREDITS; + s->mtu = RFCOMM_DEFAULT_MTU; + s->cfc = RFCOMM_CFC_UNKNOWN; list_add(&s->list, &session_list); @@ -752,7 +752,7 @@ static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d pn->ack_timer = 0; pn->max_retrans = 0; - if (d->credits) { + if (s->cfc) { pn->flow_ctrl = cr ? 0xf0 : 0xe0; pn->credits = RFCOMM_DEFAULT_CREDITS; } else { @@ -1142,28 +1142,22 @@ static int rfcomm_recv_sabm(struct rfcomm_session *s, u8 dlci) static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn) { + struct rfcomm_session *s = d->session; + BT_DBG("dlc %p state %ld dlci %d mtu %d fc 0x%x credits %d", d, d->state, d->dlci, pn->mtu, pn->flow_ctrl, pn->credits); - if (cr) { - if (pn->flow_ctrl == 0xf0) { - d->tx_credits = pn->credits; - } else { - set_bit(RFCOMM_TX_THROTTLED, &d->flags); - d->credits = 0; - } + if (pn->flow_ctrl == 0xf0 || pn->flow_ctrl == 0xe0) { + d->cfc = s->cfc = RFCOMM_CFC_ENABLED; + d->tx_credits = pn->credits; } else { - if (pn->flow_ctrl == 0xe0) { - d->tx_credits = pn->credits; - } else { - set_bit(RFCOMM_TX_THROTTLED, &d->flags); - d->credits = 0; - } + d->cfc = s->cfc = RFCOMM_CFC_DISABLED; + set_bit(RFCOMM_TX_THROTTLED, &d->flags); } d->priority = pn->priority; - d->mtu = btohs(pn->mtu); + d->mtu = s->mtu = btohs(pn->mtu); return 0; } @@ -1353,7 +1347,7 @@ static int rfcomm_recv_msc(struct rfcomm_session *s, int cr, struct sk_buff *skb return 0; if (cr) { - if (msc->v24_sig & RFCOMM_V24_FC && !d->credits) + if (msc->v24_sig & RFCOMM_V24_FC && !d->cfc) set_bit(RFCOMM_TX_THROTTLED, &d->flags); else clear_bit(RFCOMM_TX_THROTTLED, &d->flags); @@ -1444,7 +1438,7 @@ static int rfcomm_recv_data(struct rfcomm_session *s, u8 dlci, int pf, struct sk goto drop; } - if (pf && d->credits) { + if (pf && d->cfc) { u8 credits = *(u8 *) skb->data; skb_pull(skb, 1); d->tx_credits += credits; @@ -1549,20 +1543,20 @@ static inline int rfcomm_process_tx(struct rfcomm_dlc *d) struct sk_buff *skb; int err; - BT_DBG("dlc %p state %ld credits %d rx_credits %d tx_credits %d", - d, d->state, d->credits, d->rx_credits, d->tx_credits); + BT_DBG("dlc %p state %ld cfc %d rx_credits %d tx_credits %d", + d, d->state, d->cfc, d->rx_credits, d->tx_credits); /* Send pending MSC */ if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags)) rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); - if (d->credits) { + if (d->cfc) { /* CFC enabled. * Give them some credits */ if (!test_bit(RFCOMM_RX_THROTTLED, &d->flags) && - d->rx_credits <= (d->credits >> 2)) { - rfcomm_send_credits(d->session, d->addr, d->credits - d->rx_credits); - d->rx_credits = d->credits; + d->rx_credits <= (d->cfc >> 2)) { + rfcomm_send_credits(d->session, d->addr, d->cfc - d->rx_credits); + d->rx_credits = d->cfc; } } else { /* CFC disabled. @@ -1583,7 +1577,7 @@ static inline int rfcomm_process_tx(struct rfcomm_dlc *d) d->tx_credits--; } - if (d->credits && !d->tx_credits) { + if (d->cfc && !d->tx_credits) { /* We're out of TX credits. * Set TX_THROTTLED flag to avoid unnesary wakeups by dlc_send. */ set_bit(RFCOMM_TX_THROTTLED, &d->flags); @@ -1655,7 +1649,9 @@ static inline void rfcomm_accept_connection(struct rfcomm_session *s) nsock->type = sock->type; nsock->ops = sock->ops; - + + __module_get(nsock->ops->owner); + err = sock->ops->accept(sock, nsock, O_NONBLOCK); if (err < 0) { sock_release(nsock); @@ -1998,3 +1994,4 @@ module_exit(rfcomm_cleanup); MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>"); MODULE_DESCRIPTION("Bluetooth RFCOMM ver " VERSION); MODULE_LICENSE("GPL"); +MODULE_ALIAS("bt-proto-3"); diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 987a01ac4b93..27c0f9ac3007 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -1055,3 +1055,4 @@ module_exit(sco_cleanup); MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); MODULE_DESCRIPTION("Bluetooth SCO ver " VERSION); MODULE_LICENSE("GPL"); +MODULE_ALIAS("bt-proto-2"); |
