diff options
Diffstat (limited to 'drivers/bluetooth')
-rw-r--r-- | drivers/bluetooth/Kconfig | 168 | ||||
-rw-r--r-- | drivers/bluetooth/Makefile | 19 | ||||
-rw-r--r-- | drivers/bluetooth/bcm203x.c | 314 | ||||
-rw-r--r-- | drivers/bluetooth/bfusb.c | 806 | ||||
-rw-r--r-- | drivers/bluetooth/bluecard_cs.c | 1114 | ||||
-rw-r--r-- | drivers/bluetooth/bpa10x.c | 657 | ||||
-rw-r--r-- | drivers/bluetooth/bt3c_cs.c | 960 | ||||
-rw-r--r-- | drivers/bluetooth/btuart_cs.c | 880 | ||||
-rw-r--r-- | drivers/bluetooth/dtl1_cs.c | 832 | ||||
-rw-r--r-- | drivers/bluetooth/hci_bcsp.c | 749 | ||||
-rw-r--r-- | drivers/bluetooth/hci_bcsp.h | 70 | ||||
-rw-r--r-- | drivers/bluetooth/hci_h4.c | 282 | ||||
-rw-r--r-- | drivers/bluetooth/hci_h4.h | 44 | ||||
-rw-r--r-- | drivers/bluetooth/hci_ldisc.c | 593 | ||||
-rw-r--r-- | drivers/bluetooth/hci_uart.h | 82 | ||||
-rw-r--r-- | drivers/bluetooth/hci_usb.c | 1075 | ||||
-rw-r--r-- | drivers/bluetooth/hci_usb.h | 128 | ||||
-rw-r--r-- | drivers/bluetooth/hci_vhci.c | 364 | ||||
-rw-r--r-- | drivers/bluetooth/hci_vhci.h | 50 |
19 files changed, 9187 insertions, 0 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig new file mode 100644 index 000000000000..543f93e0f23f --- /dev/null +++ b/drivers/bluetooth/Kconfig @@ -0,0 +1,168 @@ + +menu "Bluetooth device drivers" + depends on BT + +config BT_HCIUSB + 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. + + 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_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. + +config BT_HCIUART + tristate "HCI UART driver" + help + Bluetooth HCI UART driver. + This driver is required if you want to use Bluetooth devices with + serial port interface. You will also need this driver if you have + UART based Bluetooth PCMCIA and CF devices like Xircom Credit Card + adapter and BrainBoxes Bluetooth PC Card. + + Say Y here to compile support for Bluetooth UART devices into the + kernel or say M to compile it as module (hci_uart). + +config BT_HCIUART_H4 + bool "UART (H4) protocol support" + depends on BT_HCIUART + help + UART (H4) is serial protocol for communication between Bluetooth + device and host. This protocol is required for most Bluetooth devices + with UART interface, including PCMCIA and CF cards. + + Say Y here to compile support for HCI UART (H4) protocol. + +config BT_HCIUART_BCSP + bool "BCSP protocol support" + depends on BT_HCIUART + help + BCSP (BlueCore Serial Protocol) is serial protocol for communication + between Bluetooth device and host. This protocol is required for non + USB Bluetooth devices based on CSR BlueCore chip, including PCMCIA and + CF cards. + + 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 + 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 + 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_HCIBPA10X + tristate "HCI BPA10x USB driver" + depends on USB + help + Bluetooth HCI BPA10x USB driver. + This driver provides support for the Digianswer BPA 100/105 Bluetooth + sniffer devices. + + Say Y here to compile support for HCI BPA10x devices into the + kernel or say M to compile it as module (bpa10x). + +config BT_HCIBFUSB + tristate "HCI BlueFRITZ! USB driver" + depends on USB + 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 + help + Bluetooth HCI DTL1 (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + Nokia DTL1 interface: + Nokia Bluetooth Card + Socket Bluetooth CF Card + + Say Y here to compile support for HCI DTL1 devices into the + kernel or say M to compile it as module (dtl1_cs). + +config BT_HCIBT3C + tristate "HCI BT3C (PC Card) driver" + depends on PCMCIA + select FW_LOADER + help + Bluetooth HCI BT3C (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + 3Com BT3C interface: + 3Com Bluetooth Card (3CRWB6096) + HP Bluetooth Card + + Say Y here to compile support for HCI BT3C devices into the + kernel or say M to compile it as module (bt3c_cs). + +config BT_HCIBLUECARD + tristate "HCI BlueCard (PC Card) driver" + depends on PCMCIA + help + Bluetooth HCI BlueCard (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + Anycom BlueCard interface: + Anycom Bluetooth PC Card + Anycom Bluetooth CF Card + + Say Y here to compile support for HCI BlueCard devices into the + kernel or say M to compile it as module (bluecard_cs). + +config BT_HCIBTUART + tristate "HCI UART (PC Card) device driver" + depends on PCMCIA + help + Bluetooth HCI UART (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + an UART interface: + Xircom CreditCard Bluetooth Adapter + Xircom RealPort2 Bluetooth Adapter + Sphinx PICO Card + H-Soft blue+Card + Cyber-blue Compact Flash Card + + Say Y here to compile support for HCI UART devices into the + kernel or say M to compile it as module (btuart_cs). + +config BT_HCIVHCI + tristate "HCI VHCI (Virtual HCI device) driver" + help + Bluetooth Virtual HCI device driver. + This driver is required if you want to use HCI Emulation software. + + Say Y here to compile support for virtual HCI devices into the + kernel or say M to compile it as module (hci_vhci). + +endmenu + diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile new file mode 100644 index 000000000000..08c10e178e02 --- /dev/null +++ b/drivers/bluetooth/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the Linux Bluetooth HCI device drivers. +# + +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_HCIBPA10X) += bpa10x.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 +obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o + +hci_uart-y := hci_ldisc.o +hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o +hci_uart-$(CONFIG_BT_HCIUART_BCSP) += hci_bcsp.o +hci_uart-objs := $(hci_uart-y) diff --git a/drivers/bluetooth/bcm203x.c b/drivers/bluetooth/bcm203x.c new file mode 100644 index 000000000000..5fd3e4cb7525 --- /dev/null +++ b/drivers/bluetooth/bcm203x.c @@ -0,0 +1,314 @@ +/* + * + * Broadcom Blutonium firmware driver + * + * Copyright (C) 2003 Maxim Krasnyansky <maxk@qualcomm.com> + * 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 int ignore = 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; + + 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, 4096); + + 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; + int size; + + BT_DBG("intf %p id %p", intf, id); + + if (ignore || (intf->cur_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); + + size = max_t(uint, firmware->size, 4096); + + data->buffer = kmalloc(size, GFP_KERNEL); + if (!data->buffer) { + BT_ERR("Can't allocate memory for mini driver"); + release_firmware(firmware); + usb_free_urb(data->urb); + kfree(data); + return -ENOMEM; + } + + 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->buffer); + 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->buffer); + 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_kill_urb(data->urb); + + usb_set_intfdata(intf, NULL); + + usb_free_urb(data->urb); + kfree(data->fw_data); + kfree(data->buffer); + 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_exit(void) +{ + usb_deregister(&bcm203x_driver); +} + +module_init(bcm203x_init); +module_exit(bcm203x_exit); + +module_param(ignore, bool, 0644); +MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c new file mode 100644 index 000000000000..c42d7e6ac1c5 --- /dev/null +++ b/drivers/bluetooth/bfusb.c @@ -0,0 +1,806 @@ +/* + * + * 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 int ignore = 0; + +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 3000 + +#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 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_kill_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); + + read_lock(&bfusb->lock); + + if (!test_bit(HCI_RUNNING, &bfusb->hdev->flags)) + goto unlock; + + 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); + } + +unlock: + 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, USB_CTRL_SET_TIMEOUT) < 0) { + BT_ERR("Can't change to loading configuration"); + return -EBUSY; + } + + bfusb->udev->toggle[0] = bfusb->udev->toggle[1] = 0; + + 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, USB_CTRL_SET_TIMEOUT)) < 0) { + BT_ERR("Can't change to running configuration"); + goto error; + } + + bfusb->udev->toggle[0] = bfusb->udev->toggle[1] = 0; + + 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, USB_CTRL_SET_TIMEOUT); + + return err; +} + +static int bfusb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_endpoint *bulk_out_ep; + struct usb_host_endpoint *bulk_in_ep; + struct hci_dev *hdev; + struct bfusb *bfusb; + + BT_DBG("intf %p id %p", intf, id); + + if (ignore) + return -ENODEV; + + /* Check number of endpoints */ + if (intf->cur_altsetting->desc.bNumEndpoints < 2) + return -EIO; + + bulk_out_ep = &intf->cur_altsetting->endpoint[0]; + bulk_in_ep = &intf->cur_altsetting->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 = le16_to_cpu(bulk_out_ep->desc.wMaxPacketSize); + + rwlock_init(&bfusb->lock); + + 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 = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + goto error; + } + + bfusb->hdev = hdev; + + hdev->type = HCI_USB; + hdev->driver_data = bfusb; + SET_HCIDEV_DEV(hdev, &intf->dev); + + 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"); + hci_free_dev(hdev); + goto error; + } + + usb_set_intfdata(intf, bfusb); + + return 0; + +release: + release_firmware(firmware); + +error: + kfree(bfusb); + +done: + return -EIO; +} + +static void bfusb_disconnect(struct usb_interface *intf) +{ + struct bfusb *bfusb = usb_get_intfdata(intf); + struct hci_dev *hdev = bfusb->hdev; + + BT_DBG("intf %p", intf); + + if (!hdev) + return; + + usb_set_intfdata(intf, NULL); + + bfusb_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); +} + +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_exit(void) +{ + usb_deregister(&bfusb_driver); +} + +module_init(bfusb_init); +module_exit(bfusb_exit); + +module_param(ignore, bool, 0644); +MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c new file mode 100644 index 000000000000..e481cc411b5d --- /dev/null +++ b/drivers/bluetooth/bluecard_cs.c @@ -0,0 +1,1114 @@ +/* + * + * Bluetooth driver for the Anycom BlueCard (LSE039/LSE041) + * + * Copyright (C) 2001-2002 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; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#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/delay.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/wait.h> + +#include <linux/skbuff.h> +#include <asm/io.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for the Anycom BlueCard (LSE039/LSE041)"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct bluecard_info_t { + dev_link_t link; + dev_node_t node; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + struct timer_list timer; /* For LED control */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + + unsigned char ctrl_reg; + unsigned long hw_state; /* Status of the hardware and LED control */ +} bluecard_info_t; + + +static void bluecard_config(dev_link_t *link); +static void bluecard_release(dev_link_t *link); +static int bluecard_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "bluecard_cs"; + +static dev_link_t *bluecard_attach(void); +static void bluecard_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + + +/* Default baud rate: 57600, 115200, 230400 or 460800 */ +#define DEFAULT_BAUD_RATE 230400 + + +/* Hardware states */ +#define CARD_READY 1 +#define CARD_HAS_PCCARD_ID 4 +#define CARD_HAS_POWER_LED 5 +#define CARD_HAS_ACTIVITY_LED 6 + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_BUFFER_NUMBER 5 /* unset = buffer one, set = buffer two */ +#define XMIT_BUF_ONE_READY 6 +#define XMIT_BUF_TWO_READY 7 +#define XMIT_SENDING_READY 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + +/* Special packet types */ +#define PKT_BAUD_RATE_57600 0x80 +#define PKT_BAUD_RATE_115200 0x81 +#define PKT_BAUD_RATE_230400 0x82 +#define PKT_BAUD_RATE_460800 0x83 + + +/* These are the register offsets */ +#define REG_COMMAND 0x20 +#define REG_INTERRUPT 0x21 +#define REG_CONTROL 0x22 +#define REG_RX_CONTROL 0x24 +#define REG_CARD_RESET 0x30 +#define REG_LED_CTRL 0x30 + +/* REG_COMMAND */ +#define REG_COMMAND_TX_BUF_ONE 0x01 +#define REG_COMMAND_TX_BUF_TWO 0x02 +#define REG_COMMAND_RX_BUF_ONE 0x04 +#define REG_COMMAND_RX_BUF_TWO 0x08 +#define REG_COMMAND_RX_WIN_ONE 0x00 +#define REG_COMMAND_RX_WIN_TWO 0x10 + +/* REG_CONTROL */ +#define REG_CONTROL_BAUD_RATE_57600 0x00 +#define REG_CONTROL_BAUD_RATE_115200 0x01 +#define REG_CONTROL_BAUD_RATE_230400 0x02 +#define REG_CONTROL_BAUD_RATE_460800 0x03 +#define REG_CONTROL_RTS 0x04 +#define REG_CONTROL_BT_ON 0x08 +#define REG_CONTROL_BT_RESET 0x10 +#define REG_CONTROL_BT_RES_PU 0x20 +#define REG_CONTROL_INTERRUPT 0x40 +#define REG_CONTROL_CARD_RESET 0x80 + +/* REG_RX_CONTROL */ +#define RTS_LEVEL_SHIFT_BITS 0x02 + + + +/* ======================== LED handling routines ======================== */ + + +static void bluecard_activity_led_timeout(u_long arg) +{ + bluecard_info_t *info = (bluecard_info_t *)arg; + unsigned int iobase = info->link.io.BasePort1; + + if (!test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + return; + + if (test_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state))) { + /* Disable activity LED */ + outb(0x08 | 0x20, iobase + 0x30); + } else { + /* Disable power LED */ + outb(0x00, iobase + 0x30); + } +} + + +static void bluecard_enable_activity_led(bluecard_info_t *info) +{ + unsigned int iobase = info->link.io.BasePort1; + + if (!test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + return; + + if (test_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state))) { + /* Enable activity LED */ + outb(0x10 | 0x40, iobase + 0x30); + + /* Stop the LED after HZ/4 */ + mod_timer(&(info->timer), jiffies + HZ / 4); + } else { + /* Enable power LED */ + outb(0x08 | 0x20, iobase + 0x30); + + /* Stop the LED after HZ/2 */ + mod_timer(&(info->timer), jiffies + HZ / 2); + } +} + + + +/* ======================== Interrupt handling ======================== */ + + +static int bluecard_write(unsigned int iobase, unsigned int offset, __u8 *buf, int len) +{ + int i, actual; + + actual = (len > 15) ? 15 : len; + + outb_p(actual, iobase + offset); + + for (i = 0; i < actual; i++) + outb_p(buf[i], iobase + offset + i + 1); + + return actual; +} + + +static void bluecard_write_wakeup(bluecard_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (!test_bit(XMIT_SENDING_READY, &(info->tx_state))) + return; + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->link.io.BasePort1; + register unsigned int offset; + register unsigned char command; + register unsigned long ready_bit; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!(info->link.state & DEV_PRESENT)) + return; + + if (test_bit(XMIT_BUFFER_NUMBER, &(info->tx_state))) { + if (!test_bit(XMIT_BUF_TWO_READY, &(info->tx_state))) + break; + offset = 0x10; + command = REG_COMMAND_TX_BUF_TWO; + ready_bit = XMIT_BUF_TWO_READY; + } else { + if (!test_bit(XMIT_BUF_ONE_READY, &(info->tx_state))) + break; + offset = 0x00; + command = REG_COMMAND_TX_BUF_ONE; + ready_bit = XMIT_BUF_ONE_READY; + } + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + if (skb->pkt_type & 0x80) { + /* Disable RTS */ + info->ctrl_reg |= REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + } + + /* Activate LED */ + bluecard_enable_activity_led(info); + + /* Send frame */ + len = bluecard_write(iobase, offset, skb->data, skb->len); + + /* Tell the FPGA to send the data */ + outb_p(command, iobase + REG_COMMAND); + + /* Mark the buffer as dirty */ + clear_bit(ready_bit, &(info->tx_state)); + + if (skb->pkt_type & 0x80) { + DECLARE_WAIT_QUEUE_HEAD(wq); + DEFINE_WAIT(wait); + + unsigned char baud_reg; + + switch (skb->pkt_type) { + case PKT_BAUD_RATE_460800: + baud_reg = REG_CONTROL_BAUD_RATE_460800; + break; + case PKT_BAUD_RATE_230400: + baud_reg = REG_CONTROL_BAUD_RATE_230400; + break; + case PKT_BAUD_RATE_115200: + baud_reg = REG_CONTROL_BAUD_RATE_115200; + break; + case PKT_BAUD_RATE_57600: + /* Fall through... */ + default: + baud_reg = REG_CONTROL_BAUD_RATE_57600; + break; + } + + /* Wait until the command reaches the baseband */ + prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + finish_wait(&wq, &wait); + + /* Set baud on baseband */ + info->ctrl_reg &= ~0x03; + info->ctrl_reg |= baud_reg; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable RTS */ + info->ctrl_reg &= ~REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Wait before the next HCI packet can be send */ + prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + finish_wait(&wq, &wait); + } + + if (len == skb->len) { + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + /* Change buffer */ + change_bit(XMIT_BUFFER_NUMBER, &(info->tx_state)); + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static int bluecard_read(unsigned int iobase, unsigned int offset, __u8 *buf, int size) +{ + int i, n, len; + + outb(REG_COMMAND_RX_WIN_ONE, iobase + REG_COMMAND); + + len = inb(iobase + offset); + n = 0; + i = 1; + + while (n < len) { + + if (i == 16) { + outb(REG_COMMAND_RX_WIN_TWO, iobase + REG_COMMAND); + i = 0; + } + + buf[n] = inb(iobase + offset + i); + + n++; + i++; + + } + + return len; +} + + +static void bluecard_receive(bluecard_info_t *info, unsigned int offset) +{ + unsigned int iobase; + unsigned char buf[31]; + int i, len; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->link.io.BasePort1; + + if (test_bit(XMIT_SENDING_READY, &(info->tx_state))) + bluecard_enable_activity_led(info); + + len = bluecard_read(iobase, offset, buf, sizeof(buf)); + + for (i = 0; i < len; i++) { + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + info->rx_skb->pkt_type = buf[i]; + + switch (info->rx_skb->pkt_type) { + + case 0x00: + /* init packet */ + if (offset != 0x00) { + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + set_bit(XMIT_SENDING_READY, &(info->tx_state)); + bluecard_write_wakeup(info); + } + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", info->rx_skb->pkt_type); + info->hdev->stat.err_rx++; + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + *skb_put(info->rx_skb, 1) = buf[i]; + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (struct hci_event_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = (struct hci_acl_hdr *)(info->rx_skb->data); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = (struct hci_sco_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + + } + + info->hdev->stat.byte_rx += len; +} + + +static irqreturn_t bluecard_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +{ + bluecard_info_t *info = dev_inst; + unsigned int iobase; + unsigned char reg; + + if (!info || !info->hdev) { + BT_ERR("Call of irq %d for unknown device", irq); + return IRQ_NONE; + } + + if (!test_bit(CARD_READY, &(info->hw_state))) + return IRQ_HANDLED; + + iobase = info->link.io.BasePort1; + + spin_lock(&(info->lock)); + + /* Disable interrupt */ + info->ctrl_reg &= ~REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + reg = inb(iobase + REG_INTERRUPT); + + if ((reg != 0x00) && (reg != 0xff)) { + + if (reg & 0x04) { + bluecard_receive(info, 0x00); + outb(0x04, iobase + REG_INTERRUPT); + outb(REG_COMMAND_RX_BUF_ONE, iobase + REG_COMMAND); + } + + if (reg & 0x08) { + bluecard_receive(info, 0x10); + outb(0x08, iobase + REG_INTERRUPT); + outb(REG_COMMAND_RX_BUF_TWO, iobase + REG_COMMAND); + } + + if (reg & 0x01) { + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + outb(0x01, iobase + REG_INTERRUPT); + bluecard_write_wakeup(info); + } + + if (reg & 0x02) { + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + outb(0x02, iobase + REG_INTERRUPT); + bluecard_write_wakeup(info); + } + + } + + /* Enable interrupt */ + info->ctrl_reg |= REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + spin_unlock(&(info->lock)); + + return IRQ_HANDLED; +} + + + +/* ======================== Device specific HCI commands ======================== */ + + +static int bluecard_hci_set_baud_rate(struct hci_dev *hdev, int baud) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + struct sk_buff *skb; + + /* Ericsson baud rate command */ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x09, 0xfc, 0x01, 0x03 }; + + if (!(skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return -1; + } + + switch (baud) { + case 460800: + cmd[4] = 0x00; + skb->pkt_type = PKT_BAUD_RATE_460800; + break; + case 230400: + cmd[4] = 0x01; + skb->pkt_type = PKT_BAUD_RATE_230400; + break; + case 115200: + cmd[4] = 0x02; + skb->pkt_type = PKT_BAUD_RATE_115200; + break; + case 57600: + /* Fall through... */ + default: + cmd[4] = 0x03; + skb->pkt_type = PKT_BAUD_RATE_57600; + break; + } + + memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd)); + + skb_queue_tail(&(info->txq), skb); + + bluecard_write_wakeup(info); + + return 0; +} + + + +/* ======================== HCI interface ======================== */ + + +static int bluecard_hci_flush(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int bluecard_hci_open(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + unsigned int iobase = info->link.io.BasePort1; + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + bluecard_hci_set_baud_rate(hdev, DEFAULT_BAUD_RATE); + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) { + /* Enable LED */ + outb(0x08 | 0x20, iobase + 0x30); + } + + return 0; +} + + +static int bluecard_hci_close(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + unsigned int iobase = info->link.io.BasePort1; + + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + bluecard_hci_flush(hdev); + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) { + /* Disable LED */ + outb(0x00, iobase + 0x30); + } + + return 0; +} + + +static int bluecard_hci_send_frame(struct sk_buff *skb) +{ + bluecard_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (bluecard_info_t *)(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); + skb_queue_tail(&(info->txq), skb); + + bluecard_write_wakeup(info); + + return 0; +} + + +static void bluecard_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int bluecard_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int bluecard_open(bluecard_info_t *info) +{ + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev; + unsigned char id; + + spin_lock_init(&(info->lock)); + + init_timer(&(info->timer)); + info->timer.function = &bluecard_activity_led_timeout; + info->timer.data = (u_long)info; + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->type = HCI_PCCARD; + hdev->driver_data = info; + + hdev->open = bluecard_hci_open; + hdev->close = bluecard_hci_close; + hdev->flush = bluecard_hci_flush; + hdev->send = bluecard_hci_send_frame; + hdev->destruct = bluecard_hci_destruct; + hdev->ioctl = bluecard_hci_ioctl; + + hdev->owner = THIS_MODULE; + + id = inb(iobase + 0x30); + + if ((id & 0x0f) == 0x02) + set_bit(CARD_HAS_PCCARD_ID, &(info->hw_state)); + + if (id & 0x10) + set_bit(CARD_HAS_POWER_LED, &(info->hw_state)); + + if (id & 0x20) + set_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state)); + + /* Reset card */ + info->ctrl_reg = REG_CONTROL_BT_RESET | REG_CONTROL_CARD_RESET; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Turn FPGA off */ + outb(0x80, iobase + 0x30); + + /* Wait some time */ + msleep(10); + + /* Turn FPGA on */ + outb(0x00, iobase + 0x30); + + /* Activate card */ + info->ctrl_reg = REG_CONTROL_BT_ON | REG_CONTROL_BT_RES_PU; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable interrupt */ + outb(0xff, iobase + REG_INTERRUPT); + info->ctrl_reg |= REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + if ((id & 0x0f) == 0x03) { + /* Disable RTS */ + info->ctrl_reg |= REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Set baud rate */ + info->ctrl_reg |= 0x03; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable RTS */ + info->ctrl_reg &= ~REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + set_bit(XMIT_SENDING_READY, &(info->tx_state)); + } + + /* Start the RX buffers */ + outb(REG_COMMAND_RX_BUF_ONE, iobase + REG_COMMAND); + outb(REG_COMMAND_RX_BUF_TWO, iobase + REG_COMMAND); + + /* Signal that the hardware is ready */ + set_bit(CARD_READY, &(info->hw_state)); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + /* Control the point at which RTS is enabled */ + outb((0x0f << RTS_LEVEL_SHIFT_BITS) | 1, iobase + REG_RX_CONTROL); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1250); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int bluecard_close(bluecard_info_t *info) +{ + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + bluecard_hci_close(hdev); + + clear_bit(CARD_READY, &(info->hw_state)); + + /* Reset card */ + info->ctrl_reg = REG_CONTROL_BT_RESET | REG_CONTROL_CARD_RESET; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Turn FPGA off */ + outb(0x80, iobase + 0x30); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static dev_link_t *bluecard_attach(void) +{ + bluecard_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int ret; + + /* Create new info device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return NULL; + memset(info, 0, sizeof(*info)); + + link = &info->link; + link->priv = info; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 8; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + link->irq.Handler = bluecard_interrupt; + link->irq.Instance = info; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &bluecard_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + bluecard_detach(link); + return NULL; + } + + return link; +} + + +static void bluecard_detach(dev_link_t *link) +{ + bluecard_info_t *info = link->priv; + dev_link_t **linkp; + int ret; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + bluecard_release(link); + + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure, free bits */ + *linkp = link->next; + + kfree(info); +} + + +static int first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = pcmcia_get_first_tuple(handle, tuple); + if (i != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + + i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) + return i; + + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static void bluecard_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + bluecard_info_t *info = link->priv; + tuple_t tuple; + u_short buf[256]; + cisparse_t parse; + config_info_t config; + int i, n, last_ret, last_fn; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + + /* Get configuration register information */ + tuple.DesiredTuple = CISTPL_CONFIG; + last_ret = first_tuple(handle, &tuple, &parse); + if (last_ret != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + i = pcmcia_get_configuration_info(handle, &config); + link->conf.Vcc = config.Vcc; + + link->conf.ConfigIndex = 0x20; + link->io.NumPorts1 = 64; + link->io.IOAddrLines = 6; + + for (n = 0; n < 0x400; n += 0x40) { + link->io.BasePort1 = n ^ 0x300; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + break; + } + + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + if (bluecard_open(info) != 0) + goto failed; + + strcpy(info->node.dev_name, info->hdev->name); + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + +failed: + bluecard_release(link); +} + + +static void bluecard_release(dev_link_t *link) +{ + bluecard_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + bluecard_close(info); + + del_timer(&(info->timer)); + + link->dev = NULL; + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +static int bluecard_event(event_t event, int priority, event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + bluecard_info_t *info = link->priv; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + bluecard_close(info); + bluecard_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + bluecard_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + + return 0; +} + +static struct pcmcia_driver bluecard_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "bluecard_cs", + }, + .attach = bluecard_attach, + .detach = bluecard_detach, +}; + +static int __init init_bluecard_cs(void) +{ + return pcmcia_register_driver(&bluecard_driver); +} + + +static void __exit exit_bluecard_cs(void) +{ + pcmcia_unregister_driver(&bluecard_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_bluecard_cs); +module_exit(exit_bluecard_cs); diff --git a/drivers/bluetooth/bpa10x.c b/drivers/bluetooth/bpa10x.c new file mode 100644 index 000000000000..2771c861f185 --- /dev/null +++ b/drivers/bluetooth/bpa10x.c @@ -0,0 +1,657 @@ +/* + * + * Digianswer Bluetooth USB driver + * + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/errno.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#ifndef CONFIG_BT_HCIBPA10X_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "0.8" + +static int ignore = 0; + +static struct usb_device_id bpa10x_table[] = { + /* Tektronix BPA 100/105 (Digianswer) */ + { USB_DEVICE(0x08fd, 0x0002) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bpa10x_table); + +#define BPA10X_CMD_EP 0x00 +#define BPA10X_EVT_EP 0x81 +#define BPA10X_TX_EP 0x02 +#define BPA10X_RX_EP 0x82 + +#define BPA10X_CMD_BUF_SIZE 252 +#define BPA10X_EVT_BUF_SIZE 16 +#define BPA10X_TX_BUF_SIZE 384 +#define BPA10X_RX_BUF_SIZE 384 + +struct bpa10x_data { + struct hci_dev *hdev; + struct usb_device *udev; + + rwlock_t lock; + + struct sk_buff_head cmd_queue; + struct urb *cmd_urb; + struct urb *evt_urb; + struct sk_buff *evt_skb; + unsigned int evt_len; + + struct sk_buff_head tx_queue; + struct urb *tx_urb; + struct urb *rx_urb; +}; + +#define HCI_VENDOR_HDR_SIZE 5 + +struct hci_vendor_hdr { + __u8 type; + __u16 snum; + __u16 dlen; +} __attribute__ ((packed)); + +static void bpa10x_recv_bulk(struct bpa10x_data *data, unsigned char *buf, int count) +{ + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + struct hci_vendor_hdr *vh; + struct sk_buff *skb; + int len; + + while (count) { + switch (*buf++) { + case HCI_ACLDATA_PKT: + ah = (struct hci_acl_hdr *) buf; + len = HCI_ACL_HDR_SIZE + __le16_to_cpu(ah->dlen); + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_ACLDATA_PKT; + hci_recv_frame(skb); + } + break; + + case HCI_SCODATA_PKT: + sh = (struct hci_sco_hdr *) buf; + len = HCI_SCO_HDR_SIZE + sh->dlen; + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_SCODATA_PKT; + hci_recv_frame(skb); + } + break; + + case HCI_VENDOR_PKT: + vh = (struct hci_vendor_hdr *) buf; + len = HCI_VENDOR_HDR_SIZE + __le16_to_cpu(vh->dlen); + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *) data->hdev; + skb->pkt_type = HCI_VENDOR_PKT; + hci_recv_frame(skb); + } + break; + + default: + len = count - 1; + break; + } + + buf += len; + count -= (len + 1); + } +} + +static int bpa10x_recv_event(struct bpa10x_data *data, unsigned char *buf, int size) +{ + BT_DBG("data %p buf %p size %d", data, buf, size); + + if (data->evt_skb) { + struct sk_buff *skb = data->evt_skb; + + memcpy(skb_put(skb, size), buf, size); + + if (skb->len == data->evt_len) { + data->evt_skb = NULL; + data->evt_len = 0; + hci_recv_frame(skb); + } + } else { + struct sk_buff *skb; + struct hci_event_hdr *hdr; + unsigned char pkt_type; + int pkt_len = 0; + + if (size < HCI_EVENT_HDR_SIZE + 1) { + BT_ERR("%s event packet block with size %d is too short", + data->hdev->name, size); + return -EILSEQ; + } + + pkt_type = *buf++; + size--; + + if (pkt_type != HCI_EVENT_PKT) { + BT_ERR("%s unexpected event packet start byte 0x%02x", + data->hdev->name, pkt_type); + return -EPROTO; + } + + hdr = (struct hci_event_hdr *) buf; + pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen; + + skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for new event packet", + data->hdev->name); + return -ENOMEM; + } + + skb->dev = (void *) data->hdev; + skb->pkt_type = pkt_type; + + memcpy(skb_put(skb, size), buf, size); + + if (pkt_len == size) { + hci_recv_frame(skb); + } else { + data->evt_skb = skb; + data->evt_len = pkt_len; + } + } + + return 0; +} + +static void bpa10x_wakeup(struct bpa10x_data *data) +{ + struct urb *urb; + struct sk_buff *skb; + int err; + + BT_DBG("data %p", data); + + urb = data->cmd_urb; + if (urb->status == -EINPROGRESS) + skb = NULL; + else + skb = skb_dequeue(&data->cmd_queue); + + if (skb) { + struct usb_ctrlrequest *cr; + + if (skb->len > BPA10X_CMD_BUF_SIZE) { + BT_ERR("%s command packet with size %d is too big", + data->hdev->name, skb->len); + kfree_skb(skb); + return; + } + + cr = (struct usb_ctrlrequest *) urb->setup_packet; + cr->wLength = __cpu_to_le16(skb->len); + + memcpy(urb->transfer_buffer, skb->data, skb->len); + urb->transfer_buffer_length = skb->len; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s submit failed for command urb %p with error %d", + data->hdev->name, urb, err); + skb_queue_head(&data->cmd_queue, skb); + } else + kfree_skb(skb); + } + + urb = data->tx_urb; + if (urb->status == -EINPROGRESS) + skb = NULL; + else + skb = skb_dequeue(&data->tx_queue); + + if (skb) { + memcpy(urb->transfer_buffer, skb->data, skb->len); + urb->transfer_buffer_length = skb->len; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s submit failed for command urb %p with error %d", + data->hdev->name, urb, err); + skb_queue_head(&data->tx_queue, skb); + } else + kfree_skb(skb); + } +} + +static void bpa10x_complete(struct urb *urb, struct pt_regs *regs) +{ + struct bpa10x_data *data = urb->context; + unsigned char *buf = urb->transfer_buffer; + int err, count = urb->actual_length; + + BT_DBG("data %p urb %p buf %p count %d", data, urb, buf, count); + + read_lock(&data->lock); + + if (!test_bit(HCI_RUNNING, &data->hdev->flags)) + goto unlock; + + if (urb->status < 0 || !count) + goto resubmit; + + if (usb_pipein(urb->pipe)) { + data->hdev->stat.byte_rx += count; + + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) + bpa10x_recv_event(data, buf, count); + + if (usb_pipetype(urb->pipe) == PIPE_BULK) + bpa10x_recv_bulk(data, buf, count); + } else { + data->hdev->stat.byte_tx += count; + + bpa10x_wakeup(data); + } + +resubmit: + if (usb_pipein(urb->pipe)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -ENODEV) { + BT_ERR("%s urb %p type %d resubmit status %d", + data->hdev->name, urb, usb_pipetype(urb->pipe), err); + } + } + +unlock: + read_unlock(&data->lock); +} + +static inline struct urb *bpa10x_alloc_urb(struct usb_device *udev, unsigned int pipe, size_t size, int flags, void *data) +{ + struct urb *urb; + struct usb_ctrlrequest *cr; + unsigned char *buf; + + BT_DBG("udev %p data %p", udev, data); + + urb = usb_alloc_urb(0, flags); + if (!urb) + return NULL; + + buf = kmalloc(size, flags); + if (!buf) { + usb_free_urb(urb); + return NULL; + } + + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + cr = kmalloc(sizeof(*cr), flags); + if (!cr) { + kfree(buf); + usb_free_urb(urb); + return NULL; + } + + cr->bRequestType = USB_TYPE_VENDOR; + cr->bRequest = 0; + cr->wIndex = 0; + cr->wValue = 0; + cr->wLength = __cpu_to_le16(0); + + usb_fill_control_urb(urb, udev, pipe, (void *) cr, buf, 0, bpa10x_complete, data); + break; + + case PIPE_INTERRUPT: + usb_fill_int_urb(urb, udev, pipe, buf, size, bpa10x_complete, data, 1); + break; + + case PIPE_BULK: + usb_fill_bulk_urb(urb, udev, pipe, buf, size, bpa10x_complete, data); + break; + + default: + kfree(buf); + usb_free_urb(urb); + return NULL; + } + + return urb; +} + +static inline void bpa10x_free_urb(struct urb *urb) +{ + BT_DBG("urb %p", urb); + + if (!urb) + return; + + if (urb->setup_packet) + kfree(urb->setup_packet); + + if (urb->transfer_buffer) + kfree(urb->transfer_buffer); + + usb_free_urb(urb); +} + +static int bpa10x_open(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + struct usb_device *udev = data->udev; + unsigned long flags; + int err; + + BT_DBG("hdev %p data %p", hdev, data); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + data->cmd_urb = bpa10x_alloc_urb(udev, usb_sndctrlpipe(udev, BPA10X_CMD_EP), + BPA10X_CMD_BUF_SIZE, GFP_KERNEL, data); + if (!data->cmd_urb) { + err = -ENOMEM; + goto done; + } + + data->evt_urb = bpa10x_alloc_urb(udev, usb_rcvintpipe(udev, BPA10X_EVT_EP), + BPA10X_EVT_BUF_SIZE, GFP_KERNEL, data); + if (!data->evt_urb) { + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + data->rx_urb = bpa10x_alloc_urb(udev, usb_rcvbulkpipe(udev, BPA10X_RX_EP), + BPA10X_RX_BUF_SIZE, GFP_KERNEL, data); + if (!data->rx_urb) { + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + data->tx_urb = bpa10x_alloc_urb(udev, usb_sndbulkpipe(udev, BPA10X_TX_EP), + BPA10X_TX_BUF_SIZE, GFP_KERNEL, data); + if (!data->rx_urb) { + bpa10x_free_urb(data->rx_urb); + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->cmd_urb); + err = -ENOMEM; + goto done; + } + + write_lock_irqsave(&data->lock, flags); + + err = usb_submit_urb(data->evt_urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s submit failed for event urb %p with error %d", + data->hdev->name, data->evt_urb, err); + } else { + err = usb_submit_urb(data->rx_urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s submit failed for rx urb %p with error %d", + data->hdev->name, data->evt_urb, err); + usb_kill_urb(data->evt_urb); + } + } + + write_unlock_irqrestore(&data->lock, flags); + +done: + if (err < 0) + clear_bit(HCI_RUNNING, &hdev->flags); + + return err; +} + +static int bpa10x_close(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + unsigned long flags; + + BT_DBG("hdev %p data %p", hdev, data); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&data->lock, flags); + + skb_queue_purge(&data->cmd_queue); + usb_kill_urb(data->cmd_urb); + usb_kill_urb(data->evt_urb); + usb_kill_urb(data->rx_urb); + usb_kill_urb(data->tx_urb); + + write_unlock_irqrestore(&data->lock, flags); + + bpa10x_free_urb(data->cmd_urb); + bpa10x_free_urb(data->evt_urb); + bpa10x_free_urb(data->rx_urb); + bpa10x_free_urb(data->tx_urb); + + return 0; +} + +static int bpa10x_flush(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("hdev %p data %p", hdev, data); + + skb_queue_purge(&data->cmd_queue); + + return 0; +} + +static int bpa10x_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct bpa10x_data *data; + + BT_DBG("hdev %p skb %p type %d len %d", hdev, skb, skb->pkt_type, skb->len); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + data = hdev->driver_data; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &(skb->pkt_type), 1); + + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + skb_queue_tail(&data->cmd_queue, skb); + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + skb_queue_tail(&data->tx_queue, skb); + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + skb_queue_tail(&data->tx_queue, skb); + break; + }; + + read_lock(&data->lock); + + bpa10x_wakeup(data); + + read_unlock(&data->lock); + + return 0; +} + +static void bpa10x_destruct(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("hdev %p data %p", hdev, data); + + kfree(data); +} + +static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct hci_dev *hdev; + struct bpa10x_data *data; + int err; + + BT_DBG("intf %p id %p", intf, id); + + if (ignore) + return -ENODEV; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + BT_ERR("Can't allocate data structure"); + return -ENOMEM; + } + + memset(data, 0, sizeof(*data)); + + data->udev = udev; + + rwlock_init(&data->lock); + + skb_queue_head_init(&data->cmd_queue); + skb_queue_head_init(&data->tx_queue); + + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + kfree(data); + return -ENOMEM; + } + + data->hdev = hdev; + + hdev->type = HCI_USB; + hdev->driver_data = data; + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = bpa10x_open; + hdev->close = bpa10x_close; + hdev->flush = bpa10x_flush; + hdev->send = bpa10x_send_frame; + hdev->destruct = bpa10x_destruct; + + hdev->owner = THIS_MODULE; + + err = hci_register_dev(hdev); + if (err < 0) { + BT_ERR("Can't register HCI device"); + kfree(data); + hci_free_dev(hdev); + return err; + } + + usb_set_intfdata(intf, data); + + return 0; +} + +static void bpa10x_disconnect(struct usb_interface *intf) +{ + struct bpa10x_data *data = usb_get_intfdata(intf); + struct hci_dev *hdev = data->hdev; + + BT_DBG("intf %p", intf); + + if (!hdev) + return; + + usb_set_intfdata(intf, NULL); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); +} + +static struct usb_driver bpa10x_driver = { + .owner = THIS_MODULE, + .name = "bpa10x", + .probe = bpa10x_probe, + .disconnect = bpa10x_disconnect, + .id_table = bpa10x_table, +}; + +static int __init bpa10x_init(void) +{ + int err; + + BT_INFO("Digianswer Bluetooth USB driver ver %s", VERSION); + + err = usb_register(&bpa10x_driver); + if (err < 0) + BT_ERR("Failed to register USB driver"); + + return err; +} + +static void __exit bpa10x_exit(void) +{ + usb_deregister(&bpa10x_driver); +} + +module_init(bpa10x_init); +module_exit(bpa10x_exit); + +module_param(ignore, bool, 0644); +MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Digianswer Bluetooth USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c new file mode 100644 index 000000000000..f71e5c76963d --- /dev/null +++ b/drivers/bluetooth/bt3c_cs.c @@ -0,0 +1,960 @@ +/* + * + * Driver for the 3Com Bluetooth PCMCIA card + * + * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> + * Jose Orlando Pereira <jop@di.uminho.pt> + * + * + * 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; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#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/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>, Jose Orlando Pereira <jop@di.uminho.pt>"); +MODULE_DESCRIPTION("Bluetooth driver for the 3Com Bluetooth PCMCIA card"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct bt3c_info_t { + dev_link_t link; + dev_node_t node; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} bt3c_info_t; + + +static void bt3c_config(dev_link_t *link); +static void bt3c_release(dev_link_t *link); +static int bt3c_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "bt3c_cs"; + +static dev_link_t *bt3c_attach(void); +static void bt3c_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + + + +/* ======================== Special I/O functions ======================== */ + + +#define DATA_L 0 +#define DATA_H 1 +#define ADDR_L 2 +#define ADDR_H 3 +#define CONTROL 4 + + +static inline void bt3c_address(unsigned int iobase, unsigned short addr) +{ + outb(addr & 0xff, iobase + ADDR_L); + outb((addr >> 8) & 0xff, iobase + ADDR_H); +} + + +static inline void bt3c_put(unsigned int iobase, unsigned short value) +{ + outb(value & 0xff, iobase + DATA_L); + outb((value >> 8) & 0xff, iobase + DATA_H); +} + + +static inline void bt3c_io_write(unsigned int iobase, unsigned short addr, unsigned short value) +{ + bt3c_address(iobase, addr); + bt3c_put(iobase, value); +} + + +static inline unsigned short bt3c_get(unsigned int iobase) +{ + unsigned short value = inb(iobase + DATA_L); + + value |= inb(iobase + DATA_H) << 8; + + return value; +} + + +static inline unsigned short bt3c_read(unsigned int iobase, unsigned short addr) +{ + bt3c_address(iobase, addr); + + return bt3c_get(iobase); +} + + + +/* ======================== Interrupt handling ======================== */ + + +static int bt3c_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + bt3c_address(iobase, 0x7080); + + /* Fill FIFO with current frame */ + while (actual < len) { + /* Transmit next byte */ + bt3c_put(iobase, buf[actual]); + actual++; + } + + bt3c_io_write(iobase, 0x7005, actual); + + return actual; +} + + +static void bt3c_write_wakeup(bt3c_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) + return; + + do { + register unsigned int iobase = info->link.io.BasePort1; + register struct sk_buff *skb; + register int len; + + if (!(info->link.state & DEV_PRESENT)) + break; + + + if (!(skb = skb_dequeue(&(info->txq)))) { + clear_bit(XMIT_SENDING, &(info->tx_state)); + break; + } + + /* Send frame */ + len = bt3c_write(iobase, 256, skb->data, skb->len); + + if (len != skb->len) { + BT_ERR("Very strange"); + } + + kfree_skb(skb); + + info->hdev->stat.byte_tx += len; + + } while (0); +} + + +static void bt3c_receive(bt3c_info_t *info) +{ + unsigned int iobase; + int size = 0, avail; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->link.io.BasePort1; + + avail = bt3c_read(iobase, 0x7006); + //printk("bt3c_cs: receiving %d bytes\n", avail); + + bt3c_address(iobase, 0x7480); + while (size < avail) { + size++; + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + info->rx_skb->pkt_type = inb(iobase + DATA_L); + inb(iobase + DATA_H); + //printk("bt3c: PACKET_TYPE=%02x\n", info->rx_skb->pkt_type); + + switch (info->rx_skb->pkt_type) { + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* Unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", info->rx_skb->pkt_type); + info->hdev->stat.err_rx++; + clear_bit(HCI_RUNNING, &(info->hdev->flags)); + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + __u8 x = inb(iobase + DATA_L); + + *skb_put(info->rx_skb, 1) = x; + inb(iobase + DATA_H); + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (struct hci_event_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = (struct hci_acl_hdr *)(info->rx_skb->data); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = (struct hci_sco_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + } + + bt3c_io_write(iobase, 0x7006, 0x0000); +} + + +static irqreturn_t bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +{ + bt3c_info_t *info = dev_inst; + unsigned int iobase; + int iir; + + if (!info || !info->hdev) { + BT_ERR("Call of irq %d for unknown device", irq); + return IRQ_NONE; + } + + iobase = info->link.io.BasePort1; + + spin_lock(&(info->lock)); + + iir = inb(iobase + CONTROL); + if (iir & 0x80) { + int stat = bt3c_read(iobase, 0x7001); + + if ((stat & 0xff) == 0x7f) { + BT_ERR("Very strange (stat=0x%04x)", stat); + } else if ((stat & 0xff) != 0xff) { + if (stat & 0x0020) { + int stat = bt3c_read(iobase, 0x7002) & 0x10; + BT_INFO("%s: Antenna %s", info->hdev->name, + stat ? "out" : "in"); + } + if (stat & 0x0001) + bt3c_receive(info); + if (stat & 0x0002) { + //BT_ERR("Ack (stat=0x%04x)", stat); + clear_bit(XMIT_SENDING, &(info->tx_state)); + bt3c_write_wakeup(info); + } + + bt3c_io_write(iobase, 0x7001, 0x0000); + + outb(iir, iobase + CONTROL); + } + } + + spin_unlock(&(info->lock)); + + return IRQ_HANDLED; +} + + + +/* ======================== HCI interface ======================== */ + + +static int bt3c_hci_flush(struct hci_dev *hdev) +{ + bt3c_info_t *info = (bt3c_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int bt3c_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int bt3c_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + bt3c_hci_flush(hdev); + + return 0; +} + + +static int bt3c_hci_send_frame(struct sk_buff *skb) +{ + bt3c_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + unsigned long flags; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (bt3c_info_t *) (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); + skb_queue_tail(&(info->txq), skb); + + spin_lock_irqsave(&(info->lock), flags); + + bt3c_write_wakeup(info); + + spin_unlock_irqrestore(&(info->lock), flags); + + return 0; +} + + +static void bt3c_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int bt3c_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static struct device *bt3c_device(void) +{ + static struct device dev = { + .bus_id = "pcmcia", + }; + kobject_set_name(&dev.kobj, "bt3c"); + kobject_init(&dev.kobj); + + return &dev; +} + + +static int bt3c_load_firmware(bt3c_info_t *info, unsigned char *firmware, int count) +{ + char *ptr = (char *) firmware; + char b[9]; + unsigned int iobase, size, addr, fcs, tmp; + int i, err = 0; + + iobase = info->link.io.BasePort1; + + /* Reset */ + bt3c_io_write(iobase, 0x8040, 0x0404); + bt3c_io_write(iobase, 0x8040, 0x0400); + + udelay(1); + + bt3c_io_write(iobase, 0x8040, 0x0404); + + udelay(17); + + /* Load */ + while (count) { + if (ptr[0] != 'S') { + BT_ERR("Bad address in firmware"); + err = -EFAULT; + goto error; + } + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + 2, 2); + size = simple_strtol(b, NULL, 16); + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + 4, 8); + addr = simple_strtol(b, NULL, 16); + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + (size * 2) + 2, 2); + fcs = simple_strtol(b, NULL, 16); + + memset(b, 0, sizeof(b)); + for (tmp = 0, i = 0; i < size; i++) { + memcpy(b, ptr + (i * 2) + 2, 2); + tmp += simple_strtol(b, NULL, 16); + } + + if (((tmp + fcs) & 0xff) != 0xff) { + BT_ERR("Checksum error in firmware"); + err = -EILSEQ; + goto error; + } + + if (ptr[1] == '3') { + bt3c_address(iobase, addr); + + memset(b, 0, sizeof(b)); + for (i = 0; i < (size - 4) / 2; i++) { + memcpy(b, ptr + (i * 4) + 12, 4); + tmp = simple_strtol(b, NULL, 16); + bt3c_put(iobase, tmp); + } + } + + ptr += (size * 2) + 6; + count -= (size * 2) + 6; + } + + udelay(17); + + /* Boot */ + bt3c_address(iobase, 0x3000); + outb(inb(iobase + CONTROL) | 0x40, iobase + CONTROL); + +error: + udelay(17); + + /* Clear */ + bt3c_io_write(iobase, 0x7006, 0x0000); + bt3c_io_write(iobase, 0x7005, 0x0000); + bt3c_io_write(iobase, 0x7001, 0x0000); + + return err; +} + + +static int bt3c_open(bt3c_info_t *info) +{ + const struct firmware *firmware; + struct hci_dev *hdev; + int err; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->type = HCI_PCCARD; + hdev->driver_data = info; + + hdev->open = bt3c_hci_open; + hdev->close = bt3c_hci_close; + hdev->flush = bt3c_hci_flush; + hdev->send = bt3c_hci_send_frame; + hdev->destruct = bt3c_hci_destruct; + hdev->ioctl = bt3c_hci_ioctl; + + hdev->owner = THIS_MODULE; + + /* Load firmware */ + err = request_firmware(&firmware, "BT3CPCC.bin", bt3c_device()); + if (err < 0) { + BT_ERR("Firmware request failed"); + goto error; + } + + err = bt3c_load_firmware(info, firmware->data, firmware->size); + + release_firmware(firmware); + + if (err < 0) { + BT_ERR("Firmware loading failed"); + goto error; + } + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1000); + + /* Register HCI device */ + err = hci_register_dev(hdev); + if (err < 0) { + BT_ERR("Can't register HCI device"); + goto error; + } + + return 0; + +error: + info->hdev = NULL; + hci_free_dev(hdev); + return err; +} + + +static int bt3c_close(bt3c_info_t *info) +{ + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + bt3c_hci_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static dev_link_t *bt3c_attach(void) +{ + bt3c_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int ret; + + /* Create new info device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return NULL; + memset(info, 0, sizeof(*info)); + + link = &info->link; + link->priv = info; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 8; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + link->irq.Handler = bt3c_interrupt; + link->irq.Instance = info; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &bt3c_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + bt3c_detach(link); + return NULL; + } + + return link; +} + + +static void bt3c_detach(dev_link_t *link) +{ + bt3c_info_t *info = link->priv; + dev_link_t **linkp; + int ret; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + bt3c_release(link); + + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure, free bits */ + *linkp = link->next; + + kfree(info); +} + +static int get_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) + return i; + + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static void bt3c_config(dev_link_t *link) +{ + static kio_addr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + client_handle_t handle = link->handle; + bt3c_info_t *info = link->priv; + tuple_t tuple; + u_short buf[256]; + cisparse_t parse; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + config_info_t config; + int i, j, try, last_ret, last_fn; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + + /* Get configuration register information */ + tuple.DesiredTuple = CISTPL_CONFIG; + last_ret = first_tuple(handle, &tuple, &parse); + if (last_ret != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + i = pcmcia_get_configuration_info(handle, &config); + link->conf.Vcc = config.Vcc; + + /* First pass: look for a config entry that looks normal. */ + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + /* Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) { + i = first_tuple(handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if (i != CS_SUCCESS) + goto next_entry; + if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && (cf->io.win[0].base != 0)) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.IOAddrLines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } +next_entry: + i = next_tuple(handle, &tuple, &parse); + } + } + + /* Second pass: try to find an entry that isn't picky about + its base address, then try to grab any standard serial port + address, and finally try to get any free port. */ + i = first_tuple(handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if ((i == CS_SUCCESS) && (cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { + link->conf.ConfigIndex = cf->index; + for (j = 0; j < 5; j++) { + link->io.BasePort1 = base[j]; + link->io.IOAddrLines = base[j] ? 16 : 3; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + BT_ERR("No usable port range found"); + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + if (bt3c_open(info) != 0) + goto failed; + + strcpy(info->node.dev_name, info->hdev->name); + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + +failed: + bt3c_release(link); +} + + +static void bt3c_release(dev_link_t *link) +{ + bt3c_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + bt3c_close(info); + + link->dev = NULL; + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +static int bt3c_event(event_t event, int priority, event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + bt3c_info_t *info = link->priv; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + bt3c_close(info); + bt3c_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + bt3c_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + + return 0; +} + +static struct pcmcia_driver bt3c_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "bt3c_cs", + }, + .attach = bt3c_attach, + .detach = bt3c_detach, +}; + +static int __init init_bt3c_cs(void) +{ + return pcmcia_register_driver(&bt3c_driver); +} + + +static void __exit exit_bt3c_cs(void) +{ + pcmcia_unregister_driver(&bt3c_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_bt3c_cs); +module_exit(exit_bt3c_cs); diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c new file mode 100644 index 000000000000..ad8d972444a5 --- /dev/null +++ b/drivers/bluetooth/btuart_cs.c @@ -0,0 +1,880 @@ +/* + * + * Driver for Bluetooth PCMCIA cards with HCI UART interface + * + * Copyright (C) 2001-2002 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; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#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/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for Bluetooth PCMCIA cards with HCI UART interface"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct btuart_info_t { + dev_link_t link; + dev_node_t node; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} btuart_info_t; + + +static void btuart_config(dev_link_t *link); +static void btuart_release(dev_link_t *link); +static int btuart_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "btuart_cs"; + +static dev_link_t *btuart_attach(void); +static void btuart_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + + +/* Maximum baud rate */ +#define SPEED_MAX 115200 + +/* Default baud rate: 57600, 115200, 230400 or 460800 */ +#define DEFAULT_BAUD_RATE 115200 + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + + + +/* ======================== Interrupt handling ======================== */ + + +static int btuart_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + /* Tx FIFO should be empty */ + if (!(inb(iobase + UART_LSR) & UART_LSR_THRE)) + return 0; + + /* Fill FIFO with current frame */ + while ((fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb(buf[actual], iobase + UART_TX); + actual++; + } + + return actual; +} + + +static void btuart_write_wakeup(btuart_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->link.io.BasePort1; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!(info->link.state & DEV_PRESENT)) + return; + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + /* Send frame */ + len = btuart_write(iobase, 16, skb->data, skb->len); + set_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (len == skb->len) { + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static void btuart_receive(btuart_info_t *info) +{ + unsigned int iobase; + int boguscount = 0; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->link.io.BasePort1; + + do { + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + info->rx_skb->pkt_type = inb(iobase + UART_RX); + + switch (info->rx_skb->pkt_type) { + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* Unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", info->rx_skb->pkt_type); + info->hdev->stat.err_rx++; + clear_bit(HCI_RUNNING, &(info->hdev->flags)); + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + *skb_put(info->rx_skb, 1) = inb(iobase + UART_RX); + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (struct hci_event_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = (struct hci_acl_hdr *)(info->rx_skb->data); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = (struct hci_sco_hdr *)(info->rx_skb->data); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 16) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +static irqreturn_t btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +{ + btuart_info_t *info = dev_inst; + unsigned int iobase; + int boguscount = 0; + int iir, lsr; + + if (!info || !info->hdev) { + BT_ERR("Call of irq %d for unknown device", irq); + return IRQ_NONE; + } + + iobase = info->link.io.BasePort1; + + spin_lock(&(info->lock)); + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + while (iir) { + + /* Clear interrupt */ + lsr = inb(iobase + UART_LSR); + + switch (iir) { + case UART_IIR_RLSI: + BT_ERR("RLSI"); + break; + case UART_IIR_RDI: + /* Receive interrupt */ + btuart_receive(info); + break; + case UART_IIR_THRI: + if (lsr & UART_LSR_THRE) { + /* Transmitter ready for data */ + btuart_write_wakeup(info); + } + break; + default: + BT_ERR("Unhandled IIR=%#x", iir); + break; + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 100) + break; + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + + } + + spin_unlock(&(info->lock)); + + return IRQ_HANDLED; +} + + +static void btuart_change_speed(btuart_info_t *info, unsigned int speed) +{ + unsigned long flags; + unsigned int iobase; + int fcr; /* FIFO control reg */ + int lcr; /* Line control reg */ + int divisor; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->link.io.BasePort1; + + spin_lock_irqsave(&(info->lock), flags); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + divisor = SPEED_MAX / speed; + + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT; + + /* + * Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and + * almost 1,7 ms at 19200 bps. At speeds above that we can just forget + * about this timeout since it will always be fast enough. + */ + + if (speed < 38400) + fcr |= UART_FCR_TRIGGER_1; + else + fcr |= UART_FCR_TRIGGER_14; + + /* Bluetooth cards use 8N1 */ + lcr = UART_LCR_WLEN8; + + outb(UART_LCR_DLAB | lcr, iobase + UART_LCR); /* Set DLAB */ + outb(divisor & 0xff, iobase + UART_DLL); /* Set speed */ + outb(divisor >> 8, iobase + UART_DLM); + outb(lcr, iobase + UART_LCR); /* Set 8N1 */ + outb(fcr, iobase + UART_FCR); /* Enable FIFO's */ + + /* Turn on interrups */ + outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); +} + + + +/* ======================== HCI interface ======================== */ + + +static int btuart_hci_flush(struct hci_dev *hdev) +{ + btuart_info_t *info = (btuart_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int btuart_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int btuart_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + btuart_hci_flush(hdev); + + return 0; +} + + +static int btuart_hci_send_frame(struct sk_buff *skb) +{ + btuart_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (btuart_info_t *)(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); + skb_queue_tail(&(info->txq), skb); + + btuart_write_wakeup(info); + + return 0; +} + + +static void btuart_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int btuart_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int btuart_open(btuart_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->type = HCI_PCCARD; + hdev->driver_data = info; + + hdev->open = btuart_hci_open; + hdev->close = btuart_hci_close; + hdev->flush = btuart_hci_flush; + hdev->send = btuart_hci_send_frame; + hdev->destruct = btuart_hci_destruct; + hdev->ioctl = btuart_hci_ioctl; + + hdev->owner = THIS_MODULE; + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + /* Initialize UART */ + outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */ + outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR); + + /* Turn on interrupts */ + // outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + btuart_change_speed(info, DEFAULT_BAUD_RATE); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1000); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int btuart_close(btuart_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + btuart_hci_close(hdev); + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static dev_link_t *btuart_attach(void) +{ + btuart_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int ret; + + /* Create new info device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return NULL; + memset(info, 0, sizeof(*info)); + + link = &info->link; + link->priv = info; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 8; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + link->irq.Handler = btuart_interrupt; + link->irq.Instance = info; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &btuart_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + btuart_detach(link); + return NULL; + } + + return link; +} + + +static void btuart_detach(dev_link_t *link) +{ + btuart_info_t *info = link->priv; + dev_link_t **linkp; + int ret; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + btuart_release(link); + + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure, free bits */ + *linkp = link->next; + + kfree(info); +} + +static int get_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) + return i; + + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static void btuart_config(dev_link_t *link) +{ + static kio_addr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + client_handle_t handle = link->handle; + btuart_info_t *info = link->priv; + tuple_t tuple; + u_short buf[256]; + cisparse_t parse; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + config_info_t config; + int i, j, try, last_ret, last_fn; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + + /* Get configuration register information */ + tuple.DesiredTuple = CISTPL_CONFIG; + last_ret = first_tuple(handle, &tuple, &parse); + if (last_ret != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + i = pcmcia_get_configuration_info(handle, &config); + link->conf.Vcc = config.Vcc; + + /* First pass: look for a config entry that looks normal. */ + tuple.TupleData = (cisdata_t *) buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + /* Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) { + i = first_tuple(handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if (i != CS_SUCCESS) + goto next_entry; + if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && (cf->io.win[0].base != 0)) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.IOAddrLines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } +next_entry: + i = next_tuple(handle, &tuple, &parse); + } + } + + /* Second pass: try to find an entry that isn't picky about + its base address, then try to grab any standard serial port + address, and finally try to get any free port. */ + i = first_tuple(handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if ((i == CS_SUCCESS) && (cf->io.nwin > 0) + && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { + link->conf.ConfigIndex = cf->index; + for (j = 0; j < 5; j++) { + link->io.BasePort1 = base[j]; + link->io.IOAddrLines = base[j] ? 16 : 3; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + BT_ERR("No usable port range found"); + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + if (btuart_open(info) != 0) + goto failed; + + strcpy(info->node.dev_name, info->hdev->name); + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + +failed: + btuart_release(link); +} + + +static void btuart_release(dev_link_t *link) +{ + btuart_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + btuart_close(info); + + link->dev = NULL; + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +static int btuart_event(event_t event, int priority, event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + btuart_info_t *info = link->priv; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + btuart_close(info); + btuart_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + btuart_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + + return 0; +} + +static struct pcmcia_driver btuart_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "btuart_cs", + }, + .attach = btuart_attach, + .detach = btuart_detach, +}; + +static int __init init_btuart_cs(void) +{ + return pcmcia_register_driver(&btuart_driver); +} + + +static void __exit exit_btuart_cs(void) +{ + pcmcia_unregister_driver(&btuart_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_btuart_cs); +module_exit(exit_btuart_cs); diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c new file mode 100644 index 000000000000..fe954e5d9a1d --- /dev/null +++ b/drivers/bluetooth/dtl1_cs.c @@ -0,0 +1,832 @@ +/* + * + * A driver for Nokia Connectivity Card DTL-1 devices + * + * Copyright (C) 2001-2002 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; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#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/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for Nokia Connectivity Card DTL-1"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct dtl1_info_t { + dev_link_t link; + dev_node_t node; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + unsigned long flowmask; /* HCI flow mask */ + int ri_latch; + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} dtl1_info_t; + + +static void dtl1_config(dev_link_t *link); +static void dtl1_release(dev_link_t *link); +static int dtl1_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "dtl1_cs"; + +static dev_link_t *dtl1_attach(void); +static void dtl1_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver States */ +#define RECV_WAIT_NSH 0 +#define RECV_WAIT_DATA 1 + + +typedef struct { + u8 type; + u8 zero; + u16 len; +} __attribute__ ((packed)) nsh_t; /* Nokia Specific Header */ + +#define NSHL 4 /* Nokia Specific Header Length */ + + + +/* ======================== Interrupt handling ======================== */ + + +static int dtl1_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + /* Tx FIFO should be empty */ + if (!(inb(iobase + UART_LSR) & UART_LSR_THRE)) + return 0; + + /* Fill FIFO with current frame */ + while ((fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb(buf[actual], iobase + UART_TX); + actual++; + } + + return actual; +} + + +static void dtl1_write_wakeup(dtl1_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_bit(XMIT_WAITING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->link.io.BasePort1; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!(info->link.state & DEV_PRESENT)) + return; + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + /* Send frame */ + len = dtl1_write(iobase, 32, skb->data, skb->len); + + if (len == skb->len) { + set_bit(XMIT_WAITING, &(info->tx_state)); + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static void dtl1_control(dtl1_info_t *info, struct sk_buff *skb) +{ + u8 flowmask = *(u8 *)skb->data; + int i; + + printk(KERN_INFO "Bluetooth: Nokia control data ="); + for (i = 0; i < skb->len; i++) { + printk(" %02x", skb->data[i]); + } + printk("\n"); + + /* transition to active state */ + if (((info->flowmask & 0x07) == 0) && ((flowmask & 0x07) != 0)) { + clear_bit(XMIT_WAITING, &(info->tx_state)); + dtl1_write_wakeup(info); + } + + info->flowmask = flowmask; + + kfree_skb(skb); +} + + +static void dtl1_receive(dtl1_info_t *info) +{ + unsigned int iobase; + nsh_t *nsh; + int boguscount = 0; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->link.io.BasePort1; + + do { + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + return; + } + + *skb_put(info->rx_skb, 1) = inb(iobase + UART_RX); + nsh = (nsh_t *)info->rx_skb->data; + + info->rx_count--; + + if (info->rx_count == 0) { + + switch (info->rx_state) { + case RECV_WAIT_NSH: + info->rx_state = RECV_WAIT_DATA; + info->rx_count = nsh->len + (nsh->len & 0x0001); + break; + case RECV_WAIT_DATA: + info->rx_skb->pkt_type = nsh->type; + + /* remove PAD byte if it exists */ + if (nsh->len & 0x0001) { + info->rx_skb->tail--; + info->rx_skb->len--; + } + + /* remove NSH */ + skb_pull(info->rx_skb, NSHL); + + switch (info->rx_skb->pkt_type) { + case 0x80: + /* control data for the Nokia Card */ + dtl1_control(info, info->rx_skb); + break; + case 0x82: + case 0x83: + case 0x84: + /* send frame to the HCI layer */ + info->rx_skb->dev = (void *) info->hdev; + info->rx_skb->pkt_type &= 0x0f; + hci_recv_frame(info->rx_skb); + break; + default: + /* unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", info->rx_skb->pkt_type); + kfree_skb(info->rx_skb); + break; + } + + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + info->rx_skb = NULL; + break; + } + + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 32) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +static irqreturn_t dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +{ + dtl1_info_t *info = dev_inst; + unsigned int iobase; + unsigned char msr; + int boguscount = 0; + int iir, lsr; + + if (!info || !info->hdev) { + BT_ERR("Call of irq %d for unknown device", irq); + return IRQ_NONE; + } + + iobase = info->link.io.BasePort1; + + spin_lock(&(info->lock)); + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + while (iir) { + + /* Clear interrupt */ + lsr = inb(iobase + UART_LSR); + + switch (iir) { + case UART_IIR_RLSI: + BT_ERR("RLSI"); + break; + case UART_IIR_RDI: + /* Receive interrupt */ + dtl1_receive(info); + break; + case UART_IIR_THRI: + if (lsr & UART_LSR_THRE) { + /* Transmitter ready for data */ + dtl1_write_wakeup(info); + } + break; + default: + BT_ERR("Unhandled IIR=%#x", iir); + break; + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 100) + break; + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + + } + + msr = inb(iobase + UART_MSR); + + if (info->ri_latch ^ (msr & UART_MSR_RI)) { + info->ri_latch = msr & UART_MSR_RI; + clear_bit(XMIT_WAITING, &(info->tx_state)); + dtl1_write_wakeup(info); + } + + spin_unlock(&(info->lock)); + + return IRQ_HANDLED; +} + + + +/* ======================== HCI interface ======================== */ + + +static int dtl1_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int dtl1_hci_flush(struct hci_dev *hdev) +{ + dtl1_info_t *info = (dtl1_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int dtl1_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + dtl1_hci_flush(hdev); + + return 0; +} + + +static int dtl1_hci_send_frame(struct sk_buff *skb) +{ + dtl1_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + struct sk_buff *s; + nsh_t nsh; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (dtl1_info_t *)(hdev->driver_data); + + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + nsh.type = 0x81; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + nsh.type = 0x82; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + nsh.type = 0x83; + break; + }; + + nsh.zero = 0; + nsh.len = skb->len; + + s = bt_skb_alloc(NSHL + skb->len + 1, GFP_ATOMIC); + skb_reserve(s, NSHL); + memcpy(skb_put(s, skb->len), skb->data, skb->len); + if (skb->len & 0x0001) + *skb_put(s, 1) = 0; /* PAD */ + + /* Prepend skb with Nokia frame header and queue */ + memcpy(skb_push(s, NSHL), &nsh, NSHL); + skb_queue_tail(&(info->txq), s); + + dtl1_write_wakeup(info); + + kfree_skb(skb); + + return 0; +} + + +static void dtl1_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int dtl1_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int dtl1_open(dtl1_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + info->rx_skb = NULL; + + set_bit(XMIT_WAITING, &(info->tx_state)); + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->type = HCI_PCCARD; + hdev->driver_data = info; + + hdev->open = dtl1_hci_open; + hdev->close = dtl1_hci_close; + hdev->flush = dtl1_hci_flush; + hdev->send = dtl1_hci_send_frame; + hdev->destruct = dtl1_hci_destruct; + hdev->ioctl = dtl1_hci_ioctl; + + hdev->owner = THIS_MODULE; + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + /* Initialize UART */ + outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */ + outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR); + + info->ri_latch = inb(info->link.io.BasePort1 + UART_MSR) & UART_MSR_RI; + + /* Turn on interrupts */ + outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(2000); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int dtl1_close(dtl1_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + dtl1_hci_close(hdev); + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static dev_link_t *dtl1_attach(void) +{ + dtl1_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int ret; + + /* Create new info device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return NULL; + memset(info, 0, sizeof(*info)); + + link = &info->link; + link->priv = info; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 8; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + link->irq.Handler = dtl1_interrupt; + link->irq.Instance = info; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &dtl1_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + dtl1_detach(link); + return NULL; + } + + return link; +} + + +static void dtl1_detach(dev_link_t *link) +{ + dtl1_info_t *info = link->priv; + dev_link_t **linkp; + int ret; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + dtl1_release(link); + + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure, free bits */ + *linkp = link->next; + + kfree(info); +} + +static int get_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) + return i; + + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + return get_tuple(handle, tuple, parse); +} + +static void dtl1_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + dtl1_info_t *info = link->priv; + tuple_t tuple; + u_short buf[256]; + cisparse_t parse; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + config_info_t config; + int i, last_ret, last_fn; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + + /* Get configuration register information */ + tuple.DesiredTuple = CISTPL_CONFIG; + last_ret = first_tuple(handle, &tuple, &parse); + if (last_ret != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + i = pcmcia_get_configuration_info(handle, &config); + link->conf.Vcc = config.Vcc; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + /* Look for a generic full-sized window */ + link->io.NumPorts1 = 8; + i = first_tuple(handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if ((i == CS_SUCCESS) && (cf->io.nwin == 1) && (cf->io.win[0].len > 8)) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.NumPorts1 = cf->io.win[0].len; /*yo */ + link->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + break; + } + i = next_tuple(handle, &tuple, &parse); + } + + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + if (dtl1_open(info) != 0) + goto failed; + + strcpy(info->node.dev_name, info->hdev->name); + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + +failed: + dtl1_release(link); +} + + +static void dtl1_release(dev_link_t *link) +{ + dtl1_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + dtl1_close(info); + + link->dev = NULL; + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +static int dtl1_event(event_t event, int priority, event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + dtl1_info_t *info = link->priv; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + dtl1_close(info); + dtl1_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + dtl1_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + + return 0; +} + +static struct pcmcia_driver dtl1_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "dtl1_cs", + }, + .attach = dtl1_attach, + .detach = dtl1_detach, +}; + +static int __init init_dtl1_cs(void) +{ + return pcmcia_register_driver(&dtl1_driver); +} + + +static void __exit exit_dtl1_cs(void) +{ + pcmcia_unregister_driver(&dtl1_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_dtl1_cs); +module_exit(exit_dtl1_cs); diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c new file mode 100644 index 000000000000..c0ed213fc857 --- /dev/null +++ b/drivers/bluetooth/hci_bcsp.c @@ -0,0 +1,749 @@ +/* + BlueCore Serial Protocol (BCSP) for Linux Bluetooth stack (BlueZ). + Copyright 2002 by Fabrizio Gennari <fabrizio.gennari@philips.com> + + Based on + hci_h4.c by Maxim Krasnyansky <maxk@qualcomm.com> + ABCSP by Carl Orsborn <cjo@csr.com> + + 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. +*/ + +/* + * $Id: hci_bcsp.c,v 1.2 2002/09/26 05:05:14 maxk Exp $ + */ + +#define VERSION "0.2" + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include "hci_uart.h" +#include "hci_bcsp.h" + +#ifndef CONFIG_BT_HCIUART_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) +#endif + +static int hciextn = 1; + +/* ---- BCSP CRC calculation ---- */ + +/* Table for calculating CRC for polynomial 0x1021, LSB processed first, +initial value 0xffff, bits shifted in reverse order. */ + +static const u16 crc_table[] = { + 0x0000, 0x1081, 0x2102, 0x3183, + 0x4204, 0x5285, 0x6306, 0x7387, + 0x8408, 0x9489, 0xa50a, 0xb58b, + 0xc60c, 0xd68d, 0xe70e, 0xf78f +}; + +/* Initialise the crc calculator */ +#define BCSP_CRC_INIT(x) x = 0xffff + +/* + Update crc with next data byte + + Implementation note + The data byte is treated as two nibbles. The crc is generated + in reverse, i.e., bits are fed into the register from the top. +*/ +static void bcsp_crc_update(u16 *crc, u8 d) +{ + u16 reg = *crc; + + reg = (reg >> 4) ^ crc_table[(reg ^ d) & 0x000f]; + reg = (reg >> 4) ^ crc_table[(reg ^ (d >> 4)) & 0x000f]; + + *crc = reg; +} + +/* + Get reverse of generated crc + + Implementation note + The crc generator (bcsp_crc_init() and bcsp_crc_update()) + creates a reversed crc, so it needs to be swapped back before + being passed on. +*/ +static u16 bcsp_crc_reverse(u16 crc) +{ + u16 b, rev; + + for (b = 0, rev = 0; b < 16; b++) { + rev = rev << 1; + rev |= (crc & 1); + crc = crc >> 1; + } + return (rev); +} + +/* ---- BCSP core ---- */ + +static void bcsp_slip_msgdelim(struct sk_buff *skb) +{ + const char pkt_delim = 0xc0; + memcpy(skb_put(skb, 1), &pkt_delim, 1); +} + +static void bcsp_slip_one_byte(struct sk_buff *skb, u8 c) +{ + const char esc_c0[2] = { 0xdb, 0xdc }; + const char esc_db[2] = { 0xdb, 0xdd }; + + switch (c) { + case 0xc0: + memcpy(skb_put(skb, 2), &esc_c0, 2); + break; + case 0xdb: + memcpy(skb_put(skb, 2), &esc_db, 2); + break; + default: + memcpy(skb_put(skb, 1), &c, 1); + } +} + +static int bcsp_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct bcsp_struct *bcsp = hu->priv; + + if (skb->len > 0xFFF) { + BT_ERR("Packet too long"); + kfree_skb(skb); + return 0; + } + + switch (skb->pkt_type) { + case HCI_ACLDATA_PKT: + case HCI_COMMAND_PKT: + skb_queue_tail(&bcsp->rel, skb); + break; + + case HCI_SCODATA_PKT: + skb_queue_tail(&bcsp->unrel, skb); + break; + + default: + BT_ERR("Unknown packet type"); + kfree_skb(skb); + break; + } + + return 0; +} + +static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, + int len, int pkt_type) +{ + struct sk_buff *nskb; + u8 hdr[4], chan; + int rel, i; + +#ifdef CONFIG_BT_HCIUART_BCSP_TXCRC + u16 BCSP_CRC_INIT(bcsp_txmsg_crc); +#endif + + switch (pkt_type) { + case HCI_ACLDATA_PKT: + chan = 6; /* BCSP ACL channel */ + rel = 1; /* reliable channel */ + break; + case HCI_COMMAND_PKT: + chan = 5; /* BCSP cmd/evt channel */ + rel = 1; /* reliable channel */ + break; + case HCI_SCODATA_PKT: + chan = 7; /* BCSP SCO channel */ + rel = 0; /* unreliable channel */ + break; + case BCSP_LE_PKT: + chan = 1; /* BCSP LE channel */ + rel = 0; /* unreliable channel */ + break; + case BCSP_ACK_PKT: + chan = 0; /* BCSP internal channel */ + rel = 0; /* unreliable channel */ + break; + default: + BT_ERR("Unknown packet type"); + return NULL; + } + + if (hciextn && chan == 5) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) data; + + if (hci_opcode_ogf(__le16_to_cpu(hdr->opcode)) == OGF_VENDOR_CMD) { + u8 desc = *(data + HCI_COMMAND_HDR_SIZE); + if ((desc & 0xf0) == 0xc0) { + data += HCI_COMMAND_HDR_SIZE + 1; + len -= HCI_COMMAND_HDR_SIZE + 1; + chan = desc & 0x0f; + } + } + } + + /* Max len of packet: (original len +4(bcsp hdr) +2(crc))*2 + (because bytes 0xc0 and 0xdb are escaped, worst case is + when the packet is all made of 0xc0 and 0xdb :) ) + + 2 (0xc0 delimiters at start and end). */ + + nskb = alloc_skb((len + 6) * 2 + 2, GFP_ATOMIC); + if (!nskb) + return NULL; + + nskb->pkt_type = pkt_type; + + bcsp_slip_msgdelim(nskb); + + hdr[0] = bcsp->rxseq_txack << 3; + bcsp->txack_req = 0; + BT_DBG("We request packet no %u to card", bcsp->rxseq_txack); + + if (rel) { + hdr[0] |= 0x80 + bcsp->msgq_txseq; + BT_DBG("Sending packet with seqno %u", bcsp->msgq_txseq); + bcsp->msgq_txseq = ++(bcsp->msgq_txseq) & 0x07; + } +#ifdef CONFIG_BT_HCIUART_BCSP_TXCRC + hdr[0] |= 0x40; +#endif + + hdr[1] = ((len << 4) & 0xff) | chan; + hdr[2] = len >> 4; + hdr[3] = ~(hdr[0] + hdr[1] + hdr[2]); + + /* Put BCSP header */ + for (i = 0; i < 4; i++) { + bcsp_slip_one_byte(nskb, hdr[i]); +#ifdef CONFIG_BT_HCIUART_BCSP_TXCRC + bcsp_crc_update(&bcsp_txmsg_crc, hdr[i]); +#endif + } + + /* Put payload */ + for (i = 0; i < len; i++) { + bcsp_slip_one_byte(nskb, data[i]); +#ifdef CONFIG_BT_HCIUART_BCSP_TXCRC + bcsp_crc_update(&bcsp_txmsg_crc, data[i]); +#endif + } + +#ifdef CONFIG_BT_HCIUART_BCSP_TXCRC + /* Put CRC */ + bcsp_txmsg_crc = bcsp_crc_reverse(bcsp_txmsg_crc); + bcsp_slip_one_byte(nskb, (u8) ((bcsp_txmsg_crc >> 8) & 0x00ff)); + bcsp_slip_one_byte(nskb, (u8) (bcsp_txmsg_crc & 0x00ff)); +#endif + + bcsp_slip_msgdelim(nskb); + return nskb; +} + +/* This is a rewrite of pkt_avail in ABCSP */ +static struct sk_buff *bcsp_dequeue(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + unsigned long flags; + struct sk_buff *skb; + + /* First of all, check for unreliable messages in the queue, + since they have priority */ + + if ((skb = skb_dequeue(&bcsp->unrel)) != NULL) { + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, skb->data, skb->len, skb->pkt_type); + if (nskb) { + kfree_skb(skb); + return nskb; + } else { + skb_queue_head(&bcsp->unrel, skb); + BT_ERR("Could not dequeue pkt because alloc_skb failed"); + } + } + + /* Now, try to send a reliable pkt. We can only send a + reliable packet if the number of packets sent but not yet ack'ed + is < than the winsize */ + + spin_lock_irqsave(&bcsp->unack.lock, flags); + + if (bcsp->unack.qlen < BCSP_TXWINSIZE && (skb = skb_dequeue(&bcsp->rel)) != NULL) { + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, skb->data, skb->len, skb->pkt_type); + if (nskb) { + __skb_queue_tail(&bcsp->unack, skb); + mod_timer(&bcsp->tbcsp, jiffies + HZ / 4); + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + return nskb; + } else { + skb_queue_head(&bcsp->rel, skb); + BT_ERR("Could not dequeue pkt because alloc_skb failed"); + } + } + + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + + /* We could not send a reliable packet, either because there are + none or because there are too many unack'ed pkts. Did we receive + any packets we have not acknowledged yet ? */ + + if (bcsp->txack_req) { + /* if so, craft an empty ACK pkt and send it on BCSP unreliable + channel 0 */ + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, NULL, 0, BCSP_ACK_PKT); + return nskb; + } + + /* We have nothing to send */ + return NULL; +} + +static int bcsp_flush(struct hci_uart *hu) +{ + BT_DBG("hu %p", hu); + return 0; +} + +/* Remove ack'ed packets */ +static void bcsp_pkt_cull(struct bcsp_struct *bcsp) +{ + unsigned long flags; + struct sk_buff *skb; + int i, pkts_to_be_removed; + u8 seqno; + + spin_lock_irqsave(&bcsp->unack.lock, flags); + + pkts_to_be_removed = bcsp->unack.qlen; + seqno = bcsp->msgq_txseq; + + while (pkts_to_be_removed) { + if (bcsp->rxack == seqno) + break; + pkts_to_be_removed--; + seqno = (seqno - 1) & 0x07; + } + + if (bcsp->rxack != seqno) + BT_ERR("Peer acked invalid packet"); + + BT_DBG("Removing %u pkts out of %u, up to seqno %u", + pkts_to_be_removed, bcsp->unack.qlen, (seqno - 1) & 0x07); + + for (i = 0, skb = ((struct sk_buff *) &bcsp->unack)->next; i < pkts_to_be_removed + && skb != (struct sk_buff *) &bcsp->unack; i++) { + struct sk_buff *nskb; + + nskb = skb->next; + __skb_unlink(skb, &bcsp->unack); + kfree_skb(skb); + skb = nskb; + } + if (bcsp->unack.qlen == 0) + del_timer(&bcsp->tbcsp); + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + if (i != pkts_to_be_removed) + BT_ERR("Removed only %u out of %u pkts", i, pkts_to_be_removed); +} + +/* Handle BCSP link-establishment packets. When we + detect a "sync" packet, symptom that the BT module has reset, + we do nothing :) (yet) */ +static void bcsp_handle_le_pkt(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + u8 conf_pkt[4] = { 0xad, 0xef, 0xac, 0xed }; + u8 conf_rsp_pkt[4] = { 0xde, 0xad, 0xd0, 0xd0 }; + u8 sync_pkt[4] = { 0xda, 0xdc, 0xed, 0xed }; + + /* spot "conf" pkts and reply with a "conf rsp" pkt */ + if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && + !memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) { + struct sk_buff *nskb = alloc_skb(4, GFP_ATOMIC); + + BT_DBG("Found a LE conf pkt"); + if (!nskb) + return; + memcpy(skb_put(nskb, 4), conf_rsp_pkt, 4); + nskb->pkt_type = BCSP_LE_PKT; + + skb_queue_head(&bcsp->unrel, nskb); + hci_uart_tx_wakeup(hu); + } + /* Spot "sync" pkts. If we find one...disaster! */ + else if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && + !memcmp(&bcsp->rx_skb->data[4], sync_pkt, 4)) { + BT_ERR("Found a LE sync pkt, card has reset"); + } +} + +static inline void bcsp_unslip_one_byte(struct bcsp_struct *bcsp, unsigned char byte) +{ + const u8 c0 = 0xc0, db = 0xdb; + + switch (bcsp->rx_esc_state) { + case BCSP_ESCSTATE_NOESC: + switch (byte) { + case 0xdb: + bcsp->rx_esc_state = BCSP_ESCSTATE_ESC; + break; + default: + memcpy(skb_put(bcsp->rx_skb, 1), &byte, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp->message_crc, byte); + bcsp->rx_count--; + } + break; + + case BCSP_ESCSTATE_ESC: + switch (byte) { + case 0xdc: + memcpy(skb_put(bcsp->rx_skb, 1), &c0, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp-> message_crc, 0xc0); + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + bcsp->rx_count--; + break; + + case 0xdd: + memcpy(skb_put(bcsp->rx_skb, 1), &db, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp-> message_crc, 0xdb); + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + bcsp->rx_count--; + break; + + default: + BT_ERR ("Invalid byte %02x after esc byte", byte); + kfree_skb(bcsp->rx_skb); + bcsp->rx_skb = NULL; + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + } + } +} + +static inline void bcsp_complete_rx_pkt(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + int pass_up; + + if (bcsp->rx_skb->data[0] & 0x80) { /* reliable pkt */ + BT_DBG("Received seqno %u from card", bcsp->rxseq_txack); + bcsp->rxseq_txack++; + bcsp->rxseq_txack %= 0x8; + bcsp->txack_req = 1; + + /* If needed, transmit an ack pkt */ + hci_uart_tx_wakeup(hu); + } + + bcsp->rxack = (bcsp->rx_skb->data[0] >> 3) & 0x07; + BT_DBG("Request for pkt %u from card", bcsp->rxack); + + bcsp_pkt_cull(bcsp); + if ((bcsp->rx_skb->data[1] & 0x0f) == 6 && + bcsp->rx_skb->data[0] & 0x80) { + bcsp->rx_skb->pkt_type = HCI_ACLDATA_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 5 && + bcsp->rx_skb->data[0] & 0x80) { + bcsp->rx_skb->pkt_type = HCI_EVENT_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 7) { + bcsp->rx_skb->pkt_type = HCI_SCODATA_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 1 && + !(bcsp->rx_skb->data[0] & 0x80)) { + bcsp_handle_le_pkt(hu); + pass_up = 0; + } else + pass_up = 0; + + if (!pass_up) { + struct hci_event_hdr hdr; + u8 desc = (bcsp->rx_skb->data[1] & 0x0f); + + if (desc != 0 && desc != 1) { + if (hciextn) { + desc |= 0xc0; + skb_pull(bcsp->rx_skb, 4); + memcpy(skb_push(bcsp->rx_skb, 1), &desc, 1); + + hdr.evt = 0xff; + hdr.plen = bcsp->rx_skb->len; + memcpy(skb_push(bcsp->rx_skb, HCI_EVENT_HDR_SIZE), &hdr, HCI_EVENT_HDR_SIZE); + bcsp->rx_skb->pkt_type = HCI_EVENT_PKT; + + hci_recv_frame(bcsp->rx_skb); + } else { + BT_ERR ("Packet for unknown channel (%u %s)", + bcsp->rx_skb->data[1] & 0x0f, + bcsp->rx_skb->data[0] & 0x80 ? + "reliable" : "unreliable"); + kfree_skb(bcsp->rx_skb); + } + } else + kfree_skb(bcsp->rx_skb); + } else { + /* Pull out BCSP hdr */ + skb_pull(bcsp->rx_skb, 4); + + hci_recv_frame(bcsp->rx_skb); + } + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_skb = NULL; +} + +/* Recv data */ +static int bcsp_recv(struct hci_uart *hu, void *data, int count) +{ + struct bcsp_struct *bcsp = hu->priv; + register unsigned char *ptr; + + BT_DBG("hu %p count %d rx_state %d rx_count %ld", + hu, count, bcsp->rx_state, bcsp->rx_count); + + ptr = data; + while (count) { + if (bcsp->rx_count) { + if (*ptr == 0xc0) { + BT_ERR("Short BCSP packet"); + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_START; + bcsp->rx_count = 0; + } else + bcsp_unslip_one_byte(bcsp, *ptr); + + ptr++; count--; + continue; + } + + switch (bcsp->rx_state) { + case BCSP_W4_BCSP_HDR: + if ((0xff & (u8) ~ (bcsp->rx_skb->data[0] + bcsp->rx_skb->data[1] + + bcsp->rx_skb->data[2])) != bcsp->rx_skb->data[3]) { + BT_ERR("Error in BCSP hdr checksum"); + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + if (bcsp->rx_skb->data[0] & 0x80 /* reliable pkt */ + && (bcsp->rx_skb->data[0] & 0x07) != bcsp->rxseq_txack) { + BT_ERR ("Out-of-order packet arrived, got %u expected %u", + bcsp->rx_skb->data[0] & 0x07, bcsp->rxseq_txack); + + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + bcsp->rx_state = BCSP_W4_DATA; + bcsp->rx_count = (bcsp->rx_skb->data[1] >> 4) + + (bcsp->rx_skb->data[2] << 4); /* May be 0 */ + continue; + + case BCSP_W4_DATA: + if (bcsp->rx_skb->data[0] & 0x40) { /* pkt with crc */ + bcsp->rx_state = BCSP_W4_CRC; + bcsp->rx_count = 2; + } else + bcsp_complete_rx_pkt(hu); + continue; + + case BCSP_W4_CRC: + if (bcsp_crc_reverse(bcsp->message_crc) != + (bcsp->rx_skb->data[bcsp->rx_skb->len - 2] << 8) + + bcsp->rx_skb->data[bcsp->rx_skb->len - 1]) { + + BT_ERR ("Checksum failed: computed %04x received %04x", + bcsp_crc_reverse(bcsp->message_crc), + (bcsp->rx_skb-> data[bcsp->rx_skb->len - 2] << 8) + + bcsp->rx_skb->data[bcsp->rx_skb->len - 1]); + + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + skb_trim(bcsp->rx_skb, bcsp->rx_skb->len - 2); + bcsp_complete_rx_pkt(hu); + continue; + + case BCSP_W4_PKT_DELIMITER: + switch (*ptr) { + case 0xc0: + bcsp->rx_state = BCSP_W4_PKT_START; + break; + default: + /*BT_ERR("Ignoring byte %02x", *ptr);*/ + break; + } + ptr++; count--; + break; + + case BCSP_W4_PKT_START: + switch (*ptr) { + case 0xc0: + ptr++; count--; + break; + + default: + bcsp->rx_state = BCSP_W4_BCSP_HDR; + bcsp->rx_count = 4; + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + BCSP_CRC_INIT(bcsp->message_crc); + + /* Do not increment ptr or decrement count + * Allocate packet. Max len of a BCSP pkt= + * 0xFFF (payload) +4 (header) +2 (crc) */ + + bcsp->rx_skb = bt_skb_alloc(0x1005, GFP_ATOMIC); + if (!bcsp->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + return 0; + } + bcsp->rx_skb->dev = (void *) hu->hdev; + break; + } + break; + } + } + return count; +} + + /* Arrange to retransmit all messages in the relq. */ +static void bcsp_timed_event(unsigned long arg) +{ + struct hci_uart *hu = (struct hci_uart *) arg; + struct bcsp_struct *bcsp = hu->priv; + struct sk_buff *skb; + unsigned long flags; + + BT_DBG("hu %p retransmitting %u pkts", hu, bcsp->unack.qlen); + + spin_lock_irqsave(&bcsp->unack.lock, flags); + + while ((skb = __skb_dequeue_tail(&bcsp->unack)) != NULL) { + bcsp->msgq_txseq = (bcsp->msgq_txseq - 1) & 0x07; + skb_queue_head(&bcsp->rel, skb); + } + + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + hci_uart_tx_wakeup(hu); +} + +static int bcsp_open(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp; + + BT_DBG("hu %p", hu); + + bcsp = kmalloc(sizeof(*bcsp), GFP_ATOMIC); + if (!bcsp) + return -ENOMEM; + memset(bcsp, 0, sizeof(*bcsp)); + + hu->priv = bcsp; + skb_queue_head_init(&bcsp->unack); + skb_queue_head_init(&bcsp->rel); + skb_queue_head_init(&bcsp->unrel); + + init_timer(&bcsp->tbcsp); + bcsp->tbcsp.function = bcsp_timed_event; + bcsp->tbcsp.data = (u_long) hu; + + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + + return 0; +} + +static int bcsp_close(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + hu->priv = NULL; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&bcsp->unack); + skb_queue_purge(&bcsp->rel); + skb_queue_purge(&bcsp->unrel); + del_timer(&bcsp->tbcsp); + + kfree(bcsp); + return 0; +} + +static struct hci_uart_proto bcsp = { + .id = HCI_UART_BCSP, + .open = bcsp_open, + .close = bcsp_close, + .enqueue = bcsp_enqueue, + .dequeue = bcsp_dequeue, + .recv = bcsp_recv, + .flush = bcsp_flush +}; + +int bcsp_init(void) +{ + int err = hci_uart_register_proto(&bcsp); + if (!err) + BT_INFO("HCI BCSP protocol initialized"); + else + BT_ERR("HCI BCSP protocol registration failed"); + + return err; +} + +int bcsp_deinit(void) +{ + return hci_uart_unregister_proto(&bcsp); +} + +module_param(hciextn, bool, 0644); +MODULE_PARM_DESC(hciextn, "Convert HCI Extensions into BCSP packets"); diff --git a/drivers/bluetooth/hci_bcsp.h b/drivers/bluetooth/hci_bcsp.h new file mode 100644 index 000000000000..a2b3bb92274b --- /dev/null +++ b/drivers/bluetooth/hci_bcsp.h @@ -0,0 +1,70 @@ +/* + BlueCore Serial Protocol (BCSP) for Linux Bluetooth stack (BlueZ). + Copyright 2002 by Fabrizio Gennari <fabrizio.gennari@philips.com> + + Based on + hci_h4.c by Maxim Krasnyansky <maxk@qualcomm.com> + ABCSP by Carl Orsborn <cjo@csr.com> + + 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. +*/ + +/* + * $Id: hci_bcsp.h,v 1.2 2002/09/26 05:05:14 maxk Exp $ + */ + +#ifndef __HCI_BCSP_H__ +#define __HCI_BCSP_H__ + +#define BCSP_TXWINSIZE 4 + +#define BCSP_ACK_PKT 0x05 +#define BCSP_LE_PKT 0x06 + +struct bcsp_struct { + struct sk_buff_head unack; /* Unack'ed packets queue */ + struct sk_buff_head rel; /* Reliable packets queue */ + struct sk_buff_head unrel; /* Unreliable packets queue */ + + unsigned long rx_count; + struct sk_buff *rx_skb; + u8 rxseq_txack; /* rxseq == txack. */ + u8 rxack; /* Last packet sent by us that the peer ack'ed */ + struct timer_list tbcsp; + + enum { + BCSP_W4_PKT_DELIMITER, + BCSP_W4_PKT_START, + BCSP_W4_BCSP_HDR, + BCSP_W4_DATA, + BCSP_W4_CRC + } rx_state; + + enum { + BCSP_ESCSTATE_NOESC, + BCSP_ESCSTATE_ESC + } rx_esc_state; + + u16 message_crc; + u8 txack_req; /* Do we need to send ack's to the peer? */ + + /* Reliable packet sequence number - used to assign seq to each rel pkt. */ + u8 msgq_txseq; +}; + +#endif /* __HCI_BCSP_H__ */ diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c new file mode 100644 index 000000000000..ade94a57bb11 --- /dev/null +++ b/drivers/bluetooth/hci_h4.c @@ -0,0 +1,282 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * Bluetooth HCI UART(H4) protocol. + * + * $Id: hci_h4.c,v 1.3 2002/09/09 01:17:32 maxk Exp $ + */ +#define VERSION "1.2" + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include "hci_uart.h" +#include "hci_h4.h" + +#ifndef CONFIG_BT_HCIUART_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) +#endif + +/* Initialize protocol */ +static int h4_open(struct hci_uart *hu) +{ + struct h4_struct *h4; + + BT_DBG("hu %p", hu); + + h4 = kmalloc(sizeof(*h4), GFP_ATOMIC); + if (!h4) + return -ENOMEM; + memset(h4, 0, sizeof(*h4)); + + skb_queue_head_init(&h4->txq); + + hu->priv = h4; + return 0; +} + +/* Flush protocol data */ +static int h4_flush(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + + BT_DBG("hu %p", hu); + skb_queue_purge(&h4->txq); + return 0; +} + +/* Close protocol */ +static int h4_close(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + hu->priv = NULL; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&h4->txq); + if (h4->rx_skb) + kfree_skb(h4->rx_skb); + + hu->priv = NULL; + kfree(h4); + return 0; +} + +/* Enqueue frame for transmittion (padding, crc, etc) */ +static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct h4_struct *h4 = hu->priv; + + BT_DBG("hu %p skb %p", hu, skb); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &skb->pkt_type, 1); + skb_queue_tail(&h4->txq, skb); + return 0; +} + +static inline int h4_check_data_len(struct h4_struct *h4, int len) +{ + register int room = skb_tailroom(h4->rx_skb); + + BT_DBG("len %d room %d", len, room); + if (!len) { + BT_DMP(h4->rx_skb->data, h4->rx_skb->len); + hci_recv_frame(h4->rx_skb); + } else if (len > room) { + BT_ERR("Data length is too large"); + kfree_skb(h4->rx_skb); + } else { + h4->rx_state = H4_W4_DATA; + h4->rx_count = len; + return len; + } + + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_skb = NULL; + h4->rx_count = 0; + return 0; +} + +/* Recv data */ +static int h4_recv(struct hci_uart *hu, void *data, int count) +{ + struct h4_struct *h4 = hu->priv; + register char *ptr; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + register int len, type, dlen; + + BT_DBG("hu %p count %d rx_state %ld rx_count %ld", + hu, count, h4->rx_state, h4->rx_count); + + ptr = data; + while (count) { + if (h4->rx_count) { + len = min_t(unsigned int, h4->rx_count, count); + memcpy(skb_put(h4->rx_skb, len), ptr, len); + h4->rx_count -= len; count -= len; ptr += len; + + if (h4->rx_count) + continue; + + switch (h4->rx_state) { + case H4_W4_DATA: + BT_DBG("Complete data"); + + BT_DMP(h4->rx_skb->data, h4->rx_skb->len); + + hci_recv_frame(h4->rx_skb); + + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_skb = NULL; + continue; + + case H4_W4_EVENT_HDR: + eh = (struct hci_event_hdr *) h4->rx_skb->data; + + BT_DBG("Event header: evt 0x%2.2x plen %d", eh->evt, eh->plen); + + h4_check_data_len(h4, eh->plen); + continue; + + case H4_W4_ACL_HDR: + ah = (struct hci_acl_hdr *) h4->rx_skb->data; + dlen = __le16_to_cpu(ah->dlen); + + BT_DBG("ACL header: dlen %d", dlen); + + h4_check_data_len(h4, dlen); + continue; + + case H4_W4_SCO_HDR: + sh = (struct hci_sco_hdr *) h4->rx_skb->data; + + BT_DBG("SCO header: dlen %d", sh->dlen); + + h4_check_data_len(h4, sh->dlen); + continue; + } + } + + /* H4_W4_PACKET_TYPE */ + switch (*ptr) { + case HCI_EVENT_PKT: + BT_DBG("Event packet"); + h4->rx_state = H4_W4_EVENT_HDR; + h4->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + break; + + case HCI_ACLDATA_PKT: + BT_DBG("ACL packet"); + h4->rx_state = H4_W4_ACL_HDR; + h4->rx_count = HCI_ACL_HDR_SIZE; + type = HCI_ACLDATA_PKT; + break; + + case HCI_SCODATA_PKT: + BT_DBG("SCO packet"); + h4->rx_state = H4_W4_SCO_HDR; + h4->rx_count = HCI_SCO_HDR_SIZE; + type = HCI_SCODATA_PKT; + break; + + default: + BT_ERR("Unknown HCI packet type %2.2x", (__u8)*ptr); + hu->hdev->stat.err_rx++; + ptr++; count--; + continue; + }; + ptr++; count--; + + /* Allocate packet */ + h4->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!h4->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_count = 0; + return 0; + } + h4->rx_skb->dev = (void *) hu->hdev; + h4->rx_skb->pkt_type = type; + } + return count; +} + +static struct sk_buff *h4_dequeue(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + return skb_dequeue(&h4->txq); +} + +static struct hci_uart_proto h4p = { + .id = HCI_UART_H4, + .open = h4_open, + .close = h4_close, + .recv = h4_recv, + .enqueue = h4_enqueue, + .dequeue = h4_dequeue, + .flush = h4_flush, +}; + +int h4_init(void) +{ + int err = hci_uart_register_proto(&h4p); + if (!err) + BT_INFO("HCI H4 protocol initialized"); + else + BT_ERR("HCI H4 protocol registration failed"); + + return err; +} + +int h4_deinit(void) +{ + return hci_uart_unregister_proto(&h4p); +} diff --git a/drivers/bluetooth/hci_h4.h b/drivers/bluetooth/hci_h4.h new file mode 100644 index 000000000000..b95ff54bfd47 --- /dev/null +++ b/drivers/bluetooth/hci_h4.h @@ -0,0 +1,44 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * $Id: hci_h4.h,v 1.2 2002/09/09 01:17:32 maxk Exp $ + */ + +#ifdef __KERNEL__ +struct h4_struct { + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head txq; +}; + +/* H4 receiver States */ +#define H4_W4_PACKET_TYPE 0 +#define H4_W4_EVENT_HDR 1 +#define H4_W4_ACL_HDR 2 +#define H4_W4_SCO_HDR 3 +#define H4_W4_DATA 4 + +#endif /* __KERNEL__ */ diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c new file mode 100644 index 000000000000..9075bbb56ad4 --- /dev/null +++ b/drivers/bluetooth/hci_ldisc.c @@ -0,0 +1,593 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * Bluetooth HCI UART driver. + * + * $Id: hci_ldisc.c,v 1.5 2002/10/02 18:37:20 maxk Exp $ + */ +#define VERSION "2.1" + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +#ifndef CONFIG_BT_HCIUART_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) +#endif + +static int reset = 0; + +static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; + +int hci_uart_register_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (hup[p->id]) + return -EEXIST; + + hup[p->id] = p; + return 0; +} + +int hci_uart_unregister_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (!hup[p->id]) + return -EINVAL; + + hup[p->id] = NULL; + return 0; +} + +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +{ + if (id >= HCI_UART_MAX_PROTO) + return NULL; + return hup[id]; +} + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + /* Update HCI stat counters */ + switch (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.cmd_tx++; + break; + } +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + if (!skb) + skb = hu->proto->dequeue(hu); + else + hu->tx_skb = NULL; + return skb; +} + +int hci_uart_tx_wakeup(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + return 0; + } + + BT_DBG(""); + +restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->driver->write(tty, skb->data, skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, skb->pkt_type); + kfree_skb(skb); + } + + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; + + clear_bit(HCI_UART_SENDING, &hu->tx_state); + return 0; +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + BT_DBG("%s %p", hdev->name, hdev); + + /* Nothing to do for UART driver */ + + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + struct hci_uart *hu = (struct hci_uart *) hdev->driver_data; + struct tty_struct *tty = hu->tty; + + BT_DBG("hdev %p tty %p", hdev, tty); + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hu->proto->flush(hu); + + return 0; +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + BT_DBG("hdev %p", hdev); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_uart_flush(hdev); + return 0; +} + +/* Send frames from HCI layer */ +static int hci_uart_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct tty_struct *tty; + struct hci_uart *hu; + + if (!hdev) { + BT_ERR("Frame for uknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hu = (struct hci_uart *) hdev->driver_data; + tty = hu->tty; + + BT_DBG("%s: type %d len %d", hdev->name, skb->pkt_type, skb->len); + + hu->proto->enqueue(hu, skb); + + hci_uart_tx_wakeup(hu); + return 0; +} + +static void hci_uart_destruct(struct hci_dev *hdev) +{ + struct hci_uart *hu; + + if (!hdev) return; + + BT_DBG("%s", hdev->name); + + hu = (struct hci_uart *) hdev->driver_data; + kfree(hu); +} + +/* ------ LDISC part ------ */ +/* hci_uart_tty_open + * + * Called when line discipline changed to HCI_UART. + * + * Arguments: + * tty pointer to tty info structure + * Return Value: + * 0 if success, otherwise error code + */ +static int hci_uart_tty_open(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *) tty->disc_data; + + BT_DBG("tty %p", tty); + + if (hu) + return -EEXIST; + + if (!(hu = kmalloc(sizeof(struct hci_uart), GFP_KERNEL))) { + BT_ERR("Can't allocate controll structure"); + return -ENFILE; + } + memset(hu, 0, sizeof(struct hci_uart)); + + tty->disc_data = hu; + hu->tty = tty; + + spin_lock_init(&hu->rx_lock); + + /* Flush any pending characters in the driver and line discipline. */ + /* FIXME: why is this needed. Note don't use ldisc_ref here as the + open path is before the ldisc is referencable */ + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + + return 0; +} + +/* hci_uart_tty_close() + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void hci_uart_tty_close(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG("tty %p", tty); + + /* Detach from the tty */ + tty->disc_data = NULL; + + if (hu) { + struct hci_dev *hdev = hu->hdev; + hci_uart_close(hdev); + + if (test_and_clear_bit(HCI_UART_PROTO_SET, &hu->flags)) { + hu->proto->close(hu); + hci_unregister_dev(hdev); + hci_free_dev(hdev); + } + } +} + +/* hci_uart_tty_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_tty_wakeup(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG(""); + + if (!hu) + return; + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != hu->tty) + return; + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_tty_room() + * + * Callback function from tty driver. Return the amount of + * space left in the receiver's buffer to decide if remote + * transmitter is to be throttled. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: number of bytes left in receive buffer + */ +static int hci_uart_tty_room (struct tty_struct *tty) +{ + return 65536; +} + +/* hci_uart_tty_receive() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static void hci_uart_tty_receive(struct tty_struct *tty, const __u8 *data, char *flags, int count) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + if (!hu || tty != hu->tty) + return; + + if (!test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return; + + spin_lock(&hu->rx_lock); + hu->proto->recv(hu, (void *) data, count); + hu->hdev->stat.byte_rx += count; + spin_unlock(&hu->rx_lock); + + if (test_and_clear_bit(TTY_THROTTLED,&tty->flags) && tty->driver->unthrottle) + tty->driver->unthrottle(tty); +} + +static int hci_uart_register_dev(struct hci_uart *hu) +{ + struct hci_dev *hdev; + + BT_DBG(""); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hu->hdev = hdev; + + hdev->type = HCI_UART; + hdev->driver_data = hu; + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + hdev->destruct = hci_uart_destruct; + + hdev->owner = THIS_MODULE; + + if (reset) + set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks); + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static int hci_uart_set_proto(struct hci_uart *hu, int id) +{ + struct hci_uart_proto *p; + int err; + + p = hci_uart_get_proto(id); + if (!p) + return -EPROTONOSUPPORT; + + err = p->open(hu); + if (err) + return err; + + hu->proto = p; + + err = hci_uart_register_dev(hu); + if (err) { + p->close(hu); + return err; + } + return 0; +} + +/* hci_uart_tty_ioctl() + * + * Process IOCTL system call for the tty device. + * + * Arguments: + * + * tty pointer to tty instance data + * file pointer to open file object for device + * cmd IOCTL command code + * arg argument for IOCTL call (cmd dependent) + * + * Return Value: Command dependent + */ +static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct hci_uart *hu = (void *)tty->disc_data; + int err = 0; + + BT_DBG(""); + + /* Verify the status of the device */ + if (!hu) + return -EBADF; + + switch (cmd) { + case HCIUARTSETPROTO: + if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + err = hci_uart_set_proto(hu, arg); + if (err) { + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + return err; + } + tty->low_latency = 1; + } else + return -EBUSY; + + case HCIUARTGETPROTO: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->proto->id; + return -EUNATCH; + + default: + err = n_tty_ioctl(tty, file, cmd, arg); + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr) +{ + return 0; +} +static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *data, size_t count) +{ + return 0; +} +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, struct file *filp, poll_table *wait) +{ + return 0; +} + +#ifdef CONFIG_BT_HCIUART_H4 +int h4_init(void); +int h4_deinit(void); +#endif +#ifdef CONFIG_BT_HCIUART_BCSP +int bcsp_init(void); +int bcsp_deinit(void); +#endif + +static int __init hci_uart_init(void) +{ + static struct tty_ldisc hci_uart_ldisc; + int err; + + BT_INFO("HCI UART driver ver %s", VERSION); + + /* Register the tty discipline */ + + memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc)); + hci_uart_ldisc.magic = TTY_LDISC_MAGIC; + hci_uart_ldisc.name = "n_hci"; + hci_uart_ldisc.open = hci_uart_tty_open; + hci_uart_ldisc.close = hci_uart_tty_close; + hci_uart_ldisc.read = hci_uart_tty_read; + hci_uart_ldisc.write = hci_uart_tty_write; + hci_uart_ldisc.ioctl = hci_uart_tty_ioctl; + hci_uart_ldisc.poll = hci_uart_tty_poll; + hci_uart_ldisc.receive_room= hci_uart_tty_room; + hci_uart_ldisc.receive_buf = hci_uart_tty_receive; + hci_uart_ldisc.write_wakeup= hci_uart_tty_wakeup; + hci_uart_ldisc.owner = THIS_MODULE; + + if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) { + BT_ERR("HCI line discipline registration failed. (%d)", err); + return err; + } + +#ifdef CONFIG_BT_HCIUART_H4 + h4_init(); +#endif +#ifdef CONFIG_BT_HCIUART_BCSP + bcsp_init(); +#endif + + return 0; +} + +static void __exit hci_uart_exit(void) +{ + int err; + +#ifdef CONFIG_BT_HCIUART_H4 + h4_deinit(); +#endif +#ifdef CONFIG_BT_HCIUART_BCSP + bcsp_deinit(); +#endif + + /* Release tty registration of line discipline */ + if ((err = tty_register_ldisc(N_HCI, NULL))) + BT_ERR("Can't unregister HCI line discipline (%d)", err); +} + +module_init(hci_uart_init); +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_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_HCI); diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h new file mode 100644 index 000000000000..0a57d72790ec --- /dev/null +++ b/drivers/bluetooth/hci_uart.h @@ -0,0 +1,82 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * $Id: hci_uart.h,v 1.2 2002/09/09 01:17:32 maxk Exp $ + */ + +#ifndef N_HCI +#define N_HCI 15 +#endif + +/* Ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) + +/* UART protocols */ +#define HCI_UART_MAX_PROTO 4 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 + +#ifdef __KERNEL__ +struct hci_uart; + +struct hci_uart_proto { + unsigned int id; + int (*open)(struct hci_uart *hu); + int (*close)(struct hci_uart *hu); + int (*flush)(struct hci_uart *hu); + int (*recv)(struct hci_uart *hu, void *data, int len); + int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb); + struct sk_buff *(*dequeue)(struct hci_uart *hu); +}; + +struct hci_uart { + struct tty_struct *tty; + struct hci_dev *hdev; + unsigned long flags; + + struct hci_uart_proto *proto; + void *priv; + + struct sk_buff *tx_skb; + unsigned long tx_state; + spinlock_t rx_lock; +}; + +/* HCI_UART flag bits */ +#define HCI_UART_PROTO_SET 0 + +/* TX states */ +#define HCI_UART_SENDING 1 +#define HCI_UART_TX_WAKEUP 2 + +int hci_uart_register_proto(struct hci_uart_proto *p); +int hci_uart_unregister_proto(struct hci_uart_proto *p); +int hci_uart_tx_wakeup(struct hci_uart *hu); + +#endif /* __KERNEL__ */ diff --git a/drivers/bluetooth/hci_usb.c b/drivers/bluetooth/hci_usb.c new file mode 100644 index 000000000000..b120ecf7b8c9 --- /dev/null +++ b/drivers/bluetooth/hci_usb.c @@ -0,0 +1,1075 @@ +/* + HCI USB driver for Linux Bluetooth protocol stack (BlueZ) + Copyright (C) 2000-2001 Qualcomm Incorporated + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + Copyright (C) 2003 Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * Bluetooth HCI USB driver. + * Based on original USB Bluetooth driver for Linux kernel + * Copyright (c) 2000 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (c) 2000 Mark Douglas Corner <mcorner@umich.edu> + * + */ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/unistd.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/skbuff.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_usb.h" + +#ifndef CONFIG_BT_HCIUSB_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#undef BT_DMP +#define BT_DMP(D...) +#endif + +#ifndef CONFIG_BT_HCIUSB_ZERO_PACKET +#undef URB_ZERO_PACKET +#define URB_ZERO_PACKET 0 +#endif + +static int ignore = 0; +static int reset = 0; + +#ifdef CONFIG_BT_HCIUSB_SCO +static int isoc = 2; +#endif + +#define VERSION "2.8" + +static struct usb_driver hci_usb_driver; + +static struct usb_device_id bluetooth_ids[] = { + /* Generic Bluetooth USB device */ + { USB_DEVICE_INFO(HCI_DEV_CLASS, HCI_DEV_SUBCLASS, HCI_DEV_PROTOCOL) }, + + /* AVM BlueFRITZ! USB v2.0 */ + { USB_DEVICE(0x057c, 0x3800) }, + + /* Bluetooth Ultraport Module from IBM */ + { USB_DEVICE(0x04bf, 0x030a) }, + + /* ALPS Modules with non-standard id */ + { USB_DEVICE(0x044e, 0x3001) }, + { USB_DEVICE(0x044e, 0x3002) }, + + /* Ericsson with non-standard id */ + { USB_DEVICE(0x0bdb, 0x1002) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, bluetooth_ids); + +static struct usb_device_id blacklist_ids[] = { + /* Broadcom BCM2033 without firmware */ + { USB_DEVICE(0x0a5c, 0x2033), .driver_info = HCI_IGNORE }, + + /* Broadcom BCM2035 */ + { USB_DEVICE(0x0a5c, 0x200a), .driver_info = HCI_RESET | HCI_BROKEN_ISOC }, + { USB_DEVICE(0x0a5c, 0x2009), .driver_info = HCI_BCM92035 }, + + /* Microsoft Wireless Transceiver for Bluetooth 2.0 */ + { USB_DEVICE(0x045e, 0x009c), .driver_info = HCI_RESET }, + + /* ISSC Bluetooth Adapter v3.1 */ + { USB_DEVICE(0x1131, 0x1001), .driver_info = HCI_RESET }, + + /* RTX Telecom based adapter with buggy SCO support */ + { USB_DEVICE(0x0400, 0x0807), .driver_info = HCI_BROKEN_ISOC }, + + /* Digianswer devices */ + { USB_DEVICE(0x08fd, 0x0001), .driver_info = HCI_DIGIANSWER }, + { USB_DEVICE(0x08fd, 0x0002), .driver_info = HCI_IGNORE }, + + /* CSR BlueCore Bluetooth Sniffer */ + { USB_DEVICE(0x0a12, 0x0002), .driver_info = HCI_SNIFFER }, + + { } /* Terminating entry */ +}; + +static struct _urb *_urb_alloc(int isoc, int gfp) +{ + struct _urb *_urb = kmalloc(sizeof(struct _urb) + + sizeof(struct usb_iso_packet_descriptor) * isoc, gfp); + if (_urb) { + memset(_urb, 0, sizeof(*_urb)); + usb_init_urb(&_urb->urb); + } + return _urb; +} + +static struct _urb *_urb_dequeue(struct _urb_queue *q) +{ + struct _urb *_urb = NULL; + unsigned long flags; + spin_lock_irqsave(&q->lock, flags); + { + struct list_head *head = &q->head; + struct list_head *next = head->next; + if (next != head) { + _urb = list_entry(next, struct _urb, list); + list_del(next); _urb->queue = NULL; + } + } + spin_unlock_irqrestore(&q->lock, flags); + return _urb; +} + +static void hci_usb_rx_complete(struct urb *urb, struct pt_regs *regs); +static void hci_usb_tx_complete(struct urb *urb, struct pt_regs *regs); + +#define __pending_tx(husb, type) (&husb->pending_tx[type-1]) +#define __pending_q(husb, type) (&husb->pending_q[type-1]) +#define __completed_q(husb, type) (&husb->completed_q[type-1]) +#define __transmit_q(husb, type) (&husb->transmit_q[type-1]) +#define __reassembly(husb, type) (husb->reassembly[type-1]) + +static inline struct _urb *__get_completed(struct hci_usb *husb, int type) +{ + return _urb_dequeue(__completed_q(husb, type)); +} + +#ifdef CONFIG_BT_HCIUSB_SCO +static void __fill_isoc_desc(struct urb *urb, int len, int mtu) +{ + int offset = 0, i; + + BT_DBG("len %d mtu %d", len, mtu); + + for (i=0; i < HCI_MAX_ISOC_FRAMES && len >= mtu; i++, offset += mtu, len -= mtu) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = mtu; + BT_DBG("desc %d offset %d len %d", i, offset, mtu); + } + if (len && i < HCI_MAX_ISOC_FRAMES) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = len; + BT_DBG("desc %d offset %d len %d", i, offset, len); + i++; + } + urb->number_of_packets = i; +} +#endif + +static int hci_usb_intr_rx_submit(struct hci_usb *husb) +{ + struct _urb *_urb; + struct urb *urb; + int err, pipe, interval, size; + void *buf; + + BT_DBG("%s", husb->hdev->name); + + size = le16_to_cpu(husb->intr_in_ep->desc.wMaxPacketSize); + + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) { + kfree(buf); + return -ENOMEM; + } + _urb->type = HCI_EVENT_PKT; + _urb_queue_tail(__pending_q(husb, _urb->type), _urb); + + urb = &_urb->urb; + pipe = usb_rcvintpipe(husb->udev, husb->intr_in_ep->desc.bEndpointAddress); + interval = husb->intr_in_ep->desc.bInterval; + usb_fill_int_urb(urb, husb->udev, pipe, buf, size, hci_usb_rx_complete, husb, interval); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s intr rx submit failed urb %p err %d", + husb->hdev->name, urb, err); + _urb_unlink(_urb); + _urb_free(_urb); + kfree(buf); + } + return err; +} + +static int hci_usb_bulk_rx_submit(struct hci_usb *husb) +{ + struct _urb *_urb; + struct urb *urb; + int err, pipe, size = HCI_MAX_FRAME_SIZE; + void *buf; + + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) { + kfree(buf); + return -ENOMEM; + } + _urb->type = HCI_ACLDATA_PKT; + _urb_queue_tail(__pending_q(husb, _urb->type), _urb); + + urb = &_urb->urb; + pipe = usb_rcvbulkpipe(husb->udev, husb->bulk_in_ep->desc.bEndpointAddress); + usb_fill_bulk_urb(urb, husb->udev, pipe, buf, size, hci_usb_rx_complete, husb); + urb->transfer_flags = 0; + + BT_DBG("%s urb %p", husb->hdev->name, urb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk rx submit failed urb %p err %d", + husb->hdev->name, urb, err); + _urb_unlink(_urb); + _urb_free(_urb); + kfree(buf); + } + return err; +} + +#ifdef CONFIG_BT_HCIUSB_SCO +static int hci_usb_isoc_rx_submit(struct hci_usb *husb) +{ + struct _urb *_urb; + struct urb *urb; + int err, mtu, size; + void *buf; + + mtu = le16_to_cpu(husb->isoc_in_ep->desc.wMaxPacketSize); + size = mtu * HCI_MAX_ISOC_FRAMES; + + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + _urb = _urb_alloc(HCI_MAX_ISOC_FRAMES, GFP_ATOMIC); + if (!_urb) { + kfree(buf); + return -ENOMEM; + } + _urb->type = HCI_SCODATA_PKT; + _urb_queue_tail(__pending_q(husb, _urb->type), _urb); + + urb = &_urb->urb; + + urb->context = husb; + urb->dev = husb->udev; + urb->pipe = usb_rcvisocpipe(husb->udev, husb->isoc_in_ep->desc.bEndpointAddress); + urb->complete = hci_usb_rx_complete; + + urb->interval = husb->isoc_in_ep->desc.bInterval; + + urb->transfer_buffer_length = size; + urb->transfer_buffer = buf; + urb->transfer_flags = URB_ISO_ASAP; + + __fill_isoc_desc(urb, size, mtu); + + BT_DBG("%s urb %p", husb->hdev->name, urb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s isoc rx submit failed urb %p err %d", + husb->hdev->name, urb, err); + _urb_unlink(_urb); + _urb_free(_urb); + kfree(buf); + } + return err; +} +#endif + +/* Initialize device */ +static int hci_usb_open(struct hci_dev *hdev) +{ + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + int i, err; + unsigned long flags; + + BT_DBG("%s", hdev->name); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&husb->completion_lock, flags); + + err = hci_usb_intr_rx_submit(husb); + if (!err) { + for (i = 0; i < HCI_MAX_BULK_RX; i++) + hci_usb_bulk_rx_submit(husb); + +#ifdef CONFIG_BT_HCIUSB_SCO + if (husb->isoc_iface) + for (i = 0; i < HCI_MAX_ISOC_RX; i++) + hci_usb_isoc_rx_submit(husb); +#endif + } else { + clear_bit(HCI_RUNNING, &hdev->flags); + } + + write_unlock_irqrestore(&husb->completion_lock, flags); + return err; +} + +/* Reset device */ +static int hci_usb_flush(struct hci_dev *hdev) +{ + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + int i; + + BT_DBG("%s", hdev->name); + + for (i = 0; i < 4; i++) + skb_queue_purge(&husb->transmit_q[i]); + return 0; +} + +static void hci_usb_unlink_urbs(struct hci_usb *husb) +{ + int i; + + BT_DBG("%s", husb->hdev->name); + + for (i = 0; i < 4; i++) { + struct _urb *_urb; + struct urb *urb; + + /* Kill pending requests */ + while ((_urb = _urb_dequeue(&husb->pending_q[i]))) { + urb = &_urb->urb; + BT_DBG("%s unlinking _urb %p type %d urb %p", + husb->hdev->name, _urb, _urb->type, urb); + usb_kill_urb(urb); + _urb_queue_tail(__completed_q(husb, _urb->type), _urb); + } + + /* Release completed requests */ + while ((_urb = _urb_dequeue(&husb->completed_q[i]))) { + urb = &_urb->urb; + BT_DBG("%s freeing _urb %p type %d urb %p", + husb->hdev->name, _urb, _urb->type, urb); + if (urb->setup_packet) + kfree(urb->setup_packet); + if (urb->transfer_buffer) + kfree(urb->transfer_buffer); + _urb_free(_urb); + } + + /* Release reassembly buffers */ + if (husb->reassembly[i]) { + kfree_skb(husb->reassembly[i]); + husb->reassembly[i] = NULL; + } + } +} + +/* Close device */ +static int hci_usb_close(struct hci_dev *hdev) +{ + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + unsigned long flags; + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + BT_DBG("%s", hdev->name); + + /* Synchronize with completion handlers */ + write_lock_irqsave(&husb->completion_lock, flags); + write_unlock_irqrestore(&husb->completion_lock, flags); + + hci_usb_unlink_urbs(husb); + hci_usb_flush(hdev); + return 0; +} + +static int __tx_submit(struct hci_usb *husb, struct _urb *_urb) +{ + struct urb *urb = &_urb->urb; + int err; + + BT_DBG("%s urb %p type %d", husb->hdev->name, urb, _urb->type); + + _urb_queue_tail(__pending_q(husb, _urb->type), _urb); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s tx submit failed urb %p type %d err %d", + husb->hdev->name, urb, _urb->type, err); + _urb_unlink(_urb); + _urb_queue_tail(__completed_q(husb, _urb->type), _urb); + } else + atomic_inc(__pending_tx(husb, _urb->type)); + + return err; +} + +static inline int hci_usb_send_ctrl(struct hci_usb *husb, struct sk_buff *skb) +{ + struct _urb *_urb = __get_completed(husb, skb->pkt_type); + struct usb_ctrlrequest *dr; + struct urb *urb; + + if (!_urb) { + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) + return -ENOMEM; + _urb->type = skb->pkt_type; + + dr = kmalloc(sizeof(*dr), GFP_ATOMIC); + if (!dr) { + _urb_free(_urb); + return -ENOMEM; + } + } else + dr = (void *) _urb->urb.setup_packet; + + dr->bRequestType = husb->ctrl_req; + dr->bRequest = 0; + dr->wIndex = 0; + dr->wValue = 0; + dr->wLength = __cpu_to_le16(skb->len); + + urb = &_urb->urb; + usb_fill_control_urb(urb, husb->udev, usb_sndctrlpipe(husb->udev, 0), + (void *) dr, skb->data, skb->len, hci_usb_tx_complete, husb); + + BT_DBG("%s skb %p len %d", husb->hdev->name, skb, skb->len); + + _urb->priv = skb; + return __tx_submit(husb, _urb); +} + +static inline int hci_usb_send_bulk(struct hci_usb *husb, struct sk_buff *skb) +{ + struct _urb *_urb = __get_completed(husb, skb->pkt_type); + struct urb *urb; + int pipe; + + if (!_urb) { + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) + return -ENOMEM; + _urb->type = skb->pkt_type; + } + + urb = &_urb->urb; + pipe = usb_sndbulkpipe(husb->udev, husb->bulk_out_ep->desc.bEndpointAddress); + usb_fill_bulk_urb(urb, husb->udev, pipe, skb->data, skb->len, + hci_usb_tx_complete, husb); + urb->transfer_flags = URB_ZERO_PACKET; + + BT_DBG("%s skb %p len %d", husb->hdev->name, skb, skb->len); + + _urb->priv = skb; + return __tx_submit(husb, _urb); +} + +#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); + struct urb *urb; + + if (!_urb) { + _urb = _urb_alloc(HCI_MAX_ISOC_FRAMES, GFP_ATOMIC); + if (!_urb) + return -ENOMEM; + _urb->type = skb->pkt_type; + } + + BT_DBG("%s skb %p len %d", husb->hdev->name, skb, skb->len); + + urb = &_urb->urb; + + urb->context = husb; + urb->dev = husb->udev; + urb->pipe = usb_sndisocpipe(husb->udev, husb->isoc_out_ep->desc.bEndpointAddress); + urb->complete = hci_usb_tx_complete; + urb->transfer_flags = URB_ISO_ASAP; + + urb->interval = husb->isoc_out_ep->desc.bInterval; + + urb->transfer_buffer = skb->data; + urb->transfer_buffer_length = skb->len; + + __fill_isoc_desc(urb, skb->len, le16_to_cpu(husb->isoc_out_ep->desc.wMaxPacketSize)); + + _urb->priv = skb; + return __tx_submit(husb, _urb); +} +#endif + +static void hci_usb_tx_process(struct hci_usb *husb) +{ + struct sk_buff_head *q; + struct sk_buff *skb; + + BT_DBG("%s", husb->hdev->name); + + do { + clear_bit(HCI_USB_TX_WAKEUP, &husb->state); + + /* Process command queue */ + q = __transmit_q(husb, HCI_COMMAND_PKT); + if (!atomic_read(__pending_tx(husb, HCI_COMMAND_PKT)) && + (skb = skb_dequeue(q))) { + if (hci_usb_send_ctrl(husb, skb) < 0) + skb_queue_head(q, skb); + } + +#ifdef CONFIG_BT_HCIUSB_SCO + /* Process SCO queue */ + q = __transmit_q(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); + } +#endif + + /* Process ACL queue */ + q = __transmit_q(husb, HCI_ACLDATA_PKT); + while (atomic_read(__pending_tx(husb, HCI_ACLDATA_PKT)) < HCI_MAX_BULK_TX && + (skb = skb_dequeue(q))) { + if (hci_usb_send_bulk(husb, skb) < 0) { + skb_queue_head(q, skb); + break; + } + } + } while(test_bit(HCI_USB_TX_WAKEUP, &husb->state)); +} + +static inline void hci_usb_tx_wakeup(struct hci_usb *husb) +{ + /* Serialize TX queue processing to avoid data reordering */ + if (!test_and_set_bit(HCI_USB_TX_PROCESS, &husb->state)) { + hci_usb_tx_process(husb); + clear_bit(HCI_USB_TX_PROCESS, &husb->state); + } else + set_bit(HCI_USB_TX_WAKEUP, &husb->state); +} + +/* Send frames from HCI layer */ +static int hci_usb_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct hci_usb *husb; + + if (!hdev) { + BT_ERR("frame for uknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); + + husb = (struct hci_usb *) 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; + +#ifdef CONFIG_BT_HCIUSB_SCO + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; +#endif + + default: + kfree_skb(skb); + return 0; + } + + read_lock(&husb->completion_lock); + + skb_queue_tail(__transmit_q(husb, skb->pkt_type), skb); + hci_usb_tx_wakeup(husb); + + read_unlock(&husb->completion_lock); + return 0; +} + +static inline int __recv_frame(struct hci_usb *husb, int type, void *data, int count) +{ + BT_DBG("%s type %d data %p count %d", husb->hdev->name, type, data, count); + + husb->hdev->stat.byte_rx += count; + + while (count) { + struct sk_buff *skb = __reassembly(husb, type); + struct { int expect; } *scb; + int len = 0; + + if (!skb) { + /* Start of the frame */ + + switch (type) { + case HCI_EVENT_PKT: + if (count >= HCI_EVENT_HDR_SIZE) { + struct hci_event_hdr *h = data; + len = HCI_EVENT_HDR_SIZE + h->plen; + } else + return -EILSEQ; + break; + + case HCI_ACLDATA_PKT: + if (count >= HCI_ACL_HDR_SIZE) { + struct hci_acl_hdr *h = data; + len = HCI_ACL_HDR_SIZE + __le16_to_cpu(h->dlen); + } else + return -EILSEQ; + break; +#ifdef CONFIG_BT_HCIUSB_SCO + case HCI_SCODATA_PKT: + if (count >= HCI_SCO_HDR_SIZE) { + struct hci_sco_hdr *h = data; + len = HCI_SCO_HDR_SIZE + h->dlen; + } else + return -EILSEQ; + break; +#endif + } + BT_DBG("new packet len %d", len); + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for the packet", husb->hdev->name); + return -ENOMEM; + } + skb->dev = (void *) husb->hdev; + skb->pkt_type = type; + + __reassembly(husb, type) = skb; + + scb = (void *) skb->cb; + scb->expect = len; + } else { + /* Continuation */ + scb = (void *) skb->cb; + len = scb->expect; + } + + len = min(len, count); + + memcpy(skb_put(skb, len), data, len); + + scb->expect -= len; + if (!scb->expect) { + /* Complete frame */ + __reassembly(husb, type) = NULL; + hci_recv_frame(skb); + } + + count -= len; data += len; + } + return 0; +} + +static void hci_usb_rx_complete(struct urb *urb, struct pt_regs *regs) +{ + struct _urb *_urb = container_of(urb, struct _urb, urb); + struct hci_usb *husb = (void *) urb->context; + struct hci_dev *hdev = husb->hdev; + int err, count = urb->actual_length; + + BT_DBG("%s urb %p type %d status %d count %d flags %x", hdev->name, urb, + _urb->type, urb->status, count, urb->transfer_flags); + + read_lock(&husb->completion_lock); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + goto unlock; + + if (urb->status || !count) + goto resubmit; + + if (_urb->type == HCI_SCODATA_PKT) { +#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, + urb->iso_frame_desc[i].status, + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length); + + if (!urb->iso_frame_desc[i].status) + __recv_frame(husb, _urb->type, + urb->transfer_buffer + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length); + } +#else + ; +#endif + } else { + err = __recv_frame(husb, _urb->type, urb->transfer_buffer, count); + if (err < 0) { + BT_ERR("%s corrupted packet: type %d count %d", + husb->hdev->name, _urb->type, count); + hdev->stat.err_rx++; + } + } + +resubmit: + urb->dev = husb->udev; + err = usb_submit_urb(urb, GFP_ATOMIC); + BT_DBG("%s urb %p type %d resubmit status %d", hdev->name, urb, + _urb->type, err); + +unlock: + read_unlock(&husb->completion_lock); +} + +static void hci_usb_tx_complete(struct urb *urb, struct pt_regs *regs) +{ + struct _urb *_urb = container_of(urb, struct _urb, urb); + struct hci_usb *husb = (void *) urb->context; + struct hci_dev *hdev = husb->hdev; + + BT_DBG("%s urb %p status %d flags %x", hdev->name, urb, + urb->status, urb->transfer_flags); + + atomic_dec(__pending_tx(husb, _urb->type)); + + urb->transfer_buffer = NULL; + kfree_skb((struct sk_buff *) _urb->priv); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (!urb->status) + hdev->stat.byte_tx += urb->transfer_buffer_length; + else + hdev->stat.err_tx++; + + read_lock(&husb->completion_lock); + + _urb_unlink(_urb); + _urb_queue_tail(__completed_q(husb, _urb->type), _urb); + + hci_usb_tx_wakeup(husb); + + read_unlock(&husb->completion_lock); +} + +static void hci_usb_destruct(struct hci_dev *hdev) +{ + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(husb); +} + +static void hci_usb_notify(struct hci_dev *hdev, unsigned int evt) +{ + BT_DBG("%s evt %d", hdev->name, evt); +} + +static int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_endpoint *bulk_out_ep = NULL; + struct usb_host_endpoint *bulk_in_ep = NULL; + struct usb_host_endpoint *intr_in_ep = NULL; + struct usb_host_endpoint *ep; + struct usb_host_interface *uif; + struct usb_interface *isoc_iface; + struct hci_usb *husb; + struct hci_dev *hdev; + int i, e, size, isoc_ifnum, isoc_alts; + + BT_DBG("udev %p intf %p", udev, intf); + + if (!id->driver_info) { + const struct usb_device_id *match; + match = usb_match_id(intf, blacklist_ids); + if (match) + id = match; + } + + if (ignore || id->driver_info & HCI_IGNORE) + return -ENODEV; + + if (intf->cur_altsetting->desc.bInterfaceNumber > 0) + return -ENODEV; + + /* Find endpoints that we need */ + uif = intf->cur_altsetting; + for (e = 0; e < uif->desc.bNumEndpoints; e++) { + ep = &uif->endpoint[e]; + + switch (ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_INT: + if (ep->desc.bEndpointAddress & USB_DIR_IN) + intr_in_ep = ep; + break; + + case USB_ENDPOINT_XFER_BULK: + if (ep->desc.bEndpointAddress & USB_DIR_IN) + bulk_in_ep = ep; + else + bulk_out_ep = ep; + break; + } + } + + if (!bulk_in_ep || !bulk_out_ep || !intr_in_ep) { + BT_DBG("Bulk endpoints not found"); + goto done; + } + + if (!(husb = kmalloc(sizeof(struct hci_usb), GFP_KERNEL))) { + BT_ERR("Can't allocate: control structure"); + goto done; + } + + memset(husb, 0, sizeof(struct hci_usb)); + + husb->udev = udev; + husb->bulk_out_ep = bulk_out_ep; + husb->bulk_in_ep = bulk_in_ep; + husb->intr_in_ep = intr_in_ep; + + if (id->driver_info & HCI_DIGIANSWER) + husb->ctrl_req = USB_TYPE_VENDOR; + else + husb->ctrl_req = USB_TYPE_CLASS; + + /* Find isochronous endpoints that we can use */ + size = 0; + isoc_iface = NULL; + isoc_alts = 0; + isoc_ifnum = 1; + +#ifdef CONFIG_BT_HCIUSB_SCO + if (isoc && !(id->driver_info & (HCI_BROKEN_ISOC | HCI_SNIFFER))) + isoc_iface = usb_ifnum_to_if(udev, isoc_ifnum); + + if (isoc_iface) { + int a; + struct usb_host_endpoint *isoc_out_ep = NULL; + struct usb_host_endpoint *isoc_in_ep = NULL; + + for (a = 0; a < isoc_iface->num_altsetting; a++) { + uif = &isoc_iface->altsetting[a]; + for (e = 0; e < uif->desc.bNumEndpoints; e++) { + ep = &uif->endpoint[e]; + + switch (ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_ISOC: + if (le16_to_cpu(ep->desc.wMaxPacketSize) < size || + uif->desc.bAlternateSetting != isoc) + break; + size = le16_to_cpu(ep->desc.wMaxPacketSize); + + isoc_alts = uif->desc.bAlternateSetting; + + if (ep->desc.bEndpointAddress & USB_DIR_IN) + isoc_in_ep = ep; + else + isoc_out_ep = ep; + break; + } + } + } + + if (!isoc_in_ep || !isoc_out_ep) + BT_DBG("Isoc endpoints not found"); + else { + BT_DBG("isoc ifnum %d alts %d", isoc_ifnum, isoc_alts); + if (usb_driver_claim_interface(&hci_usb_driver, isoc_iface, husb) != 0) + BT_ERR("Can't claim isoc interface"); + else if (usb_set_interface(udev, isoc_ifnum, isoc_alts)) { + BT_ERR("Can't set isoc interface settings"); + husb->isoc_iface = isoc_iface; + usb_driver_release_interface(&hci_usb_driver, isoc_iface); + husb->isoc_iface = NULL; + } else { + husb->isoc_iface = isoc_iface; + husb->isoc_in_ep = isoc_in_ep; + husb->isoc_out_ep = isoc_out_ep; + } + } + } +#endif + + rwlock_init(&husb->completion_lock); + + for (i = 0; i < 4; i++) { + skb_queue_head_init(&husb->transmit_q[i]); + _urb_queue_init(&husb->pending_q[i]); + _urb_queue_init(&husb->completed_q[i]); + } + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + goto probe_error; + } + + husb->hdev = hdev; + + hdev->type = HCI_USB; + hdev->driver_data = husb; + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = hci_usb_open; + hdev->close = hci_usb_close; + hdev->flush = hci_usb_flush; + hdev->send = hci_usb_send_frame; + hdev->destruct = hci_usb_destruct; + hdev->notify = hci_usb_notify; + + hdev->owner = THIS_MODULE; + + if (reset || id->driver_info & HCI_RESET) + set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks); + + if (id->driver_info & HCI_SNIFFER) { + if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x997) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + } + + if (id->driver_info & HCI_BCM92035) { + unsigned char cmd[] = { 0x3b, 0xfc, 0x01, 0x00 }; + struct sk_buff *skb; + + skb = bt_skb_alloc(sizeof(cmd), GFP_KERNEL); + if (skb) { + memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd)); + skb_queue_tail(&hdev->driver_init, skb); + } + } + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + goto probe_error; + } + + usb_set_intfdata(intf, husb); + return 0; + +probe_error: + if (husb->isoc_iface) + usb_driver_release_interface(&hci_usb_driver, husb->isoc_iface); + kfree(husb); + +done: + return -EIO; +} + +static void hci_usb_disconnect(struct usb_interface *intf) +{ + struct hci_usb *husb = usb_get_intfdata(intf); + struct hci_dev *hdev; + + if (!husb || intf == husb->isoc_iface) + return; + + usb_set_intfdata(intf, NULL); + hdev = husb->hdev; + + BT_DBG("%s", hdev->name); + + hci_usb_close(hdev); + + if (husb->isoc_iface) + usb_driver_release_interface(&hci_usb_driver, husb->isoc_iface); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); +} + +static struct usb_driver hci_usb_driver = { + .owner = THIS_MODULE, + .name = "hci_usb", + .probe = hci_usb_probe, + .disconnect = hci_usb_disconnect, + .id_table = bluetooth_ids, +}; + +static int __init hci_usb_init(void) +{ + int err; + + BT_INFO("HCI USB driver ver %s", VERSION); + + if ((err = usb_register(&hci_usb_driver)) < 0) + BT_ERR("Failed to register HCI USB driver"); + + return err; +} + +static void __exit hci_usb_exit(void) +{ + usb_deregister(&hci_usb_driver); +} + +module_init(hci_usb_init); +module_exit(hci_usb_exit); + +module_param(ignore, bool, 0644); +MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + +#ifdef CONFIG_BT_HCIUSB_SCO +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_DESCRIPTION("Bluetooth HCI USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/hci_usb.h b/drivers/bluetooth/hci_usb.h new file mode 100644 index 000000000000..29936b43d4f8 --- /dev/null +++ b/drivers/bluetooth/hci_usb.h @@ -0,0 +1,128 @@ +/* + HCI USB driver for Linux Bluetooth protocol stack (BlueZ) + Copyright (C) 2000-2001 Qualcomm Incorporated + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + Copyright (C) 2003 Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* Class, SubClass, and Protocol codes that describe a Bluetooth device */ +#define HCI_DEV_CLASS 0xe0 /* Wireless class */ +#define HCI_DEV_SUBCLASS 0x01 /* RF subclass */ +#define HCI_DEV_PROTOCOL 0x01 /* Bluetooth programming protocol */ + +#define HCI_IGNORE 0x01 +#define HCI_RESET 0x02 +#define HCI_DIGIANSWER 0x04 +#define HCI_SNIFFER 0x08 +#define HCI_BROKEN_ISOC 0x10 +#define HCI_BCM92035 0x20 + +#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 { + struct list_head head; + spinlock_t lock; +}; + +struct _urb { + struct list_head list; + struct _urb_queue *queue; + int type; + void *priv; + struct urb urb; +}; + +static inline void _urb_free(struct _urb *_urb) +{ + kfree(_urb); +} + +static inline void _urb_queue_init(struct _urb_queue *q) +{ + INIT_LIST_HEAD(&q->head); + spin_lock_init(&q->lock); +} + +static inline void _urb_queue_head(struct _urb_queue *q, struct _urb *_urb) +{ + unsigned long flags; + spin_lock_irqsave(&q->lock, flags); + list_add(&_urb->list, &q->head); _urb->queue = q; + spin_unlock_irqrestore(&q->lock, flags); +} + +static inline void _urb_queue_tail(struct _urb_queue *q, struct _urb *_urb) +{ + unsigned long flags; + spin_lock_irqsave(&q->lock, flags); + list_add_tail(&_urb->list, &q->head); _urb->queue = q; + spin_unlock_irqrestore(&q->lock, flags); +} + +static inline void _urb_unlink(struct _urb *_urb) +{ + struct _urb_queue *q = _urb->queue; + unsigned long flags; + if (q) { + spin_lock_irqsave(&q->lock, flags); + list_del(&_urb->list); _urb->queue = NULL; + spin_unlock_irqrestore(&q->lock, flags); + } +} + +struct hci_usb { + struct hci_dev *hdev; + + unsigned long state; + + struct usb_device *udev; + + struct usb_host_endpoint *bulk_in_ep; + struct usb_host_endpoint *bulk_out_ep; + struct usb_host_endpoint *intr_in_ep; + + struct usb_interface *isoc_iface; + 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 */ + + rwlock_t completion_lock; + + atomic_t pending_tx[4]; /* Number of pending requests */ + struct _urb_queue pending_q[4]; /* Pending requests */ + struct _urb_queue completed_q[4]; /* Completed requests */ +}; + +/* States */ +#define HCI_USB_TX_PROCESS 1 +#define HCI_USB_TX_WAKEUP 2 diff --git a/drivers/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c new file mode 100644 index 000000000000..3256192dcde8 --- /dev/null +++ b/drivers/bluetooth/hci_vhci.c @@ -0,0 +1,364 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * Bluetooth HCI virtual device driver. + * + * $Id: hci_vhci.c,v 1.3 2002/04/17 17:37:20 maxk Exp $ + */ +#define VERSION "1.1" + +#include <linux/config.h> +#include <linux/module.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/init.h> +#include <linux/random.h> + +#include <linux/skbuff.h> +#include <linux/miscdevice.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include "hci_vhci.h" + +/* HCI device part */ + +static int hci_vhci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + +static int hci_vhci_flush(struct hci_dev *hdev) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) hdev->driver_data; + skb_queue_purge(&hci_vhci->readq); + return 0; +} + +static int hci_vhci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_vhci_flush(hdev); + return 0; +} + +static void hci_vhci_destruct(struct hci_dev *hdev) +{ + struct hci_vhci_struct *vhci; + + if (!hdev) return; + + vhci = (struct hci_vhci_struct *) hdev->driver_data; + kfree(vhci); +} + +static int hci_vhci_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct hci_vhci_struct *hci_vhci; + + if (!hdev) { + BT_ERR("Frame for uknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hci_vhci = (struct hci_vhci_struct *) hdev->driver_data; + + memcpy(skb_push(skb, 1), &skb->pkt_type, 1); + skb_queue_tail(&hci_vhci->readq, skb); + + if (hci_vhci->flags & VHCI_FASYNC) + kill_fasync(&hci_vhci->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&hci_vhci->read_wait); + + return 0; +} + +/* Character device part */ + +/* Poll */ +static unsigned int hci_vhci_chr_poll(struct file *file, poll_table * wait) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; + + poll_wait(file, &hci_vhci->read_wait, wait); + + if (skb_queue_len(&hci_vhci->readq)) + return POLLIN | POLLRDNORM; + + return POLLOUT | POLLWRNORM; +} + +/* Get packet from user space buffer(already verified) */ +static inline ssize_t hci_vhci_get_user(struct hci_vhci_struct *hci_vhci, const char __user *buf, size_t count) +{ + struct sk_buff *skb; + + if (count > HCI_MAX_FRAME_SIZE) + return -EINVAL; + + if (!(skb = bt_skb_alloc(count, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + + skb->dev = (void *) hci_vhci->hdev; + skb->pkt_type = *((__u8 *) skb->data); + skb_pull(skb, 1); + + hci_recv_frame(skb); + + return count; +} + +/* Write */ +static ssize_t hci_vhci_chr_write(struct file * file, const char __user * buf, + size_t count, loff_t *pos) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; + + if (!access_ok(VERIFY_READ, buf, count)) + return -EFAULT; + + return hci_vhci_get_user(hci_vhci, buf, count); +} + +/* Put packet to user space buffer(already verified) */ +static inline ssize_t hci_vhci_put_user(struct hci_vhci_struct *hci_vhci, + struct sk_buff *skb, char __user *buf, + int count) +{ + int len = count, total = 0; + char __user *ptr = buf; + + len = min_t(unsigned int, skb->len, len); + if (copy_to_user(ptr, skb->data, len)) + return -EFAULT; + total += len; + + hci_vhci->hdev->stat.byte_tx += len; + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hci_vhci->hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hci_vhci->hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hci_vhci->hdev->stat.cmd_tx++; + break; + }; + + return total; +} + +/* Read */ +static ssize_t hci_vhci_chr_read(struct file * file, char __user * buf, size_t count, loff_t *pos) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; + DECLARE_WAITQUEUE(wait, current); + struct sk_buff *skb; + ssize_t ret = 0; + + add_wait_queue(&hci_vhci->read_wait, &wait); + while (count) { + set_current_state(TASK_INTERRUPTIBLE); + + /* Read frames from device queue */ + if (!(skb = skb_dequeue(&hci_vhci->readq))) { + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + /* Nothing to read, let's sleep */ + schedule(); + continue; + } + + if (access_ok(VERIFY_WRITE, buf, count)) + ret = hci_vhci_put_user(hci_vhci, skb, buf, count); + else + ret = -EFAULT; + + kfree_skb(skb); + break; + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&hci_vhci->read_wait, &wait); + + return ret; +} + +static loff_t hci_vhci_chr_lseek(struct file * file, loff_t offset, int origin) +{ + return -ESPIPE; +} + +static int hci_vhci_chr_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return -EINVAL; +} + +static int hci_vhci_chr_fasync(int fd, struct file *file, int on) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; + int ret; + + if ((ret = fasync_helper(fd, file, on, &hci_vhci->fasync)) < 0) + return ret; + + if (on) + hci_vhci->flags |= VHCI_FASYNC; + else + hci_vhci->flags &= ~VHCI_FASYNC; + + return 0; +} + +static int hci_vhci_chr_open(struct inode *inode, struct file * file) +{ + struct hci_vhci_struct *hci_vhci = NULL; + struct hci_dev *hdev; + + if (!(hci_vhci = kmalloc(sizeof(struct hci_vhci_struct), GFP_KERNEL))) + return -ENOMEM; + + memset(hci_vhci, 0, sizeof(struct hci_vhci_struct)); + + skb_queue_head_init(&hci_vhci->readq); + init_waitqueue_head(&hci_vhci->read_wait); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(hci_vhci); + return -ENOMEM; + } + + hci_vhci->hdev = hdev; + + hdev->type = HCI_VHCI; + hdev->driver_data = hci_vhci; + + hdev->open = hci_vhci_open; + hdev->close = hci_vhci_close; + hdev->flush = hci_vhci_flush; + hdev->send = hci_vhci_send_frame; + hdev->destruct = hci_vhci_destruct; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + kfree(hci_vhci); + hci_free_dev(hdev); + return -EBUSY; + } + + file->private_data = hci_vhci; + return nonseekable_open(inode, file); +} + +static int hci_vhci_chr_close(struct inode *inode, struct file *file) +{ + struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; + struct hci_dev *hdev = hci_vhci->hdev; + + if (hci_unregister_dev(hdev) < 0) { + BT_ERR("Can't unregister HCI device %s", hdev->name); + } + + hci_free_dev(hdev); + + file->private_data = NULL; + return 0; +} + +static struct file_operations hci_vhci_fops = { + .owner = THIS_MODULE, + .llseek = hci_vhci_chr_lseek, + .read = hci_vhci_chr_read, + .write = hci_vhci_chr_write, + .poll = hci_vhci_chr_poll, + .ioctl = hci_vhci_chr_ioctl, + .open = hci_vhci_chr_open, + .release = hci_vhci_chr_close, + .fasync = hci_vhci_chr_fasync +}; + +static struct miscdevice hci_vhci_miscdev= +{ + VHCI_MINOR, + "hci_vhci", + &hci_vhci_fops +}; + +static int __init hci_vhci_init(void) +{ + BT_INFO("VHCI driver ver %s", VERSION); + + if (misc_register(&hci_vhci_miscdev)) { + BT_ERR("Can't register misc device %d\n", VHCI_MINOR); + return -EIO; + } + + return 0; +} + +static void hci_vhci_cleanup(void) +{ + misc_deregister(&hci_vhci_miscdev); +} + +module_init(hci_vhci_init); +module_exit(hci_vhci_cleanup); + +MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>"); +MODULE_DESCRIPTION("Bluetooth VHCI driver ver " VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/hci_vhci.h b/drivers/bluetooth/hci_vhci.h new file mode 100644 index 000000000000..53b11f9ef76d --- /dev/null +++ b/drivers/bluetooth/hci_vhci.h @@ -0,0 +1,50 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com> + + 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. +*/ + +/* + * $Id: hci_vhci.h,v 1.1.1.1 2002/03/08 21:03:15 maxk Exp $ + */ + +#ifndef __HCI_VHCI_H +#define __HCI_VHCI_H + +#ifdef __KERNEL__ + +struct hci_vhci_struct { + struct hci_dev *hdev; + __u32 flags; + wait_queue_head_t read_wait; + struct sk_buff_head readq; + struct fasync_struct *fasync; +}; + +/* VHCI device flags */ +#define VHCI_FASYNC 0x0010 + +#endif /* __KERNEL__ */ + +#define VHCI_DEV "/dev/vhci" +#define VHCI_MINOR 250 + +#endif /* __HCI_VHCI_H */ |